Nim早茶之使用棧實(shí)現(xiàn)計(jì)算器

我們這次實(shí)現(xiàn)的命令行計(jì)算器搜囱,支持加減乘除、括號(hào)帽撑、浮點(diǎn)數(shù)脸甘、負(fù)數(shù),以及查看歷史和退出功能喷面。

主要的思路:read - parse - print - loop星瘾。

read 階段是指從讀取用戶在提示符(cal> )之后輸入的字符串。

parse 階段包括:將用戶輸入的字符串分割成單個(gè)對(duì)象比如:符號(hào) +惧辈,或者數(shù)字 1.2 琳状。其次將中綴表達(dá)式轉(zhuǎn)化后綴表達(dá)式,接下來計(jì)算后綴表達(dá)式的數(shù)值盒齿。

print 階段指的是將 parse 階段的數(shù)值打印在終端上念逞。

loop 階段是指重復(fù)上述操作,除非用戶輸入 exit 符號(hào)或者 Ctrl + C 終止程序運(yùn)行边翁。

實(shí)現(xiàn)效果圖:

55.gif

為了實(shí)現(xiàn)后面的中綴翎承、后綴表達(dá)式,我們需要借助棧這種數(shù)據(jù)結(jié)構(gòu)符匾。

棧是一種先進(jìn)后出的數(shù)據(jù)結(jié)構(gòu)叨咖,函數(shù)調(diào)用時(shí)就會(huì)使用棧來保存當(dāng)前程序的地址及其他信息,前面的深度優(yōu)先搜索就是基于棧實(shí)現(xiàn)的。

之前寫了一篇文章介紹了棧的用法甸各,下面簡(jiǎn)單說明棧的使用:

push 函數(shù)在棧的頂部添加元素垛贤,pop 函數(shù)刪除棧頂?shù)脑兀瑃op 函數(shù)查看棧頂函數(shù)趣倾。

# nim編程
type Stack*[T] = ref object
  container*: seq[T] 


proc len*[T](s: Stack[T]): int =
    s.container.len

proc push*[T](s: Stack[T], elem: T) = 
  s.container.add(elem)

proc pop*[T](s: Stack[T]): T =
  s.container.pop()

proc top*[T](s: Stack[T]): T =
  if s.len > 0:
    result = s.container[s.len - 1]

分詞

分詞是 parse 階段的重要部分聘惦。

  • 我們定義符號(hào) + - * / ( )。

  • 將這些字符串中的這些符號(hào)替換為:空格 + 符號(hào) + 空格

+ => 空格+空格
  • 將字符串按空格分割誊酌,得到最終的分詞

經(jīng)過上述操作部凑,就可以實(shí)現(xiàn)對(duì)正數(shù)以及操作符的分詞功能。

# nim 編程
iterator token*(expressions: string): string = 
  var sentences: string = expressions
  for symbol in symbols:
    # 替換字符串
    sentences = sentences.replace(symbol, fmt" {symbol} ") 
  var temp: seq[string]
  # 分割字符串
  for sentence in sentences.split:
    if sentence != "":
      temp.add(sentence)

為了實(shí)現(xiàn)對(duì)負(fù)數(shù)的分詞碧浊,我們需要迭代分詞序列涂邀,將‘-’ 與后面的數(shù)字轉(zhuǎn)換成一個(gè)整體。我們有兩種情況將 - 視作負(fù)號(hào)箱锐,第一種情況比勉,負(fù)號(hào)位于字符串的開頭;第二種情況驹止,負(fù)號(hào)前一個(gè)符號(hào)為左括號(hào)浩聋。

  var target: seq[string] = temp
  for (i, item) in temp.pairs:
    if item == "-": 
      # 兩種情況下,當(dāng)前符號(hào)設(shè)置為 ""
      # 下一個(gè)符號(hào)設(shè)置為負(fù)數(shù)
      if (i-1) < 0 or temp[i-1] == "(": 
        target[i] = ""
        target[i+1] = fmt"-{target[i+1]}"
    # 若字符串不為空臊恋,打印字符串
    if target[i] != "":
      yield target[i]

中綴表達(dá)式

中綴表達(dá)式就是我們寫的各種表達(dá)式衣洁,如 3 + 2 * 4。我們希望將其轉(zhuǎn)化為后綴表達(dá)式抖仅,示例如下:

中綴表達(dá)式 ==> 1 + 5 * 4 + (3 * 2 + 4) * 6
后綴表達(dá)式 ==> 1 5 4 * + 3 2 * 4 + 6 * + 

我們需要準(zhǔn)備一個(gè)棧 stack 坊夫,用于臨時(shí)存放操作符;準(zhǔn)備一個(gè)序列 seq撤卢,用來存放輸出結(jié)果环凿,也就是后綴表達(dá)式。

轉(zhuǎn)化規(guī)則:

迭代分詞序列放吩,遇到數(shù)字智听,就將數(shù)字輸出到序列 seq 中。

定義各個(gè)符號(hào)優(yōu)先級(jí)渡紫,其中 乘除 > 加減 > 右括號(hào)到推。比較當(dāng)前符號(hào)與棧頂元素的優(yōu)先級(jí),如果當(dāng)前元素的優(yōu)先級(jí)小于等于棧頂元素的優(yōu)先級(jí)惕澎,就彈出棧頂元素莉测,輸出到 seq 序列中,然后將當(dāng)前元素壓入棧中集灌。如果當(dāng)前元素的優(yōu)先級(jí)大于棧頂元素的優(yōu)先級(jí)悔雹,或者當(dāng)前元素為右括號(hào),我們直接將當(dāng)前元素壓入棧中欣喧。

如果遇到左括號(hào)腌零,我們就彈出棧中元素,并輸出到 seq 序列中唆阿。其中益涧,左右括號(hào)并不輸出到序列中。

最后驯鳖,將棧中剩余的符號(hào)輸出到 seq 序列中闲询。

# nim編程
# 迭代 1 + 5 * 4 + (3 * 2 + 4) * 6
# 讀入 1 并輸出到 seq 中,將 + 壓入棧中
# 讀入 5 并輸出到 seq 中
stack ==> +
seq   ==> 1 5
# 讀入 *, * 優(yōu)先級(jí) > + 優(yōu)先級(jí)
# 將 * 壓入棧中, 讀入 4 并輸出
stack ==> + *
seq   ==> 1 5 4
# 讀入 +浅辙, + 優(yōu)先級(jí) < * 優(yōu)先級(jí)
# 將 * 輸出到 seq 序列中
# + 優(yōu)先級(jí) <= + 優(yōu)先級(jí)扭弧,+ 輸出
# 將 + 壓入棧中
stack ==> +
seq   ==> 1 5 4 * +
# 讀入 (, 壓入棧中,讀入 3记舆,壓入棧中
stack ==> + (
seq   ==> 1 5 4 * + 3
# 讀入 *, 壓入棧中鸽捻,讀入 2,壓入棧中
stack ==> + ( *
seq   ==> 1 5 4 * + 3 2
# 讀入 +泽腮,* 輸出御蒲,+ 壓入棧中, 4 輸出
stack ==> + ( +
seq   ==> 1 5 4 * + 3 2 * 4
# 讀入 )诊赊,+ 輸出厚满,彈出 (
stack ==> +
seq   ==> 1 5 4 * + 3 2 * 4 +
# 讀入 *, 壓入棧中, 6 輸出
stack ==> + *
seq   ==> 1 5 4 * + 3 2 * 4 + 6
# 將棧中剩余符號(hào)輸出
stack ==> 
seq   ==> 1 5 4 * + 3 2 * 4 + 6 * +

代碼如下:

# nim編程
proc parseInfix(expressions: string): seq[string] = 
  var s: Stack[string] = Stack[string](container: @[])
  var target: seq[string] = @[]
  for expression in token(expressions):
    let flag = (expression in symbols)
    if not isFloat(expression) and not flag:
      raise newException(ValueError, "error")

    if isFloat(expression):
      target.add(expression)

    elif expression == ")":
      while s.top != "(": 
        target.add(s.pop)
      discard s.pop

    elif expression == "(":
      s.push(expression)

    elif s.top != "" and comparePriority(expression, s.top):
      target.add(s.pop)
      while s.top != "" and comparePriority(expression, s.top):
        target.add(s.pop)
      s.push(expression)
    else:
      s.push(expression)

  while s.len != 0:
    target.add(s.pop)
  result = target

后綴表達(dá)式

后綴表達(dá)式的規(guī)則:遇到數(shù)值就壓入棧中;遇到符號(hào)碧磅,就從棧中彈出兩個(gè)元素碘箍,計(jì)算兩個(gè)元素運(yùn)算結(jié)果,將得到的結(jié)果壓入棧中续崖。

# nim編程
proc parseSufix(expressions: seq[string]): float =
  var target: float
  var s: Stack[string] = Stack[string](container: @[])
  for expression in expressions:
    if isFloat(expression):
      s.push(expression)
    else:
      var elem2 = parseFloat(s.pop)
      var elem1 = parseFloat(s.pop)
      target = evaluate(expression, elem1, elem2)
      s.push(fmt"{target}")
  if s.len > 0:
    target = parseFloat(s.pop)
  result = target 

總結(jié)

接下來還有添加查看歷史和退出功能以及整個(gè)程序的組成敲街,關(guān)注微信公眾號(hào)nim編程在后臺(tái)回復(fù) 2019813獲取源代碼及 exe 文件。
Nim 個(gè)人博客:https://tea.nim-cn.com/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末严望,一起剝皮案震驚了整個(gè)濱河市多艇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌像吻,老刑警劉巖峻黍,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異拨匆,居然都是意外死亡姆涩,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門惭每,熙熙樓的掌柜王于貴愁眉苦臉地迎上來骨饿,“玉大人亏栈,你說我怎么就攤上這事『曜福” “怎么了绒北?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)察署。 經(jīng)常有香客問我闷游,道長(zhǎng),這世上最難降的妖魔是什么贴汪? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任脐往,我火速辦了婚禮,結(jié)果婚禮上扳埂,老公的妹妹穿的比我還像新娘业簿。我一直安慰自己,他們只是感情好阳懂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布辖源。 她就那樣靜靜地躺著,像睡著了一般希太。 火紅的嫁衣襯著肌膚如雪克饶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天誊辉,我揣著相機(jī)與錄音矾湃,去河邊找鬼。 笑死堕澄,一個(gè)胖子當(dāng)著我的面吹牛邀跃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蛙紫,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼拍屑,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了坑傅?” 一聲冷哼從身側(cè)響起僵驰,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎唁毒,沒想到半個(gè)月后蒜茴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡浆西,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年粉私,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片近零。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡诺核,死狀恐怖抄肖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情窖杀,我是刑警寧澤憎瘸,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站陈瘦,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏潮售。R本人自食惡果不足惜痊项,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望酥诽。 院中可真熱鬧鞍泉,春花似錦、人聲如沸肮帐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)训枢。三九已至托修,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間恒界,已是汗流浹背睦刃。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留十酣,地道東北人涩拙。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像耸采,于是被迫代替她去往敵國(guó)和親兴泥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容