3

我想计算两个Naturals 的商。我需要满足的要求是我有一些配置项必须动态定义为另一个的共享(即一个容器具有X内存,该容器中进程的两个配置键定义为X / YX / Z)。

我的第一个想法是使用递归,但这种方法不起作用:

let quotient =
  \(n: Natural) ->
  \(m: Natural) ->
    if lessThan n m then 0
    else 1 + quotient (Natural/subtract m n) m

特别是,quotient当我尝试调用它时,Dhall 抱怨说尚未定义。鉴于 Dhall 的全部功能范式(以及我对它的不熟悉),这似乎是合理的。我认为可能有某种方法可以做到这一点,但不幸的是我无法做到。

我使用Natural/fold该方法进行了另一次尝试,但我不确定这是否有意义。

let quotient =
      λ(n : Natural) →
      λ(m : Natural) →
        let div =
              λ(n : Natural) →
              λ(m : Natural) →
                let Div = { q : Natural, r : Natural }

                in  Natural/fold
                      n
                      Div
                      ( λ(d : Div) →
                          if    Natural/isZero m
                          then  d
                          else  if lessThan d.r m
                          then  d
                          else  { q = d.q + 1, r = Natural/subtract m d.r }
                      )
                      { q = 0, r = n }

        in  (div n m).q

这通过了以下所有断言。

let example1 = assert : quotient 1 1 ≡ 1
let example2 = assert : quotient 2 2 ≡ 1
let example3 = assert : quotient 3 1 ≡ 3
let example4 = assert : quotient 3 2 ≡ 1
let example5 = assert : quotient 9 4 ≡ 2
let example6 = assert : quotient 4 5 ≡ 0
let example7 = assert : quotient 0 1 ≡ 0
let example8 = assert : quotient 0 2 ≡ 0
let example9 = assert : quotient 1 0 ≡ 0
let example9 = assert : quotient 0 0 ≡ 0
let example9 = assert : quotient 2 0 ≡ 0

0在我的情况下,除以时返回0很好。

有没有更惯用的方法来实现这一目标?我在里面找了一个现成的整数除法函数Prelude但是没找到。

4

2 回答 2

2

TL;WR 编辑:

let Natural/div = λ(n : Natural) → λ(m : Natural) → 
    let div = https://github.com/jcaesar/dhall-div/releases/download/1/quotient.dhall sha256:d6a994f4b431081e877a0beac02f5dcc98f3ea5b027986114487578056cb3db9
    in (div n m).q

加布里埃尔·冈萨雷斯(Gabriel Gonzalez)的回答提到了二分搜索,这让我非常讨厌。有一段时间,我在圈子里跑,尝试是否无法通过将数字转换为来实现搜索所需的除以二List Bool(嗯,它可以,与下面的问题相同),然后我注意到你可以实现长除法:

let Natural/le = λ(a : Natural) → λ(b : Natural) → Natural/isZero (Natural/subtract b a)
let Natural/equals = λ(a : Natural) → λ(b : Natural) → Natural/le a b && Natural/le b a
  
let bits =    
      λ(bits : Natural) →
        Natural/fold
          bits          
          (List Natural)
          ( λ(l : List Natural) →
                l
              # [ merge
                    { Some = λ(i : Natural) → i * 2, None = 1 }
                    (List/last Natural l)
                ] 
          )
          ([] : List Natural)

in  λ(w : Natural) →
      let bits = bits w
      in  λ(n : Natural) →
          λ(m : Natural) → 
            let T = { r : Natural, q : Natural } : Type
            let div =
                  List/fold
                    Natural
                    bits
                    T 
                    ( λ(bit : Natural) →
                      λ(t : T) →
                        let m = m * bit
                        in  if    Natural/le m t.r
                            then  { r = Natural/subtract m t.r, q = t.q + bit }
                            else  t 
                    )               
                    { r = n, q = 0 }
            in  if Natural/equals m 0 then 0 else div.q

唯一的问题是,由于没有对数,因此您不能在长除法的画面中进行左对齐,即您不知道 MSB 的位置如何n或需要多长时间subs

我内心的理论家很伤心,因为我只是将除法简化为(粗略的近似)对数,但实践者说“你一直对 u64 很满意,闭嘴。”


[编辑] 经过一番思考,我仍然无法有效地计算所有输入的对数(我认为这是不可能的)。但我可以从对数中找到下一个 2 的幂,直到一个固定的大限制(2^2^23 或 42 × 10^2525221,但见下文)。可以通过以下方式修改上述函数(我们称之为quotient):

let Natural/less =
      λ(a : Natural) → 
      λ(b : Natural) →
        if Natural/isZero (Natural/subtract a b) then False else True

let max = 23

let powpowT = { l : Natural, n : Natural }
      
let powpow =
      Natural/fold
        max
        (List powpowT)
        ( λ(ts : List powpowT) →
            merge 
              { Some = 
                  λ(t : powpowT) → [ { l = t.l + t.l, n = t.n * t.n } ] # ts
              , None = [ { l = 1, n = 2 } ]
              }
              (List/head powpowT ts)
        )
        ([] : List powpowT)

let powpow = List/reverse powpowT powpow
      
let bitapprox =
      λ(n : Natural) →
        List/fold
          powpowT 
          powpow
          Natural 
          ( λ(e : powpowT) →
            λ(l : Natural) →
              if Natural/less n e.n then e.l else l
          )
          0

in  λ(n : Natural) → λ(m : Natural) → quotient (bitapprox n) n m

这给出了一个可接受的商的有效实现,一个长除法表最大是必要的两倍。在我的桌面 (62GB) m 上,我可以在 11 秒内计算出例如 2^(2^18) / 7,但是对于更大的数字,内存不足。

无论如何,如果您对这么大的数字感到不满,那么您就使用了错误的语言。

于 2020-10-01T13:20:54.287 回答
1

目前Natural除了您已经编写的内容之外,没有更简单的方法来实现除法,但可能有更有效的方法。另一种会给出对数时间复杂度但实现要复杂得多的方法是“猜测和检查”,您可以在所需数字周围进行二进制搜索以找到最大的数字x,例如x * m = n.

不过,我真的不建议这样做,而且我认为可能更好的方法是查看是否有一个明智的内置函数可以添加到可以有效驱动整数除法的语言中。理想情况下,这样的内置函数将对所有输入进行明确定义,因此直接添加整数除法可能不起作用(因为x / 0没有明确定义)。但是(我在这里吐槽),也许内置的 likeNatural/safeDivide x y == x / (y + 1)可以工作,然后用户可以定义自己的包装器,如果他们想允许除以0. 就内置的外观征求意见的最佳地点可能是 Discourse 论坛:

https://discourse.dhall-lang.org/

于 2020-10-01T04:39:55.030 回答