以下两个lambda函数的空间复杂度

时间:2016-02-19 18:36:54

标签: haskell

我正在阅读以下内容:https://en.wikibooks.org/wiki/Haskell/Graph_reduction,并写了以下内容:

  

棘手的空间泄漏示例:

(\xs -> head xs + last xs) [1..n]
(\xs -> last xs + head xs) [1..n]
     

第一个版本在 O(1)空间上运行。 O(n)中的第二个。

这句话是否正确?我无法看出他们是如何不同的。一旦执行,它们似乎都占用相同的时间和空间,在执行之前,您可以懒惰地确定它们的类型是兼容的。

1 个答案:

答案 0 :(得分:2)

执行此操作的一种方法是手动评估表达式,并观察中间表达式的最大长度如何随n的变化而增长。这比听起来要难得多,因为在你的lambas定义中共享xs,但我会这样做,希望它会有所帮助。

首先,让我们为headlast编写定义:

head :: [a] -> a
head (a:_) = a

last :: [a] -> a
last (a:[]) = a
last (_:as) = last as

现在,有了这个,让我们做第一个:

(\xs -> head xs + last xs) [1..n]
  => let xs = [1..n] in head xs + last xs
  => let xs = 1:[2..n] in head xs + last xs
  => let xs = 1:[2..n] in 1 + last xs
  => 1 + last [2..n]
  => 1 + last (2:[3..n])
  => 1 + last [3..n]
  => 1 + last (3:[4..n])
  .
  .
  .
  => 1 + last (n:[])
  => 1 + n

正如您所看到的,中间步骤中的表达式在其大小上有一些不变的上限,而n的值无关紧要。现在让我们试试你的第二个例子。我选择通过在let中增加绑定列表来说明结构共享和强制:

(\xs -> last xs + head xs) [1..n]
  => let xs = [1..n] in last xs + head xs
  => let xs = 1:[2..n] in last xs + head xs
  => let xs  = 1:xs2
         xs2 = 2:[3..]
     in last xs2 + head xs
  => let xs  = 1:xs2
         xs2 = 2:xs3
         xs3 = 3:[4..]
     in last xs3 + head xs
  .
  .
  .
  => let xs  = 1:xs2
         xs2 = 2:xs3
         xs3 = 3:xs4
             .
             .
             .
         xsn = n:[]
     in last xsn + head xs
  => let xs  = 1:xs2
         xs2 = 2:xs3
         xs3 = 3:xs4
             .
             .
             .
         xsn = n:[]
     in n + head xs
  => n + 1

如您所见,最大表达式的大小将与n成比例。这意味着评估使用O(n)空间。

两个示例之间的区别在于+函数在强制第二个参数之前强制其第一个参数这一事实。作为一般规则,当你有一个具有多个参数的函数时,必须在其他参数之前强制它们之一,并且它发生的顺序可以产生类似这些的微妙效果。