如何在agda中将数字转换为字符串?

时间:2014-02-26 22:09:51

标签: string agda

我需要写一些东西,用agda将数字转换成字符串。我发现之前有人问过将字符串传输到agda的方式。

Agda: parse a string with numbers

我想到了向后使用它,

row-to-stringh : (m : ℕ) → string
row-to-stringh 0 = "0"
row-to-stringh 1 = "1"
row-to-stringh 2 = "2"
row-to-stringh 3 = "3"
row-to-stringh 4 = "4"
row-to-stringh 5 = "5"
row-to-stringh 6 = "6"
row-to-stringh 7 = "7"
row-to-stringh 8 = "8"
row-to-stringh 9 = "9"
row-to-stringh _ = ""

但还不够好。当数字大于9时,它只会将其转换为“”而不是“(该数字)”。有人可以帮我这个吗?

1 个答案:

答案 0 :(得分:4)

如果您不想自己实现此功能,标准库中有show函数。

如果您想自己编写:将数字转换为字符串的常用方法是通过重复除以余数来提取数字。例如(余数是用parens写的):

7214 / 10 = 721 (4)
721  / 10 = 72  (1)
72   / 10 = 7   (2)
7    / 10 = 0   (7)

然后,您只需将余数收集到列表中,将其反转并将数字转换为字符。在Agda中尝试这一点可能很诱人,但是,你会遇到终止检查器的问题。

首先,你必须说服divMod(即余数 - 模数除法)终止。您可以将除数硬编码到函数中,并说服终止检查器变得容易。

困难的部分显示重复将数字除以10实际终止。这很可能涉及一些相当复杂的技巧(例如有根据的递归)。


如果您想知道如何以这种方式完成,请查看上面链接的实现。无论如何,这样做的效率要低一些,但要简单得多。

让我们用自然数列表来表示数字。

Digits = List ℕ

我们想写一个函数addOne,(顾名思义)将一个函数添加到由数字列表表示的数字中,即:

addOne : Digits → Digits

为此,我们将使用原始笔&纸张方法:将一个加到最低有效数字;如果结果小于10,我们就完成了;如果不是,写0并将1带到下一个数字。所以,这是我们的随身携带:

data Carry : Set where
  +0 : Carry
  +1 : Carry

这是执行添加的函数 - 第二个Carry参数可以被认为是添加前两位数的进位。

ripple-carry : Digits → Carry → Digits
ripple-carry ns       +0 = ?
ripple-carry []       +1 = ?
ripple-carry (n ∷ ns) +1 with suc n ≤? 9
... | yes _ = ?
... | no  _ = ?

实际实施是一项练习 - 使用上面给出的描述。请注意,我们以相反的顺序存储数字(这允许更有效和更容易的实现)。例如,123由3 ∷ 2 ∷ 1 ∷ []表示,0由[]表示。

我们可以恢复addOne功能:

addOne : Digits → Digits
addOne n = ripple-carry n +1

其余的只是管道。

toDigits : ℕ → Digits
toDigits zero    = []
toDigits (suc n) = addOne (toDigits n)

show : ℕ → String
show 0 = "0"
show n = (fromList ∘ map convert ∘ reverse ∘ toDigits) n
  where
  convert : ℕ → Char
  convert 0 = '0'
  convert 1 = '1'
  convert 2 = '2'
  convert 3 = '3'
  convert 4 = '4'
  convert 5 = '5'
  convert 6 = '6'
  convert 7 = '7'
  convert 8 = '8'
  convert 9 = '9'
  convert _ = ' ' -- Never happens.

二手模块:

open import Data.Char
open import Data.List
open import Data.Nat
open import Data.String
open import Function
open import Relation.Nullary

我做了一些测试,结果证明这种方法实际上非常有效(特别是与标准库中的函数相比)。

上述算法需要访问 O (n)个数字(addOne需要在90%的情况下仅访问一个数字,两个数字在9%,三个在0.9%等等给定数量n。除非我们有一些更快的原始操作(例如_+_在幕后使用Haskell的Integer),这是我们能够获得的最快速度 - 毕竟我们正在使用一元数字。

标准库使用上面提到的重复划分,这也是(除非我的数学错误) O (n)。但是,这不计算证据的处理,这增加了巨大的开销,减慢了停止。让我们做一个比较:

open import Data.Nat
open import Data.Nat.Show
open import Function
open import IO

main = (run ∘ putStrLn ∘ show) n

以下是编译代码的时间(在Emacs中使用C-c C-x C-c)。来自标准库的show

n       time
———————————————
1000     410 ms
2000    2690 ms
3000    8640 ms

如果我们使用上面定义的show,我们会得到:

n         time
———————————————
100000    26 ms
200000    41 ms
300000    65 ms