Lua的使用現在基本上屬於遊戲行業的必備技能了,由於項目中沒有使用,所以看過幾次教程,實驗過幾次,一直沒有系統的寫點東西,導致看完過了多久就忘了。這兩天又看了遍,準備着手隨便做點什麼,熟悉下,做什麼呢,當時是做計算器啊~
以前用c#做過另類計算器,思路上基本是一樣的,即把沒有括號的式子的數字和符號分割,然後遍歷符號計算結果,計算之後替換原式子,把括號消完就好了,具體描述見另一篇文章https://blog.csdn.net/a598211757/article/details/86591827,但是由於lua裏面沒有部分api,以及我是用sublime編碼的,提示也不全,所以還是踩了很多坑的,記錄下。
首先是計算沒有括號的式子,把數字和符號分別放在兩個數組裏面。這裏把四則運算存在一個字符串裏面,通過find來判斷字符是否爲運算符,分類存放在兩個數組中,c#的話直接string.split就可以了,lua的話沒有現成的api,需要自己去分割。需要注意的有以下幾點:
- 遍歷字符串的時候不能直接全部逐個字符存放,因爲有多位數的存在,逐個存放會有問題,比如2*300-5,逐個存放之後數字數組爲{2,3,0,0,5},符號數組爲{*,-},無法對應,應該是數字數組長度=符號數組長度+1。所以這裏遍歷的時候需要判斷下上一個存放的是數字還是符號,如果是數字,應該與上一個存放的值合併。
- 因爲式子有可能會以“-”或“+”開頭,這樣的話就不符合上面的長度規則,處理方式爲在前面補0。即-3*5+1處理成0-3*5+1,然後再進行分割
- string.find方法,被這個坑了很久,看下這個方法: string.find (s, pattern [, init [, plain]]) 參數依次爲:要操作的字符串;匹配的字符串(規則);起始位置;true/false,true表示只匹配字符串,即特殊字符串比如“(+-*/)”,前面不需要加“%”,默認爲false。意思即如果最後一個參數沒有傳入true,那麼會按照第二個參數傳入的規則去查找字符串,比如傳入參數爲string.find(str,“.”),本意是在str裏面查找“.”這個字符,但是實際上查找的卻是任意字符,因爲“.”與任意字符是匹配的,即只要不爲空串,這個返回必爲1。正確的寫法爲string.find(str,“%.”),加上轉義符,表示單純查找字符串“.”,或者寫成string.find(str,“.”,1,true),表示從str第一個位置開始查找字符串“.”,這裏單純的匹配字符串,不會使用規則。
- 取字符串單個字符,遍歷的時候使用string.sub方法
代碼如下:
AllSigns = "+-*/"
--無括號計算,分割出數字和符號
function GetNoBracketValue(str)
print(string.format("要計算的表達式爲:%s",str))
str = DealSigns(str)
print(string.format("處理符號後式子爲:%s",str))
local numbers = {}
local signs = {}
setmetatable(signs,stringMetatable)
--local value
if(string.find(str,"-")==1 or string.find(str,"+")==1)
then
str = "0"..str
end
local lastTypeIsSign = true
for i=1,#str do
local value = string.sub(str,i,i)
--print(value)
if(string.find(AllSigns,value,1,true))
then
table.insert(signs,value)
lastTypeIsSign = true
else
if(lastTypeIsSign)
then
table.insert(numbers,value)
else
local tempValue = numbers[#numbers]..value
table.remove(numbers)
table.insert(numbers,tempValue)
end
lastTypeIsSign = false
end
end
local finalValue =string.format("%0.2f",DealTables(numbers,signs))
print(string.format("計算結果爲:%s",finalValue))
return finalValue
-- body
end
其中DealSigns與DealTables方法後文再提。可以看到符號數組signs我這邊設置了一個元表,這個是我這邊做log用的,跟邏輯沒有關係,可有可無,只是看log的時候更加清晰點。通過上面的過程把數字和符號分割成兩個數組,此時就可以通過遍歷符號數組,根據 數字數組長度=符號數組長度+1 的規則去計算這兩個數組結合起來的值。
處理數字數組與符號數組,先乘除後加減,處理完一個符號,將該符號從數組裏面踢出,同時將計算結果替換到數字數組,直到符號數組長度爲0,此時數字數組的唯一值即爲計算結果,代碼如下,這段代碼感覺寫的非常垃圾...極其垃圾,但是還沒想好怎麼去改:
index = 0
function DealTables(numbers,signs)
if(#signs ==0)
then
return numbers[1]
end
for k,v in pairs(numbers) do
print(string.format("第%s次計算後的numbers:",index)..v)
end
for k,v in pairs(signs) do
print(string.format("第%s次計算後的signs:",index)..v)
end
--print(signs)
local signsStr = signs..""
--print("篩選出所有的符號:"..signsStr)
local FormulaStr = signs..numbers
print(string.format("第%s次計算後的表達式:%s",index,FormulaStr))
local mulIndex = string.find(signsStr,"*",1,true)
local divIndex = string.find(signsStr,"/",1,true)
if(mulIndex and divIndex)
then
if(mulIndex<divIndex)
then
numbers[mulIndex] = numbers[mulIndex] * numbers[mulIndex+1]
table.remove(numbers,mulIndex+1)
table.remove(signs,mulIndex)
else
numbers[divIndex] = numbers[divIndex] / numbers[divIndex+1]
table.remove(numbers,divIndex+1)
table.remove(signs,divIndex)
end
elseif(mulIndex)
then
numbers[mulIndex] = numbers[mulIndex] * numbers[mulIndex+1]
table.remove(numbers,mulIndex+1)
table.remove(signs,mulIndex)
elseif(divIndex)
then
numbers[divIndex] = numbers[divIndex] / numbers[divIndex+1]
table.remove(numbers,divIndex+1)
table.remove(signs,divIndex)
else
if(signs[1]=="+")
then
numbers[1] = numbers[1]+numbers[2]
table.remove(numbers,2)
table.remove(signs,1)
else
numbers[1] = numbers[1]-numbers[2]
table.remove(numbers,2)
table.remove(signs,1)
end
end
index = index + 1
return DealTables(numbers,signs)
-- body
end
如果有乘除,就根據從左到右的順序,計算符號兩邊的值,同時操作兩個數組,比如式子 “3+8*4/9”,分割後分別爲{3,8,4,9}{+,*,/},根據上面代碼得出mulIndex = 2 <divIndex=3,則先計算數字數組index分別爲2, 2+1計算的值,即8與4的值,計算之後刪除,替換得新數組分別爲{3,32,9},{+,/}。遞歸刪掉所有符號即可得到最終的值,上文保留了兩位小數點。
通過上文兩個方法,可以算出一個小括號內運算式的值,然後再去替換原式子中該括號的字符串即可,但是由於小括號前的運算符號是不定的,所以需要處理,首先是從原式子中提出小括號這段運算式,使用string.sub即可,找第一個左小括號與右小括號,注意別把括號也切進去即可,然後使用上文的GetNoBracketValue方法即可取得這個值,本來我是想簡單的使用string.gsub去用計算結果替換括號內容,但是毫無疑問又踩坑了,因爲string.gsub還是優先使用規則去匹配,類似上面提到的string.find,而且也沒有找到這個方法有哪個參數可以限制單純按字符串匹配的,所以只能通過先截後合的方法去實現,代碼如下:
--處理括號
function DealBracket(str)
if(string.find(str,"-")==1 or string.find(str,"+")==1)
then
str = "0"..str
end
print(string.format("去括號前式子爲:%s",str))
local slBracket = string.find(str,"%(")
local mlBracket = string.find(str,"%[")
local llBracket = string.find(str,"%{")
if(slBracket)
then
local srBracket = string.find(str,"%)",slBracket)
local BracketFormula = string.sub(str,slBracket+1,srBracket-1)
local sBracketValue = GetNoBracketValue(BracketFormula)
print(string.format("括號計算結果爲:%s",sBracketValue))
local leftFormula
if(slBracket==1)
then
leftFormula = ""
else
leftFormula = string.sub(str,1,slBracket-1)
end
--print(leftFormula)
local rightFormula = string.sub(str,srBracket+1)
--print(rightFormula)
str = leftFormula..sBracketValue..rightFormula
print(string.format("替換結果後式子爲:%s",str))
str = string.gsub(str,"%+%-","%-")
str = string.gsub(str,"%-%-","%+")
print(string.format("處理加減後式子爲:%s",str))
return DealBracket(str)
elseif(mlBracket) then
str = string.gsub(str,"%[","%(")
str = string.gsub(str, "%]", "%)")
return DealBracket(str)
--todo
elseif(llBracket) then
str = string.gsub(str,"%{","%(")
str = string.gsub(str, "%}", "%)")
return DealBracket(str)
else
return GetNoBracketValue(str)
--todo
end
--todo
end
其中需要注意的是拼接字符串的時候,如果式子最左邊爲左小括號,這時應給leftFormula 賦值爲空串。拼接之後可能會有“*-”,“*+”,“--”等類似情況出現,針對這些字符,此時只需處理上文中處理的那些,“*-”,“/-”放在最後處理無括號計算式時處理,原因在於處理“*-”時,方法是找到左邊最近的一個“+”或“-”,然後合併符號,但是如果此時處理的話,因爲式子中還有括號,可能會跨括號去合併符號,例如[3-5]*-5,此時處理就變成了[3+5]*5,顯然是不對的。這樣各個括號依次計算,得到一個沒有括號的式子,再用上文的GetNoBracketValue處理即可,但是由於此時的式子可能是雜亂無章的,比如3*(1-2)+10/(1-3)去掉括號結果爲3*-1+10/-2,用上文方法肯定是沒法解決的,因爲它不符合規律 數字數組長度=符號數組長度+1,所以需要繼續對其進行處理。
處理“*-”,“/-”的規則上面已經說過,找到左邊最近的一個“+”或“-”,然後合併符號。依次處理每一個“*-”,“/-”,直到替換完畢之後即可以計算,代碼如下:
--處理符號
function DealSigns(str)
-- body
if string.find(str,"*-",1,true) or string.find(str,"/-",1,true) then
--todo
local mulSub = string.find(str,"*-",1,true) or 9999999
local divSub = string.find(str,"/-",1,true) or 9999999
--print(mulSub)
--print(divSub)
local temp = math.min(mulSub,divSub)
--print(temp)
if temp==9999999 then
--todo
else
--todo
str = string.reverse(str)
--print(str)
local sub = string.find(str,"-",#str - temp+1,true) or 9999999
local add = string.find(str,"+",#str - temp+1,true) or 9999999
local subaddTemp = math.min(sub,add)
--print(subaddTemp)
--subaddTemp = #str - subaddTemp +1
print(subaddTemp)
if subaddTemp==9999999 then
str = string.reverse(str)
local left = string.sub(str,1,temp)
local right = string.sub(str,temp+2)
str ="0-"..left..right
else
str = string.reverse(str)
subaddTemp = #str - subaddTemp +1
local left = string.sub(str,1,subaddTemp)
local mid = string.sub(str,subaddTemp+1,temp)
local right = string.sub(str,temp+2)
str =left.."-"..mid..right
end
end
str = string.gsub(str,"%*%+","%*")
str = string.gsub(str,"%/%+","%/")
str = string.gsub(str,"%+%-","%-")
str = string.gsub(str,"%-%-","%+")
print(string.format("處理乘除後式子爲:%s",str))
return(DealSigns(str))
else
return(str)
end
end
判斷是否有這兩個字符,沒有直接返回。找出最左邊的“*-”,“/-”。上文是這樣寫的 local sub = string.find(str,"-",#str - temp+1,true) or 9999999 ,然後使用了math.min去獲取最小值,即爲最左側的“*-”或“/-”所在的索引,這裏用or運算符是不想在下面進行多餘的判斷。如果得出temp值爲9999999,即兩個find返回都爲nil,所以直接返回該串即可,否則進行下一步,找最左側的“+”或“-”,起初我在find裏面傳入負索引,使用倒序查找,結果返回結果一直不對,只能反轉字符串,原理同上,只是注意subaddTemp的值是反轉後的索引,所以二次反轉時要進行換算。
監聽輸入,調用方法:
Formula = io.read()
local value = DealBracket(Formula)
print(string.format("最終結果爲:%s",value))
完畢。