假设以下(不起作用)代码采用诸如(==2)
之类的谓词和整数列表,并且仅删除满足谓词的列表的最后一个元素:
cutLast :: (a -> Bool) -> [Int] -> [Int]
cutLast a [] = []
cutLast pred (as:a)
| (pred) a == False = (cutLast pred as):a
| otherwise = as
此代码不起作用,因此清楚地列表不能像这样反向迭代。我怎么能实现这个想法?我不是百分之百确定代码是否正确 - 但希望它能得到这个想法。
答案 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