# The version of this code that I posted on Feb 12 had a pretty blatant error in
# leaf_count, and some redundancies elsewhere. This is Danny's most-recent 
# version with a few minor style alterations.

class Tree:
    """Bare-bones Tree ADT"""

    def __init__(self: 'Tree',
                 value: object =None, children: list =None):
        """Create a node with value and any number of children"""

        self.value = value
        if not children:
            self.children = []
        else:
            self.children = children[:] # quick-n-dirty copy of list

    def __repr__(self: 'Tree') -> str:
        """Return representation of Tree as a string"""

        return ('Tree(' + repr(self.value) + ', ' +
                repr(self.children) + ')')

    def __contains__(self: 'Tree' , value: object) -> bool:
        """True if Tree has a node with value

        >>> tn2 = Tree(2, [Tree(4), Tree(4.5), Tree(5), Tree(5.75)])
        >>> tn3 = Tree(3, [Tree(6), Tree(7)])
        >>> tn1 = Tree(1, [tn2, tn3])
        >>> tn1.__contains__(5.75) # equivalent to 5.75 in tn1
        True
        >>> tn1.__contains__(7.2)
        False
        """
        return (self.value == value or
                any([t.__contains__(value) for t in self.children]))

# module-level functions, alternatively these could be implemented as methods
def arity(t: Tree) -> int:
    """Maximum branching factor of tree T

    >>> tn2 = Tree(2, [Tree(4), Tree(4.5), Tree(5), Tree(5.75)])
    >>> tn3 = Tree(3, [Tree(6), Tree(7)])
    >>> tn1 = Tree(1, [tn2, tn3])
    >>> arity(tn1)
    4
    """
    return max([len(t.children)] + [arity(n) for n in t.children])

def count(t: Tree) -> int:
    """How many nodes in this Tree?

    >>> tn2 = Tree(2, [Tree(4), Tree(4.5), Tree(5), Tree(5.75)])
    >>> tn3 = Tree(3, [Tree(6), Tree(7)])
    >>> tn1 = Tree(1, [tn2, tn3])
    >>> count(tn1)
    9
    """
    return 1 + sum([count(n) for n in t.children])

def height(t: Tree) -> int:
    """Return length of longest path of t

    >>> tn2 = Tree(2, [Tree(4), Tree(4.5), Tree(5), Tree(5.75)])
    >>> tn3 = Tree(3, [Tree(6), Tree(7)])
    >>> tn1 = Tree(1, [tn2, tn3])
    >>> height(tn1)
    2
    """
    # 0 for a node with no children, and otherwise    
    # 1 more than the maximum height of a child  
    if len(t.children) == 0:
        return 0
    else:
        return 1 + max([height(c) for c in t.children])

def leaf_count(t: Tree) -> int:
    """Return number of leaves in t

    >>> tn2 = Tree(2, [Tree(4), Tree(4.5), Tree(5), Tree(5.75)])
    >>> tn3 = Tree(3, [Tree(6), Tree(7)])
    >>> tn1 = Tree(1, [tn2, tn3])
    >>> leaf_count(tn1)
    6
    """
    if len(t.children) == 0:
        return 1
    else:
        return sum([leaf_count(c) for c in t.children])

def contains_test_passer(t: Tree, test: 'function') -> bool:
    """Return whether t contains a value that test(value) returns True for

    >>> tn2 = Tree(2, [Tree(4), Tree(4.5), Tree(5), Tree(5.75)])
    >>> tn3 = Tree(3, [Tree(6), Tree(7)])
    >>> tn1 = Tree(1, [tn2, tn3])
    >>> def greater_than_nine(n): return n > 9
    >>> contains_test_passer(tn1, greater_than_nine)
    False
    >>> def even(n): return n % 2 == 0
    >>> contains_test_passer(tn1, even)
    True
    """
    return test(t.value) or any([test(c.value) for c in t.children])


if __name__ == '__main__':
    import doctest
    doctest.testmod(verbose=True)
