从列表中删除第一个 x 实例

3

我是Haskell的新手,一直在努力掌握基础知识。

假设我有以下列表y:

3:3:2:1:9:7:3:[]

我正在尝试找到一种方法来删除列表y中第一个出现的3。是否可以使用简单的列表理解实现?

我尝试过以下方法(该方法会从列表中删除所有实例):

deleteFirst _ [] = [] 
deleteFirst a (b:bc) | a == b    = deleteFirst a bc 
                     | otherwise = b : deleteFirst a bc

3:3:2:1:9:7:3 不是一个列表。你可能想表达的是 [3,3,2,1,9,7,3],这是 3:3:2:1:9:7:3:[] 的语法糖。 - Joachim Breitner
谢谢你指出这个问题。抱歉,那是个打错字了,现在已经编辑好了。 - AnchovyLegend
1
你正在尝试重新实现Data.List.delete,它被定义为deleteBy (==)。如果你卡住了,可以随时在 Hackage 上查看源代码。 - David
4个回答

6
不,使用列表推导式不可能实现此操作。在列表推导式中,你只根据每个元素本身来决定是否保留它。在你的例子中,你想把前3个3与其他3区分开来(因为你只想删除第一个),所以这个决定不仅依赖于该元素本身。因此,列表推导式不能解决这个问题。
你使用递归函数的尝试已经非常接近了,但是,正如你所说,它会删除所有实例。为什么会删除所有实例呢?因为在你删除第一个实例后,你又对剩余的列表调用了deleteFirst,这将继续删除下一个实例,以此类推。要修复这个问题,只需在删除第一个实例后不再调用deleteFirst即可。因此,在这种情况下,只需使用bc而不是deleteFirst a bc即可。

4

正如其他人已经提到的,列表推导式不是这个任务的合适解决方案(很难在一步中终止执行)。

你几乎写出了正确的解决方案,只是在与匹配值相等的情况下,你需要通过返回列表中剩余的元素而终止计算:

deleteFirst _ [] = [] 
deleteFirst a (b:bc) | a == b    = bc 
                     | otherwise = b : deleteFirst a bc

> print $ deleteFirst 3 (3:3:2:1:9:7:3:[])
> [3,2,1,9,7,3]

哦,我看到sepp2k和Joachim Breitner已经提到了修复方法。 - David Unric
Prelude里面有这个的内置功能吗? - theonlygusti

2
我不相信可以用列表推导式来实现这个(至少不是以任何惯用的方式)。
你的deleteFirst 已经接近正确。你需要改变的只有在第一个匹配之后停止删除,即将第一个子句中的 deleteFirst a bc 替换为 bc

0

sepp2k关于列表推导的评论是一个重要的理解点;像mapfilterfoldr等列表操作都会统一地处理所有列表项,而需要理解的重要事情是每个步骤可用的信息以及如何将每个步骤的结果与其他步骤的结果相结合。

但我想强调的是,我认为你应该尝试使用库函数来解决这些问题。将我之前的回答的解决方案适应到你的问题中:

deleteFirst x xs = beforeX ++ afterX
    -- Split the list into two pieces:
    --   * prefix = all items before first x
    --   * suffix = all items after first x
    where (beforeX, xAndLater) = break (/=x) xs
          afterX = case xAndLater of
                     [] -> []
                     (_:xs) -> xs

诀窍在于break已经内置了“一直到第一个命中”的行为。作为进一步的练习,您可以尝试编写自己版本的break; 学习编写这些小型、通用和可重复使用的函数总是很有益的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接