foldl with(++)比foldr慢很多

时间:2016-11-13 12:55:49

标签: haskell

为什么foldl有时比foldr慢? 我有一个列表“a”,如

a = [[1],[2],[3]]

并希望使用fold

将其更改为列表
foldr (++) [] a

工作正常(时间复杂度为O(n))。但是,如果我使用foldl,它非常慢(时间复杂度为O(n ^ 2))。

foldl (++) [] a

如果foldl只是从左侧折叠输入列表,

(([] ++ [1]) ++ [2]) ++ [3]

和foldr来自右侧,

[1] ++ ([2] ++ ([3] ++ []))

在两种情况下,计算次数(++)应该相同。为什么折叠这么慢?根据时间复杂度,foldl看起来好像扫描输入列表的次数是foldr的两倍。我用以下计算机来计算时间

length $ fold (++) [] $ map (:[]) [1..N]   (fold is either foldl or foldr)

1 个答案:

答案 0 :(得分:11)

这是因为++的工作方式。请注意,is是通过第一个参数的归纳来定义的。

[]     ++ ys = ys
(x:xs) ++ ys = x : (xs ++ ys)

递归调用的数量仅取决于length xs

因此,在xs ++ ys中,使用小xs和大ys比使用其他方式更方便。

请注意,右侧的折叠实现了这一点:

[1] ++ ([2] ++ ([3] ++ ...

++左侧只有短(O(1) - 长度)列表,折现成本为O(n)。

相反,在左边折叠很糟糕:

((([] ++ [1]) ++ [2]) ++ [3]) ++ ...

左参数变得越来越大。因此,我们在这里得到O(n ^ 2)复杂度。

考虑到输出列表的需求,可以更准确地说明这个参数。可以观察到foldr在"流媒体"中产生了它的结果。时尚,要求例如第一个输出列表单元只强制输入的一小部分 - 只需要执行第一个++,并且实际上只需要它的第一个递归步骤!相反,即使只需要foldl结果中的第一项,也会强制所有 ++次调用,这使得它非常昂贵(即使每次调用只需要一个递归步骤,也有O(n)致电)。