from traverse import traverse_depth_first OPERATORS = [ ('+', '-'), ('*', '/', 'mod'), ('^', ) ] 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_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 '' 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) > 0: 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 if op == '^' and not i: exp = '(' + exp + ')' elif child_pred < node_pred: exp = '(' + exp + ')' elif child_pred == node_pred: if i and (op != child.title() \ 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 left_end = e[0][-1] right_begin = e[1][0] left_suited = left_end == ')' or left_end.isdigit() or is_id(left) right_suited = right_begin == '(' if not right_suited and not right.negated: right_suited = is_id(right) or is_power(right) if left_suited and right_suited: 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: content[node] = construct_function(node) # Add negations content[node] = '-' * node.negated + content[node] # Merge binary plus and unary minus signs into binary minus. return content[root].replace('+ -', '- ')