The performance of (++) with lazy evaluation
我一直在想这件事,但还没有找到令人满意的答案。
为什么
1 | xs ++ ys |
在必要之前,甚至在那时,我们只会在需要的时候评估我们需要的部分。
有人能解释一下我遗漏了什么吗?
如果您访问整个结果列表,那么Lazy Evaluation将不会保存任何计算。它只会延迟到您需要每个特定元素为止,但最后,您必须计算相同的东西。
如果遍历连接列表
所以,如果你把
如果将长度为
k 的n 列表与(..(xs1 ++ xs2) ... ) ++ xsn 一样左对齐,那么访问第一个k 元素的每个元素将花费O(n) 时间,访问下一个k 元素的每个元素将花费O(n-1) 等,因此遍历整个列表将花费O(k n^2) 。你可以查一下需要很长时间。
如果您将长度为
k 的n 列表与右边的xs1 ++ ( ..(xsn_1 ++ xsn) .. ) 列表相关联,那么每个元素的开销都是恒定的,因此遍历整个列表将仅为O(k n) 。你可以查一下是相当合理的。
编辑:这只是隐藏在
你们两个都可以查一下
1 |
和
1 |
在合理的时间内运行(
它本身并不太贵,当你开始从左到右组合大量的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | ( ([1,2] ++ [3,4]) ++ [5,6] ) ++ [7,8] ≡ let a = ([1,2] ++ [3,4]) ++ [5,6] ≡ let b = [1,2] ++ [3,4] ≡ let c = [1,2] in head c : tail c ++ [3,4] ≡ 1 : [2] ++ [3,4] ≡ 1 : 2 : [] ++ [3,4] ≡ 1 : 2 : [3,4] ≡ [1,2,3,4] in head b : tail b ++ [5,6] ≡ 1 : [2,3,4] ++ [5,6] ≡ 1:2 : [3,4] ++ [5,6] ≡ 1:2:3 : [4] ++ [5,6] ≡ 1:2:3:4 : [] ++ [5,6] ≡ 1:2:3:4:[5,6] ≡ [1,2,3,4,5,6] in head a : tail a ++ [7,8] ≡ 1 : [2,3,4,5,6] ++ [7,8] ≡ 1:2 : [3,4,5,6] ++ [7,8] ≡ 1:2:3 : [4,5,6] ++ [7,8] ≡ 1:2:3:4 : [5,6] ++ [7,8] ≡ 1:2:3:4:5 : [6] ++ [7,8] ≡ 1:2:3:4:5:6 : [] ++ [7,8] ≡ 1:2:3:4:5:6 : [7,8] ≡ [1,2,3,4,5,6,7,8] |
你可以清楚地看到二次复杂性。即使您只想评估第n个元素,您仍然需要在所有这些
除此之外,与
我想在彼得的回答中加上一两件事。
正如他指出的那样,在开始时反复添加列表是相当便宜的,而在底部添加列表则不是。这是真的,只要你使用哈斯克尔的列表。但是,在某些情况下,您必须附加到末尾(例如,您正在构建一个要打印的字符串)。对于常规列表,您必须处理他回答中提到的二次复杂性,但是在这些情况下有一种更好的解决方案:差异列表(另请参见我关于主题的问题)。
长话短说,通过将列表描述为函数的组合,而不是将较短的列表串联,您可以在差异列表的开始或结束时,通过在固定时间内组合函数来附加列表或单个元素。完成后,可以在线性时间(以元素的数量)中提取常规列表。