【问题标题】:foldl with (++) is a lot slower than foldrfoldl with (++) 比 foldr 慢很多
【发布时间】:2016-11-13 12:55:49
【问题描述】:

为什么有时 foldl 比 foldr 慢? 我有一个列表“a”之类的列表

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

并希望通过使用折叠将其更改为列表

foldr (++) [] a

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

foldl (++) [] a

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

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

而foldr是从右边开始的,

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

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

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

【问题讨论】:

  • (++)result 在这两种情况下应该是相同的。但这并不意味着计算次数是相同的。
  • (大列表)++(小列表)比(小列表)++(大列表)慢
  • 我看到你在这里待了很长时间,问了很多问题却没有接受。如果答案对您有帮助,请考虑accepting。另请花 2 分钟时间发送tour 以了解该网站的运作方式
  • 嗨,我刚做了。对不起,我不知道。 eii

标签: haskell


【解决方案1】:

这是因为++ 的工作原理。请注意,它是由第一个参数的归纳定义的。

[]     ++ 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 结果中的第一项,也会强制 all ++ 调用,这使得它非常昂贵(即使每个调用只需要一个递归步骤,也有 O(n)电话)。

【讨论】:

    猜你喜欢
    • 2011-08-28
    • 2015-01-08
    • 1970-01-01
    • 2010-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-04
    • 2019-04-03
    相关资源
    最近更新 更多