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))

完毕。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值