我很难强调为什么++
被认为是O(n),而differential lists被认为是“O(1)”。
如果是++
,我们假设它被定义为:
(++) :: [a] -> [a] -> [a]
(a:as) ++ b = a:(as ++ b)
[] ++ b = b
现在,如果我们需要在a ++ b
中获取访问第一个元素,我们可以在O(1)中执行它(假设{1}}可以在1步中成为HNF),类似于第二个等。它会随着将多个列表设置为Ω(1)/ O(m)而更改,其中m是未评估的附加数。访问最后一个元素可以用Θ(n + m)来完成,其中n是列表的长度,除非我遗漏了一些东西。如果我们有差分列表,我们也可以访问Θ(m)中的第一个元素,而最后一个元素是Θ(n + m)。
我想念什么?
答案 0 :(得分:7)
O(1)指的是DLists的附加只是(.)
,其中一个减少,而(++)
是O(n)。
++
具有二次性能,当您使用它重复添加到现有字符串的末尾时,因为每次添加另一个列表时,您都会遍历现有列表,所以
"Existing long ...... answer" ++ "newbit"
每次添加新位时,遍历"Existing long ....... answer"
。
另一方面,
("Existing long ..... answer" ++ ) . ("newbit"++)
当函数链应用于"Existing long ...... answer"
以转换为列表时,只会实际遍历[]
一次。
多年前,当我还是一个年轻的Haskeller时,我编写了一个程序,正在寻找一个猜想的反例,所以一直在向磁盘输出数据,直到我停止它,除了一旦我取下测试制动器,它输出没有什么因为我的左关联尾部递归构建一个字符串,我意识到我的程序不够懒惰 - 它不能输出任何东西,直到它附加了最后的字符串,但没有最后的字符串!我推出了自己的DList(这是在编写DList库的那个之前的千禧年),并且我的程序运行得非常漂亮,愉快地在服务器上制作了大量的非反例,直到我们放弃了该项目。
如果你搞得足够大的例子,你可以看到性能差异,但对于小的有限输出并不重要。它当然教会了我懒惰的好处。
愚蠢的例子来证明我的观点:
plenty f = f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f
alot f = plenty f.plenty f.plenty f
让我们做两种追加,首先是DList方式
compose f = f . ("..and some more.."++)
append xs = xs ++ "..and some more.."
insufficiently_lazy = alot append []
sufficiently_lazy = alot compose id []
给出:
ghci> head $ sufficiently_lazy
'.'
(0.02 secs, 0 bytes)
ghci> head $ insufficiently_lazy
'.'
(0.02 secs, 518652 bytes)
和
ghci> insufficiently_lazy
-- (much output skipped)
..and some more....and some more....and some more.."
(0.73 secs, 61171508 bytes)
ghci> sufficiently_lazy
-- (much output skipped)
..and some more....and some more....and some more.."
(0.31 secs, 4673640 bytes).
-- less than a tenth the space and half the time
所以它在实践和理论上都更快。
答案 1 :(得分:2)
如果您反复追加列表片段,DL主题通常最有用。好的,
foldl1 (++) [a,b,c,d,e] == (((a ++ b) ++ c) ++ d) ++ e
非常糟糕
foldr1 (++) [a,b,c,d,e] == a ++ (b ++ (c ++ (d ++ e)))
距离n
位置仍然n
步。不幸的是,您经常通过遍历结构并附加到累积字符串的末尾来构建字符串,因此左折叠方案并不罕见。因此,DList在您重复构建字符串(如Blaze / ByteString Builder库)的情况下非常有用。
答案 2 :(得分:2)
[经过进一步的思考和阅读其他答案后,我相信我知道出了什么问题 - 但我认为没有完全解释,所以我加入了自己的答案。]
假设您有列表a1:a2:[]
b1:b2:[]
和c1:c2:[]
。现在你追加他们(a ++ b) ++ c
。这给了:
(a1:a2:[] ++ b1:b2:[]) ++ c1:c2:[]
现在要抬头,你需要采取O(m)步,其中m是附加数。这给了thunk如下:
a1:((a2:[] ++ b1:b2:[]) ++ c1:c2:[])
要给出下一个元素,你需要执行m或m-1步骤(我认为它在我的推理中是免费的)。因此,在2米或2米-1步之后,视图如下:
a1:a2(([] ++ b1:b2:[]) ++ c1:c2:[])
等等。在最坏的情况下,当遍历thunks 每个时间时,它会给m * n时间遍历列表。
编辑 - 看起来the answer to duplicate的照片效果会更好。