列表连接的性能实现为左侧折叠

时间:2018-05-14 09:28:23

标签: performance list haskell time-complexity lazy-evaluation

考虑将左侧折叠实现的列表连接,即foldl (++) []。 在惰性求值语言中,例如Haskell,这种实现的复杂性是什么? 我理解,在严格的语言中,表现是元素总数的二次方,但是当涉及懒惰时会发生什么?

我试图手动评估表达式([1,2,3] ++ [4,5,6]) ++ [7,8,9](对应foldl (++) [] [[1,2,3], [4,5,6], [7,8,9]]) 似乎我们只遍历每个元素一次,但我不确定我的推理是否正确:

([1,2,3] ++ [4,5,6]) ++ [7,8,9]
= { rewrite expression in prefix notation }
(++) ((++) [1,2,3] [4,5,6]) [7,8,9]
= { the first (++) operation needs to pattern match on its first argument; so it evaluates the first argument, which pattern matches on [1,2,3] }
(++) (case [1,2,3] of {[] -> [4,5,6]; x:xs' -> x:(++) xs' [4,5,6]}) [7,8,9]
= { x = 1, xs' = [2,3] }
(++) (1:(++) [2,3] [4,5,6]) [7,8,9]
= { the first (++) operation can now pattern match on its first argument }
1:([2,3] ++ [4,5,6]) ++ [7,8,9]

我假设了(++)的以下实现:

(++) :: [a] -> [a] -> [a]
xs ++ ys = case xs of [] -> ys
                      (x:xs') -> x : (xs' ++ ys)

1 个答案:

答案 0 :(得分:8)

我们说([1,2,3]++[4,5,6])++[7,8,9]

([1,2,3]++[4,5,6])++[7,8,9]
(1:([2,3]++[4,5,6))++[7,8,9]
1:(([2,3]++[4,5,6])++[7,8,9])
1:((2:([3]++[4,5,6])++[7,8,9])
1:2:(([3]++[4,5,6])++[7,8,9])
1:2:(3:([]++[4,5,6])++[7,8,9])
1:2:3:(([]++[4,5,6])++[7,8,9])
1:2:3:([4,5,6]++[7,8,9])
1:2:3:4:([5,6] ++ [7,8,9])
1:2:3:4:5:([6] ++ [7,8,9])
1:2:3:4:5:6:([] ++ [7,8,9])
1:2:3:4:5:6:[7,8,9]
[1,2,3,4,5,6,7,8,9]

注意第一个列表中的每个元素必须如何移动两次?那是因为它最后是两个。一般情况下,如果我们(((a1++a2)++a3)++..an)列表ai中的每个元素都必须移动n-i次。

所以,如果你想要整个列表,那就是二次方。如果你想要第一个元素,并且你有n个列表,那就是n-1 *个操作(我们需要执行++ n次的步骤)。如果你想要i元素,它就是它之前所有元素的操作数,加上k-1,它位于k列表中,从中计算结束。

*加上n本身的foldl操作,如果我们想要迂腐