递归小例(二)

Applied Example II of Recursion in F#

原创:顾远山
著作权归作者所有,转载请标明出处。

递归是一种直接或间接调用自身的计算过程,无关计算机,也无关具体的编程语言,只是递归的嵌套特性不适合人脑计算,通常会借助计算机等工具执行罢了,今时今日几乎所有现代的编程语言都支持递归,本文使用的是函数式编程语言F#。

递归小例(一)中笔者通过Excel列数转列名对递归的应用进行了简示。有偶无独,最近还是处理超大Excel文件过程中,笔者又发现了一个场景, 也可以通过应用递归实现解决方案。

问题描述

把汉字表述的数字转换成阿拉伯数字。

问题分析

对Excel熟悉的同学应该知道,有个函数叫numberstring,可以很方便地把阿拉伯数字转换成汉字表述的数字,比如:

Excel中的numberstring函数

我们的问题正好是Excel中numberstring函数反过来的场景,然而Excel并没有提供现成的函数供大家直接使用。虽然网上也有一堆插件可以完成快速转换,但独立思考加自己动手,是防止少年智障、青年孕傻和老年痴呆的必要操作,建议大家多实践。

动手写码前,最好先分析一下汉字表述的数字都有哪些特点。假设待转换的数字都是简体中文汉字表述的整数(无小数点及小数部分),则该数字可由两种元素组合而成:

  • 普通数字,如“五”、“六”、“七”等;
  • 量级单位,如“十”、“百”、“千”等。
    其中普通数字可以作为前缀修饰量级单位,比如“五十”、“六百”、“七千”等;量级单位也可以作为前缀修饰量级单位,比如“八千万”、“九万亿”等。根据实际应用,我们不妨约定对于汉字表述的数字,有效的量级单位仅限于:
  • 亿

为了避免不规范的表述,我们可以进一步约定:量级单位能且仅能被小于其自身的量级单位所修饰。比如,“六万”、“七亿”和“八千万亿”等,这些都是有效的汉字数字;但“六十”、“七亿百”和“八亿万千”等,这些并不是有效的汉字数字;甚至类似“三千”、“四万”和“五亿亿”这种充满年代感的汉字数字,也被排除在外。

约定好普通数字和量级单位的规则后,问题突然就变得非常简单,我们同样可以直接套用递归的思路求解:

  • 欲求整体汉字数字对应的阿拉伯数字,只要先求得“亿”前的汉字数字对应的阿拉伯数字,乘以100000000再加上“亿”后的汉字数字对应的阿拉伯数字即可
  • 欲求“亿”前(后)的汉字数字对应的阿拉伯数字,只要先求得“万”前的汉字数字对应的阿拉伯数字,乘以10000再加上“万”后的汉字数字对应的阿拉伯数字即可
  • 欲求“万”前(后)的汉字数字对应的阿拉伯数字,只要先求得“千”、“百”、“十”前的汉字数字对应的阿拉伯数字,分别乘以1000、100、10再加上“十”后的汉字数字对应的阿拉伯数字即可
  • ……

上面这段文字略显冗长不好理解,我们用符号把它简化一下,其实就是以下过程:

  • 转换(整个数字) = 转换((亿前)+亿+(亿后))
  • 转换(亿前) = 转换((万前)++(万后))
  • 转换(万前) = 转换((千前)++(百前)++(十前)++各位数字)
  • ……

之所以说它是递归的思路,是因为转换这个过程一直都在一层一层地调用它自己,只是被调用时传入的对象不同罢了。

我们举一个直观的例子,把汉字表述的数字“八千万亿”转换成阿拉伯数字,应用递归的思路计算过程如下:

  • 八千万亿 = (八千万) 亿 = (八千万) x 100000000
  • 八千 = (八千) = (八千) x 10000
  • = (八) = (八) x 1000
  • 八 = 8

综上,八千万亿 = 8 x 1000 x 10000 x 100000000 = 8000000000000000

换一个例子,从下面的图示可以看得更清楚:


递归的转换思路

分析到此,计算过程无非是:通过递归的方式可以很方便地求出各个量级前对应的数字,乘以量级对应的倍数再求和,便能得到整个汉字数字对应的阿拉伯数字。

逻辑非常直接,但必须小心的是:并非每个汉字数字包含所有量级,中间缺失若干量级是很常见的,比如一千二百零三万就缺失了“亿”及以上所有量级、“十(万)”、“千”、“百”、“十”和个位,而且根据实际情况,汉字数字缺失量级的个数和位置都很灵活,所以准确定位到量级关键字并切分其前后部分相当关键,可以使用正则表达式处理(辅助函数R可参考笔者之前的文章活动模式小例(二)
RegexMatch活动模式)。

解决方案

我们使用函数式编程语言F#实现。
首先,转换普通数字,如下:

let d v = 
    match "零一二三四五六七八九".ToCharArray() |> Array.tryFindIndex (fun e -> (e|>string)=v) with 
    | Some x -> x |> bigint 
    | None -> -1I

其次,汉字数字去零(“零”在汉字数字中用作缺失量级的补足,需要去掉,避免影响计算逻辑),如下:

let z (s:string) = s.Replace("零","")

最后,结合正则表达式活动模式易得递归的转换函数p,如下:

let rec p c a =
    match z c with
    | "" -> a
    | R "^(.{1,})(亿)(.*)$" [_;v;_;r]-> p r (a + (p v 0I)*100000000I)
    | R "^(.{1,})(万)(.*)$" [_;v;_;r]-> p r (a + (p v 0I)*10000I)
    | R "^(.{1,})(千)(.*)$" [_;v;_;r]-> p r (a + (d v)*1000I)
    | R "^(.{1,})(百)(.*)$" [_;v;_;r]-> p r (a + (d v)*100I)
    | R "^(.{1,})(十)(.*)$" [_;v;_;r]-> p r (a + (d v)*10I)
    | R "^(十)(.*)$"        [_;_;r  ]-> p r (a + 1I*10I)
    | v -> a + (d v)

上述代码中有个特殊处理的逻辑:在汉字数字中“十”前面部分没有数字时等价于“一十”,需要与其他量级单位的匹配模式有所区分。

还有一个有趣的点——例子用到了大数类型,其实用Int64类型也可以,毕竟人最大值为9223372036854775807,远远够用。若要换成Int64类型,那上面的F#的代码中数字的后缀“I”改成“L”即可。

结果验证

随意给定两个测试用例,调用上述转换函数p,如下:

["一千二百三十四万五千六百七十八亿零九万零一";"十六万亿"] |> List.iter (fun e -> printfn "%s:%A" e (p e 0I))

转换结果为:
一千二百三十四万五千六百七十八亿零九万零一: 1234567800090001
十六万亿: 16000000000000

符合预期,测试通过,解决方案可用。

小结

本文通过把汉字表述的数字转换成阿拉伯数字的小例,演示了递归在日常数据处理中的应用。F#作为函数式编程语言,编写递归函数解决问题,不但逻辑清晰,而且简单易读,事半功倍。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容