Haskell中两个累积和(cumsum)函数的复杂性

时间:2014-07-02 22:13:30

标签: haskell complexity-theory ghci cumulative-sum

考虑以下两个累积和(cumsum)函数:

cumsum :: Num a => [a] -> [a]
cumsum [] = []
cumsum [x] = [x]
cumsum (x:y:ys) = x : (cumsum $ (x+y) : ys)

cumsum' :: Num a => [a] -> [a]
cumsum' x = [sum $ take k x | k <- [1..length x]]

当然,我更喜欢cumsum的定义与cumsum'的定义,我理解前者具有线性复杂性。

但为什么cumsum'也具有线性复杂性? take本身在其参数长度上具有线性复杂性,k1length x因此我预计 cumsum'的二次复杂度。

此外,cumsum'的常数低于cumsum的常数。这是由于后者的递归列表附加吗?

注意:欢迎任何累积金额的智能定义。

编辑:我正在使用(在GHCi中启用:set +s后)测量执行时间:

last $ cumsum [1..n]

1 个答案:

答案 0 :(得分:9)

这是由懒惰引起的测量误差。

Haskell中的每个值都是惰性的:在必要之前不会对其进行评估。这包括值的子结构 - 例如,当我们看到一个模式(x:xs)时,这只会强制对列表进行足够的评估,以确定该列表是非空的,但它并不强迫头部x或尾xs

last的定义类似于:

last [x] = x
last (x:xs) = last xs

因此,当last应用于cumsum'的结果时,它会递归检查列表推导,但仅足以追踪最后一个条目。它不强制任何条目,但它确实返回最后一个条目。

当最后一个条目以ghci或其他方式打印时,它会被强制执行,这需要按预期的线性时间。但其他条目从未计算过,因此我们没有看到“预期的”二次行为。

使用maximum代替last确实证明cumnorm'是二次的,而cumnorm是线性的。

[注意:这种解释有点波动:真正的评估完全取决于最终结果所需的内容,所以即使last也只是评估,因为它的结果是必需的。搜索“Haskell评估顺序”和“弱头正常形式”之类的内容,以获得更精确的解释。]