F#

递归小例(二)

2021-08-21  本文已影响0人  顾远山

Applied Example II of Recursion in F#

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

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

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

问题描述

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

问题分析

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

Excel中的numberstring函数

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

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

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

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

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

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

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

综上,八千万亿 = 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#作为函数式编程语言,编写递归函数解决问题,不但逻辑清晰,而且简单易读,事半功倍。

上一篇 下一篇

猜你喜欢

热点阅读