了解差异列表的概念

时间:2019-02-06 08:25:50

标签: haskell optimization difference-lists

当我从Real World Haskell阅读第13章时,我想到了Difference Lists的概念。
作者说,在命令式语言中,如果我们要将元素添加到列表中,则成本为O(1),因为我们会保留指向最后一个元素的指针。 但是在Haskell中,我们有immutable个对象,因此我们每次都需要遍历列表并将元素追加到末尾,因此0(n)

代替[1,2,3]++[4]++[5]
我们可以使用部分应用程序:(++4).(++5) [1,2,3]

由于以下原因,我不知道这样的效率如何: -当我执行(++[5]) [1,2,3]时,它仍然是O(n),然后是0(n)的另一个(++4)

报价

There are two very interesting things about this approach. The first is that the cost of a partial application is constant, so the cost of many partial applications is linear. The second is that when we finally provide a [] value to
unlock the final list from its chain of partial applications, application 
proceeds from right to left. This keeps the left operand of (++) small, and so 
the overall cost of all of these appends is linear, not quadratic


我知道这种方法会很急切,因此我们不像作者所说的那样保留yet to be applied methods的内容,而是保留左操作数small,但是....我们仍然执行每个附件上有一个遍历。

给出一个列表:[1...100],并且想要附加1,2,自从我这样做以来,我仍然会遍历2次:

  1. f(f([1..100]))= f([1..100,1])-遍历了1次并附加了1

  2. [1..100,1,2]-第二次遍历以追加2

有人可以向我说明时间复杂度如何提高吗? (因为space的复杂性是由于不再thunks(如foldl')造成的)


PS

我知道规范的答案,并且也阅读了this chapter,对我很有帮助。
我知道您可以通过使用{{追加到左侧来实现O(1)的复杂性1}},但与:相似。

如果我在++上使用f=(:)
a= [1,2,3]
我可以说我每次追加(f 4 . f 5) $ a的效率,因为我总是在左边追加,但我不会得到0(1),我会得到{{ 1}},那么在这种情况下,[1,2,3,4,5]对于附加一个元素的统一操作的效率如何?

1 个答案:

答案 0 :(得分:4)

您需要对时间更加小心,例如,何时发生这种情况。

我们从列表[1,2,3]开始而不是列表{

f1 = ([1,2,3] ++)

然后将“ 4”,“ 5”“添加”到不断增长的差异列表的末尾,我们有

f2 = f1 . ([4] ++)
f3 = f2 . ([5] ++)

每个这样的添加到增长差异列表末尾的 O(1)

当我们最终完成构建时,我们会根据应用将其转换为“正常”列表

xs = f3 []    -- or f3 [6..10] or whatever

然后,仔细地,我们得到

xs = ((([1,2,3] ++) . ([4] ++)) . ([5] ++)) []
   =  (([1,2,3] ++) . ([4] ++)) ( ([5] ++)  [] )
   =   ([1,2,3] ++) ( ([4] ++)  ( ([5] ++)  [] ))
   =     1:2:3:     (   4  :    (   5  :    [] ))

(++)定义。

规范答案:Why are difference lists more efficient than regular concatenation?


a1 = (++ [4]) [1..]本身 也是 O(1)运算,a2 = (++ [5]) a1a3 = (++ [6]) a2也是一样,因为Haskell是懒惰,重击的创建是 O(1)

只有当我们访问最终结果时,整体操作才会变成二次方,因为访问++结构不会重新排列它-仍然是嵌套的,因此 quadratic 从顶部重复访问。

通过将左侧嵌套的.结构应用于[]来转换为普通列表,如规范答案所述,内部将其内部重新排列为右侧嵌套$结构,因此从顶部重复访问这种结构是线性的。

因此,((++ [5]) . (++ [4])) [1,2,3](差)和((([1,2,3] ++) . ([4] ++)) . ([5] ++)) [](差)之间是有区别的。是的,构建函数链((++ [4]) . (++ [5]))本身是线性的,但是它创建的结构是二次访问的。

但是((([1,2,3] ++) . ([5] ++)) . ([4] ++)) []变成([1,2,3] ++) (([5] ++) (([4] ++) []))