是(反向,反向)有效吗?

时间:2011-08-22 18:57:47

标签: haskell

我很多时候看到在列表头部操作的函数,例如:

trimHead ('\n':xs) = xs
trimHead xs        = xs

然后我看到了定义:

trimTail = reverse . trimHead . reverse

然后我看到了:

trimBoth = trimHead . trimTail

它们很干净,但trimTailtrimBoth有效吗?还有更好的方法吗?

4 个答案:

答案 0 :(得分:13)

考虑这种替代实施

trimTail2 [] = []
trimTail2 ['\n'] = []
trimTail2 (x:xs) = x : trimTail2 xs

trimBoth2 = trimHead . trimTail2

很容易确认trimTailtrimBoth要求评估整个列表,而trimTail2trimBoth2仅根据需要评估列表的大小。

*Main> head $ trimTail ('h':undefined)
*** Exception: Prelude.undefined
*Main> head $ trimBoth ('h':undefined)
*** Exception: Prelude.undefined
*Main> head $ trimTail2 ('h':undefined)
'h'
*Main> head $ trimBoth2 ('h':undefined)
'h'

这意味着如果不需要整个结果,您的版本效率会降低。

答案 1 :(得分:5)

在某种意义上说,流式传输是不可能的,因为需要对整个列表进行评估以获得单个元素。但是更好的解决方案很难,因为您需要评估列表的其余部分以了解是否要修剪换行符。一种稍微有效的方法是展望是否要修剪换行并做出适当的反应:

trimTail, trimHead, trimBoth :: String -> String
trimTail ('\n':xs) | all (=='\n') xs = ""
trimTail (x:xs)                      = x : trimTail xs

trimHead = dropWhile (=='\n')

trimBoth = trimTail . trimHead

如果要修剪换行符,上面的解决方案仅根据字符串的需要进行评估。一种更好的方法是结合知识,即不要修剪下一个 n 字符。实现这一点是留给读者的练习。

以这种方式编写trimTail更好(也更短)的方式(通过 rotsor ):

trimTail = foldr step [] where
  step '\n' [] = []
  step x xs = x:xs

通常,尽量避免使用reverse。通常有更好的方法来解决问题。

答案 2 :(得分:5)

假设要评估整个列表(如果你不需要整个列表,为什么要修改结束?),它的效率大约是你从不可变列表中获得的效率的一半,但它具有相同的效率渐近复杂度O(n)。

新名单至少需要:

  1. 你必须找到结束:n指针遍历。
  2. 你必须修改结尾,从而指出结尾等等:使用新指针的现有数据的缺点。
  3. reverse . trimHead . reverse大约执行两次:

    1. 第一个reverse执行n个指针遍历和n cons。
    2. trimHead可能执行1次指针遍历。
    3. 第二个reverse执行n个指针遍历和n cons。
    4. 这值得担心吗?在某些情况下,也许吧。代码是否太慢,这是否被称为很多?在其他人,也许不是。基准!使用reverse的实现很好且易于理解,这很重要。

      有一个相当自然的递归逐步列表解决方案,它只会评估所消耗的输出量,所以在你不知道是否需要整个字符串的情况下,你可以可能会保留一些评估。

答案 3 :(得分:2)

  

trimHead和trimTail是否有效?

它们都采用 O(n)时间(时间与列表大小成正比),因为必须遍历整个列表两次才能执行两次反转。

  

有更好的方法吗?

那么,你必须使用列表吗?使用Data.Sequence,您可以在固定时间内修改列表的任一端。如果您遇到列表,请查看此处建议的其他解决方案。如果您可以改为使用序列,那么只需修改FUZxxl的答案即可使用dropWhileR