diff --git a/mapytex/calculus/core/tree.py b/mapytex/calculus/core/tree.py index c94634a..d9b0419 100644 --- a/mapytex/calculus/core/tree.py +++ b/mapytex/calculus/core/tree.py @@ -78,7 +78,7 @@ class Tree(object): > 4 """ t = MutableTree.from_str(expression) - return cls.from_mutable_tree(t) + return cls.from_any_tree(t) @classmethod def from_nested_parenthesis(cls, nested_parenthesis): @@ -120,29 +120,86 @@ class Tree(object): node = nested_parenthesis[0] try: - left_value = Tree.from_nested_parenthesis(nested_parenthesis[1][0]) + left_value = cls.from_nested_parenthesis(nested_parenthesis[1][0]) except ValueError: left_value = nested_parenthesis[1][0] try: - right_value = Tree.from_nested_parenthesis(nested_parenthesis[1][1]) + right_value = cls.from_nested_parenthesis(nested_parenthesis[1][1]) except ValueError: right_value = nested_parenthesis[1][1] return cls(node, left_value, right_value) @classmethod - def from_mutable_tree(cls, mutable_tree): - """ Initial a Tree from a MutableTree + def from_list(cls, node, leafs): + """ Initiate a balanced tree with one node and a list of leafs + + :param node: node for all node of the tree + :param leafs: list of leafs + + :example: + >>> t = Tree.from_list("+", [1, 2]) + >>> print(t) + + + > 1 + > 2 + >>> t = Tree.from_list("+", [1, 2, 3]) + >>> print(t) + + + > 1 + > + + | > 2 + | > 3 + >>> t = Tree.from_list("+", [1, 2, 3, 4]) + >>> print(t) + + + > + + | > 1 + | > 2 + > + + | > 3 + | > 4 + >>> t = Tree.from_list("+", [1, 2]) + >>> t2 = Tree.from_list("*", [1, t]) + >>> print(t2) + * + > 1 + > + + | > 1 + | > 2 + + + """ + len_leafs = len(leafs) + if len_leafs < 2: + raise ValueError(f"Not enough leafs. Need at least 2 got {len(leafs)}") + elif len_leafs == 2: + l_value = leafs[0] + r_value = leafs[1] + elif len_leafs == 3: + l_value = leafs[0] + r_value = cls.from_list(node, leafs[1:]) + else: + l_value = cls.from_list(node, leafs[:len_leafs//2]) + r_value = cls.from_list(node, leafs[len_leafs//2:]) + return cls(node, l_value, r_value) + + @classmethod + def from_any_tree(cls, tree): + """ Initial a Tree from an other type of tree (except LeafTree) + + It also work to initiate MutableTree, AssocialTree or LeafTree from + any tree. :example: >>> t = MutableTree("*", 1, 2) - >>> print(Tree.from_mutable_tree(t)) + >>> print(Tree.from_any_tree(t)) * > 1 > 2 >>> t1 = MutableTree("*", 1, 2) >>> t2 = MutableTree("*", t1, 3) - >>> print(Tree.from_mutable_tree(t2)) + >>> print(Tree.from_any_tree(t2)) * > * | > 1 @@ -153,15 +210,22 @@ class Tree(object): * > 1 > None - >>> Tree.from_mutable_tree(t) + >>> Tree.from_any_tree(t) Traceback (most recent call last): ... TypeError: Tree can't have empty node or leaf + >>> tl = LeafTree("/", 1, 4) + >>> t2 = MutableTree("*", tl, 3) + >>> t = Tree.from_any_tree(t2) + >>> type(t) + + >>> type(t.left_value) + """ - node = mutable_tree.node - left_value = mutable_tree.left_value - right_value = mutable_tree.right_value + node = tree.node + left_value = tree.left_value + right_value = tree.right_value if node is None or \ left_value is None or \ @@ -169,13 +233,23 @@ class Tree(object): raise TypeError("Tree can't have empty node or leaf") try: - l_value = Tree.from_mutable_tree(left_value) + left_value.IMLEAF except AttributeError: + try: + l_value = cls.from_any_tree(left_value) + except AttributeError: + l_value = left_value + else: l_value = left_value try: - r_value = Tree.from_mutable_tree(right_value) + right_value.IMLEAF except AttributeError: + try: + r_value = cls.from_any_tree(right_value) + except AttributeError: + r_value = right_value + else: r_value = right_value return cls(node, l_value, r_value) @@ -188,10 +262,7 @@ class Tree(object): :example: - >>> nested_par = ("+", ( - ... ("*", (3, 4)), - ... 2)) - >>> t = Tree.from_nested_parenthesis(nested_par) + >>> t = Tree.from_str("3*4+2") >>> print(t) + > * @@ -227,10 +298,7 @@ class Tree(object): :example: - >>> nested_par = ("+", ( - ... ("*", (3, 4)), - ... 2)) - >>> t = Tree.from_nested_parenthesis(nested_par) + >>> t = Tree.from_str("3*4+2") >>> print(t) + > * @@ -286,6 +354,7 @@ class Tree(object): >>> t = Tree.from_nested_parenthesis(nested_par) >>> t.apply(to_nested) ('+', (1, 2)) + >>> assert t.apply(to_nested) == nested_par >>> nested_par = ("+", ( ... ("*", (3, 4)), @@ -293,6 +362,7 @@ class Tree(object): >>> t = Tree.from_nested_parenthesis(nested_par) >>> t.apply(to_nested) ('+', (('*', (3, 4)), 2)) + >>> assert t.apply(to_nested) == nested_par >>> from .evaluate import compute >>> t.apply(compute) @@ -319,9 +389,9 @@ class Tree(object): :example: - >>> t = Tree.from_str("3*4+2") + >>> t = Tree.from_str("3+4+5*2") >>> [l for l in t.get_leafs()] - [3, 4, 2] + [3, 4, 5, 2] >>> {type(l) for l in t.get_leafs()} {} """ @@ -475,8 +545,6 @@ class Tree(object): """ return self.apply(postfix_concatenate) - - class MutableTree(Tree): """ @@ -776,11 +844,127 @@ class LeafTree(Tree): - apply_on_last_level """ + IMLEAF = 1 + def apply(self, *args): raise AttributeError("Can't use apply on a LeafTree") def apply_on_last_level(self, *args): - raise AttributeError("Can't use apply on a LeafTree") + raise AttributeError("Can't use apply_on_last_level on a LeafTree") + +class AssocialTree(Tree): + + """ Tree which concider every subtree with a node different from itself + as a Lead + """ + def map_on_leaf(self, function): + """ Map on leafs a function + + :param function: function on a single value or a tree + :returns: Tree with calculated leaf + + :example: + + >>> t = Tree.from_str("3*4+2") + >>> print(t) + + + > * + | > 3 + | > 4 + > 2 + >>> print(t.map_on_leaf(lambda x:2*x)) + + + > * + | > 6 + | > 8 + > 4 + + """ + try: + left_applied = self.left_value.map_on_leaf(function) + except AttributeError: + left_applied = function(self.left_value) + + try: + right_applied = self.right_value.map_on_leaf(function) + except AttributeError: + right_applied = function(self.right_value) + + return Tree(self.node, left_applied, right_applied) + + def apply_on_last_level(self, function): + raise AttributeError("apply_on_last_level not available for AssocialTree") + + def apply(self, function): + """ Apply the function on every node of the tree + + :param function: (op, a, a) -> b + :returns: b + + :example: + + >>> def to_nested(op, left, right): + ... try: + ... l = f"tree({left.node})" + ... except AttributeError: + ... l = left + ... try: + ... r = f"tree({right.node})" + ... except AttributeError: + ... r = right + ... return (op, (l, r)) + >>> t = AssocialTree.from_str("3+4+5*2") + >>> t.apply(to_nested) + ('+', (('+', (3, 4)), 'tree(*)')) + + """ + try: + if self.left_value.node == self.node: + left_value = self.left_value.apply(function) + else: + left_value = self.left_value + except AttributeError: + left_value = self.left_value + + try: + if self.right_value.node == self.node: + right_value = self.right_value.apply(function) + else: + right_value = self.right_value + except AttributeError: + right_value = self.right_value + + return function(self.node, left_value, right_value) + + def get_leafs(self, callback = lambda x:x): + """ Generator which yield all the leaf value of the tree. + Callback act on every leaf. + + :param callback: function on leaf or Tree + + :example: + + >>> t = AssocialTree.from_str("3+4+5*2") + >>> [l for l in t.get_leafs(str)] + ['3', '4', '*\\n > 5\\n > 2'] + >>> [ l for l in t.get_leafs(type) ] + [, , ] + """ + try: + if self.left_value.node == self.node: + yield from self.left_value.get_leafs(callback) + else: + yield callback(self.left_value) + except AttributeError: + yield callback(self.left_value) + + try: + if self.right_value.node == self.node: + yield from self.right_value.get_leafs(callback) + else: + yield callback(self.right_value) + except AttributeError: + yield callback(self.right_value) # ----------------------------- # Reglages pour 'vim'