Evaluate a Lisp-Style Expression Language with let, add and mult Scoping
GoogleSoftware Engineer · OtherLast updated
A Google coding problem asking you to evaluate a small Lisp-like expression language with integer literals, variables, and add, mult and let forms. It tests tokenizing and parsing nested expressions, lexical scoping in which inner bindings shadow outer ones, and sequential rebinding inside a let.
Reported from a Google software engineering coding interview.
Write an evaluator for a tiny Lisp-like expression language. Given one valid expression as a string, return its integer value.
An expression is one of the following:
- Integer literal: an optional leading
-followed by one or more digits, such as7or-12. - Variable: a lowercase letter followed by zero or more lowercase letters or digits, such as
xorv1. Its value comes from the nearest enclosing binding (see Rules). - Addition:
(add e1 e2)evaluates to the value ofe1plus the value ofe2. - Multiplication:
(mult e1 e2)evaluates to the value ofe1times the value ofe2. - Binding:
(let v1 e1 v2 e2 ... vn en body)withn >= 1. Working left to right, evaluatee1and bindv1to it, then evaluatee2and bindv2to it, and so on. The value of the wholeletis the value ofbody. After theletkeyword there is always an odd number of elements; the last one is the body and the others form variable-expression pairs.
Function Signature
def evaluate_lisp(expression: str) -> int:
Rules
- Each
letopens a new scope. Its bindings are visible to its own later expressions, to its body, and to everything nested inside them, but not outside thelet. - Inside one
let, an expression may use variables bound earlier in the samelet, and binding a name a second time in the sameletreplaces the earlier value from that point on. - A variable is looked up in the innermost scope that binds it, then in the next scope outward, and so on. Every variable that is referenced is bound in some enclosing scope.
add,multandletare keywords and never appear as variable names.- Tokens are separated by exactly one space; an opening parenthesis is immediately followed by a keyword, and a closing parenthesis directly follows the last token of its expression. There are no leading or trailing spaces.
Constraints
1 <= len(expression) <= 2000- The expression is always valid.
- Every literal, every intermediate result and the final answer lie in the 32-bit signed range
[-2^31, 2^31 - 1].
Examples
Example 1
- Input:
expression = "(let a 5 (add a (let a 2 b 3 (mult a b))))" - Output:
11 - Explanation: the inner
letbindsa = 2andb = 3, so its body is2 * 3 = 6. Back in the outer scopeais 5, so the sum is5 + 6 = 11.
Example 2
- Input:
expression = "(let n 4 n (mult n -2) (add n 1))" - Output:
-7 - Explanation:
nis bound to 4, then rebound to4 * -2 = -8; the body evaluates to-8 + 1 = -7.
Example 3
- Input:
expression = "(let x 1 (add (let x 10 x) x))" - Output:
11 - Explanation: the inner
letevaluates to 10, and its binding ofxends with it, so the second operand ofaddsees the outerx = 1.
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.