Lua實現另類計算器

Lua的使用現在基本上屬於遊戲行業的必備技能了,由於項目中沒有使用,所以看過幾次教程,實驗過幾次,一直沒有系統的寫點東西,導致看完過了多久就忘了。這兩天又看了遍,準備着手隨便做點什麼,熟悉下,做什麼呢,當時是做計算器啊~

以前用c#做過另類計算器,思路上基本是一樣的,即把沒有括號的式子的數字和符號分割,然後遍歷符號計算結果,計算之後替換原式子,把括號消完就好了,具體描述見另一篇文章https://blog.csdn.net/a598211757/article/details/86591827,但是由於lua裏面沒有部分api,以及我是用sublime編碼的,提示也不全,所以還是踩了很多坑的,記錄下。

首先是計算沒有括號的式子,把數字和符號分別放在兩個數組裏面。這裏把四則運算存在一個字符串裏面,通過find來判斷字符是否爲運算符,分類存放在兩個數組中,c#的話直接string.split就可以了,lua的話沒有現成的api,需要自己去分割。需要注意的有以下幾點:

  1. 遍歷字符串的時候不能直接全部逐個字符存放,因爲有多位數的存在,逐個存放會有問題,比如2*300-5,逐個存放之後數字數組爲{2,3,0,0,5},符號數組爲{*,-},無法對應,應該是數字數組長度=符號數組長度+1。所以這裏遍歷的時候需要判斷下上一個存放的是數字還是符號,如果是數字,應該與上一個存放的值合併。
  2. 因爲式子有可能會以“-”或“+”開頭,這樣的話就不符合上面的長度規則,處理方式爲在前面補0。即-3*5+1處理成0-3*5+1,然後再進行分割
  3. 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第一個位置開始查找字符串“.”,這裏單純的匹配字符串,不會使用規則。
  4. 取字符串單個字符,遍歷的時候使用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))

完畢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章