小程序简单计算器实现思路

465 阅读3分钟

好久不写了,这回又是点最近业务相关的😂。这篇断断续续也写了三个月(喂!) 要在小程序上做个小程序,整个重点分成2个部分:

  • 1、输入控制;
  • 2、算式计算。

输入控制是为了保证我们最后计算的算式是个合法算式。而算式计算,因为eval的不安全性,小程序把该方法禁用了,我们得自己实现简单的‘+-*/’。如图:

输入控制:

我们把算式分成两个,一个是真实的用于计算的算式,另一个是显示用的给用户观看的便于理解的算式。

  1. 真实的算式:
  • 保证算式第一位是数字,如果输入的是符号,在前面补零;
  • 保证最后一位是数字,如果不是抛出错误提示
  • 保证算式形式是'数字-符号-数字'形式,当输入符号时,当前最后一位如果也是符号,则替换
  • 如果当前是初始默认的0,如果首次输入为数字则替换为真实输入。
  1. 显示的算式:
  • 替换'*'、'/'符号,变成方便观看的'×'、'÷';
  • 首位如果输入了符号,再前面补零;

当输入时,真实的算式和显示的算式一起变动,保证算式合法有效。

算式计算

以下拿“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
}