使用 Haskell 將十進(jìn)制數(shù)字轉(zhuǎn)成羅馬數(shù)字

最近一邊看「Haskell 函數(shù)式編程入門(mén)」一邊自學(xué) Haskell。函數(shù)式編程對(duì)筆者這種受OOP毒害頗深(雖然我完全不會(huì) Java,但是經(jīng)常會(huì)被別人來(lái)自 Java 背景的(:」∠)_)的菜鳥(niǎo)來(lái)說(shuō),還是很難適應(yīng)的钞速。想著目前主力語(yǔ)言是 C++,一種多范式編程語(yǔ)言,學(xué)習(xí) Haskell 也算是自然而然吧聋溜。
學(xué)一門(mén)新語(yǔ)言還是很痛苦的,但是如果能做出什么的話(huà)還是很高興的叭爱!廢話(huà)就不多說(shuō)了撮躁。

已知

羅馬數(shù)字像是一種很有趣的五進(jìn)制,說(shuō)是五進(jìn)制买雾,但還不準(zhǔn)確把曼。在羅馬數(shù)字中杨帽,i 為 1,v 為 5嗤军,x 為 10注盈,l 為 50,c 為 100叙赚,但是 4、 9震叮、40、90 分別用 iv朴则、ix、xl钓简、xc 來(lái)表示乌妒,將小一級(jí)的羅馬數(shù)字放在左邊表示減法。1~10 羅馬數(shù)字為:i撤蚊、ii损话、iii侦啸、iv、v丧枪、vi光涂、vii、viii忘闻、ix恋博、x。

求解

在此筆者和「Haskell函數(shù)式編程入門(mén)」作者一樣只考慮 5000 以?xún)?nèi)的羅馬數(shù)字债沮。首先將幾個(gè)特殊的羅馬數(shù)字和與之對(duì)應(yīng)的十進(jìn)制數(shù)放在一起:

romeNotation :: [String]
romeNotation =
    ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]

romeAmount :: [Int]
romeAmount = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]

pair :: [(Int, String)]
pair = zip romeAmount romeNotation

為什么是倒序的,請(qǐng)看下面的代碼:

subtrahend :: Int -> (Int, String)
subtrahend n = head (dropWhile (\(a, _) -> a > n) pair)

不難看出當(dāng)給這個(gè)函數(shù)傳入一個(gè)不大于 5000 的正整數(shù)時(shí)硅蹦,它將從 pair 列表中取得第一個(gè)比這個(gè)正整數(shù)小的數(shù)字,通過(guò) dropWhile 將 pair 中比給定正整數(shù)大的元組去掉提针,再取得列表第一個(gè)元素。有了這個(gè)元素饲宛,我們就能獲取到這個(gè)正整數(shù)對(duì)應(yīng)的羅馬數(shù)字嗜价。那么剩下的就簡(jiǎn)單了,只需要先將傳入的正整數(shù)減去這個(gè)元素對(duì)應(yīng)的數(shù)字久锥,然后再將差遞歸地轉(zhuǎn)換成羅馬數(shù)字即可。

> subtrahend 5
(5,"V")
> subtrahend 86
(50,"L")

下面定義函數(shù) convert 來(lái)將十進(jìn)制數(shù)轉(zhuǎn)換為羅馬數(shù)字絮重,首先定義遞歸的基本條件歹苦。如果轉(zhuǎn)換的數(shù)字是 0,那么返回空列表殴瘦,因?yàn)榱_馬數(shù)字中沒(méi)有表示 0 的符號(hào),只需要返回 (0,"") 即可蚪腋。 0 在數(shù)字中其實(shí)是一個(gè)非常抽象的概念。在當(dāng)時(shí)立帖,也許羅馬人也不知道用什么來(lái)表示 0悠砚,這 里用的空字符串。下面再定義遞歸函數(shù)哩簿,使用 subtrahend 得到了減數(shù)节榜,得到了對(duì)應(yīng)的羅馬數(shù)字 rome 與對(duì)應(yīng)的數(shù)字 m,再遞歸地調(diào)用 convert 函數(shù)轉(zhuǎn)換余下的十進(jìn)制數(shù)宗苍,即 convert (n-m),最后返回未轉(zhuǎn)換的部分和兩個(gè)羅馬數(shù)字字符串連接:

convert :: Int -> String
convert 0 = ""
convert n = let (rome, m) = subtrahend n in m ++ convert (n - rome)

> convert 12
"XII"
> convert 109
"CIX"
> convert 1925
"MCMXXV"
> convert 4567
"MMMMDLXVII"

是不是很簡(jiǎn)單让歼???幾個(gè)小時(shí)前的筆者是跪了的╮(╯▽╰)╭丽啡,所以筆者決定貼心的用等式推導(dǎo)來(lái)演算一下 convert 17 的計(jì)算過(guò)程:

  convert 17
= "X" ++ convert (17 - 10)
= "X" ++ "V" ++ convert (7 - 5)
= "X" ++ "V" ++ "I" convert (2 - 1)
= "X" ++ "V" ++ "I" ++ "I" convert (1 - 1)
= "X" ++ "V" ++ "I" ++ "I" ++ ""
= "XVII"

聰明的各位應(yīng)該已經(jīng)看出來(lái)問(wèn)題了,在計(jì)算的時(shí)候改执,要暫時(shí)存儲(chǔ)中間的值坑雅。"X", "V", "I", "I" 這些中間的值在計(jì)算到達(dá)基本條件前沒(méi)有任何的用處。顯然裹粤,這樣對(duì)于內(nèi)存空間的使用效率是不高的。所以應(yīng)該將 convert 改成尾遞歸的形式拇泣。不過(guò)筆者比較菜突那,聰明的你可以試試。

擴(kuò)展

那么既然已經(jīng)可以把十進(jìn)制數(shù)字轉(zhuǎn)成羅馬數(shù)字了愕难,理所當(dāng)然也應(yīng)該將一個(gè) 5000 以?xún)?nèi)的羅馬數(shù)字轉(zhuǎn)換為一個(gè)十進(jìn)制數(shù)字。
思路也很簡(jiǎn)單葱弟,首先從大到小匹配羅馬數(shù)字是否以 ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"] 中的字符串開(kāi)頭猜丹,只需要找到第一個(gè)符合的字符串,就知道對(duì)應(yīng)的十進(jìn)制正整數(shù)藏杖,然后截?cái)嗔_馬數(shù)字脉顿,把剩下的羅馬數(shù)字字符串遞歸執(zhí)行同一函數(shù),直到羅馬數(shù)字全部處理完艾疟,此時(shí)所有十進(jìn)制正整數(shù)相加即可敢辩。
所以我們只需要稍微修改一下 subtrahend 和 convert 即可:

import           Data.List
import           Data.Maybe

subtrahend' :: String -> (Int, String)
subtrahend' n = head (dropWhile (\(_, a) -> not (a `isPrefixOf` n)) pair)

convert' :: String -> Int
convert' [] = 0
convert' n =
    let (rome, m) = subtrahend' n
    in  rome + convert' (fromMaybe "" (stripPrefix m n))
    
    
> convert' "XII"
12
> convert' "CIX"
109
> convert' "MCMXXV"
1925
> convert' "MMMMDLXVII"
4567

當(dāng)然也可以改成尾遞歸弟疆,而且還應(yīng)該有異常處理,但這里就不繼續(xù)展開(kāi)了同廉。

后記

相信看到這里嘀略,大家也對(duì) Haskell 這么語(yǔ)言有一定的了解了吧。在沒(méi)學(xué) Haskell 之前經(jīng)常聽(tīng)說(shuō)函數(shù)在 Haskell 中是一等公民咒程,不是很理解讼育,現(xiàn)在看何止是一等公民啊,是壓根就一個(gè)公民(:」∠)_
而且在 Haskell 中也沒(méi)有 for loop 這種迭代利器奶段,所以很多時(shí)間逼著你考慮遞歸,但是野語(yǔ)有之曰:

"To iterate is human, to recur, divine." - L. Peter Deutsch

遞歸這種神跡對(duì)于筆者這樣的菜雞凡人還是很難的呢铆,所以要學(xué)好 Haskell 還是任重而道遠(yuǎn)啊蹲缠。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市娜谊,隨后出現(xiàn)的幾起案子斤讥,更是在濱河造成了極大的恐慌,老刑警劉巖芭商,帶你破解...
    沈念sama閱讀 211,948評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铛楣,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蛉艾,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)拓瞪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)助琐,“玉大人,你說(shuō)我怎么就攤上這事蛆橡【蚱” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,490評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵睦焕,是天一觀(guān)的道長(zhǎng)靴拱。 經(jīng)常有香客問(wèn)我,道長(zhǎng)袜炕,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,521評(píng)論 1 284
  • 正文 為了忘掉前任乌助,我火速辦了婚禮评架,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘上祈。我一直安慰自己浙芙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布嗡呼。 她就那樣靜靜地躺著,像睡著了一般揍很。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上窒悔,一...
    開(kāi)封第一講書(shū)人閱讀 49,842評(píng)論 1 290
  • 那天简珠,我揣著相機(jī)與錄音,去河邊找鬼聋庵。 笑死,一個(gè)胖子當(dāng)著我的面吹牛氧映,可吹牛的內(nèi)容都是我干的脱货。 我是一名探鬼主播,決...
    沈念sama閱讀 38,997評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼疗绣,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼铺韧!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起哈打,我...
    開(kāi)封第一講書(shū)人閱讀 37,741評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤料仗,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后立轧,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,203評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡帐萎,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評(píng)論 2 327
  • 正文 我和宋清朗相戀三年胜卤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片澈段。...
    茶點(diǎn)故事閱讀 38,673評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖李剖,靈堂內(nèi)的尸體忽然破棺而出囤耳,到底是詐尸還是另有隱情偶芍,我是刑警寧澤,帶...
    沈念sama閱讀 34,339評(píng)論 4 330
  • 正文 年R本政府宣布椎麦,位于F島的核電站,受9級(jí)特大地震影響观挎,放射性物質(zhì)發(fā)生泄漏段化。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評(píng)論 3 313
  • 文/蒙蒙 一雄嚣、第九天 我趴在偏房一處隱蔽的房頂上張望喘蟆。 院中可真熱鬧,春花似錦蕴轨、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,770評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至荆残,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蕴潦,已是汗流浹背像啼。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,000評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工忽冻, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留此疹,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,394評(píng)論 2 360
  • 正文 我出身青樓湖笨,卻偏偏與公主長(zhǎng)得像蹦骑,于是被迫代替她去往敵國(guó)和親慈省。 傳聞我的和親對(duì)象是個(gè)殘疾皇子眠菇,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評(píng)論 2 349

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