中缀表达式的特征是运算符的位置在两个参数之间,例如“13+14×10”、“18÷5+12”。一看便知,中缀表达式就是自然运算式,只不过现在为了有别于后缀表达式,另起了一个名字。
现在来讲解一下如何将中缀表达式转换成后缀表达式。在 Python 代码中可以创建两个空列表,一个用来临时存储运算符,另一个用来存储后缀表达式的列表形式。比如对于中缀表达式“13+14×10”,遍历它,首先将 13 存到后缀表达式列表中索引 0 的位置;第二次循环将 ‘+’ 存到运算符列表中;第三次把 14 存到后缀表达式中,索引为1;接下来遇到‘×’,将它与已经存在于运算符列表的 ‘+’ 进行比较,因为数学运算规则中乘法优先级高于加法,所以将 ‘×’ 存到运算符列表 ‘+’ 之后的位置(反之,如果是先遇到 ‘×’ 后遇到 ‘+’,则将 ‘×’ 弹出并追加到后缀表达式列表的最后位置);第五次遍历,将 10 存到后缀表达式列表索引 2 的位置。中缀表达式已经遍历完成,下面将运算符列表中的元素倒序追加到后缀表达式列表末尾,最终生成的后缀表达式列表就是 [13,14,10,'×','+']。
| 中缀表达式遍历元素 | 运算符列表 | 后缀表达式列表 |
| 13 | [] | [13] |
| '+' | ['+'] | [13] |
| 14 | ['+'] | [13,14] |
| '×' | ['+','×'] | [13,14] |
| 10 | ['+','×'] | [13,14,10] |
| 已遍历完 | ['+','×'] | [13,14,10,'×','+'] |
接下来讲一下后缀表达式的运算步骤。需要用到一个临时的栈。在这个项目里,只需实现一个简单的栈即可。还是用上边这个例子,遍历这个列表,如果遇到数字,就压入栈;如果遇到运算符,就把栈顶的两个数字弹出,执行相应的运算,将运算结果再压入栈;判断当后缀表达式列表长度为0时结束循环,如果这时栈中只有一个元素,弹出这个元素并打印,这个元素就是运算结果。
| 后缀表达式列表 | 栈 |
| [13,14,10,'×','+'] | [] |
| [14,10,'×','+'] | [13] |
| [10,'×','+'] | [14,13] |
| ['×','+'] | [10,14,13] |
| ['+'] | [140,13] |
| [] | [153] |
括号的运算优先级是最高的,所以要在转换后缀表达式的代码中加一点逻辑。在遍历中缀表达式时如果遇到右括号,就在运算符列表里向前找到左括号的索引值,将从这个索引值加一对应的元素到列表末尾的元素从列表中弹出,追加到后缀表达式列表中,左括号则只弹出,不追加到后缀表达式列表中。
初始化时只需要定义计算器实例的表达式这一个变量。
- def __init__(self, exp):
- self.exp = exp
这个步骤指的是在用户输入了表达式之后,程序会对表达式的逻辑是否正确进行判断,比如连续输入了两个运算符。
- def preCheck(self):
- exp = self.exp
- for index in range(len(exp)):
- if exp[index] in "+-×÷" and exp[index - 1] in "+-×÷":
- return False
- if exp[index] == "." and exp[index - 1] == ".":
- return False
- return self.exp
这一步的作用是将用户输入的字符串改造成列表,这样可以使数字和运算符独立存在,方便后续操作。
- def formatChange(self):
- self.midfix = []
- tmp = ''
- for index in range(len(self.exp)):
- // 如果第一个字符是负号“-”
- if index == 0 and self.exp[index] == "-":
- tmp += "-"
- // 如果字符是负号“-”且前一个字符是左括号
- elif index > 0 and self.exp[index - 1] == "(" and self.exp[index] == "-":
- tmp += str(self.exp[index])
- // 如果字符是数字或小数点或“π”
- elif self.exp[index].isdigit() or self.exp[index] == '.' or self.exp[index] == 'π':
- tmp += str(self.exp[index])
- else:
- if tmp != '':
- self.midfix.append(tmp)
- tmp = ''
- self.midfix.append(self.exp[index])
- if tmp != '':
- self.midfix.append(tmp)
- return self.midfix
- def makeSuffixExp(self):
- opList = []
- opDict = self.opDict
- self.suffixExpList = []
- for item in self.midfix:
- // 如果是数
- if self.isNumber(item):
- self.suffixExpList.append(item)
- // 如果是π
- if item == 'π':
- self.suffixExpList.append(str(math.pi))
- // 如果是这几种运算符号(+-×÷^!)
- if item in "+-×÷^!":
- if opList == []:
- opList.append(item)
- else:
- if opDict[item] <= opDict[opList[len(opList)-1]]:
- for index in range(len(opList)):
- self.suffixExpList.append(opList[index])
- del opList[index]
- opList.append(item)
- // 如果是左括号
- if item == "(":
- opList.append(item)
- // 如果是右括号
- if item == ")":
- pt = len(opList) - 1
- while opList[pt] != "(":
- pt -= 1
- pt += 1
- for index in range(pt, len(opList)):
- self.suffixExpList.append(opList[index])
- opList.pop(index)
- opList.pop()
- self.suffixExpList.extend(opList[::-1])
- return self.suffixExpList
其中,opDict 是我定义的一个字典,定义了运算符的优先级:
opDict = {"+":1, "-":1, "×":2, "÷":2, "(":0, "^":3, "!":3}
这里也引用了我自定义的一个函数 isNumber,用来判断一个变量是否是数字:
- def isNumber(self, num):
- try:
- float(num)
- return True
- except ValueError:
- pass
- try:
- import unicodedata
- unicodedata.numeric(num)
- return True
- except (TypeError, ValueError):
- pass
- return False
- def calculate(self):
- stack = Stack()
- while expLength := len(self.suffixExpList):
- poppedItem = self.suffixExpList.pop(0)
- if poppedItem in "+-×÷^":
- num2 = stack.pop()
- num1 = stack.pop()
- if poppedItem == "+":
- newNum = self.add(num1, num2)
- elif poppedItem == "-":
- newNum = self.sub(num1, num2)
- elif poppedItem == "×":
- newNum = self.mul(num1, num2)
- elif poppedItem == "÷":
- newNum = self.dev(num1, num2)
- else:
- newNum = self.power(num1, num2)
- stack.push(newNum)
- elif poppedItem == "!":
- num = stack.pop()
- newNum = self.factorial(num)
- stack.push(newNum)
- else:
- stack.push(float(poppedItem))
- expLength -= 1
- if len(stack) == 1:
- return stack.pop()
这个函数中引用了几个自定义的运算函数:
- // 加
- def add(self, num1, num2):
- return num1 + num2
- // 减
- def sub(self, num1, num2):
- return num1 - num2
- // 乘
- def mul(self, num1, num2):
- return num1 * num2
- // 除
- def dev(self, num1, num2):
- return num1 / num2
- // 指数
- def power(self, num1, num2):
- return math.pow(num1, num2)
- // 阶乘
- def factorial(self, num):
- result = 1
- for i in range(2, int(num)+1):
- result *= i
- return result
- def execute(self):
- if self.preCheck():
- self.formatChange()
- self.makeSuffixExp()
- return self.cancelZero(self.calculate())
- else:
- return "ERROR"
这个函数里引用的cancelZero函数的作用是如果结果的小数位为0,则在显示时去掉小数部分:
- def cancelZero(self, num):
- if str(num).endswith(".0"):
- return int(num)
- else:
- return num
在第 5 步运算这个函数中,使用了 Stack 类的实例,这个类的实现放在这里,就是一个简单的用链表实现的栈:
- class Node:
-
- def __init__(self, data, next=None):
- self.data = data
- self.next = next
-
- class Stack:
-
- def __init__(self):
- self.head = None
-
- def __iter__(self):
- def visitNodes(node):
- if node != None:
- visitNodes(node.next)
- tempList.append(node.data)
- tempList = list()
- visitNodes(self.head)
- return iter(tempList)
-
- def peak(self):
- if self.isEmpty():
- raise KeyError("The stack is empty.")
- return self.head.data
-
- def isEmpty(self):
- return self.head is None
-
- def __len__(self):
- if cur := self.head:
- self.size = 1
- else:
- return 0
- while cur.next is not None:
- self.size += 1
- cur = cur.next
- return self.size
-
- def push(self, data):
- self.head = Node(data, self.head)
-
- def pop(self):
- poppedData = self.head.data
- self.head = self.head.next
- return poppedData
tkinter 在这个项目中的作用就比较简单了,我主要的想法就是在窗口最上部放一个显示屏,下边都是计算器的各种按钮,布局比较简单。为了不逼死强迫症(我就是强迫症),我设计了5X5共25个按钮,在以上的基础上又加入了开根号、清除一个数字的C按钮、清除所有数字的CE按钮。以下是我的代码,抛砖引玉吧:
- import tkinter as tk
- from turtle import bgcolor
- from calculator import Expression
-
- window = tk.Tk()
- window.title("计算器")
- window.geometry('450x490')
- window.configure(background="#4169e1", cursor="mouse")
- exp = tk.StringVar()
- exp.set('')
- entry_panel = tk.Entry(window, textvariable=exp, width=25, font=("Arial", 20), foreground="#00bfff", background="#191970", relief="sunken")
- entry_panel.grid(row=0, column=4, columnspan=5)
-
- //输入字符的功能
- def enterNum(num):
- if exp.get() == "ERROR":
- exp.set("")
- exp.set(exp.get() + num)
-
- //清空显示屏的功能
- def clearEntry():
- exp.set('')
-
- //退格功能
- def backspace():
- exp.set(exp.get()[:-1])
-
- //开二次根号功能
- def sqrtExec():
- a = Expression(exp.get())
- exp.set(a.sqrtExec())
-
- //计算
- def calculate():
- a = Expression(exp.get())
- exp.set(str(a.execute()))
-
- w = 10
- h = 5
-
- buttonOne = tk.Button(text='1', width=w, height=h, command=lambda: enterNum('1'), font=(30), background="#00bfff")
- buttonOne.grid(row=2, column=4)
- buttonTwo = tk.Button(text='2', width=w, height=h, command=lambda: enterNum('2'), font=(30), background="#00bfff")
- buttonTwo.grid(row=2, column=5)
- buttonThree = tk.Button(text='3', width=w, height=h, command=lambda: enterNum('3'), font=(30), background="#00bfff")
- buttonThree.grid(row=2, column=6)
- buttonFour = tk.Button(text='4', width=w, height=h, command=lambda: enterNum('4'), font=(30), background="#00bfff")
- buttonFour.grid(row=3, column=4)
- buttonFive = tk.Button(text='5', width=w, height=h, command=lambda: enterNum('5'), font=(30), background="#00bfff")
- buttonFive.grid(row=3, column=5)
- buttonSix = tk.Button(text='6', width=w, height=h, command=lambda: enterNum('6'), font=(30), background="#00bfff")
- buttonSix.grid(row=3, column=6)
- buttonSeven = tk.Button(text='7', width=w, height=h, command=lambda: enterNum('7'), font=(30), background="#00bfff")
- buttonSeven.grid(row=4, column=4)
- buttonEight = tk.Button(text='8', width=w, height=h, command=lambda: enterNum('8'), font=(30), background="#00bfff")
- buttonEight.grid(row=4, column=5)
- buttonNine = tk.Button(text='9', width=w, height=h, command=lambda: enterNum('9'), font=(30), background="#00bfff")
- buttonNine.grid(row=4, column=6)
- buttonClear = tk.Button(text='CE', width=w, height=h, command=clearEntry, font=(30), background="#1e90ff")
- buttonClear.grid(row=2, column=8)
- buttonZero = tk.Button(text='0', width=w, height=h, command=lambda: enterNum('0'), font=(30), background="#00bfff")
- buttonZero.grid(row=5, column=5)
- buttonBackspace = tk.Button(text='C', width=w, height=h, command=backspace, font=(30), background="#1e90ff")
- buttonBackspace.grid(row=2, column=7)
- buttonAdd = tk.Button(text='+', width=w, height=h, command=lambda: enterNum('+'), font=(30), background="#87cefa")
- buttonAdd.grid(row=3, column=7)
- buttonMinus = tk.Button(text='-', width=w, height=h, command=lambda: enterNum('-'), font=(30), background="#87cefa")
- buttonMinus.grid(row=4, column=7)
- buttonMulti = tk.Button(text='×', width=w, height=h, command=lambda: enterNum('×'), font=(30), background="#87cefa")
- buttonMulti.grid(row=5, column=7)
- buttonDevide = tk.Button(text='÷', width=w, height=h, command=lambda: enterNum('÷'), font=(30), background="#87cefa")
- buttonDevide.grid(row=6, column=7)
- buttonCalc = tk.Button(text='=', width=w, height=h, command=calculate, font=(30), background="#468284")
- buttonCalc.grid(row=6, column=8)
- buttonLeftPar = tk.Button(text='(', width=w, height=h, command=lambda: enterNum('('), font=(30), background="#87cefa")
- buttonLeftPar.grid(row=6, column=5)
- buttonRightPar = tk.Button(text=')', width=w, height=h, command=lambda: enterNum(')'), font=(30), background="#87cefa")
- buttonRightPar.grid(row=6, column=6)
- buttonDot = tk.Button(text='.', width=w, height=h, command=lambda: enterNum('.'), font=(30), background="#00bfff")
- buttonDot.grid(row=6, column=4)
- buttonPower = tk.Button(text='^', width=w, height=h, command=lambda: enterNum('^'), font=(30), background="#87cefa")
- buttonPower.grid(row=3, column=8)
- buttonSqrt = tk.Button(text='√', width=w, height=h, command=sqrtExec, font=(30), background="#87cefa")
- buttonSqrt.grid(row=4, column=8)
- buttonFact = tk.Button(text='!', width=w, height=h, command=lambda: enterNum('!'), font=(30), background="#87cefa")
- buttonFact.grid(row=5, column=8)
- buttonDouZero = tk.Button(text='00', width=w, height=h, command=lambda: enterNum('00'), font=(30), background="#00bfff")
- buttonDouZero.grid(row=5, column=4)
- buttonPi = tk.Button(text='π', width=w, height=h, command=lambda: enterNum('π'), font=(30), background="#00bfff")
- buttonPi.grid(row=5, column=6)
-
- window.mainloop()
