From a751e346d3d298caf2f84e8e90bed586d8727e54 Mon Sep 17 00:00:00 2001 From: Bertrand Benjamin Date: Sun, 21 Jan 2018 11:26:34 +0300 Subject: [PATCH] Import work from other repo --- mapytex/calculus/__init__.py | 20 +- mapytex/calculus/core/__init__.py | 21 + mapytex/calculus/core/coroutine.py | 33 ++ mapytex/calculus/core/operator.py | 22 + mapytex/calculus/core/str2.py | 507 ++++++++++++++++++++++ mapytex/calculus/core/tree.py | 644 ++++++++++++++++++++++++++++ mapytex/calculus/core/tree_tools.py | 92 ++++ 7 files changed, 1331 insertions(+), 8 deletions(-) create mode 100644 mapytex/calculus/core/__init__.py create mode 100644 mapytex/calculus/core/coroutine.py create mode 100644 mapytex/calculus/core/operator.py create mode 100644 mapytex/calculus/core/str2.py create mode 100644 mapytex/calculus/core/tree.py create mode 100644 mapytex/calculus/core/tree_tools.py diff --git a/mapytex/calculus/__init__.py b/mapytex/calculus/__init__.py index fd77fb8..6b4cdfd 100644 --- a/mapytex/calculus/__init__.py +++ b/mapytex/calculus/__init__.py @@ -1,12 +1,16 @@ -#!/usr/bin/env python -# encoding: utf-8 +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# vim:fenc=utf-8 +# +# Copyright © 2017 lafrite +# +# Distributed under terms of the MIT license. -from .expression import Expression -from .polynom import Polynom -from .fraction import Fraction -from .equation import Equation -from .random_expression import random_str -from .render import txt +""" +Abstracts tools for calculs manipulations +""" + +__all__ = [] # ----------------------------- diff --git a/mapytex/calculus/core/__init__.py b/mapytex/calculus/core/__init__.py new file mode 100644 index 0000000..e999710 --- /dev/null +++ b/mapytex/calculus/core/__init__.py @@ -0,0 +1,21 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# vim:fenc=utf-8 +# +# Copyright © 2017 lafrite +# +# Distributed under terms of the MIT license. + +""" +Abstracts tools for calculs manipulations +""" + +__all__ = ["Tree"] + +from .tree import Tree + + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del diff --git a/mapytex/calculus/core/coroutine.py b/mapytex/calculus/core/coroutine.py new file mode 100644 index 0000000..b2f00db --- /dev/null +++ b/mapytex/calculus/core/coroutine.py @@ -0,0 +1,33 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# vim:fenc=utf-8 +# +# Copyright © 2017 lafrite +# +# Distributed under terms of the MIT license. + +""" +Coroutine and sink tools +""" + +from functools import wraps + +__all__ = ["coroutine", "STOOOP", "RESTAAART"] + +def coroutine(func): + @wraps(func) + def start(*args,**kwargs): + cr = func(*args,**kwargs) + next(cr) + return cr + return start + +class STOOOP(BaseException): pass +class RESTAAART(BaseException): pass + + + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del diff --git a/mapytex/calculus/core/operator.py b/mapytex/calculus/core/operator.py new file mode 100644 index 0000000..34781fa --- /dev/null +++ b/mapytex/calculus/core/operator.py @@ -0,0 +1,22 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# vim:fenc=utf-8 +# +# Copyright © 2017 lafrite +# +# Distributed under terms of the MIT license. + + +OPERATORS = { + "+": {"priority":0}, + "-": {"priority":1}, + "*": {"priority":2}, + "/": {"priority":3}, + "^": {"priority":4}, +} + + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del diff --git a/mapytex/calculus/core/str2.py b/mapytex/calculus/core/str2.py new file mode 100644 index 0000000..0fb6241 --- /dev/null +++ b/mapytex/calculus/core/str2.py @@ -0,0 +1,507 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# vim:fenc=utf-8 +# +# Copyright © 2017 lafrite +# +# Distributed under terms of the MIT license. + +""" +Converting a string with coroutines +""" + +from functools import partial +from .coroutine import * + +__all__ = ["str2", ] + +def maybe_it_is(cara): + """ Return a function which return + + Maybe if cara startwith the argument and True if it is cara + + :exemple: + + >>> it_is_pipo = maybe_it_is("pipo") + >>> it_is_pipo("pi") + 'maybe' + >>> it_is_pipo("pipo") + True + >>> it_is_pipo("uo") + False + >>> it_is_iuo = maybe_it_is("iuo") + >>> it_is_iuo("pi") + False + >>> it_is_iuo("iuo") + True + >>> it_is_iuo("uo") + False + """ + def func(c): + if c == cara: + return True + elif cara.startswith(c): + return "maybe" + else: + return False + return func + +def something_in(cara_list): + """ Return a function which sais whether a caracter is in cara_list or not + """ + def func(c): + if c in cara_list: + return True + else: + return False + return func + +@coroutine +def lookfor(condition, replace = lambda x:''.join(x)): + """ Sink which is looking for term and yield replace founded patern + with the lenght of the accumulation. + + It can be reset by sending None + + :param condition: conditional function which returns a boolean or "maybe" + :param replace: function which make a final transformation to the founded + string. Id by default. + + :example: + + >>> lf = lookfor(maybe_it_is("pipo"), lambda x: 'remp') + >>> acc = "" + >>> for i in 'popipo': + ... a = lf.send(i) + ... if a == "maybe": + ... acc += i + ... elif a: + ... acc = "" + ... print(a) + ... else: + ... print(acc + i) + ... acc = "" + po + remp + >>> lf = lookfor(maybe_it_is("po") , lambda x: ''.join(x).upper()) + >>> acc = "" + >>> for i in 'popipo': + ... a = lf.send(i) + ... if a == "maybe": + ... acc += i + ... elif a: + ... acc = "" + ... print(a) + ... else: + ... print(acc + i) + ... acc = "" + PO + pi + PO + >>> lf = lookfor(maybe_it_is("pi") , lambda x: 1) + >>> acc = "" + >>> for i in 'popipop': + ... a = lf.send(i) + ... if a == "maybe": + ... acc += i + ... elif a: + ... acc = "" + ... print(a) + ... else: + ... print(acc + i) + ... acc = "" + po + 1 + po + >>> # the p in still processing + >>> acc = "" + >>> for i in 'iop': + ... a = lf.send(i) + ... if a == "maybe": + ... acc += i + ... elif a: + ... acc = "" + ... print(a) + ... else: + ... print(acc + i) + ... acc = "" + 1 + o + >>> # the p in still processing + >>> lf.throw(RESTAAART) + False + >>> # p has been forgot + >>> acc = "" + >>> for i in 'ipopi': + ... a = lf.send(i) + ... if a == "maybe": + ... acc += i + ... elif a: + ... acc = "" + ... print(a) + ... else: + ... print(acc + i) + ... acc = "" + i + po + 1 + + """ + acc = [] + ans = False + while True: + try: + c = (yield ans) + except RESTAAART as err: + acc = [] + ans = False + else: + if c is not None: + acc.append(c) + found = condition(''.join(acc)) + if found == "maybe": + ans = "maybe" + elif found: + ans = replace(acc) + acc = [] + else: + ans = False + acc = [] + +@coroutine +def remember_lookfor(lookfor): + """ Coroutine which remember sent value before the lookfor finds something + + :example: + + >>> lkf = lookfor(maybe_it_is("pipo"), lambda x: 'remp') + >>> rmb_lkf = remember_lookfor(lkf) + >>> for i in 'popipopi': + ... print(rmb_lkf.send(i)) + maybe + False + maybe + maybe + maybe + ['p', 'o', 'remp'] + maybe + maybe + >>> for i in 'popi': + ... print(rmb_lkf.send(i)) + maybe + ['remp'] + maybe + maybe + >>> rmb_lkf.throw(RESTAAART) + ['p', 'i'] + >>> for i in 'popi': + ... print(rmb_lkf.send(i)) + maybe + False + maybe + maybe + + """ + acc = [] + mb_acc = [] + ans = False + while True: + try: + c = (yield ans) + except RESTAAART as err: + lookfor.throw(err) + ans = acc + mb_acc + acc = [] + mb_acc = [] + else: + lkf_state = lookfor.send(c) + if lkf_state == "maybe": + mb_acc.append(c) + ans = "maybe" + elif lkf_state: + ans = acc + [lkf_state] + acc = [] + mb_acc = [] + else: + acc += mb_acc + mb_acc = [] + acc.append(c) + ans = False + +@coroutine +def concurent_broadcast(target, lookfors = []): + """ Coroutine which broadcasts multiple lookfor coroutines and reinitializes + them when one found something + + Same as parallelized_lookfor but coroutine version + + :param target: target to send data to + :param lookfors: list of lookfor coroutines + + :example: + >>> lf1 = lookfor(maybe_it_is("abc"), lambda x: "".join(x).upper()) + >>> searcher = concurent_broadcast(list_sink, [lf1]) + >>> for i in "azabcab": + ... searcher.send(i) + >>> a = searcher.throw(STOOOP) + >>> print(a) + ['a', 'z', 'ABC', 'a', 'b'] + + >>> lf2 = lookfor(maybe_it_is("az")) + >>> searcher = concurent_broadcast(list_sink, [lf1, lf2]) + >>> for i in "azabcabazb": + ... searcher.send(i) + >>> a = searcher.throw(STOOOP) + >>> print(a) + ['az', 'ABC', 'a', 'b', 'az', 'b'] + + >>> lfop = lookfor(something_in("+-*/()"), lambda x: f"op{x}") + >>> searcher = concurent_broadcast(list_sink, [lfop]) + >>> for i in '12+3+234': + ... searcher.send(i) + >>> a = searcher.throw(STOOOP) + >>> print(a) + ['1', '2', "op['+']", '3', "op['+']", '2', '3', '4'] + + >>> # need to remake a searcher otherwise it remenbers old ans + >>> searcher = concurent_broadcast(list_sink, [lfop]) + >>> for i in '12*(3+234)': + ... searcher.send(i) + >>> a = searcher.throw(STOOOP) + >>> print(a) + ['1', '2', "op['*']", "op['(']", '3', "op['+']", '2', '3', '4', "op[')']"] + + >>> lfsqrt = lookfor(maybe_it_is("sqrt"), lambda x: f"op['sqrt']") + >>> searcher = concurent_broadcast(list_sink, [lfop, lfsqrt]) + >>> for i in '3+2*sqrt(3)': + ... searcher.send(i) + >>> a = searcher.throw(STOOOP) + >>> print(a) + ['3', "op['+']", '2', "op['*']", "op['sqrt']", "op['(']", '3', "op[')']"] + """ + try: + target_ = target() + except TypeError: + target_ = target + + lookfors_ = [remember_lookfor(lkf) for lkf in lookfors] + + stock = [] + ans = [] + try: + while True: + found = False + tok = yield + if tok is not None: + for lf in lookfors_: + lf_ans = lf.send(tok) + if lf_ans and lf_ans != "maybe": + found = lf_ans + break + + if found: + for lf in lookfors_: + lf.throw(RESTAAART) + for i in found: + ans = target_.send(i) + except STOOOP as err: + for lf in lookfors_: + last = lf.throw(RESTAAART) + for i in last: + target_.send(i) + yield target_.throw(err) + +@coroutine +def lookforNumbers(target): + """ Coroutine which parse numbers + + + :exemple: + >>> str2list = lookforNumbers(list_sink) + >>> for i in "12+1234*67": + ... str2list.send(i) + >>> a = str2list.throw(STOOOP) + >>> print(a) + ['12', '+', '1234', '*', '67'] + + >>> str2list = lookforNumbers(list_sink) + >>> for i in "1.2+12.34*67": + ... str2list.send(i) + >>> a = str2list.throw(STOOOP) + >>> print(a) + ['1.2', '+', '12.34', '*', '67'] + + >>> str2list = lookforNumbers(list_sink) + >>> for i in "12.3.4*67": + ... str2list.send(i) + Traceback (most recent call last): + ... + ValueError: Can't build a number with 2 dots (current is 12.3) + >>> str2list = lookforNumbers(list_sink) + >>> for i in ".34*67": + ... a = str2list.send(i) + Traceback (most recent call last): + ... + ValueError: Can't build a number starting with a dot + + """ + try: + target_ = target() + except TypeError: + target_ = target + + current = "" + ans = [] + try: + while True: + tok = yield + if tok is not None: + try: + int(tok) + except ValueError: + if tok == '.': + if "." in current: + raise ValueError(f"Can't build a number with 2 dots (current is {current})") + elif len(current) == 0: + raise ValueError(f"Can't build a number starting with a dot") + else: + current += tok + else: + if current: + target_.send(current) + current = "" + target_.send(tok) + else: + current += tok + + except STOOOP as err: + if current: + target_.send(current) + yield target_.throw(err) + +@coroutine +def pparser(target): + """ Parenthesis parser sink + + :example: + + >>> pp = pparser(list_sink) + >>> for i in "buio": + ... pp.send(i) + >>> a = pp.throw(STOOOP) + >>> a + ['b', 'u', 'i', 'o'] + >>> pp = pparser(list_sink) + >>> for i in "agg(bcz)e(i(o))": + ... pp.send(i) + >>> a = pp.throw(STOOOP) + >>> a + ['a', 'g', 'g', ['b', 'c', 'z'], 'e', ['i', ['o']]] + + """ + target_ = target() + + try: + while True: + caract = yield + if caract == "(": + a = yield from pparser(target) + target_.send(a) + elif caract == ")": + a = target_.throw(STOOOP) + return a + else: + target_.send(caract) + except STOOOP as err: + yield target_.throw(err) + +@coroutine +def list_sink(): + """ Testing sink for coroutines + + :example: + + >>> sink = list_sink() + >>> for i in '12+34 * 3': + ... sink.send(i) + >>> a = sink.throw(STOOOP) + >>> a + ['1', '2', '+', '3', '4', ' ', '*', ' ', '3'] + + """ + ans = list() + try: + while True: + c = (yield) + if c is not None: + ans.append(c) + except STOOOP: + yield ans + +def str2(sink): + """ Return a pipeline which parse an expression with the sink as an endpont + + :example: + + >>> str2nestedlist = str2(list_sink) + >>> exp = "12+3*4" + >>> t = str2nestedlist(exp) + >>> print(t) + ['12', '+', '3', '*', '4'] + >>> exp = "12*3+4" + >>> t = str2nestedlist(exp) + >>> print(t) + ['12', '*', '3', '+', '4'] + >>> exp = "12*(3+4)" + >>> t = str2nestedlist(exp) + >>> print(t) + ['12', '*', ['3', '+', '4']] + + >>> from .tree import MutableTree + >>> str2tree = str2(MutableTree.sink) + >>> exp = "12+3*4" + >>> t = str2tree(exp) + >>> print(t) + + + > 12 + > * + | > 3 + | > 4 + >>> exp = "12*3+4" + >>> t = str2tree(exp) + >>> print(t) + + + > * + | > 12 + | > 3 + > 4 + >>> exp = "12*(3+4)" + >>> t = str2tree(exp) + >>> print(t) + * + > 12 + > + + | > 3 + | > 4 + """ + lfop = lookfor(something_in("+-*/")) + operator_corout = partial(concurent_broadcast, lookfors = [lfop]) + + def pipeline(expression): + str2_corout = lookforNumbers(operator_corout(pparser(sink))) + for i in expression: + str2_corout.send(i) + a = str2_corout.throw(STOOOP) + + return a + + return pipeline + + +str2nestedlist = str2(list_sink) + + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del diff --git a/mapytex/calculus/core/tree.py b/mapytex/calculus/core/tree.py new file mode 100644 index 0000000..acf580c --- /dev/null +++ b/mapytex/calculus/core/tree.py @@ -0,0 +1,644 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# vim:fenc=utf-8 +# +# Copyright © 2017 lafrite +# +# Distributed under terms of the MIT license. + +""" +Tree class + +""" + +from .tree_tools import (to_nested_parenthesis, + infix_str_concatenate, + postfix_concatenate, + show_tree, + ) +from .coroutine import * +from .str2 import str2 +from .operator import OPERATORS + +__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 \ + left_value is None or \ + right_value is None: + raise TypeError("Tree can't have empty node or leaf") + + self.node = node + + self.left_value = left_value + self.right_value = right_value + + @classmethod + def from_str(cls, expression): + """ Initiate a tree from an string expression + + :example: + + >>> t = MutableTree.fromStr("2+3*4") + >>> print(t) + + + > 2 + > * + | > 3 + | > 4 + >>> t = MutableTree.fromStr("(2+3)*4") + >>> print(t) + * + > + + | > 2 + | > 3 + > 4 + """ + t = MutableTree.from_str(expression) + return cls.from_mutable_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) + + >>> 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 = Tree.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]) + 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 + + :example: + >>> t = MutableTree("*", 1, 2) + >>> print(Tree.from_mutable_tree(t)) + * + > 1 + > 2 + >>> t1 = MutableTree("*", 1, 2) + >>> t2 = MutableTree("*", t1, 3) + >>> print(Tree.from_mutable_tree(t2)) + * + > * + | > 1 + | > 2 + > 3 + >>> t = MutableTree("*", 1) + >>> print(t) + * + > 1 + > None + >>> Tree.from_mutable_tree(t) + Traceback (most recent call last): + ... + TypeError: Tree can't have empty node or leaf + + """ + node = mutable_tree.node + left_value = mutable_tree.left_value + right_value = mutable_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: + l_value = Tree.from_mutable_tree(left_value) + except AttributeError: + l_value = left_value + + try: + r_value = Tree.from_mutable_tree(right_value) + except AttributeError: + r_value = right_value + + return cls(node, l_value, r_value) + + def map_leaf(self, function): + """ Map on leafs a function + + :param function: take leaf value returns other value + :returns: Tree with calculated leaf + + :example: + + >>> nested_par = ("+", ( + ... ("*", (3, 4)), + ... 2)) + >>> t = Tree.from_nested_parenthesis(nested_par) + + """ + try: + left_applied = self.left_value.apply(function) + except AttributeError: + left_applied = function(self.left_value) + + try: + right_applied = self.right_value.apply(function) + except AttributeError: + right_applied = function(self.right_value) + + return Tree(self.node, left_applied, right_applied) + + def apply_on_last_branches(self, function): + """ Apply the function on last branches before leaf. + + :param function: (op, a, a) -> b + :returns: b if it is a 1 level Tree, Tree otherwise + + """ + try: + # TODO: to change when typify is done |dim. févr. 19 14:49:06 EAT 2017 + left_applied = self.left_value.apply_on_last_branches(function) + except AttributeError: + left_is_leaf = 1 + + try: + # TODO: to change when typify is done |dim. févr. 19 14:49:06 EAT 2017 + left_applied = self.left_value.apply_on_last_branches(function) + except AttributeError: + right_is_leaf = 1 + + if left_is_leaf and right_is_leaf: + return function(self.node, self.left_value, self.right_value) + else: + return Tree(self.node, left_applied, right_applied) + + def apply(self, function): + """ Apply the function to the whole 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)) + + >>> nested_par = ("+", ( + ... ("*", (3, 4)), + ... 2)) + >>> t = Tree.from_nested_parenthesis(nested_par) + >>> t.apply(to_nested) + ('+', (('*', (3, 4)), 2)) + + """ + 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 + + return function(self.node, left_value, right_value) + + 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) + + 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) + + +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 fromStr(cls, expression): + """ Initiate the MutableTree + + :example: + >>> t = MutableTree.fromStr("2+3*4") + >>> print(t) + + + > 2 + > * + | > 3 + | > 4 + >>> t = MutableTree.fromStr("(2+3)*4") + >>> print(t) + * + > + + | > 2 + | > 3 + > 4 + >>> t = MutableTree.fromStr("4*(-2+3)") + >>> print(t) + * + > 4 + > + + | > - + | | > None + | | > 2 + | > 3 + """ + str2mutTree = str2(cls.sink) + return str2mutTree(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 + + """ + ans = cls() + try: + while True: + c = yield + if c is not None: + if c in OPERATORS.keys(): + try: + ans.set_node(c) + except ValueError: + if OPERATORS[c]["priority"] > OPERATORS[ans.node]["priority"]: + ans.append_bot(c) + else: + ans.append_top(c) + else: + ans.append(c) + except STOOOP: + 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, value): + """ Append node into the tree at the top + + :example: + + >>> t = MutableTree("*", 1, 2) + >>> t.append_top("+") + >>> print(t) + + + > * + | > 1 + | > 2 + > None + """ + + 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, value, None + + def append_bot(self, value): + """ Append node into the tree at the bottom right_value + + :example: + + >>> t = MutableTree("*", 1, 2) + >>> t.append_bot("+") + >>> print(t) + * + > 1 + > + + | > 2 + | > None + """ + + rv = self.right_value + nright_value = MutableTree() + nright_value.set_node(value) + nright_value.set_left_value(rv) + + self.right_value = nright_value + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del diff --git a/mapytex/calculus/core/tree_tools.py b/mapytex/calculus/core/tree_tools.py new file mode 100644 index 0000000..88db950 --- /dev/null +++ b/mapytex/calculus/core/tree_tools.py @@ -0,0 +1,92 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# vim:fenc=utf-8 +# +# Copyright © 2017 lafrite +# +# Distributed under terms of the MIT license. + +""" +Functions to manipulate trees +""" + +__all__ = [] + +# Functions on leaf + + +# Functions on (op, left, right) + +def to_nested_parenthesis(op, left, right): + """ Get nested form for arguments + + :exemple: + + >>> to_nested_parenthesis('+', 3, 4) + ('+', (3, 4)) + """ + return (op, (left, right)) + +def infix_str_concatenate(op, left, right): + """ Concatenate arguments placing op on the middle. + + :example: + + >>> infix_str_concatenate('+', 1, 2) + '1 + 2' + """ + return f"{left} {op} {right}" + +def postfix_concatenate(op, left, right): + """ Concatenate arguments placing op on the middle. + + :example: + + >>> postfix_concatenate('+', 1, 2) + [1, 2, '+'] + >>> p = postfix_concatenate('+', 1, 2) + >>> postfix_concatenate('*', p, 3) + [1, 2, '+', 3, '*'] + >>> postfix_concatenate('*', 3, p) + [3, 1, 2, '+', '*'] + """ + if isinstance(left, list): + left_tokens = left + else: + left_tokens = [left] + if isinstance(right, list): + right_tokens = right + else: + right_tokens = [right] + + return left_tokens + right_tokens + [op] + +def show_tree(op, left, right, sep = "|", node_caracter = ">"): + """ Shape argument to make nice Tree display + + :example: + + >>> print(show_tree("+", 1, 2)) + + + > 1 + > 2 + >>> t1 = show_tree("*", 1, 2) + >>> print(show_tree("+", t1, 3)) + + + > * + | > 1 + | > 2 + > 3 + + """ + node_suffix = f"\n {node_caracter} " + leaf_suffix = f"\n {sep}" + left_slided = leaf_suffix.join(str(left).splitlines()) + right_slided = leaf_suffix.join(str(right).splitlines()) + return node_suffix.join([op, left_slided, right_slided]) + + +# ----------------------------- +# Reglages pour 'vim' +# vim:set autoindent expandtab tabstop=4 shiftwidth=4: +# cursor: 16 del