📜  计算器 - TypeScript (1)

📅  最后修改于: 2023-12-03 15:12:01.988000             🧑  作者: Mango

TypeScript 计算器

在程序员的日常工作中,计算器是一款必不可少的工具。而使用 TypeScript 开发的计算器,不仅可以满足计算的需求,还可以通过类型检查、面向对象编程等特性提高开发效率和代码质量。

功能介绍

本计算器支持基本的加、减、乘、除运算,同时支持括号运算和取余运算。在输入过程中,会实时显示当前表达式的运算结果。

计算器演示

开发环境
  • TypeScript 4.2.4
  • Visual Studio Code 1.58.2
技术要点
类型定义

在 TypeScript 中,我们可以使用类型定义来规范代码的数据类型,并在编译期进行类型检查。为计算器项目中常用的类型定义如下:

type Operator = '+' | '-' | '*' | '/' | '%';

interface Token {
  type: 'number' | 'operator' | 'lparen' | 'rparen';
  value: number | Operator;
}

其中,Operator 类型表示运算符,只能是 +-*/% 中的一种。Token 接口表示计算器中的一项,可以是数字(typenumber)、运算符(typeoperator)、左括号(typelparen)或右括号(typerparen)。

解析表达式

在计算器中,将用户输入的一串字符串转换为可以计算的表达式是一个重要的过程。解析字符串时,我们可以使用正则表达式和栈这两种数据结构。具体实现过程如下:

const parse = (expr: string): Token[] => {
  const tokens: Token[] = [];
  let match;

  const regex = /(\d+|[\+\-\*\/\%\(\)])/g;

  while ((match = regex.exec(expr)) !== null) {
    const value = match[0];
    let type: Token['type'] = 'number';

    switch (value) {
      case '+':
      case '-':
      case '*':
      case '/':
      case '%':
        type = 'operator';
        break;
      case '(':
        type = 'lparen';
        break;
      case ')':
        type = 'rparen';
        break;
    }

    let token: Token;

    if (type === 'number') {
      token = {
        type,
        value: parseFloat(value),
      };
    } else {
      token = {
        type,
        value,
      };
    }

    tokens.push(token);
  }

  return tokens;
};

在这里,通过正则表达式将字符串按照数字和运算符分割成单词,然后根据单词的类型创建 Token 对象并存储到数组 tokens 中。

计算表达式

得到表达式后,我们需要进行计算。常见的表达式计算方法有两种,分别是:

  • 通过将中缀表达式转换为后缀表达式,然后使用栈来计算后缀表达式的值;
  • 通过递归来计算中缀表达式的值。

对于本项目,我们使用第二种方法来计算表达式。具体实现过程如下:

const evaluate = (tokens: Token[]): number => {
  const stack: Token[] = [];

  const handleOperator = (op: Operator) => {
    const right = stack.pop();
    const left = stack.pop();

    if (!left || !right) {
      throw new Error(`表达式错误:缺少运算符 ${op} 的操作数`);
    }

    let result: number;

    switch (op) {
      case '+':
        result = left.value + right.value;
        break;
      case '-':
        result = left.value - right.value;
        break;
      case '*':
        result = left.value * right.value;
        break;
      case '/':
        result = left.value / right.value;
        break;
      case '%':
        result = left.value % right.value;
        break;
      default:
        throw new Error(`不支持的运算符 ${op}`);
        break;
    }

    stack.push({
      type: 'number',
      value: result,
    });
  };

  const handleToken = (token: Token) => {
    switch (token.type) {
      case 'number':
        stack.push(token);
        break;
      case 'operator':
        handleOperator(token.value);
        break;
      case 'lparen':
        stack.push(token);
        break;
      case 'rparen':
        let leftParen = false;

        while (stack.length > 0) {
          const token = stack.pop();

          if (token.type === 'lparen') {
            leftParen = true;
            break;
          }

          handleToken(token);
        }

        if (!leftParen) {
          throw new Error(`表达式错误:缺少左括号`);
        }

        break;
    }
  };

  for (const token of tokens) {
    handleToken(token);
  }

  if (stack.length === 0) {
    throw new Error(`表达式错误:未输入任何内容`);
  }

  const resultToken = stack.pop();

  if (stack.length > 0) {
    throw new Error(`表达式错误:缺少运算符或括号`);
  }

  if (resultToken) {
    return resultToken.value;
  } else {
    throw new Error(`表达式错误:未知原因`);
  }
};

在这里,我们定义了两个函数 handleOperatorhandleToken,分别用于处理运算符和计算表达式。通过遍历 tokens 数组,将每个 Token 依次传入 handleToken,在函数内部根据不同的 Token 类型来做相应的处理,直到数组遍历结束为止。

维护者
参与贡献

欢迎参与贡献!如果你发现了问题或有改进建议,请提一个 Issue提交一个 Pull Request

开源许可

本项目使用 MIT 开源许可证,你可以在保留作者版权的情况下自由使用、分发、修改本项目。