[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一定不能加引用,具体可以参见上面那个例子的分析,加了引用后外层的变量值就会受内层的影响,这是不符合题意的,参见代码如下:

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);
    }
};

参考资料:

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

LeetCode All in One 题目讲解汇总(持续更新中...)

本文转自博客园Grandyang的博客,原文链接:[LeetCode] Parse Lisp Expression 解析Lisp表达式

,如需转载请自行联系原博主。

相关文章
|
19天前
|
机器学习/深度学习 前端开发 Windows
【夯实技术基本功】「底层技术原理体系」全方位带你认识和透彻领悟正则表达式(Regular Expression)的开发手册(正则符号深入解析 )
【夯实技术基本功】「底层技术原理体系」全方位带你认识和透彻领悟正则表达式(Regular Expression)的开发手册(正则符号深入解析 )
31 0
|
2月前
|
并行计算 Java 程序员
深入解析JDK 8中的Lambda表达式:新特性的力量
本文将深入探讨JDK 8中引入的最引人注目的新特性之一:Lambda表达式。我们将详细解析Lambda表达式的概念、语法和用途,并通过实际示例展示如何利用Lambda表达式简化代码和提高编程效率。
|
3月前
|
JSON 数据库 C++
Rapidjson的使用过程-Parse解析数组遇到的问题,附自己的解决方式
关于RapidJSON,网上有很多资料,RapidJSON是腾讯开源的一个高效的C++ JSON解析器及生成器,它是只有头文件的C++库。RapidJSON是跨平台的,支持Windows, Linux, Mac OS X及iOS, Android。它的源码在https://github.com/Tencent/rapidjson/。这里也不过多介绍如何使用RapidJson,网上有很多如何使用,只介绍自己使用过程中遇到的问题,及其解决问题的方式。
47 0
|
3月前
|
JavaScript 前端开发 API
【JavaScript】<正则表达式Regular Expression>JavaScript正则表达式解析
【1月更文挑战第17天】【JavaScript】<正则表达式Regular Expression>JavaScript正则表达式解析
|
20天前
|
安全 Java 数据安全/隐私保护
【深入浅出Spring原理及实战】「EL表达式开发系列」深入解析SpringEL表达式理论详解与实际应用
【深入浅出Spring原理及实战】「EL表达式开发系列」深入解析SpringEL表达式理论详解与实际应用
42 1
|
24天前
|
XML JSON API
深入解析C++ JSON库:nlohmann::json:: parse的内部机制与应用
深入解析C++ JSON库:nlohmann::json:: parse的内部机制与应用
42 0
|
30天前
|
Java 开发者
深入解析Java中的Lambda表达式
本文深入探讨Java编程语言中的Lambda表达式,介绍了Lambda表达式的定义、优势以及在实际开发中的应用场景,旨在帮助读者更好地理解和运用这一特性。
|
1月前
|
JavaScript Java 程序员
Java 8新特性解析:Lambda表达式与函数式编程
【2月更文挑战第12天】 本文深入探讨Java 8引入的两大革命性特性:Lambda表达式和函数式编程接口,旨在为Java开发者提供一个清晰的指南,帮助他们理解和应用这些新特性以提升代码的简洁性和效率。通过对Lambda表达式的基本概念、语法及其与函数式接口的结合使用进行详细分析,本文展示了如何利用这些新特性来编写更加简洁、易读且易于维护的代码。同时,文章还将通过实例探讨Lambda表达式在实际开发中的应用,包括在集合处理、事件监听和并发编程等方面的具体使用场景,以期让读者能够充分理解并有效利用Java 8的这些新工具,从而在日常开发工作中提高效率。
24 3
|
2月前
|
并行计算 Java API
Java中的Lambda表达式应用与实例解析
【2月更文挑战第4天】本文将深入探讨Java编程语言中Lambda表达式的应用与实例解析,通过详细介绍Lambda表达式的概念、语法特点以及在实际项目开发中的运用,帮助读者更好地理解和运用这一强大的编程特性。
|
3月前
|
SQL Python
力扣刷MySQL-第二弹(详细解析)
力扣刷MySQL-第二弹(详细解析)

推荐镜像

更多