| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230 |
- from traverse import traverse_depth_first
- OPERATORS = (
- ('vv', ),
- ('^^', ),
- ('=', ),
- ('+', '-'),
- ('*', 'mod'),
- ('/', ),
- ('^', '_'),
- )
- NEG_PRED = 3
- MAX_PRED = len(OPERATORS)
- def is_operator(node):
- """
- Check if a given node is an operator (otherwise, it's a function).
- """
- label = node.title()
- return any(map(lambda x: label in x, OPERATORS))
- def pred(node):
- """
- Get the precedence of an operator node.
- """
- # Check binary and n-ary operators
- if not node.is_leaf and len(node) > 1:
- op = node.title()
- for i, group in enumerate(OPERATORS):
- if op in group:
- return i
- # Unary operator and leaves have highest precedence
- return MAX_PRED
- def is_id(node):
- return node.is_leaf and not node.title().isdigit()
- def is_int(node):
- return node.is_leaf and node.title().isdigit()
- def is_power(node):
- return not node.is_leaf and node.title() == '^'
- def generate_line(root):
- """
- Print an expression tree in a single text line. Where needed, add
- parentheses.
- >>> from node import Node, Leaf
- >>> l0, l1 = Leaf(1), Leaf(2)
- >>> print generate_line(l0)
- 1
- >>> plus = Node('+', l0, l1)
- >>> print generate_line(plus)
- 1 + 2
- >>> plus2 = Node('+', l0, l1)
- >>> times = Node('*', plus, plus2)
- >>> print generate_line(times)
- (1 + 2)(1 + 2)
- >>> l2 = Leaf(3)
- >>> uminus = Node('-', l2)
- >>> times = Node('*', plus, uminus)
- >>> print generate_line(times)
- (1 + 2) * -3
- >>> exp = Leaf('x')
- >>> inf = Leaf('oo')
- >>> minus_inf = Node('-', inf)
- >>> integral = Node('int', exp, minus_inf, inf)
- >>> print generate_line(integral)
- int(x, -oo, oo)
- >>> minus = Node('-', Leaf(2), Node('-', Node('*', Leaf(15), Leaf('x'))))
- >>> print generate_line(minus)
- 2 - -15x
- >>> left = Node('/', Leaf(22), Leaf(77))
- >>> right = Node('/', Leaf(28), Leaf(77))
- >>> minus = Node('-', left, right)
- >>> print generate_line(minus)
- 22 / 77 - 28 / 77
- >>> plus = Node('+', left, Node('-', right))
- >>> print generate_line(plus)
- 22 / 77 - 28 / 77
- """
- if not root:
- return '<empty expression>'
- if root.is_leaf:
- return str(root)
- content = {}
- def construct_unary(node):
- op = node.title()
- value = node[0]
- # -a
- # -3 * 4
- # --a
- if value.is_leaf \
- or not ' ' in content[value] or pred(value) > NEG_PRED:
- return op + content[value]
- # -(a + b)
- return '%s(%s)' % (op, content[value])
- def construct_nary(node):
- op = node.title()
- # N-ary operator
- node_pred = pred(node)
- sep = ' ' + op + ' '
- e = []
- for i, child in enumerate(node):
- exp = content[child]
- #if i and op == '+' and exp[:2] == '-(':
- # exp = '-' + exp[2:-1]
- # print 'exp:', exp
- # Check if there is a precedence conflict
- # If so, add parentheses
- child_pred = pred(child)
- if child.negated:
- # (-a) ^ b
- # -a ^ -b
- # (-a) * b
- # a * -b
- # (-a) / b
- if node_pred > NEG_PRED:
- exp = '(' + exp + ')'
- elif child_pred < node_pred:
- exp = '(' + exp + ')'
- elif child_pred == node_pred:
- if i and (op != child.title() or op == '/' \
- or (op == '+' and child[1].negated)):
- exp = '(' + exp + ')'
- elif not i and op == '^':
- exp = '(' + exp + ')'
- e.append(exp)
- if op == '*':
- # Check if an explicit multiplication sign is nessecary
- left, right = node
- # Get the previous multiplication element if the arity is
- # greater than 2
- if left.title() == '*':
- left = left[1]
- # a * b -> ab
- # a * 2 -> a * 2
- # a * (b) -> a(b)
- # (a) * b -> (a)b
- # (a) * (b) -> (a)(b)
- # 2 * a -> 2a
- l = e[0][-1]
- r = e[1][0]
- left_simple = is_id(left) or is_int(left)
- if (r in ('(', '[') and left_simple) or (l == ')' and r != '-') \
- or (left_simple and r.isalpha()):
- sep = ''
- exp = sep.join(e)
- if node.negated and op not in ('*', '/', '^'):
- exp = '(' + exp + ')'
- return exp
- def construct_function(node):
- buf = []
- for child in node:
- buf.append(content[child])
- return '%s(%s)' % (node.title(), ', '.join(buf))
- # Traverse the expression tree and construct the mathematical expression in
- # the leafs and nodes in depth first order.
- for node in traverse_depth_first(root):
- if node.is_leaf:
- content[node] = str(node)
- else:
- arity = len(node)
- if is_operator(node):
- if arity == 1:
- content[node] = construct_unary(node)
- else:
- content[node] = construct_nary(node)
- else:
- result = None
- if hasattr(node, 'construct_function'):
- children = [content[c] for c in node]
- result = node.construct_function(children)
- if result == None:
- result = construct_function(node)
- content[node] = result
- # Add negations
- content[node] = '-' * node.negated + content[node]
- # Merge binary plus and unary minus signs into binary minus.
- return content[root].replace('+ -', '- ')
|