在Haskell上,大多数数字类型都是原生数据 - Int,Float,Word32等。还有一种仅使用ADT的一元自然数的流行表示 - 即Peano编码:
data Nat = Succ Nat | Zero
该数据类型虽然优雅,但效率不高。乘法,取幂,一元数除法是不切实际的。我的问题是:如果我们没有依赖本机类型,那么数字的最有效表示 - 自然,整数,压缩,复杂等等 - 在纯粹的功能中像哈斯克尔这样的语言?数据类型和各自的算法是什么样的?
答案 0 :(得分:3)
当提到Haskell和ghc时,左派已经评论了关于术语“最有效”的歧义。如果您真正要问的是,“在支持它们的语言中,使用ADT(代数数据类型)的数字编码效率会更高吗?”,那么我会指向Basics chapter in Software Foundations,其中你可以练习定义自然数的二进制表示。
书中的一句话:
练习:3星(二进制)
考虑一种不同的,更有效的自然数表示 使用二元而不是一元系统。也就是说,而不是说 每个自然数是零或自然的后继 数字,我们可以说每个二进制数都是
- 零,
- 二进制数的两倍,
- 或二进制数的两倍以上。
(a)首先,写一个bin对应类型的归纳定义 对二进制数的描述。
(提示:回想一下nat的定义,
Inductive nat : Type := | O : nat | S : nat → nat.
答案 1 :(得分:1)
这在很大程度上取决于您想要对数字做什么,以及效率最高的是什么意思。
如果要表示自然数 n ,则需要 log n 位信息。并且由于ADT只能有有限多个不同的构造函数,因此它会编码有限数量的位,因此您需要具有至少 log n 节点的结构。
我非常推荐Chris Okasaki的Functional Data Structures章节数字表示(论文可在线获取here)。它描述了各种树状数据结构,支持不同的操作集,以及它们与自然数的关系。以下所有内容都是我从本书中学到的。
扩展Cirdec的评论:您可以定义
data N = Zero | Positive
data Positive = One | Add Positive Positive
这为您提供 O(1)加法并减去1。另一方面,结构的大小将是 O(n)。
您可以使用带有 O(log n)空间的二进制表示,但是添加将是 O(log n):
data N = Zero | Positive
data Positive = One | Twice Positive | TwicePlusOne Positive
增量和减量几乎为amortized O(1)。只有每个 2 ^ d 操作的增量序列才会深入 d ,所以平均来说,每个增量都是 O(1)。同样适用于decerements。我上面说几乎,因为如果你交换增量和减量,那么你可以在 O(log n)递增和递减操作之间切换。解决方案是添加一些冗余:
data N = Zero | Positive
data Positive = One | Twice Positive | TwicePlusOne Positive | TwicePlusTwo Positive
现在每次操作需要更深一层时,它会使当前节点保持在TwicePlusOne
,这意味着影响节点的下一个操作将停在它上面,无论它是增量还是减量。
如果你想要恒定时间添加,可以为此扩展数据结构(在书中查找有效连接列表),但是你可以再次结束如果操作顺序不好,则使用 O(n)内存。有一个开放的SO问题How can natural numbers be represented to offer constant time addition?询问是否可以同时获得这两个问题。
答案 2 :(得分:0)
正如@Cirdec已经评论过的那样,这个问题在编程语言方面没有意义。只有特定的编程问题才有意义。
例如,如果您需要确切的数字,您可能希望将有理数表示为连续分数。其中OTOH可能不适合记账。