實作範例 - 計算機
- 文章發表於
前言
本篇文章將會使用 React 實作一個簡單的計算機,並且透過實作計算機的邏輯來瞭解堆疊 (Stack) 的概念。
實作計算機
四則運算的規則
想必大家對於四則運算的規則都不陌生,但在實作演算法之前,可以透過下面表達式簡單複習一下:
4 + ((2 + 3) * 2) - 1
當我們看到這段表達式時,大多數讀者的第一反應就是先計算括號內的表達式,再從左到右根據先乘除後加減的規則來計算出答案。
這就是四則運算的規則:
- 先乘除後加減
- 從左到右
- 先括號內後括號外
而所有表達式求值法都是由以下三種元素組成:
- 運算元(Operand):數字
- 運算子(Operator):加減乘除
- 括號(Parenthesis):左右括號
如果以視覺化解析出我們思考的先後順序,就會是這樣:
4 + ((2 + 3) * 2) - 1 = 4 + (5 * 2) - 1 = 4 + 10 - 1 = 14 - 1 = 13
優先級對照表 (Precedence Table)
瞭解規則後就可以整理出運算子的優先級對照表,而這也會是我們在寫邏輯時的依據:
比較 | + | - | * | / | ( | ) |
---|---|---|---|---|---|---|
+ | > | > | < | < | < | > |
- | > | > | < | < | < | > |
* | > | > | > | > | < | > |
/ | > | > | > | > | < | > |
( | < | < | < | < | < | = |
) | > | > | > | > | = | > |
+
和-
:這兩個運算符具有相同的優先級,且低於*
和/
。就是先乘除後加減的概念。*
和/
:這些運算符也具有相同的優先級,但高於+
和-
。在缺乏括號的情況下,這些運算會先執行。(
和)
:括號用於改變正常的運算優先順序。任何在括號內的運算都會優先執行。
演算法實作
1. 建立兩個堆疊
- 運算子 (
OPERATOR
) 堆疊 :用來存放運算子, 也就是+
,-
,*
,/
,(
與)
。 - 運算元 (
OPERAND
) 堆疊:用來存放運算元 , 也就是數字。
2. 從左到右讀取運算式
- 如果是運算元,則直接放入運算元堆疊。
- 如果是運算子,則依照優先級對照表。
(
,直接放入運算子堆疊。)
,將運算子堆疊中的運算子依序彈出,直到遇到(
為止。+
,-
,*
,/
,將運算子堆疊中的運算子依序彈出,直到遇到優先級比自己低的運算子為止,最後再將自己放入運算子堆疊。- 如果是
=
,則將運算子堆疊中的運算子依序彈出,直到運算子堆疊為空為止。
實作計算機邏輯
本篇會將實作分成四個部分:
優先級 (Precede)
優先級就是比對兩個運算子的優先級,可以嘗試看看將上面的優先級對照表轉換成程式碼。
計算 (Operate) 與驗證
計算與驗證則是相對簡單,前者只是透過傳入的
operator
來計算兩個operand
的結果,後者則是驗證是否為數字。堆疊 (Stack)
堆疊則是實作一個簡單的堆疊,這邊使用陣列來實作,如果有想要了解更多可以參考筆者先前寫的 Data Structure 101 - Stack & Queue。
執行 (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` 堆疊中的最後一個元素。
使用者介面 - 計算機
將上面的邏輯實作完成後,就可以透過 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