简单四则运算解析器 | 豆包MarsCode AI刷题
简单四则运算解析器
问题描述
小F面临一个编程挑战:实现一个基本的计算器来计算简单的字符串表达式的值。该字符串表达式有效,并可能包含数字(0-9)、运算符+、-及括号()。注意,字符串中不包含空格。除法运算应只保留整数结果。请实现一个解析器计算这些表达式的值,且不使用任何内置的eval函数。
测试样例
样例1:
输入:
expression = "1+1"
输出:2
样例2:
输入:
expression = "3+4*5/(3+2)"
输出:7
样例3:
输入:
expression = "4+2*5-2/1"
输出:12
样例4:
输入:
expression = "(1+(4+5+2)-3)+(6+8)"
输出:23
样例5:
输入:
expression = "2*(5+5*2)/3+(6+8*3)"
输出:40
解题思路
实现一个基本的计算器来解析和计算字符串表达式的值,我们需要遵循运算符的优先级规则,即先乘除后加减,并且括号内的运算优先级最高
eval 函数的作用是执行一个字符串表达式,并返回表达式的值。这个字符串表达式可以包含变量、运算符、函数调用等,eval 会将其视为代码并执行。
如果允许使用 eval 来解决问题的话,依然需要小心,eval(expression) 得到的是一个浮点数,而后台的样例中要求使用整数的除法,所以再进行eval之前需要把 expression 中的除法 / 换成 //
def solution(expression):
# 将所有的除法运算符 '/' 替换为地板除法运算符 '//'
modified_expression = expression.replace('/', '//')
# 使用 eval 函数计算修改后的表达式的值
return eval(modified_expression)
if __name__ == "__main__":
# 测试用例
print(solution("9/(3+2)-4") == -3) # 现在应该输出 True
print(solution("1+1") == 2) # 应该输出 True
print(solution("3+4*5/(3+2)") == 7) # 应该输出 True
print(solution("4+2*5-2/1") == 12) # 应该输出 True
print(solution("(1+(4+5+2)-3)+(6+8)") == 23) # 应该输出 True
print(solution("2*(5+5*2)/3+(6+8*3)") == 40) # 应该输出 True
我们不使用 eval 函数,自己解析 expression
要实现表达式的解析,我们首先需要按顺序遍历 expression 字符串,并准备两个栈,一个用来放数字 values,一个用来存放符号 ops
python 中可以直接把列表作为栈使用
如果遍历到数字,就看看数字之后还有没有数字,把它们组成完整的整数,直到遇到运算符或括号,再把这整个数压入 values
如果遍历到操作符(+、-、*、/),则比较其优先级与 ops 栈顶元素的优先级:如果 ops 栈顶元素的优先级更高或相等,且不是(,则先计算 ops 栈顶的操作符。然后将当前操作符压入 ops。
如果遍历到 (,直接压入ops
如果遍历到 ),计算ops直到遇到 (,并将结果压入values
遍历完成后,计算 ops 中的所有运算,并将结果压入 values
values 的栈顶为最终结果
思路验证
Prompt
需要一点思路提示
MarsCode AI
要解决这个计算器问题,我们可以采用以下思路:
1. 理解问题
我们需要解析一个包含数字、运算符(+、-、*、/)和括号的字符串表达式,并计算其值。表达式中的运算符优先级需要正确处理,括号内的表达式需要优先计算。
2. 数据结构选择
- 栈:栈是一种非常适合处理括号和运算符优先级的数据结构。我们可以使用两个栈,一个用于存储操作数(数字),另一个用于存储运算符。
- 优先级表:我们可以定义一个运算符优先级表,用于决定何时进行运算。
3. 算法步骤
-
初始化栈:创建两个栈,一个用于操作数(
num_stack),另一个用于运算符(op_stack)。 -
遍历表达式:从左到右遍历表达式中的每个字符。
-
如果当前字符是数字,继续读取直到遇到非数字字符,然后将完整的数字压入
num_stack。 -
如果当前字符是运算符或括号:
- 如果
op_stack为空或当前运算符优先级高于栈顶运算符,直接将当前运算符压入op_stack。 - 否则,弹出
op_stack中的运算符并进行计算,直到满足上述条件。 - 如果是左括号
(,直接压入op_stack。 - 如果是右括号
),弹出op_stack中的运算符并进行计算,直到遇到左括号(。
- 如果
-
-
计算表达式:遍历结束后,如果
op_stack中还有运算符,继续进行计算,直到op_stack为空。 -
返回结果:最终
num_stack中剩下的唯一元素就是表达式的结果。
4. 处理细节
- 运算符优先级:定义运算符优先级表,例如
*和/的优先级高于+和-。 - 括号处理:遇到左括号时,直接压入栈;遇到右括号时,弹出运算符直到遇到左括号。
通过以上步骤,我们可以逐步解析并计算表达式的值。希望这些思路能帮助你更好地理解和解决这个问题。
代码实现
def solution(expression):
# 定义运算符的优先级
def precedence(op):
if op in ('+', '-'):
return 1
if op in ('*', '/'):
return 2
return 0
# 应用操作符到操作数
def apply_op(ops, values):
op = ops.pop() # 弹出操作符
right = values.pop() # 弹出右边的操作数
left = values.pop() # 弹出左边的操作数
if op == '+':
values.append(left + right) # 执行加法
elif op == '-':
values.append(left - right) # 执行减法
elif op == '*':
values.append(left * right) # 执行乘法
elif op == '/':
values.append(left // right) # 执行整除
# 解析表达式
def parse_expression(expression):
values = [] # 操作数栈
ops = [] # 操作符栈
i = 0
while i < len(expression):
if expression[i].isdigit(): # 如果是数字
num = 0
while i < len(expression) and expression[i].isdigit():
num = num * 10 + int(expression[i]) # 构建整数
i += 1
values.append(num) # 将数字压入操作数栈
elif expression[i] == '(': # 如果是左括号
ops.append(expression[i]) # 将左括号压入操作符栈
i += 1
elif expression[i] == ')': # 如果是右括号
while ops and ops[-1] != '(': # 遇到右括号,计算直到遇到左括号
apply_op(ops, values)
if ops:
ops.pop() # 弹出左括号
i += 1
elif expression[i] in ('+', '-', '*', '/'): # 如果是操作符
while ops and ops[-1] != '(' and precedence(ops[-1]) >= precedence(expression[i]):
apply_op(ops, values) # 计算优先级更高的操作符
ops.append(expression[i]) # 将当前操作符压入操作符栈
i += 1
while ops: # 计算剩余的操作符
apply_op(ops, values)
return values[0] # 返回计算结果
return parse_expression(expression) # 返回解析和计算的结果
if __name__ == "__main__":
# 测试用例
print(solution("1+1") == 2)
print(solution("3+4*5/(3+2)") == 7)
print(solution("4+2*5-2/1") == 12)
print(solution("(1+(4+5+2)-3)+(6+8)") == 23)
print(solution("2*(5+5*2)/3+(6+8*3)") == 40)
复杂度分析
对于整个算法,分为几个步骤,扫描字符串,解析字符,计算
扫描字符串的时间复杂度为,即字符串的长度
解析字符,代码中每扫描一个字符就进行一次解析,每次解析都进行时间复杂度为 的入栈,出栈或计算操作。最多进行了 n 次解析,时间复杂度为
计算,每次为时间复杂度为 的入栈,出栈或计算操作,最多进行了 n 次计算,总的时间复杂度为
因此代码中的时间复杂度为
以下由AI进行时间复杂度分析,判断自己的分析是否正确
Prompt
计算编辑区代码的复杂度
MarsCode AI
要计算编辑区代码的时间复杂度和空间复杂度,我们需要分析代码的主要部分和它们的工作方式。
时间复杂度
1. parse_expression 函数
这个函数是核心部分,负责解析和计算表达式。
- 遍历表达式:
while i < len(expression)循环会遍历表达式中的每个字符,时间复杂度为O(n),其中n是表达式的长度。 - 处理数字:在遍历过程中,如果遇到数字,会继续读取直到遇到非数字字符,这部分的时间复杂度也是
O(n)。 - 处理运算符和括号:每次遇到运算符或括号时,可能会进行多次栈操作(压栈、弹栈),但由于每个字符最多只会被处理一次,这部分的时间复杂度也是
O(n)。
综合来看,parse_expression 函数的时间复杂度为 O(n)。
2. apply_op 函数
这个函数在每次调用时会执行一次运算,时间复杂度为 O(1)。由于每次运算符栈中的运算符都会被处理一次,最多会有 O(n) 次调用,因此这部分的时间复杂度也是 O(n)。
空间复杂度
1. parse_expression 函数
- 操作数栈
values:在最坏情况下,所有数字都会被压入栈中,空间复杂度为O(n)。 - 操作符栈
ops:在最坏情况下,所有运算符和括号都会被压入栈中,空间复杂度为O(n)。
综合来看,parse_expression 函数的空间复杂度为 O(n)。
总结
- 时间复杂度:
O(n),其中n是表达式的长度。 - 空间复杂度:
O(n),其中n是表达式的长度。
希望这些分析对你理解代码的复杂度有所帮助!
知识点总结
这个题目实际上就是将中缀表达式,通过栈转化成后缀表达式进行计算。
逆波兰表示法(Reverse Polish Notation,简称 RPN),也称为后缀表达式,是一种无需括号来标识操作符优先级的数学表达式书写方式。在逆波兰表示法中,每个操作符都置于其操作数之后。这种表示法的主要优点是无歧义和易于计算,因为不需要括号来明确指出运算的顺序。