1656 lines
41 KiB
Python
1656 lines
41 KiB
Python
# vim:fenc=utf-8
|
|
#
|
|
# Copyright © 2017 lafrite <lafrite@Poivre>
|
|
#
|
|
# Distributed under terms of the MIT license.
|
|
|
|
"""
|
|
Tree class
|
|
|
|
"""
|
|
|
|
from .tree_tools import (to_nested_parenthesis,
|
|
postfix_concatenate,
|
|
show_tree,
|
|
)
|
|
from .coroutine import coroutine, STOOOP
|
|
from .str2 import str2
|
|
from .operator import OPERATORS, is_operator
|
|
|
|
__all__ = ["Tree", "MutableTree"]
|
|
|
|
|
|
class Tree(object):
|
|
|
|
"""
|
|
Binary tree
|
|
|
|
This is the chosen structure to manage calculus in PyMath.
|
|
|
|
The class is not mutable to preserve its integrity.
|
|
|
|
"""
|
|
|
|
def __init__(self, node, left_value, right_value):
|
|
"""
|
|
Initiate a tree with tuple (node, (left value, right value))
|
|
|
|
:example:
|
|
|
|
>>> t = Tree("+", 1, 2)
|
|
>>> t.node
|
|
'+'
|
|
>>> t.left_value
|
|
1
|
|
>>> t.right_value
|
|
2
|
|
"""
|
|
if node is None or right_value is None:
|
|
raise TypeError("Tree can't have an empty node or an empty right leaf")
|
|
|
|
self.node = node
|
|
|
|
self.left_value = left_value
|
|
self.right_value = right_value
|
|
|
|
@classmethod
|
|
def from_str(cls, expression, convert_to_mo=True):
|
|
""" Initiate a tree from an string expression
|
|
|
|
:example:
|
|
|
|
>>> t = Tree.from_str("2+3*4")
|
|
>>> print(t)
|
|
+
|
|
> 2
|
|
> *
|
|
| > 3
|
|
| > 4
|
|
>>> t = Tree.from_str("(2+3)*4")
|
|
>>> print(t)
|
|
*
|
|
> +
|
|
| > 2
|
|
| > 3
|
|
> 4
|
|
>>> t = Tree.from_str("2+3*n")
|
|
>>> print(t)
|
|
+
|
|
> 2
|
|
> *
|
|
| > 3
|
|
| > n
|
|
|
|
"""
|
|
t = MutableTree.from_str(expression, convert_to_mo)
|
|
return cls.from_any_tree(t)
|
|
|
|
@classmethod
|
|
def from_nested_parenthesis(cls, nested_parenthesis):
|
|
"""
|
|
Initiate recursively a tree with tuple (node, (left value, right value))
|
|
|
|
:example:
|
|
|
|
>>> nested_par = ("+", (1, 2))
|
|
>>> t = Tree.from_nested_parenthesis(nested_par)
|
|
>>> t.node
|
|
'+'
|
|
>>> t.left_value
|
|
1
|
|
>>> t.right_value
|
|
2
|
|
>>> nested_par = ("+", (
|
|
... ("*", (3, 4)),
|
|
... 2))
|
|
>>> t = Tree.from_nested_parenthesis(nested_par)
|
|
>>> t.node
|
|
'+'
|
|
>>> type(t.left_value)
|
|
<class 'mapytex.calculus.core.tree.Tree'>
|
|
>>> t.right_value
|
|
2
|
|
|
|
"""
|
|
try:
|
|
nested_len = len(nested_parenthesis)
|
|
num_len = len(nested_parenthesis[1])
|
|
except TypeError:
|
|
raise ValueError("Nested parenthesis are not composed of lists")
|
|
|
|
if nested_len != 2 and \
|
|
num_len != 2:
|
|
raise ValueError("Nested parenthesis don't have right shape")
|
|
|
|
node = nested_parenthesis[0]
|
|
|
|
try:
|
|
left_value = cls.from_nested_parenthesis(nested_parenthesis[1][0])
|
|
except ValueError:
|
|
left_value = nested_parenthesis[1][0]
|
|
try:
|
|
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_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_any_tree(t))
|
|
*
|
|
> 1
|
|
> 2
|
|
>>> t1 = MutableTree("*", 1, 2)
|
|
>>> t2 = MutableTree("*", t1, 3)
|
|
>>> print(Tree.from_any_tree(t2))
|
|
*
|
|
> *
|
|
| > 1
|
|
| > 2
|
|
> 3
|
|
>>> t = MutableTree("*", 1)
|
|
>>> print(t)
|
|
*
|
|
> 1
|
|
> None
|
|
>>> 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)
|
|
<class 'mapytex.calculus.core.tree.Tree'>
|
|
>>> type(t.left_value)
|
|
<class 'mapytex.calculus.core.tree.LeafTree'>
|
|
|
|
"""
|
|
node = tree.node
|
|
left_value = tree.left_value
|
|
right_value = tree.right_value
|
|
|
|
if node is None or \
|
|
left_value is None or \
|
|
right_value is None:
|
|
raise TypeError("Tree can't have empty node or leaf")
|
|
|
|
try:
|
|
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:
|
|
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)
|
|
|
|
def map_on_leaf(self, function):
|
|
""" Map on leafs a function
|
|
|
|
:param function: take leaf value returns other value
|
|
:returns: Tree with calculated leaf
|
|
|
|
:example:
|
|
|
|
>>> t = Tree.from_str("3*4+2", convert_to_mo=False)
|
|
>>> 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):
|
|
""" Apply the function on last level of the tree before leaf
|
|
|
|
:param function: (op, a, a) -> b function to apply on last level
|
|
:returns: b if it is a 1 level Tree, Tree otherwise
|
|
|
|
|
|
:example:
|
|
|
|
>>> t = Tree.from_str("3*4+2")
|
|
>>> print(t)
|
|
+
|
|
> *
|
|
| > 3
|
|
| > 4
|
|
> 2
|
|
>>> from .tree_tools import infix_str_concatenate
|
|
>>> tt = t.apply_on_last_level(lambda *x: infix_str_concatenate(*x))
|
|
>>> print(tt)
|
|
+
|
|
> 3 * 4
|
|
> 2
|
|
>>> tt = t.apply_on_last_level(lambda n, l, r: eval(str(l) + n + str(r)))
|
|
>>> print(tt)
|
|
+
|
|
> 12
|
|
> 2
|
|
>>> ttt = tt.apply_on_last_level(lambda n, l, r: eval(str(l) + n + str(r)))
|
|
>>> print(ttt)
|
|
14
|
|
"""
|
|
|
|
left_is_leaf = 0
|
|
try:
|
|
left_applied = self.left_value.\
|
|
apply_on_last_level(function)
|
|
except AttributeError:
|
|
left_applied = self.left_value
|
|
left_is_leaf = 1
|
|
|
|
right_is_leaf = 0
|
|
try:
|
|
right_applied = self.right_value.\
|
|
apply_on_last_level(function)
|
|
except AttributeError:
|
|
right_applied = self.right_value
|
|
right_is_leaf = 1
|
|
|
|
if left_is_leaf and right_is_leaf:
|
|
try:
|
|
return function(self.node, left_applied, right_applied)
|
|
except NotImplementedError:
|
|
return Tree(self.node, left_applied, right_applied)
|
|
else:
|
|
return Tree(self.node, left_applied, right_applied)
|
|
|
|
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):
|
|
... return (op, (left, right))
|
|
>>> nested_par = ("+", (1, 2))
|
|
>>> t = Tree.from_nested_parenthesis(nested_par)
|
|
>>> t.apply(to_nested)
|
|
('+', (1, 2))
|
|
>>> assert t.apply(to_nested) == nested_par
|
|
|
|
>>> nested_par = ("+", (
|
|
... ("*", (3, 4)),
|
|
... 2))
|
|
>>> t = Tree.from_nested_parenthesis(nested_par)
|
|
>>> t.apply(to_nested)
|
|
('+', (('*', (3, 4)), 2))
|
|
>>> assert t.apply(to_nested) == nested_par
|
|
|
|
>>> t.apply(lambda n, l, r: eval(str(l) + n + str(r)))
|
|
14
|
|
|
|
"""
|
|
try:
|
|
left_value = self.left_value.apply(function)
|
|
except AttributeError:
|
|
left_value = self.left_value
|
|
|
|
try:
|
|
right_value = self.right_value.apply(function)
|
|
except AttributeError:
|
|
right_value = self.right_value
|
|
|
|
try:
|
|
return function(self.node, left_value, right_value)
|
|
except NotImplementedError:
|
|
return Tree(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
|
|
|
|
:example:
|
|
|
|
>>> t = Tree.from_str("3+4+5*2")
|
|
>>> [l for l in t.get_leafs()]
|
|
[<MOnumber 3>, <MOnumber 4>, <MOnumber 5>, <MOnumber 2>]
|
|
>>> {type(l) for l in t.get_leafs()}
|
|
{<class 'mapytex.calculus.core.MO.mo.MOnumber'>}
|
|
"""
|
|
try:
|
|
yield from self.left_value.get_leafs(callback)
|
|
except AttributeError:
|
|
yield callback(self.left_value)
|
|
try:
|
|
yield from self.right_value.get_leafs(callback)
|
|
except AttributeError:
|
|
yield callback(self.right_value)
|
|
|
|
def get_nodes(self, callback=lambda x:x):
|
|
""" Generator which yield all nodes of the tree.
|
|
Callback act on every nodes.
|
|
|
|
:param callback: function on node
|
|
|
|
:example:
|
|
|
|
>>> t = Tree.from_str('3*4+2')
|
|
>>> [l for l in t.get_nodes()]
|
|
['+', '*']
|
|
>>> t = Tree.from_str('3*4+3*4')
|
|
>>> [l for l in t.get_nodes()]
|
|
['+', '*', '*']
|
|
"""
|
|
yield self.node
|
|
try:
|
|
yield from self.left_value.get_nodes(callback)
|
|
except AttributeError:
|
|
pass
|
|
try:
|
|
yield from self.right_value.get_nodes(callback)
|
|
except AttributeError:
|
|
pass
|
|
|
|
def depth(self):
|
|
""" Return the depth of the tree
|
|
|
|
:example:
|
|
|
|
>>> nested_par = ("+", (1, 2))
|
|
>>> t = Tree.from_nested_parenthesis(nested_par)
|
|
>>> t.depth()
|
|
1
|
|
>>> nested_par = ("+", (
|
|
... ("*", (3, 4)),
|
|
... 2))
|
|
>>> t = Tree.from_nested_parenthesis(nested_par)
|
|
>>> t.depth()
|
|
2
|
|
>>> nested_par = ("+", (
|
|
... ("*", (3,
|
|
... ("/", (4, 4))
|
|
... )),
|
|
... 2))
|
|
>>> t = Tree.from_nested_parenthesis(nested_par)
|
|
>>> t.depth()
|
|
3
|
|
|
|
"""
|
|
try:
|
|
l_depth = self.left_value.depth()
|
|
except AttributeError:
|
|
l_depth = 0
|
|
|
|
try:
|
|
r_depth = self.right_value.depth()
|
|
except AttributeError:
|
|
r_depth = 0
|
|
|
|
return 1 + max(l_depth, r_depth)
|
|
|
|
def __str__(self):
|
|
""" Overload str method
|
|
|
|
:example:
|
|
|
|
>>> nested_par = ("+", (1, 2))
|
|
>>> t = Tree.from_nested_parenthesis(nested_par)
|
|
>>> print(t)
|
|
+
|
|
> 1
|
|
> 2
|
|
>>> nested_par = ("+", (
|
|
... ("*", (3, 4)),
|
|
... 2))
|
|
>>> t = Tree.from_nested_parenthesis(nested_par)
|
|
>>> print(t)
|
|
+
|
|
> *
|
|
| > 3
|
|
| > 4
|
|
> 2
|
|
>>> nested_par = ("+", (
|
|
... ("*", (1, 2)),
|
|
... ("*", (3, 4)),
|
|
... ))
|
|
>>> t = Tree.from_nested_parenthesis(nested_par)
|
|
>>> print(t)
|
|
+
|
|
> *
|
|
| > 1
|
|
| > 2
|
|
> *
|
|
| > 3
|
|
| > 4
|
|
"""
|
|
return self.apply(show_tree)
|
|
|
|
def to_nested_parenthesis(self):
|
|
""" Transform the Tree into its nested parenthesis description
|
|
:example:
|
|
|
|
>>> nested_par = ("+", (1, 2))
|
|
>>> t = Tree.from_nested_parenthesis(nested_par)
|
|
>>> t.to_nested_parenthesis()
|
|
('+', (1, 2))
|
|
>>> nested_par = ("+", (
|
|
... ("*", (3, 4)),
|
|
... 2))
|
|
>>> t = Tree.from_nested_parenthesis(nested_par)
|
|
>>> t.to_nested_parenthesis()
|
|
('+', (('*', (3, 4)), 2))
|
|
"""
|
|
return self.apply(to_nested_parenthesis)
|
|
|
|
def to_postfix(self):
|
|
""" Transform the Tree into postfix notation
|
|
|
|
:example:
|
|
|
|
>>> nested_par = ("+", (1, 2))
|
|
>>> t = Tree.from_nested_parenthesis(nested_par)
|
|
>>> t.to_postfix()
|
|
[1, 2, '+']
|
|
>>> nested_par = ("+", (
|
|
... ("*", (3, 4)),
|
|
... 2))
|
|
>>> t = Tree.from_nested_parenthesis(nested_par)
|
|
>>> t.to_postfix()
|
|
[3, 4, '*', 2, '+']
|
|
>>> nested_par = ("+", (
|
|
... ("*", (3, 4)),
|
|
... ("*", (5, 6)),
|
|
... ))
|
|
>>> t = Tree.from_nested_parenthesis(nested_par)
|
|
>>> t.to_postfix()
|
|
[3, 4, '*', 5, 6, '*', '+']
|
|
"""
|
|
return self.apply(postfix_concatenate)
|
|
|
|
@property
|
|
def short_branch(self):
|
|
""" Get the short branch of the tree, left if same depth
|
|
|
|
:return: A tree or a leaf value
|
|
|
|
:example:
|
|
>>> t = Tree.from_str("2+3*4")
|
|
>>> print(t.short_branch)
|
|
2
|
|
>>> t = Tree.from_str("1*2*3+4*5")
|
|
>>> print(t.short_branch)
|
|
*
|
|
> 4
|
|
> 5
|
|
>>> t = Tree.from_str("2*3+4*5")
|
|
>>> print(t)
|
|
+
|
|
> *
|
|
| > 2
|
|
| > 3
|
|
> *
|
|
| > 4
|
|
| > 5
|
|
>>> print(t.short_branch)
|
|
*
|
|
> 2
|
|
> 3
|
|
"""
|
|
try:
|
|
l_depth = self.left_value.depth()
|
|
except AttributeError:
|
|
l_depth = 0
|
|
try:
|
|
r_depth = self.right_value.depth()
|
|
except AttributeError:
|
|
r_depth = 0
|
|
|
|
if l_depth <= r_depth:
|
|
return self.left_value
|
|
else:
|
|
return self.right_value
|
|
|
|
@property
|
|
def long_branch(self):
|
|
""" Get the long branch of the tree, right if same depth
|
|
|
|
:return: A tree or a leaf value
|
|
|
|
:example:
|
|
>>> t = Tree.from_str("2+3*4")
|
|
>>> print(t.long_branch)
|
|
*
|
|
> 3
|
|
> 4
|
|
>>> t = Tree.from_str("2*3+4*5")
|
|
>>> print(t)
|
|
+
|
|
> *
|
|
| > 2
|
|
| > 3
|
|
> *
|
|
| > 4
|
|
| > 5
|
|
>>> print(t.long_branch)
|
|
*
|
|
> 4
|
|
> 5
|
|
>>> t = Tree.from_str("2*3+4")
|
|
>>> print(t.long_branch)
|
|
*
|
|
> 2
|
|
> 3
|
|
"""
|
|
try:
|
|
l_depth = self.left_value.depth()
|
|
except AttributeError:
|
|
l_depth = 0
|
|
try:
|
|
r_depth = self.right_value.depth()
|
|
except AttributeError:
|
|
r_depth = 0
|
|
|
|
if l_depth <= r_depth:
|
|
return self.right_value
|
|
else:
|
|
return self.left_value
|
|
|
|
def balance(self, exclude_nodes = []):
|
|
""" Recursively balance the tree without permutting different nodes
|
|
|
|
:return: balanced tree
|
|
|
|
:example:
|
|
>>> t = Tree.from_str("1+2+3+4+5+6+7+8+9")
|
|
>>> print(t)
|
|
+
|
|
> +
|
|
| > +
|
|
| | > +
|
|
| | | > +
|
|
| | | | > +
|
|
| | | | | > +
|
|
| | | | | | > +
|
|
| | | | | | | > 1
|
|
| | | | | | | > 2
|
|
| | | | | | > 3
|
|
| | | | | > 4
|
|
| | | | > 5
|
|
| | | > 6
|
|
| | > 7
|
|
| > 8
|
|
> 9
|
|
>>> bal_t = t.balance()
|
|
>>> print(bal_t)
|
|
+
|
|
> +
|
|
| > +
|
|
| | > +
|
|
| | | > 1
|
|
| | | > 2
|
|
| | > 3
|
|
| > +
|
|
| | > 4
|
|
| | > 5
|
|
> +
|
|
| > 6
|
|
| > +
|
|
| | > 7
|
|
| | > +
|
|
| | | > 8
|
|
| | | > 9
|
|
|
|
>>> t = Tree.from_str("0*1*2*3*4+5+6+7+8+9")
|
|
>>> print(t)
|
|
+
|
|
> +
|
|
| > +
|
|
| | > +
|
|
| | | > +
|
|
| | | | > *
|
|
| | | | | > *
|
|
| | | | | | > *
|
|
| | | | | | | > *
|
|
| | | | | | | | > 0
|
|
| | | | | | | | > 1
|
|
| | | | | | | > 2
|
|
| | | | | | > 3
|
|
| | | | | > 4
|
|
| | | | > 5
|
|
| | | > 6
|
|
| | > 7
|
|
| > 8
|
|
> 9
|
|
>>> bal_t = t.balance()
|
|
>>> print(bal_t)
|
|
+
|
|
> *
|
|
| > *
|
|
| | > *
|
|
| | | > 0
|
|
| | | > 1
|
|
| | > 2
|
|
| > *
|
|
| | > 3
|
|
| | > 4
|
|
> +
|
|
| > +
|
|
| | > 5
|
|
| | > 6
|
|
| > +
|
|
| | > 7
|
|
| | > +
|
|
| | | > 8
|
|
| | | > 9
|
|
|
|
>>> t = Tree.from_str("0+1+2+3+4+5*6*7*8*9")
|
|
>>> print(t)
|
|
+
|
|
> +
|
|
| > +
|
|
| | > +
|
|
| | | > +
|
|
| | | | > 0
|
|
| | | | > 1
|
|
| | | > 2
|
|
| | > 3
|
|
| > 4
|
|
> *
|
|
| > *
|
|
| | > *
|
|
| | | > *
|
|
| | | | > 5
|
|
| | | | > 6
|
|
| | | > 7
|
|
| | > 8
|
|
| > 9
|
|
>>> bal_t = t.balance()
|
|
>>> print(bal_t)
|
|
+
|
|
> +
|
|
| > +
|
|
| | > +
|
|
| | | > 0
|
|
| | | > 1
|
|
| | > 2
|
|
| > +
|
|
| | > 3
|
|
| | > 4
|
|
> *
|
|
| > *
|
|
| | > *
|
|
| | | > 5
|
|
| | | > 6
|
|
| | > 7
|
|
| > *
|
|
| | > 8
|
|
| | > 9
|
|
>>> t = Tree.from_str("0+1+2+3+4+5/6/7/8/9")
|
|
>>> print(t)
|
|
+
|
|
> +
|
|
| > +
|
|
| | > +
|
|
| | | > +
|
|
| | | | > 0
|
|
| | | | > 1
|
|
| | | > 2
|
|
| | > 3
|
|
| > 4
|
|
> /
|
|
| > /
|
|
| | > /
|
|
| | | > /
|
|
| | | | > 5
|
|
| | | | > 6
|
|
| | | > 7
|
|
| | > 8
|
|
| > 9
|
|
>>> bal_t = t.balance(exclude_nodes=['/'])
|
|
>>> print(bal_t)
|
|
+
|
|
> +
|
|
| > +
|
|
| | > +
|
|
| | | > 0
|
|
| | | > 1
|
|
| | > 2
|
|
| > +
|
|
| | > 3
|
|
| | > 4
|
|
> /
|
|
| > /
|
|
| | > /
|
|
| | | > /
|
|
| | | | > 5
|
|
| | | | > 6
|
|
| | | > 7
|
|
| | > 8
|
|
| > 9
|
|
|
|
"""
|
|
try:
|
|
l_depth = self.left_value.depth()
|
|
except AttributeError:
|
|
l_depth = 1
|
|
try:
|
|
r_depth = self.right_value.depth()
|
|
except AttributeError:
|
|
r_depth = 1
|
|
|
|
if l_depth > r_depth+1 and\
|
|
self.node == self.left_value.node and \
|
|
self.node not in exclude_nodes:
|
|
new_left = self.left_value.long_branch
|
|
new_right = Tree(self.node,
|
|
self.left_value.short_branch,
|
|
self.right_value)
|
|
return Tree(self.node, new_left, new_right).balance(exclude_nodes)
|
|
|
|
if r_depth > l_depth+1 and\
|
|
self.node == self.right_value.node and \
|
|
self.node not in exclude_nodes:
|
|
new_right = self.right_value.long_branch
|
|
new_left = Tree(self.node,
|
|
self.left_value,
|
|
self.right_value.short_branch)
|
|
return Tree(self.node, new_left, new_right).balance(exclude_nodes)
|
|
|
|
try:
|
|
left_v = self.left_value.balance(exclude_nodes)
|
|
except AttributeError:
|
|
left_v = self.left_value
|
|
try:
|
|
right_v = self.right_value.balance(exclude_nodes)
|
|
except AttributeError:
|
|
right_v = self.right_value
|
|
|
|
return Tree(self.node, left_v, right_v)
|
|
|
|
|
|
class MutableTree(Tree):
|
|
|
|
"""
|
|
Mutable Tree.
|
|
|
|
It is used to build a new tree before fixing it into a Tree
|
|
|
|
"""
|
|
def __init__(self,
|
|
node = None,
|
|
left_value = None,
|
|
right_value = None):
|
|
"""
|
|
Initiate a tree with potentialy None values
|
|
|
|
:Example:
|
|
|
|
>>> t = MutableTree()
|
|
>>> t.node, t.left_value, t.right_value
|
|
(None, None, None)
|
|
>>> t = MutableTree('*', 1)
|
|
>>> t.node, t.left_value, t.right_value
|
|
('*', 1, None)
|
|
"""
|
|
|
|
self.node = node
|
|
|
|
self.left_value = left_value
|
|
self.right_value = right_value
|
|
|
|
@classmethod
|
|
def from_str(cls, expression, convert_to_mo=True):
|
|
""" Initiate the MutableTree
|
|
|
|
:example:
|
|
>>> t = MutableTree.from_str("2+3*4")
|
|
>>> print(t)
|
|
+
|
|
> 2
|
|
> *
|
|
| > 3
|
|
| > 4
|
|
>>> t = MutableTree.from_str("(2+3)*4")
|
|
>>> print(t)
|
|
*
|
|
> +
|
|
| > 2
|
|
| > 3
|
|
> 4
|
|
>>> t = MutableTree.from_str("4*(-2+3)")
|
|
>>> print(t)
|
|
*
|
|
> 4
|
|
> +
|
|
| > -2
|
|
| > 3
|
|
>>> t = MutableTree.from_str("1+2*3+4*5")
|
|
>>> print(t)
|
|
+
|
|
> +
|
|
| > 1
|
|
| > *
|
|
| | > 2
|
|
| | > 3
|
|
> *
|
|
| > 4
|
|
| > 5
|
|
>>> t = MutableTree.from_str("1+2*3*4")
|
|
>>> print(t)
|
|
+
|
|
> 1
|
|
> *
|
|
| > *
|
|
| | > 2
|
|
| | > 3
|
|
| > 4
|
|
>>> t = MutableTree.from_str("6x + (8 + 3) * x")
|
|
>>> print(t)
|
|
+
|
|
> *
|
|
| > 6
|
|
| > x
|
|
> *
|
|
| > +
|
|
| | > 8
|
|
| | > 3
|
|
| > x
|
|
|
|
"""
|
|
str_2_mut_tree = str2(cls.sink, convert_to_mo)
|
|
return str_2_mut_tree(expression)
|
|
|
|
@classmethod
|
|
@coroutine
|
|
def sink(cls):
|
|
""" Sink which build a build a mutabletree
|
|
|
|
Returns the built tree when STOOOP is thrown
|
|
|
|
:example:
|
|
>>> sink = MutableTree.sink()
|
|
>>> for i in ["1", "+", "2", "*", "3"]:
|
|
... sink.send(i)
|
|
>>> a = sink.throw(STOOOP)
|
|
>>> print(a)
|
|
+
|
|
> 1
|
|
> *
|
|
| > 2
|
|
| > 3
|
|
>>> sink = MutableTree.sink()
|
|
>>> for i in ["1", "*", "2", "+", "3"]:
|
|
... sink.send(i)
|
|
>>> a = sink.throw(STOOOP)
|
|
>>> print(a)
|
|
+
|
|
> *
|
|
| > 1
|
|
| > 2
|
|
> 3
|
|
>>> sink = MutableTree.sink()
|
|
>>> for i in ["-", "1", "+", "2"]:
|
|
... sink.send(i)
|
|
>>> a = sink.throw(STOOOP)
|
|
>>> print(a)
|
|
+
|
|
> -
|
|
| > None
|
|
| > 1
|
|
> 2
|
|
>>> sink = MutableTree.sink()
|
|
>>> for i in ["1", "+", "2"]:
|
|
... sink.send(i)
|
|
>>> t = sink.throw(STOOOP)
|
|
>>> sink = MutableTree.sink()
|
|
>>> for i in ["1", "+", t, "*", 5]:
|
|
... sink.send(i)
|
|
>>> a = sink.throw(STOOOP)
|
|
>>> print(a)
|
|
+
|
|
> 1
|
|
> *
|
|
| > +
|
|
| | > 1
|
|
| | > 2
|
|
| > 5
|
|
|
|
"""
|
|
ans = cls()
|
|
nested_tree = None
|
|
try:
|
|
while True:
|
|
c = yield
|
|
if c is not None:
|
|
if is_operator(c):
|
|
try:
|
|
ans.set_node(c)
|
|
except ValueError:
|
|
if OPERATORS[c]["precedence"] > OPERATORS[ans.node]["precedence"]:
|
|
# the operation has to be done first
|
|
if nested_tree is not None:
|
|
b_tree = cls(c, nested_tree, None)
|
|
nested_tree = None
|
|
ans.append(b_tree)
|
|
else:
|
|
ans.append_bot(c)
|
|
else:
|
|
if nested_tree is not None:
|
|
ans.append(nested_tree)
|
|
nested_tree = None
|
|
ans.append_top(c)
|
|
else:
|
|
if nested_tree is not None:
|
|
ans.set_left_value(nested_tree)
|
|
nested_tree = None
|
|
|
|
else:
|
|
try:
|
|
c.node
|
|
except AttributeError:
|
|
ans.append(c)
|
|
else:
|
|
nested_tree = c
|
|
except STOOOP:
|
|
if nested_tree is not None:
|
|
ans.append(nested_tree)
|
|
yield ans
|
|
|
|
def set_node(self, value):
|
|
""" Set the node value.
|
|
Once it has been changed it can't be changed again.
|
|
|
|
:example:
|
|
>>> t = MutableTree()
|
|
>>> t.node
|
|
>>> t.set_node("*")
|
|
>>> t.node
|
|
'*'
|
|
>>> t.set_node("+")
|
|
Traceback (most recent call last):
|
|
...
|
|
ValueError: The node of the tree is already set
|
|
"""
|
|
if self.node is None:
|
|
self.node = value
|
|
else:
|
|
raise ValueError("The node of the tree is already set")
|
|
|
|
def set_left_value(self, value):
|
|
""" Set the left value.
|
|
Once it has been changed it can't be changed again.
|
|
|
|
:example:
|
|
>>> t = MutableTree()
|
|
>>> t.left_value
|
|
>>> t.set_left_value(1)
|
|
>>> t.left_value
|
|
1
|
|
>>> t.set_left_value(2)
|
|
Traceback (most recent call last):
|
|
...
|
|
ValueError: The left branch is full, use set_right_value
|
|
"""
|
|
if self.left_value is None:
|
|
self.left_value = value
|
|
else:
|
|
try:
|
|
self.left_value.append(value)
|
|
except AttributeError:
|
|
raise ValueError("The left branch is full, use set_right_value")
|
|
|
|
def set_right_value(self, value):
|
|
""" Set the right value.
|
|
Once it has been changed it can't be changed again.
|
|
|
|
:example:
|
|
>>> t = MutableTree()
|
|
>>> t.right_value
|
|
>>> t.set_right_value(2)
|
|
>>> t.right_value
|
|
2
|
|
>>> t.set_right_value(3)
|
|
Traceback (most recent call last):
|
|
...
|
|
ValueError: The right branch is full, use append_top or append_bot
|
|
>>> # potentielle source de problèmes??
|
|
>>> t = MutableTree()
|
|
>>> t.set_right_value([1])
|
|
>>> t.right_value
|
|
[1]
|
|
>>> t.set_right_value(3)
|
|
"""
|
|
if self.right_value is None:
|
|
self.right_value = value
|
|
else:
|
|
try:
|
|
self.right_value.append(value)
|
|
except AttributeError:
|
|
raise ValueError("The right branch is full, use append_top or append_bot")
|
|
|
|
def append(self, value):
|
|
""" Append the value at the bottom of the tree.
|
|
|
|
In order to enable operator with arity 1, left value can be set only
|
|
before the node is set, assuming that those operator are placed before
|
|
operand.
|
|
|
|
It tries to add it on left branch first.
|
|
If it fails, the value is appened on right branch.
|
|
|
|
:example:
|
|
>>> t = MutableTree()
|
|
>>> t.append(1)
|
|
>>> t.set_node("*")
|
|
>>> t.append(2)
|
|
>>> print(t)
|
|
*
|
|
> 1
|
|
> 2
|
|
>>> t.append(3)
|
|
Traceback (most recent call last):
|
|
...
|
|
ValueError: The right branch is full, use append_top or append_bot
|
|
|
|
>>> t1 = MutableTree()
|
|
>>> t1.append(1)
|
|
>>> t1.set_node("*")
|
|
>>> t2 = MutableTree()
|
|
>>> t1.append(t2)
|
|
>>> t1.append(2)
|
|
>>> t2.set_node("+")
|
|
>>> t1.append(3)
|
|
>>> print(t1)
|
|
*
|
|
> 1
|
|
> +
|
|
| > 2
|
|
| > 3
|
|
|
|
>>> t1 = MutableTree()
|
|
>>> t1.set_node("-")
|
|
>>> t1.append(1)
|
|
>>> print(t1)
|
|
-
|
|
> None
|
|
> 1
|
|
"""
|
|
if self.node is None:
|
|
try:
|
|
self.set_left_value(value)
|
|
except ValueError:
|
|
self.set_right_value(value)
|
|
else:
|
|
self.set_right_value(value)
|
|
|
|
def append_top(self, node):
|
|
""" Append node into the tree at the top
|
|
|
|
:example:
|
|
|
|
>>> t = MutableTree("*", 1, 2)
|
|
>>> t.append_top("+")
|
|
>>> print(t)
|
|
+
|
|
> *
|
|
| > 1
|
|
| > 2
|
|
> None
|
|
"""
|
|
|
|
#self_cp = MutableTree.from_any_tree(self)
|
|
self_cp = MutableTree()
|
|
self_cp.set_node(self.node)
|
|
self_cp.set_left_value(self.left_value)
|
|
self_cp.set_right_value(self.right_value)
|
|
|
|
self.left_value, self.node, self.right_value = self_cp, node, None
|
|
|
|
def append_bot(self, node):
|
|
""" Append node into the tree at the bottom right_value
|
|
|
|
:example:
|
|
|
|
>>> t = MutableTree("*", 1, 2)
|
|
>>> t.append_bot("+")
|
|
>>> print(t)
|
|
*
|
|
> 1
|
|
> +
|
|
| > 2
|
|
| > None
|
|
>>> t.append(3)
|
|
>>> print(t)
|
|
*
|
|
> 1
|
|
> +
|
|
| > 2
|
|
| > 3
|
|
>>> t.append_bot("+")
|
|
>>> print(t)
|
|
*
|
|
> 1
|
|
> +
|
|
| > +
|
|
| | > 2
|
|
| | > 3
|
|
| > None
|
|
|
|
"""
|
|
|
|
rv = self.right_value
|
|
try:
|
|
if node == rv.node:
|
|
rv.append_top(node)
|
|
else:
|
|
rv.append_bot(node)
|
|
except AttributeError:
|
|
nright_value = MutableTree()
|
|
nright_value.set_node(node)
|
|
nright_value.set_left_value(rv)
|
|
|
|
self.right_value = nright_value
|
|
|
|
|
|
class LeafTree(Tree):
|
|
|
|
""" A LeafTree is a tree which act as if it is a leaf.
|
|
It blocks thoses methods:
|
|
- apply
|
|
- 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_last_level on a LeafTree")
|
|
|
|
|
|
class AssocialTree(Tree):
|
|
|
|
""" Tree which concider every subtree with a node different from itself
|
|
as a Leaf
|
|
"""
|
|
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 = AssocialTree.from_str("3*4+2", convert_to_mo=False)
|
|
>>> print(t)
|
|
+
|
|
> *
|
|
| > 3
|
|
| > 4
|
|
> 2
|
|
>>> print(t.map_on_leaf(lambda x:10*x))
|
|
Traceback (most recent call last):
|
|
...
|
|
TypeError: unsupported operand type(s) for *: 'int' and 'AssocialTree'
|
|
>>> def try_multiply_ten(x):
|
|
... try:
|
|
... return x*10
|
|
... except:
|
|
... return x
|
|
>>> print(t.map_on_leaf(try_multiply_ten))
|
|
+
|
|
> *
|
|
| > 3
|
|
| > 4
|
|
> 20
|
|
|
|
"""
|
|
try:
|
|
if self.left_value.node == self.node:
|
|
left_applied = self.left_value.map_on_leaf(function)
|
|
else:
|
|
left_applied = function(self.left_value)
|
|
except AttributeError:
|
|
left_applied = function(self.left_value)
|
|
|
|
try:
|
|
if self.right_value.node == self.node:
|
|
right_applied = self.right_value.map_on_leaf(function)
|
|
else:
|
|
right_applied = function(self.right_value)
|
|
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)
|
|
('+', (('+', (<MOnumber 3>, <MOnumber 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) ]
|
|
[<class 'mapytex.calculus.core.MO.mo.MOnumber'>, <class 'mapytex.calculus.core.MO.mo.MOnumber'>, <class 'mapytex.calculus.core.tree.AssocialTree'>]
|
|
"""
|
|
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)
|
|
|
|
def balance(self):
|
|
""" Balance the tree
|
|
|
|
:example:
|
|
>>> t = AssocialTree.from_str("1+2+3+4+5+6+7+8+9")
|
|
>>> print(t)
|
|
+
|
|
> +
|
|
| > +
|
|
| | > +
|
|
| | | > +
|
|
| | | | > +
|
|
| | | | | > +
|
|
| | | | | | > +
|
|
| | | | | | | > 1
|
|
| | | | | | | > 2
|
|
| | | | | | > 3
|
|
| | | | | > 4
|
|
| | | | > 5
|
|
| | | > 6
|
|
| | > 7
|
|
| > 8
|
|
> 9
|
|
>>> bal_t = t.balance()
|
|
>>> print(bal_t)
|
|
+
|
|
> +
|
|
| > +
|
|
| | > 1
|
|
| | > 2
|
|
| > +
|
|
| | > 3
|
|
| | > 4
|
|
> +
|
|
| > +
|
|
| | > 5
|
|
| | > 6
|
|
| > +
|
|
| | > 7
|
|
| | > +
|
|
| | | > 8
|
|
| | | > 9
|
|
>>> t = AssocialTree.from_str("1*2*3*4*5+6+7+8+9")
|
|
>>> print(t)
|
|
+
|
|
> +
|
|
| > +
|
|
| | > +
|
|
| | | > *
|
|
| | | | > *
|
|
| | | | | > *
|
|
| | | | | | > *
|
|
| | | | | | | > 1
|
|
| | | | | | | > 2
|
|
| | | | | | > 3
|
|
| | | | | > 4
|
|
| | | | > 5
|
|
| | | > 6
|
|
| | > 7
|
|
| > 8
|
|
> 9
|
|
>>> bal_t = t.balance()
|
|
>>> print(bal_t)
|
|
+
|
|
> +
|
|
| > *
|
|
| | > *
|
|
| | | > 1
|
|
| | | > 2
|
|
| | > *
|
|
| | | > 3
|
|
| | | > *
|
|
| | | | > 4
|
|
| | | | > 5
|
|
| > 6
|
|
> +
|
|
| > 7
|
|
| > +
|
|
| | > 8
|
|
| | > 9
|
|
|
|
|
|
:returns: Balanced Tree (not AssocialTree)
|
|
|
|
"""
|
|
leafs = self.get_leafs()
|
|
balanced_leafs = []
|
|
for l in leafs:
|
|
try:
|
|
balanced_leafs.append(l.balance())
|
|
except AttributeError:
|
|
balanced_leafs.append(l)
|
|
|
|
t = Tree.from_list(self.node, balanced_leafs)
|
|
return t
|
|
|
|
def organise_by(self,
|
|
signature=lambda x: type(x),
|
|
recursive=True,
|
|
exclude_nodes=[]):
|
|
""" Reoganise AssocialTree base on self order and groups by signature
|
|
|
|
:param signature: grouping function (default type)
|
|
:param recursive: treat nested AssocialTree the same way (default True)
|
|
:param exclude_nodes: do not organise trees with thoses nodes (default [])
|
|
|
|
:return: an organise version of self
|
|
|
|
:example:
|
|
>>> t = AssocialTree.from_list('+', [3, 4.1, 'y', 55, 2.3, 'x'])
|
|
>>> print(t)
|
|
+
|
|
> +
|
|
| > 3
|
|
| > +
|
|
| | > 4.1
|
|
| | > y
|
|
> +
|
|
| > 55
|
|
| > +
|
|
| | > 2.3
|
|
| | > x
|
|
>>> print(t.organise_by())
|
|
+
|
|
> +
|
|
| > 3
|
|
| > 55
|
|
> +
|
|
| > +
|
|
| | > 4.1
|
|
| | > 2.3
|
|
| > +
|
|
| | > y
|
|
| | > x
|
|
>>> t = AssocialTree.from_list('+', [1, 'x', 3, 'y'])
|
|
>>> T = AssocialTree.from_list('*', [5, 'v', 6, 'w', t])
|
|
>>> print(T)
|
|
*
|
|
> *
|
|
| > 5
|
|
| > v
|
|
> *
|
|
| > 6
|
|
| > *
|
|
| | > w
|
|
| | > +
|
|
| | | > +
|
|
| | | | > 1
|
|
| | | | > x
|
|
| | | > +
|
|
| | | | > 3
|
|
| | | | > y
|
|
>>> print(T.organise_by())
|
|
*
|
|
> *
|
|
| > 5
|
|
| > 6
|
|
> *
|
|
| > *
|
|
| | > v
|
|
| | > w
|
|
| > +
|
|
| | > +
|
|
| | | > 1
|
|
| | | > 3
|
|
| | > +
|
|
| | | > x
|
|
| | | > y
|
|
>>> print(T.organise_by(recursive=False))
|
|
*
|
|
> *
|
|
| > 5
|
|
| > 6
|
|
> *
|
|
| > *
|
|
| | > v
|
|
| | > w
|
|
| > +
|
|
| | > +
|
|
| | | > 1
|
|
| | | > x
|
|
| | > +
|
|
| | | > 3
|
|
| | | > y
|
|
>>> print(T.organise_by(exclude_nodes=['*']))
|
|
*
|
|
> *
|
|
| > 5
|
|
| > v
|
|
> *
|
|
| > 6
|
|
| > *
|
|
| | > w
|
|
| | > +
|
|
| | | > +
|
|
| | | | > 1
|
|
| | | | > 3
|
|
| | | > +
|
|
| | | | > x
|
|
| | | | > y
|
|
>>> t = AssocialTree.from_str('1/2/3/4')
|
|
>>> T = AssocialTree.from_list('+', [5, t])
|
|
>>> print(T)
|
|
+
|
|
> 5
|
|
> /
|
|
| > /
|
|
| | > /
|
|
| | | > 1
|
|
| | | > 2
|
|
| | > 3
|
|
| > 4
|
|
>>> print(T.organise_by(exclude_nodes=["/"]))
|
|
+
|
|
> 5
|
|
> /
|
|
| > /
|
|
| | > /
|
|
| | | > 1
|
|
| | | > 2
|
|
| | > 3
|
|
| > 4
|
|
"""
|
|
if self.node not in exclude_nodes:
|
|
groups = {}
|
|
for leaf in self.get_leafs():
|
|
if signature(leaf) in groups:
|
|
groups[signature(leaf)].append(leaf)
|
|
else:
|
|
groups[signature(leaf)] = [leaf]
|
|
|
|
subtrees = []
|
|
for group in groups.values():
|
|
try:
|
|
subtrees.append(Tree.from_list(self.node, group))
|
|
except ValueError:
|
|
subtrees.append(*group)
|
|
if len(subtrees) > 1:
|
|
tree = Tree.from_list(self.node, subtrees)
|
|
else:
|
|
tree = subtrees[0]
|
|
else:
|
|
tree = self
|
|
|
|
if recursive:
|
|
|
|
def contaminate_organise(leaf):
|
|
try:
|
|
return leaf.organise_by(signature, recursive, exclude_nodes)
|
|
except AttributeError:
|
|
return leaf
|
|
|
|
return AssocialTree.from_any_tree(tree).\
|
|
map_on_leaf(contaminate_organise)
|
|
|
|
return tree
|
|
|
|
if __name__ == "__main__":
|
|
a = MutableTree.from_str("(1+2)*3")
|
|
print(a)
|
|
|
|
# -----------------------------
|
|
# Reglages pour 'vim'
|
|
# vim:set autoindent expandtab tabstop=4 shiftwidth=4:
|
|
# cursor: 16 del
|