使用原始递归添加二进制自然数

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用原始递归添加二进制自然数相关的知识,希望对你有一定的参考价值。

给定二进制自然数,零情况下为“两次”情况,“两次加一”情况。如何使用原始递归表示加法(仅使用函数foldBNat)?

-- zero | n * 2 | n * 2 + 1
data BNat = Z | T BNat | TI BNat
  deriving (Show)

foldBNat :: BNat -> t -> (BNat -> t -> t) -> (BNat -> t -> t) -> t
foldBNat n z t ti =
  case n of
    Z -> z
    T m -> t m (foldBNat m z t ti)
    TI m -> ti m (foldBNat m z t ti)

div2 :: BNat -> BNat
div2 n = foldBNat n Z (m _ -> m) (m _ -> m)

pred :: BNat -> BNat
pred n = foldBNat n Z (\_ r -> TI r) (m _ -> T m)

succ :: BNat -> BNat
succ n = foldBNat n (TI Z) (m _ -> TI m) (\_ r -> T r)
答案

想法:要计算a + b,我们需要增加b a次。所以:

0 + b = b
1 + b = succ b
2 + b = succ (succ b)
3 + b = succ (succ (succ b))
...

我们可以从写作开始

plus a b = foldBNat a b (m r -> ...

但在这里我们陷入困境:m代表a的一半(因为a = T m在这里,即a = 2 * m)和r是增加b m次数(即m + b)的结果。我们无能为力。我们想要的是a + b = 2*m + b,我们不能直接从m + b获得。应用T只会给我们2 * (m + b) = 2*m + 2*b,这太大了,根据规则,我们不能直接递减plus来计算m + (m + b) = 2*m + b

我们需要的是更直接的方式来操纵succ操作的数量。

想法:不要直接计算数字;而是计算一个函数(将其参数增加一定次数)。所以:

incBy 0 = id
incBy 1 = succ
incBy 2 = succ . succ
incBy 3 = succ . succ . succ
...

我们可以直接实现:

incBy :: BNat -> (BNat -> BNat)
incBy n = foldBNat n id (\_ r -> r . r) (\_ r -> succ . r . r)

在这里,r . r为我们提供了一个函数,其增加的频率是r的两倍(通过应用r两次)。

现在我们可以简单地将添加定义为:

plus :: BNat -> BNat -> BNat
plus n m = (incBy n) m

(这恰好是多余的,因为plus = incBy)。

以上是关于使用原始递归添加二进制自然数的主要内容,如果未能解决你的问题,请参考以下文章

格雷码那点事——递归非递归实现

每天算法一丁点--递归算法应用:半数集

递归算法之阶乘代码实现与非递归实现

片段事务中的实例化错误

vue.js - 使用原始 json 中的嵌套数组时,递归组件不会更新

哈斯克尔。我很困惑这个代码片段是如何工作的