反向遍历列表

时间:2016-10-19 02:10:25

标签: haskell

假设以下(不起作用)代码采用诸如(==2)之类的谓词和整数列表,并且仅删除满足谓词的列表的最后一个元素:

cutLast :: (a -> Bool) -> [Int] -> [Int]
cutLast a [] = []
cutLast pred (as:a)
 | (pred) a == False = (cutLast pred as):a
 | otherwise         = as

此代码不起作用,因此清楚地列表不能像这样反向迭代。我怎么能实现这个想法?我不是百分之百确定代码是否正确 - 但希望它能得到这个想法。

2 个答案:

答案 0 :(得分:3)

myself大量借用:这类问题的问题在于,在到达列表末尾之前,您不知道要删除哪个元素。一旦我们观察到这一点,最直接的做法是使用foldr以一种方式遍历列表然后返回(第二遍遍来自事实foldr不是尾递归)。

我能想到的最干净的解决方案是在备份的过程中重建列表,删除第一个元素。

cutLast :: Eq a => (a -> Bool) -> [a] -> Either [a] [a]
cutLast f = foldr go (Left [])
    where
        go x (Right xs) = Right (x:xs)
        go x (Left xs) | f x = Right xs
                       | otherwise = Left (x:xs)

返回类型为Either,以区分未找到要从列表中删除的任何内容(Left),以及遇到并删除列表中的最后一个满意元素(Right) 。当然,如果您不关心是否丢弃元素,则可以删除该信息:

 cutLast' f = either id id . cutLast f

在评论中讨论了速度之后,我尝试将Either [a] [a]替换为(Bool,[a])。没有任何进一步的调整,这(如@dfeuer所预测的)一直有点慢(大约10%)。

在元组上使用无可辩驳的模式,我们确实可以避免强制整个输出(按照@ chi的建议),这使得懒得查询输出的速度更快。这是代码:

cutLast' :: Eq a => (a -> Bool) -> [a] -> (Bool,[a])
cutLast' f = foldr go (False,[])
    where
        go x ~(b,xs) | not (f x) = (b,x:xs)
                     | not b = (False,x:xs)
                     | otherwise = (True,xs)

然而,当强制为正常形式时,这比其他两个版本(不使用无可辩驳的模式)慢2到3倍。

答案 1 :(得分:2)

一个简单(但效率较低)的解决方案是以与cutFirst类似的方式实现filter,然后反转该函数的输入和输出。

cutLast pred = reverse . cutFirst . reverse
   where cutFirst [] = []
         cutFirst (x:xs) | pred x = xs
                         | otherwise = x : cutFirst xs