栈实现综合计算器思路及Python实现
思路
- 先建立一个“数栈”用来压入数字,还有一个“符号栈”用来压入运算符,规定:减法从栈底向栈顶方法运算,乘除法优先级高于加减法
- 具体操作过程:以 “3+26-2” 为例
(1)数栈和符号栈皆为空,指针从左向右扫描表达式,数栈入栈第一个数3,符号栈入栈第一个扫描到的运算符‘+’,指针继续移动,遇到‘2’,直接再入数字,此时2在栈顶,3在栈底,指针继续移动遇到运算符‘’
(2)因为 ‘ * ’ 的优先级高于在符号栈栈底的“+”,所以直接将乘法入栈
(3)指针继续移动,遇到6,直接入数栈,指针继续移动,遇到“-”
(4)当前遇到的“-” 先不要入栈,而是从数栈栈pop出栈顶的6,在从字符栈pop出栈顶的“*”,然后再从数栈pop出当前栈顶的2 ;三个元素运算,等到12,将12压入数栈,最后把符号压入符号栈,此时两个栈的栈顶元素分别为:12 和 “-”
(5)指针继续移动,遇到2,压入数栈,此时表达式全部扫描完,就顺序的从数栈和符号pop出数和运算符,并运算,数栈pop出栈顶元素2 符号栈pop出栈顶元素“-” ,数栈再pop出当前栈顶元素12,用规定的“减法从栈底向栈顶方法运算”,所以 是12-2=10,将10 压入数栈,继续pop 数栈栈顶元素10,pop出符号栈“+”,再从数栈pop出元素3,两者运算和为13,将13压入栈,并返回
Python实现
先详细实现 如何 构建一个栈
# 自定义异常
class StackEmptyException(Exception): # 空栈异常
pass
class StackFullException(Exception): # 满栈异常
pass
class Stack(object):
def __init__(self, max_stack):
self.stack = [] # 数组,模拟栈
self.max_stack = max_stack # 栈的大小
self.top = -1 # 栈顶指针,初始化为-1
def is_empty(self): # 栈空
return self.top == -1 # 返回的是一个bool值,注意 是“==”
def is_full(self): # 栈满
return self.top == self.max_stack - 1 # 栈等于.给出的栈大小
def push(self, val): # 压栈
if self.is_full():
raise StackFullException("your stack is full !!!")
self.top += 1
self.stack.insert(self.top, val) # 注意是用的insert,但insert实现时,会增加算法复杂度
'''
insert实现时,会让插入位置后的数组整体移动,从而增加复杂度
如果想用列表提供的索引插入方式(顺序表实现),
不同于Java,因为Python列表在如果当前是空的时候,
会抛出异常:IndexError: list assignment index out of range
异常含义:空数组不能直接指定位置
要解决这个问题,我们可以先给数组加一个值,等用索引插入时,直接覆盖就行
所以上面代码还可以这样写:
self.stack.append(1)
self.top += 1
self.stack[self.top] = val
'''
def pop(self): # 出栈
if self.is_empty():
raise StackEmptyException("your stack is empty !!!")
value = self.stack[self.top]
self.top -= 1
return value
def peek(self): # 返回当前栈顶的值,但不是真正的pop
return self.stack[self.top]
def print_stack(self): # 显示栈的情况,相当于遍历打印
if self.is_empty():
print("空栈,没有数据哦")
return
# 需要从栈顶开始显示数据
for i in range(self.top, -1, -1):
print("stack[%d]=%s" % (i, self.stack[i]))
if __name__ == '__main__':
s = Stack(5)
s.push("第一入栈")
s.push("第二入栈")
s.push("第三入栈")
s.push("第四入栈")
s.push("第五入栈")
s.print_stack() # 直接调用打印方法
# print(s.pop())
# print(s.pop())
# print(s.pop())
# print(s.pop())
# print(s.pop())
# print(s.pop()) # 抛出空栈异常
'''
输出结果:
stack[4]=第五入栈
stack[3]=第四入栈
stack[2]=第三入栈
stack[1]=第二入栈
stack[0]=第一入栈
'''
完整实现 综合计算器的功能
class StackEmptyException(Exception): # 自定义异常,空栈异常
pass
class StackFullException(Exception): # 超栈异常
pass
class Stack(object):
def __init__(self, max_stack):
self.stack = [] # 数组,模拟栈
self.max_stack = max_stack # 栈的大小
self.top = -1 # 栈顶指针,初始化为-1
def is_empty(self): # 栈空
return self.top == -1
def is_full(self): # 栈满
return self.top == self.max_stack - 1
def push(self, val): # 压栈
if self.is_full():
raise StackFullException("your stack is full !!!")
self.stack.append(1)
self.top += 1
# self.stack.insert(self.top, val)
self.stack[self.top] = val
def pop(self): # 出栈
if self.is_empty():
raise StackEmptyException("your stack is empty !!!")
value = self.stack[self.top]
self.top -= 1
return value
def peek(self): # 返回当前栈顶的值,但不是真正的pop
return self.stack[self.top]
def print_stack(self): # 显示栈的情况,相当于遍历打印
# 遍历时需要从栈顶开始显示数据
if self.is_empty():
print("空栈,没有数据哦")
return
# 需要从栈顶开始显示数据
for i in range(self.top, -1, -1):
print("stack[%d]=%s" % (i, self.stack[i]))
def is_operator(self, char): # 判断是不是一个运算符
return char == "+" or char == "-" or char == "*" or char == "/"
def operator_priority(self, operator):
if operator == "*" or operator == "/":
return 1
elif operator == "+" or operator == "-":
return 0
else:
return -1 # 假定目前的运算符只有 +,-,*,/
def calculate_num(self, cur_num, pre_num, operator): # 计算
res = 0 # res 用于存放计算结果
if operator == "+":
res = cur_num + pre_num
elif operator == "-":
res = pre_num - cur_num # 注意顺序,前一个减当前的
elif operator == "*":
res = cur_num * pre_num
elif operator == "/":
res = pre_num / cur_num
return res
class Calculator(object):
def __init__(self):
self.index = 0 # 用于扫描的指针,不断移动
self.get_char = "" # 将每次扫描到的 char保存到 get_char中
def calculator_work(self, num_stack, operator_stack, expression): # 主运算
join_num = "" # 用于拼接多位数
# 【java】 get_char= expression.substring(index,index+1).charAt(0)
# .substring(index,index+1) 返回字符串的子字符串,包前不包后
# .charAt()用于返回指定索引处的字符。索引范围为从 0 到 length() - 1
# Python可以用切片来实现
while expression:
self.get_char = expression[self.index:self.index + 1][0]
if operator_stack.is_operator(self.get_char): # 判断 get_char获取到的是什么?
# 接着判断当前符号栈是否为空
if not operator_stack.is_empty():
# 如果当前运算符优先级 小于或等于 栈中运算符
if operator_stack.operator_priority(self.get_char) <= operator_stack.operator_priority(
operator_stack.peek()):
cur_num = num_stack.pop()
pre_num = num_stack.pop()
operator = operator_stack.pop()
result = num_stack.calculate_num(cur_num, pre_num, operator)
num_stack.push(result)
operator_stack.push(self.get_char)
else: # 如果优先级大于
operator_stack.push(self.get_char)
else: # 如果为空也直接入符号栈
operator_stack.push(self.get_char)
else: # 如果是数,则直接入数栈
join_num += self.get_char
if self.index == len(expression) - 1: # 如果当前已经是最后一位,则直接入栈
num_stack.push(float(join_num))
else: # 判断下一个字符是不是数字,如果是数字则继续扫描,如果是运算符,则入栈
if operator_stack.is_operator(expression[self.index + 1:self.index + 2][0]):
num_stack.push(float(join_num))
join_num = "" # 入栈后,拼接字符串要清空
self.index += 1
if self.index >= len(expression):
break
# 当扫描完毕,就顺序的从 数栈和符号栈中pop出相应的数和符号
while True:
# 如果符号栈为空,则计算到最后的结果,数栈中只有一个数字
if operator_stack.is_empty():
break
cur_num = num_stack.pop()
pre_num = num_stack.pop()
operator = operator_stack.pop()
result = num_stack.calculate_num(cur_num, pre_num, operator)
num_stack.push(result)
finally_num = num_stack.pop()
return finally_num
if __name__ == '__main__':
num_stack = Stack(10)
operator_stack = Stack(10)
expression_example = "3+12*2-2"
obj = Calculator()
print(obj.calculator_work(num_stack, operator_stack, expression_example))
# 最后结果25.0
模拟逆波兰计算器思路及Python实现
-
前缀表达式:又称为波兰表达式
(1)上图中(3+4)*5-6 是中缀表达式,它的前缀表达式表示为:- * +3 4 5 6 ,注意表示直接从中缀表示符号顺序移动过来,即不是 + * - 这个原因是:
(2)注意是从右往左扫描,而且不分运算符优先级,只要扫描到运算符,运算符要直接为数栈中弹出的两个元素计算完,然后再把计算结果压入数栈顶部,数栈计算的顺序永远是 cur_num(当前的数)去操作 pre_num(前一个数) -
中缀表达式:就是我们最熟悉的表达式形式
-
后缀表达式(逆波兰表达式):
(1)后缀表达式:
(2)后缀表达式的计算过程:
注意:是要先求出后缀表达式,然后进行操作的是后缀表达式;因为后缀表达式是从左向右扫描,所以 数栈的数值间的操作顺序是 pre_num 操作 cur_num 和中缀一样,但和前缀不一样! -
如何求后缀表达式: 中缀表达式 如何转 后缀表达式
(1)具体操作步骤:后缀表达式适合计算式进行运算,那么获取到它的具体步骤是?
(2)实例演示:求1+((2+3)*4)-5 后缀表达式的具体过程
结果的逆序就是后缀表达式即:“ 1 2 3 + 4 * + 5 - ”
思路
- 实现一个逆波兰计算器,主要分两步,第一步是要求出后缀表达式,第二步对后缀表达式进行操作,再者,因为需要栈的功能不像综合计算器那样需要不断的扫描以及注意遇到的运算符后要有处理的先后顺序等等,后缀表达式,只要遇到数字就压入,遇到运算符就弹出两位数,进行运算!
- 我们可以用Python中的列表来模拟一个栈即可,列表的append就是不断的在尾部添加元素,非常像栈的压栈push过程,因为要后进先出,所以取得时候,我们用列表的pop()方法,不断的从列表的尾部取元素,直接用list可以大大降低实现一个栈的复杂度,因为我们不需要向之前一样用到自定义栈的多个方法,只需要用到栈的“后进先出”的性质!
Python实现
在字符串转数值时,可能会遇到的问题
ValueError: invalid literal for int() with base 10
- 这个是在用int()函数直接转换字符串为整型时产生的问题,切记int()只能转化由纯数字组成的字符串
- 具体在哪几种情况下会抛出这个异常:如下↓↓↓ 为空时,不是是纯数字时
str1='123456'
str2=''
str3='maybe123456'
int(str1)
>>> 123456
int(str2)
>>>ValueError: invalid literal for int() with base 10: '' # 不可转换空字符串为整型
int(str3)
>>>ValueError: invalid literal for int() with base 10: 'maybe123456' # 不可转换非纯数字组成的字符串
先测试下:假设已经得到了逆波兰表达式后
import re
# 因为已经得到了 逆波兰表达式,所以操作安步骤来即可
class PolandNotation(object):
def __init__(self):
self.array_list = [] # 列表用来存放切割出来的字符串
self.stack = [] # 用列表模拟一个栈
def get_expression(self, expression):
self.array_list = expression.split(" ") # 按空格切割
return self.array_list
def calculate_work(self, array_list):
for item in array_list:
if re.match(r"\d+", item): # 如果是数字,直接压入
self.stack.append(item)
else: # 如果是运算符
# 注意这里转换字符串,用的是float,至于原因看开头说明
cur_num = float(self.stack.pop()) # 当前的数,先弹出的
pre_num = float(self.stack.pop()) # 前一个数,后弹出的
result = 0
if item == "+":
result = cur_num + pre_num
elif item == "-":
result = pre_num - cur_num
elif item == "*":
result = cur_num * pre_num
elif item == "/":
result = pre_num / cur_num
else:
print("运算符有误")
self.stack.append(str(result))
return float(self.stack.pop()) # 转换字符串用的是float
if __name__ == '__main__':
suffix_expression = "4 5 * 8 - 60 + 8 2 / +"
obj = PolandNotation()
print(obj.calculate_work(obj.get_expression(suffix_expression)))
完整实现 逆波兰计算器,包括 中缀表达式 转 后缀表达式
扫描二维码关注公众号,回复: 9192430 查看本文章
import re
class PolandNotation(object):
def __init__(self):
self.stack = [] # 总栈,用来处理后缀表达式的栈
self.array_list = [] # 用来存放开始传入式子的list
self.work_stack = [] # 主要的操作栈
self.temp_list = [] # 上述讲到的s2这个栈全程不需pop操作,最后只需要完成逆序,所以可以只用列表的功能即可
def get_infix_expression_list(self, str_exp): # 将中缀表达式元素放入list中
# 这次我们不用split切割空格特殊情况来得到list,因为传入的可能是不不规则的!
index = 0
while index < len(str_exp):
if ord(str_exp[index]) < 48 or ord(str_exp[index]) > 57: # 0-9 数字范围是 48-57
self.array_list.append(str_exp[index])
index += 1 # 如果得到是非数字,则添加到list中,索引后移一位
else: # 如果是一个数,需要考虑多为数
join_str = "" # 现将 拼接字符串置为空
while index < len(str_exp) and ord(str_exp[index]) >= 48 and ord(str_exp[index]) <= 57:
join_str += str_exp[index]
index += 1
self.array_list.append(join_str)
return self.array_list
def parse_suffix_expression_list(self, str_list): # 将中缀表达式 转换成 后缀表达式
for item in str_list:
if re.match(r"\d+", item): # 如果是一个数
self.temp_list.append(item)
elif item == "(":
self.work_stack.append(item)
elif item == ")":
while self.work_stack[-1] != "(": # 如果当前栈顶元素还没到“(”就让元素一直弹出
self.temp_list.append(self.work_stack.pop())
self.work_stack.pop() # 遇到了“(”把它弹出,等于消掉了一对括号
else: # 如果是 运算符,需要对当前运算符和栈中运算符 优先级进行比较
while len(self.work_stack) != 0 and self.operator_priority(item) <= self.operator_priority(
self.work_stack[-1]):
self.temp_list.append(self.work_stack.pop())
self.work_stack.append(item) # 最后把当前的运算符压入栈中
# 将栈中剩下的运算符,依次弹出加入到temp_list中
while len(self.work_stack) != 0:
self.temp_list.append(self.work_stack.pop())
return self.temp_list # 最后按照列表顺序输出即可
def operator_priority(self, operator): # 处理运算符优先级
if operator == "*" or operator == "/":
return 1
elif operator == "+" or operator == "-":
return 0
else:
return -1 # 假定目前的运算符只有 +,-,*,/
def calculate_work(self, suffix_list): # 计算主功能区,传入的是已经得到的后缀表达式
for item in suffix_list:
if re.match(r"\d+", item): # 如果是数字,直接压入
self.stack.append(item)
else: # 如果是运算符
# 注意这里转换字符串,用的是float,至于原因看开头说明
cur_num = float(self.stack.pop()) # 当前的数,先弹出的
pre_num = float(self.stack.pop()) # 前一个数,后弹出的
result = 0
if item == "+":
result = cur_num + pre_num
elif item == "-":
result = pre_num - cur_num
elif item == "*":
result = cur_num * pre_num
elif item == "/":
result = pre_num / cur_num
else:
print("运算符有误")
self.stack.append(str(result))
return float(self.stack.pop()) # 转换字符串用的是float
if __name__ == '__main__':
expression = "1+((2+3)*4)-5" # 输入一个表达式
obj = PolandNotation()
get_infix_list = obj.get_infix_expression_list(expression) # 得到一个中缀表达式
print(get_infix_list) # 打印中缀表达式
# ['1', '+', '(', '(', '2', '+', '3', ')', '*', '4', ')', '-', '5']
get_suffix_list = obj.parse_suffix_expression_list(get_infix_list) # 得到一个后缀表达式
print(get_suffix_list) # 打印后缀表达式
# ['1', '2', '3', '+', '4', '*', '+', '5', '-']
print(obj.calculate_work(get_suffix_list))
# 16.0
正则表达式实现计算器
- 对于上面完成计算器功能的代码,主要是通过栈的思想来完成的,但是没有处理如果传入的是小数的情况,对于小数按照上面的方法来做的话,只需增加判断就可以完成,整体来说逻辑又会更加复杂,所以既然上面都用到了正则表达式,那么,我们就用正则表达式的天然优势来做,可以轻松处理括号和小数的情况
import re
def atmo_cal(exp): # 原子计算器,最小单位不可再拆分了,处理乘除法,如:5*0.6;6/2
if "*" in exp:
a, b = exp.split("*") # 最小了,只有两位
return str(float(a) * float(b))
elif "/" in exp:
a, b = exp.split("/")
return str(float(a) / float(b))
def add_sub(exp): # 计算加减法
ret = re.findall("[-+]?\d+(?:\.\d+)?", exp) # 取消分组优先,得到一个列表
exp_sum = 0
for i in ret:
exp_sum += float(i)
return exp_sum
def mul_div(exp): # 计算乘除法
while True:
ret = re.search("\d+(?:\.\d+)?[/*]-?\d+(?:\.\d+)?", exp)
# 用的是search,所以从左往右匹配到第一项就返回结果
if ret:
atmo_exp = ret.group() # 分组取出原子每一项
res = atmo_cal(atmo_exp) # 调用原子计算器,打印结果为"-22.0"
# "2-1*-22+3-4/-5" "-22.0" 替换原式子中的位置即:"2--22.0-3-4/-5"
exp = exp.replace(atmo_exp, res) # 参数位置 旧的,新的 将旧的替换成新的
else: # 2--22.0-3--0.8 乘除法都运算完毕了
return exp
def format(exp): # 对于遇到 两个符号的情况处理
exp = exp.replace('--', '+')
exp = exp.replace('+-', '-')
exp = exp.replace('-+', '-')
exp = exp.replace('++', '+')
return exp
def cal(exp): # 将乘法或者除法匹配出来;"2-1*-22-3-4/-5" 用来计算用的
exp = mul_div(exp) # 调用乘除法函数
exp = format(exp) # 调用格式化函数后:2+22.0-3+0.8
exp_sum = add_sub(exp) # 调用计算加法的函数 21.8
return exp_sum # 返回结果
# print(cal("2-1*-22-3-4/-5"))
def main(exp): # 主函数
exp = exp.replace(" ", "") # 去掉用户输入内容的空格
while True:
ret = re.search("\([^()]+\)", exp) # (-40/5)
if ret:
inner_bracket = ret.group() # 这里可能得到各种运算
res = str(cal(inner_bracket))
# print(inner_bracket,type(res))
exp = exp.replace(inner_bracket, res)
# print(exp) 再格式化下 1-2*((60-30+-8.0*(9-2*5/3+7/3*99/4*2998+10*568/14))-(-4*3)/(16-3*2))
exp = format(exp)
# print(exp)
else:
break
# print(exp,type(exp)) ;1-2*-1388335.8476190479 <class 'str'>
return cal(exp)
s = '1 - 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) )'
ret = main(s)
print(ret, type(ret))
print(eval(s), type(eval(s))) # 用户可能再用到,所以像eval一样返回原来的格式
# 2776672.6952380957 <class 'float'>
# 2776672.6952380957 <class 'float'>