我以前在知乎上回答了问题 按照运算符优先数法,画出算术表达式求值时,操做数栈和运算符栈的变化过程 。此次一方面也算是温故而知新,另外一方面借此领略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
此程序只是为了演示,并不通用。写惯了命令式程序,很容易被函数式语言弄得晕头转向。欲练神功,先自废武功~
另:感谢 敏敏郡主 ^_^