文章

實作範例 - 計算機

文章發表於

前言

本篇文章將會使用 React 實作一個簡單的計算機,並且透過實作計算機的邏輯來瞭解堆疊 (Stack) 的概念。

實作計算機

四則運算的規則

想必大家對於四則運算的規則都不陌生,但在實作演算法之前,可以透過下面表達式簡單複習一下:

4 + ((2 + 3) * 2) - 1

當我們看到這段表達式時,大多數讀者的第一反應就是先計算括號內的表達式,再從左到右根據先乘除後加減的規則來計算出答案。

這就是四則運算的規則:

  1. 先乘除後加減
  2. 從左到右
  3. 先括號內後括號外

而所有表達式求值法都是由以下三種元素組成:

  • 運算元(Operand):數字
  • 運算子(Operator):加減乘除
  • 括號(Parenthesis):左右括號

如果以視覺化解析出我們思考的先後順序,就會是這樣:

4 + ((2 + 3) * 2) - 1 = 4 + (5 * 2) - 1 = 4 + 10 - 1 = 14 - 1 = 13

優先級對照表 (Precedence Table)

瞭解規則後就可以整理出運算子的優先級對照表,而這也會是我們在寫邏輯時的依據:

比較+-*/()
+>><<<>
->><<<>
*>>>><>
/>>>><>
(<<<<<=
)>>>>=>
  1. +-:這兩個運算符具有相同的優先級,且低於 */。就是先乘除後加減的概念。
  2. */:這些運算符也具有相同的優先級,但高於 +-。在缺乏括號的情況下,這些運算會先執行。
  3. ():括號用於改變正常的運算優先順序。任何在括號內的運算都會優先執行。

演算法實作

1. 建立兩個堆疊

  • 運算子 (OPERATOR) 堆疊 :用來存放運算子, 也就是 +, -, *, /, ()
  • 運算元 (OPERAND) 堆疊:用來存放運算元 , 也就是數字。

2. 從左到右讀取運算式

  • 如果是運算元,則直接放入運算元堆疊。
  • 如果是運算子,則依照優先級對照表。
    • (,直接放入運算子堆疊。
    • ),將運算子堆疊中的運算子依序彈出,直到遇到 ( 為止。
    • +, -, *, /,將運算子堆疊中的運算子依序彈出,直到遇到優先級比自己低的運算子為止,最後再將自己放入運算子堆疊。
    • 如果是 =,則將運算子堆疊中的運算子依序彈出,直到運算子堆疊為空為止。

實作計算機邏輯

本篇會將實作分成四個部分:

  1. 優先級 (Precede)

    優先級就是比對兩個運算子的優先級,可以嘗試看看將上面的優先級對照表轉換成程式碼。

  2. 計算 (Operate) 與驗證

    計算與驗證則是相對簡單,前者只是透過傳入的 operator 來計算兩個 operand 的結果,後者則是驗證是否為數字。

  3. 堆疊 (Stack)

    堆疊則是實作一個簡單的堆疊,這邊使用陣列來實作,如果有想要了解更多可以參考筆者先前寫的 Data Structure 101 - Stack & Queue

  4. 執行 (Evaluate)

    執行則將上面的邏輯組合起來,而以下是 Pseudo Code:

4.1 從左到右讀取運算式
4.2 如果是運算元 (`0-9` 或是 `.`),就啟動迴圈讀取完整的數字,並放入運算元堆疊 (`OPERAND`)。
4.3 如果是運算子
4.3.1 如果是 `(`,直接放入運算子堆疊 (`OPERATOR`)。因為它的優先級最高,所以不用比較。
4.3.2 如果是 `)`,則將運算子堆疊 (`OPERATOR`) 中的運算子依序彈出,直到遇到 `(` 為止。
4.3.3 如果是普通運算符(如`+`, `-`, `*`, `/`),則根據優先級判斷是否需要先執行堆疊中的操作符。
4.4. 如果 `OPERAND` 不為空,則將 `OPERAND` 堆疊中的運算元 (`OPERAND1``OPERAND2`) 進行計算,最後再將計算結果放入 `OPERAND` 堆疊中。
4.5. 回傳 `OPERAND` 堆疊中的最後一個元素。
<!DOCTYPE html>
<html>

<head>
  <title>Parcel Sandbox</title>
  <meta charset="UTF-8" />
  <link rel="stylesheet" href="/styles.css" />
</head>

<body>
  <h1>Hello world</h1>
</body>

</html>

使用者介面 - 計算機

將上面的邏輯實作完成後,就可以透過 React 實作一個簡單的計算機。

import React, { useState } from 'react'
import evaluate from './evaluate.js'
import { layoutConfig } from './layout.js'
import './calculator.css'

function Calculator() {
  const [input, setInput] = useState('')
  const [error, setError] = useState(false)

  const handleInput = (buttonValue) => {
    switch (buttonValue) {
      case 'C':
        reset()
        break
      case '=':
        calculateResult()
        break
      default:
        if (error) {
          setInput(buttonValue)
          setError(false)
        } else {
          setInput(input + buttonValue)
        }
    }
  }

  const calculateResult = () => {
    try {
      setInput(String(evaluate(input)))
      setError(false)
    } catch (e) {
      setInput('Error')
      setError(true)
    }
  }

  const reset = () => {
    setInput('')
    setError(false)
  }

  return (
    <div className="calculator">
      <div className="display">{error ? 'Error' : input || '0'}</div>
      <div className="button-grid">
        {layoutConfig.map((button, index) => (
          <button
            key={index}
            onClick={() => handleInput(button.value)}
            className={button.className || ''}
          >
            {button.display}
          </button>
        ))}
      </div>
    </div>
  )
}

export default Calculator

如果您喜歡這篇文章,請點擊下方按鈕分享給更多人,這將是對筆者創作的最大支持和鼓勵。