我需要写一些东西,用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时,它只会将其转换为“”而不是“(该数字)”。有人可以帮我这个吗?
答案 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