我以前在知乎上回答了問題 按照運算符優先數法,畫出算術表達式求值時,操做數棧和運算符棧的變化過程 。此次一方面也算是溫故而知新,另外一方面藉此領略Clojure函數式編程之美。爲了節省篇幅,本文只考慮 +、-、*、/ 這幾種基本運算符以及只考慮小於10的整數運算。
java
咱們人類使用的算術表達式是中綴表達式,而計算機採用後綴表達式,也即逆波蘭式。第一種方式是將表達式轉爲後綴表達式,再進行求值;第二種方式是直接對中綴表達式進行求值。本文就直接採用第二種方式了。git
好比要對錶達式 9-2*4+9/3 進行求值。首先須要使用2個棧來分別保存運算數和運算符,分別記爲 opnd 和 optr。對錶達式逐家符讀入:github
一、讀入字符 '9',由於是運算數,則放入 opnd 棧中,剩餘待讀入字符串爲:-2*4+9/3,此時 opnd 棧和 optr 棧以下圖所示:shell
二、讀入字符 '-',發現是運算符,則先和 optr 棧頂做比較,由於 optr 棧是空棧,故直接入棧 optr。剩餘待讀入字符串爲:2*4+9/3,此時 opnd 棧和 optr 棧以下圖所示:編程
三、接下來讀入字符'2',發現是運算數,則入棧 opnd,剩餘待讀入字符串爲:*4+9/3,此時opnd 棧和 optr 棧以下圖所示:promise
四、接下來讀入字符 '*',發現是運算符,則跟 optr 棧頂字符 '-' 進行比較,'*' 優先級高於 '-',所以直接將 '*' 入棧 optr,剩餘待讀入字符串爲:4+9/3,此時 opnd 棧和 optr 棧以下圖所示:併發
五、接下來讀入字符 '4',由於是運算數,則直接入棧 opnd,剩餘待讀入字符串爲:+9/3,此時 opnd 棧和 optr 棧以下圖所示:編程語言
六、接下來讀入字符 '+',發現是運算符,則跟 optr 棧頂元素 '*' 進行比較,發現 '+' 的優先級小於 '*',則將 optr 出棧,獲得運算符 '*',將 opnd 出2次棧,獲得字符 '4' 和 '2',令 '4' 和 '2' 分別做爲運算符 '*' 的左右操做數(即 4 * 2)獲得結果爲 '8',將 '8' 入棧 opnd。剩餘待讀入字符爲:9/3,此時 opnd 棧和 optr 棧以下圖所示:函數式編程
七、此時 '+' 運算符還未入棧 optr,繼續與 optr 棧頂元素 '-' 進行比較,'+' 優先級不小於 '-',則出棧 optr 獲得運算符 '-',將 opnd 出2次棧,獲得字符 '9' 和 '8',令 '9' 和 '8' 分別做爲運算符 '-' 的左右操做數(即 9 - 8)獲得結果爲 '1',將 '1' 入棧 opnd,此時 optr 棧爲空,將 '+' 運算符入棧 optr,剩餘待讀入字符爲:9/3,此時 opnd 棧和 optr 棧以下圖所示:函數
八、讀入字符 '9',入棧 opnd,剩餘待讀入字符串爲:/3,此時 opnd 棧和 optr 棧以下圖所示:
九、讀入字符 '/',發現是運算符,跟 optr 棧頂 '+' 進行比較,由於 '/' 的優先級高於 '+',則直接將 '/' 入棧 optr,剩餘待讀入字符串爲:3,此時 opnd 棧和 optr 棧以下圖所示;
十、讀入字符 '3',入棧 opnd,此時表達式已所有讀取完成,此時 opnd 棧和 optr 棧以下圖所示:
十一、optr 棧不爲空,則出棧獲得運算符 '/',從 opnd 出棧2次,獲得 '9' 和 '3' 分別做爲左右操做數進行運算(即9/3),獲得結果爲 '3',併入棧 opnd。此時 opnd 棧和 optr 棧以下圖所示:
十二、optr 棧不爲空,則出棧 optr,獲得運算符 '+',從 opnd 出棧2次,獲得 '1' 和 '3' 分別做爲運算符 '+' 的左右操做數進行運算(即1+3),獲得結果 '4'併入棧 opnd,此時 opnd 棧和 optr 棧以下圖所示:
1三、此時 optr 棧已爲空,則將 opnd 出棧,獲得 '4',即爲表達式 9-2*4+9/3 的求值結果。
一、首先需準備2個棧結構,分別用於保存操做數和運算符;
二、需對運算符的優先級進行劃分;
三、表達式從左到右逐字符讀入;
四、操做數直接入棧;
五、運算符入棧前需先進行斷定:若是運算符棧爲空,則直接入棧。不然取出棧頂,進行優先級比較,若是待讀入運算符比棧頂運算符優先級高,則直接入棧待讀入運算符。不然須先出棧,進行運算完後方入棧待讀入運算符;
六、當執行計算時,從操做數棧中連續出棧2次,先出棧的爲右操做數,後出棧的爲左操做數;
七、當表達式所有讀取完成,則以運算符棧是否爲空爲依據,依次出棧對操做數進行計算,計算的結果再次入棧操做數棧;
八、當運算符棧爲空時,出棧操做數棧,即爲這次表達式的計算結果。
本文限於篇幅,只考慮的運算符爲:+、-、*、/,操做數爲小於10的整。因此無須處理複雜的運算符優先級關係,而且在讀入表達式時,只須對單個字符判斷爲操做數或運算符。
clojure是基於JVM的函數式編程語言,它借簽了Haskell、Lisp等函數式語言的優勢,並造成本身的獨有風格。它也是一門動態語言:變量不可變,atom、ref、promise、future、delay使它天生適用於多核高併發開發任務。完備的宏定義、數據協議和類型、多重方法等賦予這門語言無限的可能。極盡JVM之所能,易於和java的互操做性,立於JVM的強大生態之上,又還有什麼理由不選擇Clojure呢?
Clojure是基於JVM的語言,因此你應該已經安裝好JVM環境了。
wget https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein mv lein ~/bin/ chmod a+x ~/bin/lein
lein腳本已經處於你的PATH之中,如若否則,請在 ~/.profile 裏添加:
export PATH=~/.profile:$PATH
而後執行:
source ~/.profile
第一次執行 lein 會進行自我安裝,安裝完畢便可經過 lein repl 進入交互式開發環境。
咱們經過 lein new 來建立項目:
lein new calc
棧直接用java提供的 java.util.Stack 類,源代碼在 src/calc/core.clj 中,以下:
(ns calc.core (:import [java.util Stack])) ; 運算符及優先級定義 (def ^:private op_map {\+ 5, \- 5, \* 6, \/ 6}) (defn isdigit? [c] (#{\0 \1 \2 \3 \4 \5 \6 \7 \8 \9} c)) (defn -main [& args] (let [_expr (seq (nth args 0))] (loop [expr _expr opnd (Stack.) optr (Stack.)] (if (empty? expr) (do ;(println opnd optr) (loop [not_ept (not (empty? optr))] (if (not not_ept) (println (str "計算結果爲:" (.. opnd (pop)))) (do (let [op_c (.. optr (pop)) rn (.. opnd (pop)) ln (.. opnd (pop)) v (case op_c \+ (+ ln rn) \- (- ln rn) \* (* ln rn) \/ (/ ln rn))] ;(println op_c ln rn v opnd optr) (.. opnd (push v)) (recur (not (empty? optr)))))))) (let [[c & _rest] expr] (if (isdigit? c) (.. opnd (push (Integer. (str c)))) (if (empty? optr) (.. optr (push c)) (loop [not_ept (not (empty? optr))] (if (not not_ept) (.. optr (push c)) (let [op_c (.. optr (peek))] (if (> (op_map c) (op_map op_c)) (.. optr (push c)) (do (.. optr (pop)) (let [rn (.. opnd (pop)) ln (.. opnd (pop)) v (case op_c \+ (+ ln rn) \- (- ln rn) \* (* ln rn) \/ (/ ln rn))] (.. opnd (push v)) ;(println ln rn v opnd optr) (recur (not (empty? optr))))) )))))) (recur _rest opnd optr))))))
在 project.clj 中配置入口,增長一行:
:main calc.core/-main
在命令行中執行:
$ lein run 9-2*4+9/3 計算結果爲:4
此程序只是爲了演示,並不通用。寫慣了命令式程序,很容易被函數式語言弄得暈頭轉向。欲練神功,先自廢武功~
另:感謝 敏敏郡主 ^_^