Haskell列表的复杂性

时间:2014-02-27 16:37:54

标签: list haskell concatenation

抱歉,如果已经提出这个问题,我没找到。抱歉我的英语很差。

我正在学习Haskell并尝试使用列表。 我写了一个函数,它按照特定模式转换列表,我无法检查它是否现在有效,但我想是的。

这个函数不是尾调用函数,所以我认为用一个大的列表计算这个函数会很糟糕:

transform :: [Int] -> [Int]
transform list = case list of
  (1:0:1:[])  -> [1,1,1,1]
  (1:1:[])    -> [1,0,1]
  [1]         -> [1,1]
  (1:0:1:0:s) -> 1:1:1:1: (transform s)
  (1:1:0:s)   -> 1:0:1: (transform s)
  (1:0:s)     -> 1:1: (transform s)
  (0:s)       -> 0: (transform s)

所以我想到了另一个功能,它会“更好”:

transform = reverse . aux []
  where
    aux buff (1:0:[1])   = (1:1:1:1:buff)
    aux buff (1:[1])     = (1:0:1:buff)
    aux buff [1]         = (1:1:buff)
    aux buff (1:0:1:0:s) = aux (1:1:1:1:buff) s
    aux buff (1:1:0:s)   = aux (1:0:1:buff) s
    aux buff (1:0:s)     = aux (1:1:buff) s
    aux buff (0:s)       = aux (0:buff) s

问题是我不知道它是如何编译的,如果我错了第二个函数。你能解释一下列表是如何工作的吗?最后使用(++)或反转列表是否更好?

提前感谢您的回答

2 个答案:

答案 0 :(得分:7)

第一个功能非常好,在我看来比第二个更好。

原因是懒惰。如果代码中有transform someList表达式,则除非您需要,否则不会评估结果列表。特别是列表将仅根据需要评估;与print $ take 10 $ transform xs相比,print $ take 20 $ transform xs的工作量会减少。

严格的语言transform确实会阻塞堆栈,因为它必须在返回任何使用之前评估整个列表(以非尾递归的方式)。在Haskell中transform (0:xs)求值为0 : transform xs,这是一个可用的部分结果。我们可以在不接触尾部的情况下检查该结果的头部。堆栈溢出也没有危险:在列表的尾部,任何时候最多只有一个未评估的thunk(如前例中的transform xs)。如果你需要更多的元素,thunk将被推回更远,并且前一个thunk的堆栈帧可以被垃圾收集。

如果我们总是完全评估列表,那么这两个函数的性能应该是相似的,或者即使这样,由于缺少逆转或额外的++ - s,懒惰版本可能会更快一些。因此,通过切换到第二个功能,我们会失去懒惰并且不会获得额外的性能。

答案 1 :(得分:3)

你的第一个版本对我来说看起来好多了 1 。这很好,它不是尾递归的:你不希望它是尾递归的,你希望它是懒惰的。第二个版本在不处理整个输入列表的情况下甚至不能生成单个元素,因为为了reverse aux的结果,必须知道整个aux。但是,

take 10 . transform $ cycle [1,0,0,1,1,1]

可以正常使用transform的第一个定义,因为您只需要根据需要使用尽可能多的列表来做出决定。

1 但请注意(1:0:1:[])只是[1,0,1]