[LeetCode] Parse Lisp Expression 解析Lisp表達式


 

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. 

  • An expression is either an integer, a let-expression, an add-expression, a mult-expression, or an assigned variable. Expressions always evaluate to a single integer.
  • (An integer could be positive or negative.)
  • A let-expression takes the form (let v1 e1 v2 e2 ... vn en expr), where let is always the string "let", then there are 1 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.
  • An add-expression takes the form (add e1 e2) where add is always the string "add", there are always two expressions e1, e2, and this expression evaluates to the addition of the evaluation of e1 and the evaluation of e2.
  • A mult-expression takes the form (mult e1 e2) where mult is always the string "mult", there are always two expressions e1, e2, and this expression evaluates to the multiplication of the evaluation of e1 and the evaluation of e2.
  • For the purposes of this question, we will use a smaller subset of variable names. A variable starts with a lowercase letter, then zero or more lowercase letters or digits. Additionally for your convenience, the names "add", "let", or "mult" are protected and will never be used as variable names.
  • Finally, there is the concept of scope. When an expression of a variable name is evaluated, within the context of that evaluation, the innermost scope (in terms of parentheses) is checked first for the value of that variable, and then outer scopes are checked sequentially. It is guaranteed that every expression is legal. Please see the examples for more details on scope.

Evaluation Examples:

Input: (add 1 2)
Output: 3

Input: (mult 3 (add 2 3))
Output: 15

Input: (let x 2 (mult x 5))
Output: 10

Input: (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.

Input: (let x 3 x 2 x)
Output: 2
Explanation: Assignment in let statements is processed sequentially.

Input: (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.

Input: (let x 2 (add (let x 3 (let x 4 x)) x))
Output: 6
Explanation: Even though (let x 4 x) has a deeper scope, it is outside the context
of the final x in the add-expression.  That final x will equal 2.

Input: (let a1 3 b2 (add a1 1) b2) 
Output 4
Explanation: Variable names can contain digits after the first character.

Note:

  • The given string expression is well formatted: There are no leading or trailing spaces, there is only a single space separating different components of the string, and no space between adjacent parentheses. The expression is guaranteed to be legal and evaluate to an integer.
  • The length of expression is at most 2000. (It is also non-empty, as that would not be a legal expression.)
  • The answer and all intermediate calculations of that answer are guaranteed to fit in a 32-bit integer.

 

這道題讓我們解析Lisp語言的表達式,以前聽說過Lisp語言,但是完全沒有接觸過,看了題目中的描述和給的例子,感覺很叼。估計題目只讓我們處理一些簡單的情況,畢竟不可能讓我們寫一個編譯器出來。題目中說了給定的表達式都是合法的,這樣也降低了難度。還有一個好的地方是題目給了充足的例子,讓我們去更好的理解這門新的語言。我們通過分析例子發現,所有的命令都是用括號來包裹的,而且里面還可以嵌套小括號即子命令。讓我們處理的命令只有三種,add,mult,和let。其中add和mult比較簡單就是加法和乘法,就把后面兩個數字或者子表達式的值加起來或成起來即可。let命令稍稍麻煩一些,后面可以跟好多變量或表達式,最簡單的是三個,一般第一個是個變量,比如x,后面會跟一個數字或子表達式,就是把后面的數字或子表達式的值賦值給前面的變量,第三個位置是個表達式,其值是當前let命令的返回值。還有一個比較重要的特性是外層的變量值不會隨着里層的變量值改變,比如對於下面這個例子:

(let x 2 (add (let x 3 (let x 4 x)) x))

剛開始x被賦值為2了,然后在返回值表達式中,又有一個add操作,add操作的第一個變量又是一個子表達式,在這個子表達式中又定義了一個變量x,並復制為3,再其返回值表達式又定義了一個變量x,賦值為4,並返回這個x,那么最內層的表達式的返回值是4,那么x被賦值為3的那層的返回值也是4,此時add的第一個數就是4,那么其第二個x是多少,其實這個x並沒有被里層的x的影響,仍然是剛開始賦值的2,那么我們就看出特點了,外層的變量是能影響里層變量的,而里層變量無法影響外層變量。那么我們只要在遞歸的時候不加引用就行了,這樣值就不會在遞歸函數中被更改了。

對於這種長度不定且每個可能包含子表達式的題,遞歸是一個很好的選擇,由於需要給變量賦值,所以需要建立一個變量和其值之間的映射,然后我們就要來寫遞歸函數了,最開始我們給定的表達式肯定是有括號的,所以我們先處理這種情況,括號對於我們的解析沒有用,所以要去掉首尾的括號,然后我們用一個變量cur表示當前指向字符的位置,初始化為0,下面要做的就是先解析出命令單詞,我們調用一個子函數parse,在parse函數中,簡單的情況就是解析出add,mult,或let這三個命令單詞,我們用一個指針來遍歷字符,當越界或遇到空格就停止,但是如果我們需要解析的是個子表達式,而且里面可能還有多個子表達式,那么我們就需要找出最外面這個左括號對應的右括號,因為中間可能還會有別的左右括號,里面的內容就再之后再次調用遞歸函數時處理。判斷的方法就是利用匹配括號的方法,用變量cnt來表示左括號的的個數,初始化為1,當要parse的表達式第一個字符是左括號時,進入循環,循環條件是cnt不為0,當遇到左括號時cnt自增1,反之當遇到右括號時cnt自減1,每次指針end都向右移動一個,最后我們根據end的位置減去初始時cur的值(保存在變量t中),可以得到表達式。如果解析出的是命令let,那么進行while循環,然后繼續解析后面的內容,如果此時cur大於s的長度了,說明此時是let命令的最后一個部分,也就是返回值部分,直接調用遞歸函數返回即可。否則就再解析下一個部分,說明此時是變量和其對應值,我們要建立映射關系。如果之前解析出來的是add命令,那么比較簡單,就直接解析出后面的兩個部分的表達式,並分別調用遞歸函數,將遞歸函數的返回值累加並返回即可。對於mult命令同樣的處理方式,只不過是將兩個遞歸函數的返回值乘起來並返回。然后我們再來看如果表達式不是以左括號開頭的,說明只能是數字或者變量,那么先來檢測數字,如果第一個字符是負號或者0到9之間的數字,那么直接將表達式轉為int型即可;否則的話就是變量,我們直接從哈希map中取值即可。最后需要注意的就是遞歸函數的參數哈希map一定不能加引用,具體可以參見上面那個例子的分析,加了引用后外層的變量值就會受內層的影響,這是不符合題意的,參見代碼如下:

 

class Solution {
public:
    int evaluate(string expression) {
        unordered_map<string, int> m;
        return helper(expression, m);
    }
    int helper(string str, unordered_map<string, int> m) {
        if (str[0] == '-' || (str[0] >= '0' && str[0] <= '9')) return stoi(str);
        else if (str[0] != '(') return m[str];
        string s = str.substr(1, str.size() - 2);
        int cur = 0;
        string cmd = parse(s, cur);
        if (cmd == "let") {
            while (true) {
                string var = parse(s, cur);
                if (cur > s.size()) return helper(var, m);
                string t = parse(s, cur);
                m[var] = helper(t, m);
            }
        } else if (cmd == "add") {
            return helper(parse(s, cur), m) + helper(parse(s, cur), m);
        } else if (cmd == "mult") {
            return helper(parse(s, cur), m) * helper(parse(s, cur), m);
        }
    }
    string parse(string& s, int& cur) {
        int end = cur + 1, t = cur, cnt = 1;
        if (s[cur] == '(') {
            while (cnt != 0) {
                if (s[end] == '(') ++cnt;
                else if (s[end] == ')') --cnt;
                ++end;
            }
        } else {
            while (end < s.size() && s[end] != ' ') ++end;
        }
        cur = end + 1;
        return s.substr(t, end - t);
    }
};

 

類似題目:

Ternary Expression Parser

 

參考資料:

https://discuss.leetcode.com/topic/112079/c-recursion-solution-with-explaination/2

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM