好久不写了,这回又是点最近业务相关的😂。这篇断断续续也写了三个月(喂!) 要在小程序上做个小程序,整个重点分成2个部分:
- 1、输入控制;
- 2、算式计算。
输入控制是为了保证我们最后计算的算式是个合法算式。而算式计算,因为eval的不安全性,小程序把该方法禁用了,我们得自己实现简单的‘+-*/’。如图:
输入控制:
我们把算式分成两个,一个是真实的用于计算的算式,另一个是显示用的给用户观看的便于理解的算式。
- 真实的算式:
- 保证算式第一位是数字,如果输入的是符号,在前面补零;
- 保证最后一位是数字,如果不是抛出错误提示
- 保证算式形式是'数字-符号-数字'形式,当输入符号时,当前最后一位如果也是符号,则替换
- 如果当前是初始默认的0,如果首次输入为数字则替换为真实输入。
- 显示的算式:
- 替换'*'、'/'符号,变成方便观看的'×'、'÷';
- 首位如果输入了符号,再前面补零;
当输入时,真实的算式和显示的算式一起变动,保证算式合法有效。
算式计算
以下拿“2+5*6-10”为例子。
大概思想是,格式化得到的有效算式字符串,以符号为分割基准,将算式分割成有效数字和符号部分,如:
"2+5*6-10" => ['2', '+', '5', '*', '6', '-', '10']
再遍历所得字符串,按优先级计算,把算式中的计算分割成最小式子进行计算。
1.先写个计算函数,用于简单的'+-*/'计算。
/*
* @param sign 计算符号
* @param num1 输入数字1
* @param num2 输入数字2
*/
signCompute(sign:string, num1:number, num2:number): number {
switch(sign){
case '+':
return num1 + num2;
case '-':
return num1 - num2;
case '*':
return Math.round(num1 * num2);
case '/':
return Math.round(num1 / num2);
default:
return num1
}
}
2.将扁平的算式字符串变成有效数组,符号间的字符转换成数字。
/*
* 将算式字符串转化成有效的数组
* @param formula 一个合法的字符串算式
*/
formulaToArray(formula: string) {
const length = formula.length
let str = '' // 储存字符,如果遇到的是符号,则将存好的字符串push进数组
const regex = /[*\/+-]/ // 是否是符号判断
const formulaList = [] // 最后需要返回的正确数组
for (let index=0; index < length; index++){ // 开始遍历字符串
const item = formula[index]
// 如果当前元素是符号,则将str中存储的数字和当前符号push进数组中,并清空str,进入下一轮循环。
if (regex.test(item)) {
formulaList.push(str)
formulaList.push(item)
str = ''
continue
}
// 当前字符非符号时,将当前字符并入str中
str += item
// 当前字符为最后一个字符,将str存储的字符push入数组
if (index == length - 1) formulaList.push(str)
}
return formulaList
}
3.遍历2得到的合法算式数组,进行计算。
若只进行“+-*/”计算,算式的优先级分为2个,因此要遍历两次:
- 第一次先将第一优先级的进行乘除计算。从左到右遍历字数组,遇到乘除算式进行计算,并将结果替换掉原本的算式位置。例如:第一次遍历先计算“5*6”,得出结果30,将原等式变为“2+30-10”。
- 第二次遍历,从左到右将第二优先级的“+-”进行计算。“2+30-10” ➡️ “32-10” ➡️ “22”。
- 如遇到不合法的参数,则将错误进行抛出,后续将在外层进行捕获。
/*
* @param list 算式数组
*/
computeHasPriority(list: Array<string>) {
// 第一次遍历,计算第一优先级
for (let index = 0; index < list.length; index++) {
const item = list[index]
const preIndex = index - 1 // 指向前一个字符的下标
const lastIndex = index + 1 // 指向后一个字符的下标
// 如果当前元素不是“*”或“/”,则结束当前循环
if (item !== '*' && item !== '/') continue
// 除数为0抛出错误
if (item == '/' && list[lastIndex] == '0') throw new Error('除数不能为0哦')
// 计算,得到值(当前index指向的是符号未)
const res = this.signCompute(item, Number(list[preIndex]), Number(list[lastIndex]))
// 将符号位的后一个字符替换为得到的算式值
list[lastIndex] = res.toString()
// 删除符号位和符号位前的字符
list.splice(preIndex, 2)
// 当前index向前移两位,重新指向刚刚得到的算式值
index = index - 2
}
// 第二次遍历计算第二优先级,如需计算则直接返回结果
for (let index = 0; index < list.length; index++) {
const item = list[index]
const preIndex = index - 1
const lastIndex = index + 1
// 如果当前元素不是“+”或“-”,则结束当前循环
if (item !== '+' && item !== '-') continue
const res = this.signCompute(item, Number(list[preIndex]), Number(list[lastIndex]))
list[lastIndex] = res.toString()
// 如果符号位后一个字符位置是最后一个,则直接返回计算结果
if (lastIndex == list.length - 1) return res.toString()
// 删除符号位和符号位前的字符
list.splice(preIndex, 2)
// 当前index向前移两位,重新指向刚刚得到的算式值
index = index - 2
}
// 如果算式只有一位数 直接返回该数
if (list.length == 1) return Number(list[0])
else return 0
}
使用
const formula = '2+5*6-10'
const formulaList = formulaToArray(formula)
try{
const result = computeHasPriority(formulaList)
return true
}catch(e) {
console.log(e)
this.setData({ error: '您的算式不正确哦' })
return false
}