You are given a string expression representing a Lisp-like expression to return the integer value of.
The syntax for these expressions is given as follows.
"(let v1 e1 v2 e2 ... vn en expr)"
, where let is always the string "let"
, then there are one or more pairs of alternating variables and expressions, meaning that the first variable v1
is assigned the value of the expression e1
, the second variable v2
is assigned the value of the expression e2
, and so on sequentially; and then the value of this let expression is the value of the expression expr
."(add e1 e2)"
where add is always the string "add"
, there are always two expressions e1
, e2
and the result is the addition of the evaluation of e1
and the evaluation of e2
."(mult e1 e2)"
where mult is always the string "mult"
, there are always two expressions e1
, e2
and the result is the multiplication of the evaluation of e1 and the evaluation of e2."add"
, "let"
, and "mult"
are protected and will never be used as variable names.
Example 1:
Input: expression = "(let x 2 (mult x (let x 3 y 4 (add x y))))" Output: 14 Explanation: In the expression (add x y), when checking for the value of the variable x, we check from the innermost scope to the outermost in the context of the variable we are trying to evaluate. Since x = 3 is found first, the value of x is 3.
Example 2:
Input: expression = "(let x 3 x 2 x)" Output: 2 Explanation: Assignment in let statements is processed sequentially.
Example 3:
Input: expression = "(let x 1 y 2 x (add x y) (add x y))" Output: 5 Explanation: The first (add x y) evaluates as 3, and is assigned to x. The second (add x y) evaluates as 3+2 = 5.
Constraints:
1 <= expression.length <= 2000
expression
.expression
.This problem involves parsing Lisp-like expressions, which requires handling nested expressions, variable assignments (with scoping), and arithmetic operations. The solution uses a recursive approach with a helper function eval
to traverse and evaluate the expression.
Recursive eval
Function: The core of the solution is the recursive eval
function. It handles different expression types:
scope
(a dictionary/hash map storing variable values), and returns the value.let
Expression: If it encounters a let
expression, it processes variable assignments sequentially. For each variable, it evaluates its expression, stores the result in the scope
, and adds it to the stack. After all assignments, it evaluates the final expression and returns the result. Importantly, it cleans up the scope after evaluating the let
expression by removing the temporarily added variables. This manages the scope correctly.add
/mult
Expressions: For add
and mult
expressions, it recursively evaluates the two subexpressions and performs the corresponding arithmetic operation.Helper Functions: Helper functions like parseVar
and parseInt
assist in parsing variable names and integers from the input string, respectively.
Scope Management: The scope
dictionary/hash map is crucial for managing variable scopes. It's a stack-like data structure. When a let
expression is encountered, the variables are added to the scope
with their evaluated values. After the let
expression is processed, these variables are removed from the scope
to ensure correct scoping. The last added value for a variable will be retrieved if the variable is encountered in a nested expression.
eval
function visits each character in the expression at most once.scope
dictionary/hash map. In the worst case, a deeply nested expression could lead to a stack of size O(N) and potentially O(N) variables stored in scope
.The Python code effectively implements this approach. Let's break down key parts:
def evaluate(self, expression: str) -> int:
# ... (helper functions parseVar, parseInt) ...
def eval():
nonlocal i # Access and modify the outer 'i'
# ... (Variable and Integer handling) ...
if expression[i] == 'l': #let expression
# ... (process let expression, manage scope using scope[var].append/pop) ...
else: # add or mult expression
# ... (process add/mult, recursively call eval) ...
return ans
# ... (initialization, call eval, return result) ...
The nonlocal i
statement is essential. It allows the inner function eval
to modify the i
variable of the outer evaluate
function, enabling the traversal of the input string. The scope
dictionary keeps track of variable values within their scopes. The code meticulously handles scope using append
and pop
operations on the lists in scope
to simulate stack-like behavior for each variable.
The other language implementations (Java, C++, Go) follow a similar structure and logic, adapting the syntax and data structures to their respective languages. The core concept of recursive evaluation and scope management remains consistent across all implementations.