Lua implements alternative calculators

Lua's use is now essentially a necessary skill in the game industry. Because it is not used in the project, I have seen several tutorials, experimented several times, and I haven't written something systematically, so I forget how long I have read it. I've seen it again these two days, and I'm ready to start doing something casually. I'm familiar with it and what to do. At that time, I was working as a calculator.~

In the past, we used c # to make an alternative calculator. The idea is basically the same: divide the numbers and symbols of formulas without parentheses, then traverse the calculation results of symbols, replace the original formulas after calculation, and eliminate the parentheses. See another article for a detailed description. https://blog.csdn.net/a598211757/article/details/86591827 But because there is no api in lua, and I'm encoding with sublime, and the hints are not complete, so I still stepped on a lot of pits, recorded.

The first is to calculate the formula without parentheses, and put numbers and symbols in two arrays. Here, four operations exist in a string. find is used to determine whether a character is an operator or not. Classification is stored in two arrays. If c, string.split is enough. If lua has no ready-made api, it needs to be segmented by itself. The following points should be noted:

  1. When traversing a string, you can't store all characters one by one directly. Because of the existence of multiple digits, there will be problems in storing one by one, such as 2*300-5. After storing one by one, the number array is {2,3,0,0,5} and the symbol array is {*,-}. It can't correspond. It should be the number array length = the symbol array length + 1. So when traversing here, we need to decide whether the last one is a number or a symbol. If it is a number, it should be merged with the last one.
  2. Because the formula may start with "-" or "+", it does not conform to the length rule above, and the way to deal with it is to fill in 0 in the front. That is - 3 * 5 + 1 is processed into 0 - 3 * 5 + 1 and then segmented.
  3. The string.find method, which has been pitted for a long time, has the following parameters: string.find (s, pattern [, init [, plain]). The sequence of parameters is: the string to be operated on; the matched string (rule); the starting position; true/false, true means only matching strings, that is, special strings such as "(+-*/)", before. No need to add "%", default to false. That is, if the last parameter does not pass in true, the string will be looked up according to the rule of the second parameter, such as the input parameter is string.find (str, "..."), which is intended to find the character "..." in str, but in fact it is any character, because "..." and any character are. Matching, that is, as long as it is not an empty string, the return must be 1. The correct expression is string.find (str, "%."), with an escape character, to simply find the string ".", or to write string.find (str, ".", 1, true), to start looking for the string from the first position of STR ".", where the simple matching string does not use rules.
  4. Take a single character of a string and use the string.sub method to traverse it.

The code is as follows:

AllSigns = "+-*/"

--Unbracketed calculation,Segmentation of numbers and symbols
function GetNoBracketValue(str)
	print(string.format("The expression to be calculated is:%s",str))

	str = DealSigns(str)

	print(string.format("The postulate of the processing symbol is:%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("The results are as follows:%s",finalValue))
	return finalValue

	-- body
end

DealSigns and DealTables methods are mentioned later. You can see that symbol array signs I set up a metatable here, this is what I do for logs, and it has nothing to do with logic, but it is more clear when looking at logs. By dividing numbers and symbols into two arrays through the above process, we can calculate the combined values of the two arrays by traversing the symbolic arrays and according to the rule that the length of the digital arrays = the length of the symbolic arrays + 1.

Processing digital arrays and symbolic arrays, first multiply and divide, then add and subtract, after processing a symbol, kick the symbol out of the array, and replace the calculation results with digital arrays until the length of the symbolic array is 0, then the only value of the digital array is the result of calculation, the code is as follows, this code feels very rubbish written. Extremely rubbish, but I haven't figured out how to change it yet.

index = 0
function DealTables(numbers,signs)
	if(#signs ==0)
	then
		return numbers[1]
	end

	for k,v in pairs(numbers) do
		print(string.format("The first%s After secondary calculation numbers: ",index)..v)
	end

	for k,v in pairs(signs) do
		print(string.format("The first%s After secondary calculation signs: ",index)..v)
	end

	--print(signs)

	local signsStr = signs..""
	--print("Screen out all the symbols:"..signsStr)

	local FormulaStr  = signs..numbers
	print(string.format("The first%s The expression after the second calculation:%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

If there is multiplication and division, the values on both sides of the symbol are calculated according to the order from left to right, and two arrays are operated at the same time, such as formula "3+8*4/9". After division, mulIndex = 2 < divIndex = 3 is obtained from the above code. Then the values calculated by index of the number array are 2, 2+1, that is, the values of 8 and 4, respectively. After calculation, delete and replace the new array with {3,32,9}, {+, /} respectively. The final value is obtained by deleting all symbols recursively. Two decimal points are reserved above.

Through the above two methods, we can calculate the value of the operation formula in parentheses, and then replace the string in parentheses in the original formula. But because the operation symbol before the parentheses is indefinite, we need to deal with it. First, we put forward the operation formula in parentheses from the original formula and use string.sub to find the first one. A left parenthesis and a right parenthesis, please don't cut the parentheses in, and then use the GetNoBracketValue method above to get this value. I wanted to simply use string.gsub to replace the parentheses with the results of calculation, but no doubt I stepped in again, because string.gsub still preferred the rules. To match, similar to the string.find mentioned above, and did not find any parameters of this method can restrict matching only by strings, so it can only be achieved by first truncation followed by closure, the code is as follows:

--Processing parentheses
function DealBracket(str)

	if(string.find(str,"-")==1 or string.find(str,"+")==1)
	then
		str = "0"..str
	end

	print(string.format("To remove parentheses, the prefix is:%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("The results in parentheses are as follows:%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("The substitution result is followed by:%s",str))
		str = string.gsub(str,"%+%-","%-")
		str = string.gsub(str,"%-%-","%+")
		print(string.format("The formula for processing addition and subtraction is as follows:%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

It should be noted that when splicing strings, if the left parentheses are the leftmost side of the formula, then the leftFormula should be assigned an empty string. Similar situations may occur after splicing, such as "*-"*+"and"-". For these characters, only the"*-"/-" and "*-" processed above are processed when the bracketless formula is last processed. The reason is that when dealing with "*-", the method is to find the nearest "+" or "-" on the left. Then the symbols are merged, but if processed at this time, because there are brackets in the formula, the symbols may be merged across brackets, such as [3-5]*-5, and then the processing becomes [3+5]*5, which is obviously wrong. In this way, each parenthesis is calculated in turn to get a formula without parentheses, which can be processed with GetNoBracketValue above, but because the formula at this time may be disorderly, such as 3*(1-2)+10/(1-3) parentheses are removed and the result is 3*-1+10/-2, it is certainly impossible to solve with the above method, because it is not in conformity with the rules. Law number array length = symbol array length + 1, so it needs to be processed further.

The rules dealing with "*-" and "/-" have been said above. Find the nearest "+" or "-" on the left and merge the symbols. Processing each "*-" and "/-" in turn until the replacement is complete can be calculated, the code is as follows:

--Processing symbols
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("After processing multiplication and division, the formula is as follows:%s",str))
		return(DealSigns(str))
	else
		return(str)

	end
end

Judge whether there are two characters, not directly returned. Find the left-most "*-" and "/-". The local sub = string. find (str, "-", STR - Temp + 1, true) or 999999 is written above, and then math.min is used to get the minimum value, that is, the index of the left most "*-" or "/-", where the or operator is not intended to make redundant judgments below. If temp value is 9999999, that is, both finds return to nil, so you can return to the string directly. Otherwise, the next step is to find the left-most "+" or "-". At first, I put a negative index in the find, using reverse order search, the result has been wrong, can only reverse the string, the principle is the same, just note. The value of subaddTemp is the index after inversion, so the second inversion should be converted.

Listen for input, call method:

Formula = io.read()
local value = DealBracket(Formula)
print(string.format("The final result is:%s",value))

Complete.

Tags: calculator encoding sublime

Posted on Thu, 29 Aug 2019 02:05:08 -0700 by rharter