【问题标题】:Iterating through list in reverse反向遍历列表
【发布时间】:2016-10-19 02:10:24
【问题描述】:

假设下面的代码(不起作用)接受一个谓词,如(==2) 和一个整数列表,并且只删除列表中满足谓词的最后一个元素:

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

此代码不起作用,因此显然不能像这样反向迭代列表。我怎样才能实现这个想法?我不能 100% 确定代码是否正确 - 但希望它能传达这个想法。

【问题讨论】:

  • 如果没有满足条件的元素,你期望什么行为?原样返回列表?
  • @Alec:正确。
  • 如果不清楚,[Int] 可能是您的问题的不良数据结构(尽管我们需要知道您打算支持的其他操作以提出更好的建议)
  • 在编写该伪代码时,我将整数视为可能的测试参数 - 在我的最终代码中,我使用 cutLast :: (a -> Bool) -> [a] -> [a] 解决了这个问题。

标签: haskell


【解决方案1】:

大量借鉴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

在讨论了 cmets 中的速度之后,我尝试将 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 倍。

【讨论】:

  • 另外,可以使用(Bool, [a]) 代替同构类型Either [a] [a]。对于那些在命令式编程中使用标志的人来说,这可能会感觉更自然。从 FP 的角度来看,我认为这两种选择都可以。
  • @chi,在这种情况下,我会认为标志更自然,但Either 在 GHC 下似乎表现更好。
  • @dfeuer 有趣的是,我认为在某些情况下可能正好相反。我的意思是,(Bool,[a])not (f x) 允许在不强制评估标志的情况下添加x 时,比我们可以使用 sum 类型更懒惰。当f x 大部分为真时,更严格的 sum 类型可能会表现得更好,尽管 (?)
  • @chi,我没有对这个特定示例进行基准测试,但我看到 sum 的工作速度比标签快得多。我认为可能与 GHC 指针标记有关,但我真的不确定。
  • @chi 我们可以调整它的元组版本,以便可以懒惰地查询输出,但是如果我们强制整个输出。
【解决方案2】:

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

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

【讨论】:

  • Alec 的版本确实是更有效的选择,但是对于像我这样的 Haskell 新手来说,这对我来说更容易理解,所以我选择了这个。同样感谢 chepner 和 @Alec 的出色工作。
猜你喜欢
  • 1970-01-01
  • 2014-11-22
  • 1970-01-01
  • 2013-07-01
  • 2011-05-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多