Haskell (反向f反向)有效吗?

Haskell (反向f反向)有效吗?,haskell,Haskell,很多时候,我看到的功能都在列表的开头,例如: trimHead ('\n':xs) = xs trimHead xs = xs 然后我看到了定义: trimTail = reverse . trimHead . reverse 然后我明白了: trimBoth = trimHead . trimTail 它们是干净的,但是trimTail和trimTail都有效吗?有更好的方法吗?从某种意义上讲,流式传输是不可能的,因为需要对整个列表进行评估,才能得到一个元素。但更好的解决方

很多时候,我看到的功能都在列表的开头,例如:

trimHead ('\n':xs) = xs
trimHead xs        = xs
然后我看到了定义:

trimTail = reverse . trimHead . reverse
然后我明白了:

trimBoth = trimHead . trimTail

它们是干净的,但是
trimTail
trimTail
都有效吗?有更好的方法吗?

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

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):


通常,尽量避免
反转
。通常有更好的方法来解决这个问题。

考虑这个替代实现

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

trimBoth2 = trimHead . trimTail2
很容易确认
trimTail
trimTail
要求评估整个列表,而
trimTail2
trimTail2
只评估必要的列表

*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'

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

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

新列表至少需要:

  • 您必须找到end:n指针遍历
  • 您必须修改端点,从而修改指向端点的内容,等等:n个现有数据的cons和新指针
  • 相反。三叶草。反向执行此操作大约两倍:

  • 第一个
    reverse
    执行n个指针遍历和n个cons
  • trimHead
    可能执行1个指针遍历
  • 第二个
    reverse
    执行n个指针遍历和n个cons
  • 这值得担心吗?在某些情况下,也许是这样。代码是否太慢,这是不是太多了?在其他国家,也许不是。基准!使用
    reverse
    的实现很好而且易于理解,这一点很重要

    在列表解决方案中有一个相当自然的递归步骤,它只计算所消耗的输出量,因此在不知道是否需要整个字符串的情况下,可以保存一些计算

    trimHead和trimTail是否有效

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

    有更好的办法吗


    那么,你必须使用列表吗?使用
    Data.Sequence
    可以在固定时间内修改列表的任意一端。如果你被列表困住了,那么看看这里建议的其他解决方案。如果您可以使用序列,那么只需修改fuzzxl的答案即可。

    好吧,使用
    all
    既可爱又微妙。如果你期望文本在中间有大量的<代码> \n\/COD>字符,这有点坏(连续的新行数中的二次数)-但是这种特殊情况似乎不太可能,并且修复这个问题应该是很容易出现的。回答得好!称渐进较慢的算法为“更高效”并不完全正确!下面是“更好的方法”:顺便说一句:
    trimTail=foldr步骤[],其中步骤'\n'[]=[];步骤xxs=x:xs
    @Rotsor很好。我可以把这一点纳入我的答案中吗?或者你想打开另一个答案。澄清一下:你想在结尾处修剪一行还是尽可能多的换行?您的代码提示第一个,但第二个似乎也是可能的。我的答案是第二种情况,第一种情况用哈马尔的。
    *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'