用棧實現簡單的計算器
我們知道在計算的時候,運算子的優先順序是很關鍵的,如乘除法的優先順序要高於加減法,而括號裡面的優先順序要高於括號外面的優先順序。為了表示運算子的優先順序,我們先定一個雜湊表來表示運算子和其優先順序
# value越大 優先順序越高
symbolDict = {"+":0,"-":0,"*":1,"/":1,"^":2,"(":3,")":3}
如果我們要實現一個簡單的計算器,我們就需要正確的處理運算子的優先順序。其基本思想就是先完成優先順序高的運算,再完成優先順序較低的運算。由於優先順序較低的運算可能出現在優先順序高的運算之前,所以我們用一個棧來儲存運算子,當它的優先順序大於等於下一個運算子時,將其離棧進行計算。下面介紹中綴表示式
中綴表示式
中綴表示式就是我們生活中最常用的表示式,運算子在兩個運算元的中間,如result=a+b。對於一個複合的中綴表示式result=a+b*c,通常分步先計算d=b*c,再計算result=a+b。
字尾表示式
顧名思義,字尾表示式表示運算子在兩個運算元的後面,如:ab+對應的中綴表示式為a+b。對於複合的字尾表示式abc*+來說,對應的中綴表示式為a+b*c。字尾表示式的優點在於不用考慮運算子的優先順序,直接根據表示式就可以從左到右計算出結果。
字尾表示式的求解程式碼為:
def cal(back):
'''
back 是一個儲存字尾表示式的list
如:[1,2,"+",3,"*"] 對應12+3* 即(1+2)*3
'''
# 運算元的棧
numsStack = []
for i in back:
# 遍歷到運算元時 將運算元push進棧
if i not in symbolDict.keys():
numsStack.append(i)
# 遍歷到運算子時 進行計算 並將計算結果push進棧
else:
# 從棧中取出對應兩個運算元
a,b = numsStack.pop(),numsStack.pop()
# 進行計算 並將結果push進棧
# 需要注意的是在這裡b是第一個運算元而a是第二個運算元
numsStack.append(baseCal(a,b,i))
# 返回結果
return numsStack.pop()
執行結果如下:
In [2]: back
Out[2]: [1.0, 5.0, 10.0, 8.0, 4.0, 2.0, '-', '/', '-', '*', '+']
對應的:1+5*(10-8/(4-2))
In [3]: result = cal(back)
In [4]: result
Out[4]: 31.0
In [5]: 1+5*(10-8/(4-2))
Out[5]: 31
所以將輸入的中綴表示式轉換成字尾表示式,就可以從左到右計算出結果了。通過比較python自帶的計算器可以看出,我們的程式的結果是正確的。
中綴表示式轉化成字尾表示式
中綴表示式轉字尾表示式最主要的就是確定表示式的計算過程。字尾表示式和中綴表示式中的運算元的順序是不變的,而運算子則根據優先順序進行重新排列,所以當我們在進行轉化的過程中,對於運算元就直接輸出。而對於運算子的操作,我們也需要一個棧來進行儲存(因為運算子在兩個操作後面,所以每個運算子都需要進棧進行儲存),而棧頂元素出棧的判斷條件就是它的優先順序是否比下一個運算子高或與之相等(判斷表示式運算執行的先後順序)。對於括號的操作,遇到”(“時將其push進棧,遇到”)”時,對棧進行輸出,直到輸出為”(“時停止(注:”(“和”)”不輸出)。
具體程式如下:
def midToBack(equation):
'''
str equation 輸入的中綴表示式的字串
'''
# 運算子棧
symbolStack = []
# 運算元
nums = ""
# 返回的字尾表示式(list)
retval = []
# 從左到右便利equation
i = 0
while i<len(equation):
# 判斷當前是否為運算子 若不是 繼續讀取運算元
if not symbolDict.has_key(equation[i]):
nums+=equation[i]
else:
# 判斷是否讀取了運算元,主要是帶括號的表示式會連續出現多個運算子
if nums:
# 直接輸出運算元
retval.append(float(nums))
nums = ""
# 處理當前的運算子
# 如果棧為空,將運算子push進棧
if not symbolStack:
symbolStack.append(equation[i])
else:
# 處理")"的情況
if equation[i] == ")":
while symbolStack and symbolStack[-1]!="(":
retval.append(symbolStack.pop())
symbolStack.pop()
# 判斷哪些棧中的運算子可以出棧
else:
while symbolStack and symbolDict[equation[i]]<=symbolDict[symbolStack[-1]]:
if symbolStack[-1] == "(":
break
retval.append(symbolStack.pop())
symbolStack.append(equation[i])
i += 1
# 將剩餘的運算元和運算子輸出
if nums:
retval.append(float(nums))
while symbolStack:
retval.append(symbolStack.pop())
return retval
執行結果如下:
In [1]: back = midToBack("1+5*(10-8/(4-2))")
1+5*(10-8/(4-2))
In [2]: back
Out[2]: [1.0, 5.0, 10.0, 8.0, 4.0, 2.0, '-', '/', '-', '*', '+']