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))
完毕。