当我从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
次:
f(f([1..100]))= f([1..100,1])
-遍历了1次并附加了1
[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]
对于附加一个元素的统一操作的效率如何?
答案 0 :(得分:4)
您需要对时间更加小心,例如,何时发生这种情况。
我们从列表[1,2,3]
开始而不是列表{1>
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]) a1
和a3 = (++ [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] ++) []))
。