Katkat

Simplify an Arithmetic Expression Tree with Constant Folding and Identities

AmazonSoftware Engineer · Onsite

Last updated

Given the abstract syntax tree of an arithmetic expression as a binary tree, write a traversal that returns an equivalent simplified tree using constant folding and algebraic identities. It tests recursive tree processing, cascading rewrites, correct handling of non-commutative operators and recursion-depth limits.

You are given the root of a binary tree that is the abstract syntax tree (AST) of an arithmetic expression. Every internal node holds a binary operator and has exactly two children; every leaf holds either an integer constant or a variable name. Write a function that traverses the tree and returns an equivalent, simplified expression tree.

Assume the following node shape, and adapt it to your language:

class Node:
 def __init__(self, val, left=None, right=None):
 self.val = val # "+", "-" or "*" for internal nodes; an int or a variable name (str) for leaves
 self.left = left
 self.right = right

The interview report does not list the exact simplification rules, so agree on them with the interviewer before coding. As a baseline, assume your function must at least:

  • fold any operator node whose two operands are both constants into a single constant; and
  • apply the identities x + 0 = x, 0 + x = x, x - 0 = x, x * 1 = x, 1 * x = x, x * 0 = 0 and 0 * x = 0, where x is any subexpression;

and it must apply these rules everywhere in the tree, including where one simplification makes another one possible. For example, the tree for (x * 1) + (2 * 3) should become the tree for x + 6, and the tree for (y + 0) * (4 - 4) should become the single constant 0.

Clarifying Questions

  • Which operators can appear? In particular, can there be division or unary minus, and if so, how should division by a constant zero be treated?
  • Beyond the baseline, should the function combine like terms (for example x + x) or reassociate constants (for example (x + 2) + 3)?
  • Should the input tree be left unmodified with a new tree returned, or may it be simplified in place?
  • Is the expected output a tree, a printed string, or both? If a string, how should parentheses be handled?
  • How deep can the tree be, and is recursion acceptable?

Hints

Hint 1

Decide when a node is ready: Ask what a node needs to know about its children before it can decide whether it can be folded or removed, and let that decide the order in which you visit nodes.

Follow-up questions

  • Print the simplified tree as a string using the fewest parentheses that preserve its meaning, given operator precedence and the left-associativity of `-`.
  • Extend the simplifier to combine like terms so that `x + x` becomes `2 * x` and `2 * x + 3 * x` becomes `5 * x`. What representation makes this easier than rewriting the tree?
  • Add integer division. Which of your identities stop being safe, and what should happen to a subexpression that divides by zero?
  • The tree is a left-leaning chain of one million nodes. What fails in a recursive solution, and how do you make the traversal iterative?