使用 Shunting yard 算法在 HTML 中使用 JavaScript 创建计算器

2025年1月9日 | 阅读9分钟

在学习JavaScript时,构建一个计算器是第一个挑战。我们可以使用不同的方法来构建计算器,例如使用eval()、循环等。该网站上已经有两篇关于如何使用eval()和for循环创建计算器的教程。然而,鉴于eval()的使用可能带来的安全问题以及更多的循环可能增加的代码复杂性,这两种方法都不是特别高效。本教程将解释一种我们可以用来构建计算器的有效方法。我们将使用Dijkstra提出的Shunting Yard算法来解决计算器中的表达式,我们将使用两个堆栈而不是一个。

HTML、CSS 和 JS 简介

  1. HTML 是用于构建网页结构的标记语言。
  2. CSS 是用于设计网页元素布局和外观的样式语言。
  3. JS 是用于定义网页元素行为和功能的脚本语言。

Shunting Yard算法简介

通常,该算法用于将中缀表达式转换为后缀表达式,也称为逆波兰表示法(RPN)。a + b 形式的表达式是中缀表示法,而 ab+ 形式是其后缀表示法。评估后缀表达式很容易,通常,计算机将任何给定的中缀表达式转换为后缀表达式,然后使用堆栈来计算答案。

中缀转后缀的步骤

  1. 从左到右读取给定的表达式。如果扫描到的元素是操作数,则将其附加到结果后缀表达式中。
  2. 如果扫描到的运算符是运算符且堆栈为空,则将运算符推入堆栈。
  3. 如果扫描到的元素是运算符,并且该运算符的优先级大于或等于堆栈顶部运算符的优先级,则将其推入堆栈。
  4. 如果扫描到的运算符是运算符,并且扫描到的运算符的优先级小于堆栈顶部运算符的优先级,则弹出堆栈顶部的运算符,并将其添加到后缀表达式,直到堆栈变空或堆栈顶部的运算符的优先级小于扫描到的运算符的优先级。然后,将扫描到的运算符推入堆栈。

例如 3 + 8 - 9 / 8

扫描到的元素Stack后缀表达式
3-3
++3
8+3 8
--3 8 +
9-3 8 + 9
/-/3 8 + 9
8-/3 8 + 9 8
 后缀表达式3 8 + 9 8 / -

评估后缀表达式

示例

取上面转换的后缀表示法。首先,让我们从中缀表示法得到结果

3 + 8 - 9 / 8

= 3 + 8 - 1.125

= 11 - 1.125

= 9.875

  • 求值顺序:科学计算器(BODMAS)

现在,让我们取上一步转换得到的后缀表达式

3 8 + 9 8 / -

  1. 从左到右读取表达式,使用与表达式长度相同的堆栈进行初始化。
  2. 如果遇到的元素是操作数,则将其推入堆栈。
  3. 如果遇到的元素是运算符,则从堆栈中弹出两个操作数a和b,应用运算符(b 运算符 a),然后将结果推回堆栈。
  4. 在上例中:3 8 + 9 8 / -
Calculator using Shunting yard algorithm in HTML using JavaScript

现在,为了使用Shunting Yard算法构建计算器,我们将使用两个堆栈,因为除了转换为后缀表达式之外,我们还需要评估后缀表达式。

计算器的示例HTML和CSS

CSS

JavaScript

  1. 点击数字或运算符时,按钮的值必须显示在显示屏上。
  2. 点击 C 时,显示屏必须清空。
  3. 点击 B 时,显示屏上的最后一个字符必须被删除。
  4. 点击“=”按钮后,显示屏上的所有内容都将被求解,然后显示屏将显示输入表达式的结果。

因此,我们需要在JavaScript中编写4个函数来实现四种类型按钮的功能。

您可以使用CSS以任何您喜欢的方式为计算器设置样式。为了简单起见,我们没有使用太多样式功能。我们将以与我们在使用eval()创建计算器的教程中相同的设计来创建计算器。

我们将创建一个这样的计算器

Calculator using Shunting yard algorithm in HTML using JavaScript
  • 我们使用io来开发代码。您可以使用文本编辑器或任何您想要的软件。

代码

与其一次性展示所有代码,不如将其分解。首先,显示部分

  1. fun(a)
    • 点击所有数字和运算符按钮时,我们调用 fun() 并将数字或运算符的值作为参数传递。
    • 在函数中,我们需要将该值打印到显示输入框中,因此,我们将参数连接到输入框中已有的值。
  2. C(): 我们只需将显示输入框的值重新赋给一个空字符串即可清除显示。
  3. B(): 我们将获取显示屏上的整个字符串,对其进行切片,删除最后一个字符,然后将其重新赋给显示屏。
  4. res(): 此函数在点击 = 时激活。

我们使用trim()来去除任何额外的、不必要的空格,然后,将表达式输入另一个函数fun1()。

fun1()

该函数在接收表达式作为输入后,包含计算器的实际功能或后端。

代码

说明

  1. 我们声明了两个数组(堆栈),values[] 和 operators[]。
  2. opc和clc分别代表开括号和闭括号。
  3. 在下一个for循环中,我们检查开括号的数量是否等于闭括号的数量。否则,它将返回提供的表达式无效。
  4. 根据BODMAS原则,我们需要首先解决括号内的子表达式。因此,我们检查是否有任何括号,如果有,则提取括号之间的子表达式,并再次使用子表达式作为输入调用fun1()。
  5. 如果表达式中有括号,这种递归将继续。
  6. 现在,进入实际的求解部分
    1. 我们检查表达式是否有前导符号,如果有,则将0推入values。假设,如果表达式是-8:0-8仍然是-8。
    2. 然后,我们检查是否有连续的运算符,这会再次返回无效表达式。
    3. 如果存在操作数,则将其推入values堆栈。
    4. 现在,如果存在运算符
      1. 如果operators堆栈为空,则将运算符推入堆栈。
      2. 如果找到的运算符的优先级高于operators堆栈顶部的运算符的优先级,则将运算符推入堆栈。
      3. 如果找到的运算符的优先级小于或等于operators堆栈顶部的运算符的优先级,则从operators堆栈中弹出最后一个运算符,从values堆栈中弹出两个操作数,对这两个操作数执行运算,并将结果推入values堆栈。
      4. 继续该过程,直到找到的运算符的优先级高于堆栈顶部的运算符的优先级为止。
  7. 现在,我们处理了+和-可以连续出现的情况。这种情况通常发生在有括号的地方:3 + (2 - 3) -> 3 + - 1 -> 2
  8. 在这种情况下,我们将带负号的操作数取出来,将其转换为浮点数,并将其像普通操作数一样推入values堆栈。
  9. 最后,如果operators堆栈不为空,这意味着仍然有一些运算符和值需要处理,则从values堆栈中弹出操作数,对operators堆栈顶部的运算符进行运算,然后从operators堆栈中弹出运算符。
  10. 继续该过程,直到operators和values堆栈中没有运算符和操作数为止。
  11. precedence函数用于管理BODMAS/PEDMAS中的优先级规则。

示例

表达 3 + (5 + 9 * 9)

  1. 括号内的表达式再次传递给函数。
    • 将5推入values堆栈。
    • 将+推入operators堆栈。
    • 将9推入values堆栈。
    • *的优先级高于+,因此,它被推入values堆栈。
    • 将9推入values堆栈。

      Calculator using Shunting yard algorithm in HTML using JavaScript
      运算符
    • 从values堆栈中弹出两个操作数:9和9,并在operators堆栈顶部应用运算符:9 * 9 = 81。将81推入values堆栈。
      Calculator using Shunting yard algorithm in HTML using JavaScript
      81 + 5 = 86.
  2. 现在,86被连接到原始字符串中括号内的表达式的位置。
  3. 表达式:3 + 86
  4. 将3推入values堆栈。
  5. 将+推入operators堆栈。
  6. 将86推入values堆栈。
    Calculator using Shunting yard algorithm in HTML using JavaScript
  7. 86 + 3 = 89 是最终结果

输出屏幕

Calculator using Shunting yard algorithm in HTML using JavaScript
Calculator using Shunting yard algorithm in HTML using JavaScript