772.基本计算器(javaScript)

185 阅读3分钟

需求:实现一个基本的计算器来计算简单的表达式字符串

说明:表达式字符串只包含非负整数, +  -  *  / 操作符,左括号 ( ,右括号 ) 和空格。整数除法需要向下截断。

解法思路一:只有加号

解法:遇到数字就不断相加

// 1+2+10
const calculate = function(s) {
   const sArr = s.split('+')
  // 用一个队列数组存放
  const q = [];
  // 把表达式字符存放在队列中
  for (let i = 0; i < sArr.length; i++) {
    if (i !== sArr.length -1) {
      q.push(sArr[i], '+')
    } else {
      q.push(sArr[i])
    }
  }
  // 定义两个变量,num 用来表示当前的数字,sum 用来记录最后的和 
   let num = 0, sum = 0;
   // 遍历数组队列
   while(q.length) {
     // 取出队列中的第一个字符
     const c = q.shift()
     // 是字符
     if (isNaN(c - 0)) {
       sum += num
       num = 0
     } else {
       // 是数字: 如果数字字符是带有空格,也是不影响的,所以可以不用特殊处理空格字符
       num = c - 0
     }
   }
   // 最后一项是没有加号的,需要再加一次
   sum += num
   return sum
}

解法思路二:引入减号

解法:两个队列存放,一个队列存放数字,一个队列存放操作符

// 1+10-2
const calculate = function(s) {
  // 存放数字的队列
  const q = s.split(/[+-]/);
  // 存放操作符的队列:
  const op = s.split(/\d*/).filter(v => v.trim() !== '');
  
  // 定义两个变量, sum 用来记录最后的和 
   let sum = q.shift() - 0;
   // 遍历数字队列
   while(q.length) {
     // 取出操作符
     const opV = op.shift()
     // 取出下一个数字
     const c = q.shift()
     if (opV === '+') {
       sum += c - 0
     } else if (opV === '-') {
       sum -= c - 0
     }
   }
  
   return sum
}

解法思路三:引入乘除

解法:要考虑符号的优先级问题,不能再简单得对 sum 进行单向的操作。例如计算:1 + 2 * 10,当遇到乘号的时候:sum = 1,num = 2,而乘法的优先级比较高,得先处理 2 x 10 才能加 1。对此,就把它们暂时记录下来,具体操作如下

const calculate = function(s) {
  // 存放数字的队列
  const q = s.split(/[+-*/]/).filter(v => v);
  // 存放操作符的队列:
  const op = s.split(/\d*/).filter(v => v.trim() !== '');
  // 用一个新的变量,记录要被处理的数, 设置一个初始项,为了当*或者/作为第一个操作符时计算:例如 2*10+2
  const stack = [q.shift() - 0]
  // 定义两个变量,sum 用来记录最后的和 
   let sum = 0;
   // 遍历数字队列
   while(q.length) {
     // 取出操作符
     const opV = op.shift()
     // 取出下一个数字
     const c = q.shift()
     if (opV === '+') {
       // 遇到加号,把当前的数压入到堆栈中
       stack.push(c - 0)
     } else if (opV === '-') {
       // 减号:把当前数的相反数压入到堆栈中
       stack.push(-c)
     } else if (opV === '*') {
       // 遇到乘法:取出stack中前一个数取出 和当前数相乘,把结果入栈中
       stack.push(stack.pop() * c)
     } else if (opV === '/') {
       // 除号,把前一个数从堆栈中取出,然后除以当前的数,再把结果放回堆栈
       stack.push(Math.floor(stack.pop() / c))
     }
   }
   // 把堆栈中的数字求和
   while(stack.length) {
     sum += stack.shift()
   }
   // 返回最终结果
   return sum
}

解题思路四:引入小括号

小括号的表达式优先计算:先利用上面的方法计算小括号里面的表达式;当遇到左括号的时候,可以递归的处理;当遇到右括号的时候,表明小括号里面的处理完毕,递归已经返回。

// 1+2*10+(3+4)=28
const calculate = function(s) {
  // 存放数字的队列
  const q = s.split(/[+-*/()]/).filter(v => v);
  // 存放操作符的队列:
  const op = s.split(/\d*/).filter(v => v.trim() !== '');
  // 定义两个变量, sum 用来记录最后的和
  let sum = 0;
  return calculateRecursion(q, op, sum)
}

const calculateRecursion = function(q, op, sum) {
  // 用一个新的变量,记录要被处理的数
  const stack = [q.shift() - 0]
   // 遍历数字队列
   while(q.length) {
     // 取出操作符
     const opV = op.shift()
     // 取出下一个数字
     const c = q.shift()
     // 遇到一个左括号,开始递归调用,求得括号里的计算结果,
     if (opV === '(') {
        // 将当前取出的值还放进队列中
        q.push(c)
        sum = calculateRecursion(q, op, sum)
     } else {
       if (opV === '+') {
         // 遇到加号,把当前的数压入到堆栈中
         stack.push(c - 0)
       } else if (opV === '-') {
         // 减号:把当前数的相反数压入到堆栈中
         stack.push(-c)
       } else if (opV === '*') {
         // 遇到乘法:取出stack中前一个数取出 和当前数相乘,把结果入栈中
         stack.push(stack.pop() * c)
       } else if (opV === '/') {
         // 除号,把前一个数从堆栈中取出,然后除以当前的数,再把结果放回堆栈
         stack.push(Math.floor(stack.pop() / c))
       } else if (opV === ')') {
       // 遇到右括号,就可以结束循环,直接返回当前的总和 
         break
       }
     }
   }
   // 把堆栈中的数字求和
   while(stack.length) {
     sum += stack.shift()
   }
   return sum
}