我很多时候看到在列表头部操作的函数,例如:
trimHead ('\n':xs) = xs
trimHead xs = xs
然后我看到了定义:
trimTail = reverse . trimHead . reverse
然后我看到了:
trimBoth = trimHead . trimTail
它们很干净,但trimTail
和trimBoth
有效吗?还有更好的方法吗?
答案 0 :(得分:13)
考虑这种替代实施
trimTail2 [] = []
trimTail2 ['\n'] = []
trimTail2 (x:xs) = x : trimTail2 xs
trimBoth2 = trimHead . trimTail2
很容易确认trimTail
和trimBoth
要求评估整个列表,而trimTail2
和trimBoth2
仅根据需要评估列表的大小。
*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)。
新名单至少需要:
reverse . trimHead . reverse
大约执行两次:
reverse
执行n个指针遍历和n cons。trimHead
可能执行1次指针遍历。reverse
执行n个指针遍历和n cons。这值得担心吗?在某些情况下,也许吧。代码是否太慢,这是否被称为很多?在其他人,也许不是。基准!使用reverse
的实现很好且易于理解,这很重要。
有一个相当自然的递归逐步列表解决方案,它只会评估所消耗的输出量,所以在你不知道是否需要整个字符串的情况下,你可以可能会保留一些评估。
答案 3 :(得分:2)
trimHead和trimTail是否有效?
它们都采用 O(n)时间(时间与列表大小成正比),因为必须遍历整个列表两次才能执行两次反转。
有更好的方法吗?
那么,你必须使用列表吗?使用Data.Sequence
,您可以在固定时间内修改列表的任一端。如果您遇到列表,请查看此处建议的其他解决方案。如果您可以改为使用序列,那么只需修改FUZxxl的答案即可使用dropWhileR
。