#! /usr/bin/env python # -*- coding: utf-8 -*- # vim:fenc=utf-8 # # Copyright © 2017 lafrite # # Distributed under terms of the MIT license. """ Expression """ from functools import partial from ..core import AssocialTree, Tree, compute, typing, TypingError from ..core.MO import moify from .tokens import factory from .renders import render class Expression(object): """ Expression class :example: >>> e = Expression.from_str("2+3*4") >>> e2 = e.simplify() >>> print(e2) 14 >>> for s in e2.explain(): ... print(s) 2 + 3 * 4 2 + 12 14 """ def __init__(self, tree, ancestor=None): """ """ self._tree = tree self._ancestor = ancestor @classmethod def from_str(cls, string, typing=True): """ Initiate the expression from a string :param string: String to parse to generate the Expression :returns: the expression :example: >>> e = Expression.from_str("2 + 3 * 4") >>> e >>> e = Expression.from_str("2/3") >>> e >>> e = Expression.from_str("2x + 1") >>> e >>> e = Expression.from_str("2x + 1 + 5x^2") >>> e >>> e = Expression.from_str("2x + 1 + 5x") >>> e """ t = Tree.from_str(string) if typing: return cls._post_processing(t) return cls(t) @classmethod def _post_processing(cls, t): """ Post process the tree by typing it """ tt = cls(t)._typing() try: return factory(tt) except TypeError as e: return cls(t) def __str__(self): return render(self._tree) def __repr__(self): return f"" def _order(self, exclude_nodes=["*", "/", "**"]): """ Order the expression base on types :example: >>> e = Expression.from_str("1 + 2x + 3 + 4x") >>> print(e) 1 + 2x + 3 + 4x >>> #print(e._order()) 1 + 3 + 2x + 4x >>> e = Expression.from_str("x + 6x^3 + 1 + 2x^2 + 3 + 4x^2 + 5x") >>> print(e._order()) x + 5x + 6x^3 + 2x^2 + 4x^2 + 1 + 3 """ def signature(leaf): try: leaf.node except AttributeError: try: return leaf.signature except AttributeError: return type(leaf) else: try: typed_leaf = typing( leaf.node, leaf.left_value, leaf.right_value) return typed_leaf.signature except (AttributeError, NotImplementedError, TypingError): return type(leaf) try: self._tree.node except AttributeError: return self organised = AssocialTree.from_any_tree(self._tree).organise_by( signature, recursive=True, exclude_nodes=exclude_nodes ) return Expression(organised) def _optimize(self, exclude_nodes=["/", "**"]): """ Return a copy of self with an optimize tree :example: >>> e = Expression.from_str("2x^2+2x+3x") >>> print(e._tree) + > + | > * | | > 2 | | > ^ | | | > x | | | > 2 | > * | | > 2 | | > x > * | > 3 | > x >>> print(e._optimize()._tree) + > * | > 2 | > ^ | | > x | | > 2 > + | > * | | > 2 | | > x | > * | | > 3 | | > x """ try: # TODO: need to test exclude_nodes |ven. oct. 5 08:51:02 CEST 2018 return Expression(self._tree.balance(exclude_nodes=exclude_nodes)) except AttributeError: return self def _typing(self): """ Build a copy of self with as much typing as possible :example: >>> e = Expression.from_str("2x", typing=False) >>> print(e._tree.map_on_leaf(lambda x: type(x).__name__)) * > MOnumber > MOstr >>> typed_e = e._typing() >>> print(type(typed_e._tree)) >>> typed_e = e._typing() >>> print(type(typed_e._tree)) >>> e = Expression.from_str("2x+3+4/5", typing=False) >>> print(e._tree.map_on_leaf(lambda x: type(x).__name__)) + > + | > * | | > MOnumber | | > MOstr | > MOnumber > / | > MOnumber | > MOnumber >>> typed_e = e._typing() >>> print(e._tree.map_on_leaf(lambda x: type(x).__name__)) + > + | > * | | > MOnumber | | > MOstr | > MOnumber > / | > MOnumber | > MOnumber """ try: return Expression(self._tree.apply(typing)) except AttributeError: return self def _compute(self): """" Compute one step of self """ try: return Expression(self._tree.apply_on_last_level(compute)) except AttributeError: return self def set_ancestor(self, ancestor): """ Set ancestor """ self._ancestor = ancestor def _simplify(self, optimize=True): """ Compute as much as possible the expression :param optimize: bool to optimize tree when it's possible :return: an expression :example: >>> e = Expression.from_str("2+3*4") >>> e >>> f = e._simplify() >>> f >>> f._ancestor """ typed_exp = self._typing() if optimize: organized_exp = typed_exp._order() opt_exp = organized_exp._optimize() else: opt_exp = typed_exp comp_exp = opt_exp._compute() if typed_exp != comp_exp: comp_exp.set_ancestor(self) return comp_exp._simplify(optimize=optimize) typed_exp.set_ancestor(self._ancestor) return typed_exp def simplify(self, optimize=True): """ Simplify the expression, keep the history and factory child :param optimize: bool to optimize tree when it's possible :return: an expression :example: >>> e = Expression.from_str("2+3*4") >>> e >>> f = e.simplify() >>> f >>> f._ancestor """ self._child = self._simplify(optimize=optimize) return factory(self._child, ancestor=self._child._ancestor) def explain(self): """ Yield every calculus step which have lead to self :example: >>> e = Expression.from_str("2+3*4") >>> f = e.simplify() >>> for s in f.explain(): ... print(s) 2 + 3 * 4 2 + 12 14 >>> e = Expression.from_str("1+2+3+4+5+6+7+8+9") >>> f = e.simplify() >>> for s in f.explain(): ... print(s) 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 3 + 7 + 11 + 7 + 17 10 + 11 + 24 10 + 35 45 >>> e = Expression.from_str("1+2+3+4+5+6+7+8+9") >>> f_no_balance = e.simplify(optimize=False) >>> for s in f_no_balance.explain(): ... print(s) 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 3 + 3 + 4 + 5 + 6 + 7 + 8 + 9 6 + 4 + 5 + 6 + 7 + 8 + 9 10 + 5 + 6 + 7 + 8 + 9 15 + 6 + 7 + 8 + 9 21 + 7 + 8 + 9 28 + 8 + 9 36 + 9 45 >>> e = Expression.from_str("1+2+3+4+5*6*7*8*9") >>> f = e.simplify() >>> for s in f.explain(): ... print(s) 1 + 2 + 3 + 4 + 5 * 6 * 7 * 8 * 9 3 + 7 + 30 * 7 * 72 10 + 210 * 72 10 + 15120 15130 >>> e = Expression.from_str("1+2+3+4+5*6*7*8*9") >>> f_no_balance = e.simplify(optimize=False) >>> for s in f_no_balance.explain(): ... print(s) 1 + 2 + 3 + 4 + 5 * 6 * 7 * 8 * 9 3 + 3 + 4 + 30 * 7 * 8 * 9 6 + 4 + 210 * 8 * 9 10 + 1680 * 9 10 + 15120 15130 >>> e = Expression.from_str("1+2/3/4/5") >>> f = e.simplify() >>> for s in f.explain(): ... print(s) 1 + 2 / 3 / 4 / 5 1 + (2 / 3 * 1 / 4) / 5 1 + (2 * 1) / (3 * 4) / 5 1 + 2 / 12 / 5 1 + 2 / 12 * 1 / 5 1 + (2 * 1) / (12 * 5) 1 + 2 / 60 1 / 1 + 2 / 60 (1 * 60) / (1 * 60) + 2 / 60 60 / 60 + 2 / 60 (60 + 2) / 60 62 / 60 """ try: yield from self._ancestor.explain() except AttributeError: yield self else: yield self def __call__(self, value): """ Call a Expression to evaluate itself on value :param value: evaluate the Expression with this value :return: Expression simplified if the value is not a string with a length greater than 1. :examples: >>> f = Expression.from_str("3*x^2 + 2x + 1") >>> for s in f(2).explain(): ... print(s) 3 * 2^2 + 2 * 2 + 1 3 * 4 + 4 + 1 12 + 5 17 >>> f(f(2)) >>> f(17) >>> f("n") >>> f("u_n") >>> f(f) """ tree = self._tree variable = (set(tree.get_leafs(extract_variable)) - {None}).pop() try: dest = value._mo except AttributeError: dest = moify(value) replace_var = partial(replace, origin=variable, dest=dest) tree = tree.map_on_leaf(replace_var) if isinstance(value, str) and len(value) > 1: return Expression(tree) return Expression(tree).simplify() def extract_variable(leaf): try: return leaf.variable except AttributeError: return None def replace(leaf, origin, dest): """ Recursively replace origin to dest in leaf """ try: leaf.tree except AttributeError: if leaf == origin: return dest return leaf replace_var = partial(replace, origin=origin, dest=dest) return leaf.tree.map_on_leaf(replace_var) # ----------------------------- # Reglages pour 'vim' # vim:set autoindent expandtab tabstop=4 shiftwidth=4: # cursor: 16 del