List 如果元素存在,则删除该元素,但将其添加到不存在的元素中的函数
我正在寻找一种更简洁的方法来编写一个函数,如果列表中不包含元素,就向列表中添加元素。或者如果列表中确实包含它,则删除它,我现在使用的是List 如果元素存在,则删除该元素,但将其添加到不存在的元素中的函数,list,haskell,List,Haskell,我正在寻找一种更简洁的方法来编写一个函数,如果列表中不包含元素,就向列表中添加元素。或者如果列表中确实包含它,则删除它,我现在使用的是if子句,函数正在工作 但我正试图找到一个更像哈斯凯尔的方法来纠正这一点 这是我的代码: removeElemOrAdd :: Eq a => a -> [a] -> [a] removeElemOrAdd elem list = if (List.elem elem list) then
if
子句,函数正在工作
但我正试图找到一个更像哈斯凯尔的方法来纠正这一点
这是我的代码:
removeElemOrAdd :: Eq a => a -> [a] -> [a]
removeElemOrAdd elem list = if (List.elem elem list)
then (filter(\x -> x /= elem) list)
else (elem:list)
注意:您的问题中有一点模棱两可,即当原始列表中已多次出现
x
时,该怎么办。我假设这不会发生,如果发生,只删除第一个事件。这意味着删除lemoradd 2[4,2,5,2,7]
将导致[4,5,2,7]
。此外,还未指定应在何处添加项目。因为它有一些优点,所以我选择在列表末尾这样做
不使用任何库方法的实现如下所示:
removeElemOrAdd :: Eq a => a -> [a] -> [a]
removeElemOrAdd x (y:ys) | x == y = ys
| otherwise = y : removeElemOrAdd x ys
removeElemOrAdd x _ = [x]
或更简短的版本:
removeElemOrAdd :: Eq a => a -> [a] -> [a]
removeElemOrAdd x = reoa
where reoa (y:ys) | x == y = ys
| otherwise = y : reoa ys
reoa _ = [x]
或同等实施(见下文讨论):
该函数的工作原理如下:如果我们讨论的列表中至少有一个项(y:ys)
,我们将x
与y
进行比较,如果它们相等,我们返回ys
:在这种情况下,我们删除了元素,就完成了
现在如果两者不相等,我们返回一个列表结构(:)
,头部带有y
,因为我们需要保留y
,在尾部,我们将使用x
和ys
执行递归调用removeElemOrAdd
。事实上:可能在尾部的某个地方有一个x
要删除,而且如果没有,我们仍然需要将x
添加到列表中
该子句将在列表中递归循环。从它找到y
的那一刻起,x==y
它将删除该y
。但是,有可能我们到达了列表的末尾,仍然没有找到元素。在这种情况下,我们称之为最终条款。在这里,我们知道列表是空的(我们可以编写removeElemOrAdd x[]
),但是为了使函数定义在语法上完整,我选择使用下划线。只有在列表中找不到x
时,我们才能达到此状态,因此我们通过返回[x]
将其添加到列表的尾部
与使用if-then-else
相比,这种方法的一个优点是可以一次完成所有任务(检查、删除和添加),从而提高效率
另一个优点是,它可以在“无限”列表上运行(比如素数列表)。列表是惰性计算的,因此如果要获取前三项,此函数将仅检查前三项的相等性。我将使用折叠删除所有副本:
removeOrAdd x xs = foldr go (bool [x] []) xs False where
go y r found
| y == x = r True
| otherwise = y : r found
要仅删除一个,似乎需要一个准形态:
removeOrAdd x = para go [x] where
go y ys r
| y == x = ys
| otherwise = y : r
我喜欢其他方法,但不喜欢它们的行为与规范不同。因此,这里有一种方法:
injectNE
操作将对列表中的单个元素执行此操作,然后我们将使用foldMap
从一个元素扩展到整个输入列表
import Data.Monoid
injectNE :: Eq a => a -> a -> (All, [a])
injectNE old new = (All ne, [new | ne]) where
ne = old /= new
removeElemOrAdd :: Eq a => a -> [a] -> [a]
removeElemOrAdd x xs = case foldMap (injectNE x) xs of
(All nex, noxs) -> [x | nex] ++ noxs
在最后一个模式中,您应该将nex
解读为“没有元素等于x
”,将noxs
解读为“没有任何x
副本的列表”(明白了吗?“没有x
s”?ba dum tsh)
不过,有点遗憾的是,规范是按原样编写的:特别是,漂亮折叠的卖点之一是,它产生的一次性折叠对垃圾收集器更友好。但是规范使这变得非常困难,因为在决定结果列表的第一个元素应该是什么之前,我们必须遍历整个输入列表。我们可以通过放宽上述第(2)点(但保留第(1)点和第(3)点),显著提高对垃圾收集器的友好性;此外,区别仅仅是将参数交换为(++)
,这在您的修订历史记录中是一个很好的语义差异:
-- <snipped identical code>
removeElemOrAdd x xs = case ... of
... -> noxs ++ [x | nex]
——
removeElemOrAdd x xs=案例。。。属于
... -> noxs++[x|nex]
您正在寻找哪种“清洁剂”?关于简洁性,您的解决方案看起来不错。关于性能,您可以扫描列表一次以查找、删除和添加,但时间会更长。(顺便说一句,对称性被设计破坏了:你删除了所有匹配的元素,但只添加了一个。)一般来说,这对我来说很好——我猜你可以使用一个防护而不是if/then/else,但它会以相同的方式(递归地)运行,这就是“haskellish”@9000对这里的对称性提出了很高的要求though@CommuSoft:很难说无限列表中是否缺少一个项目,所以我认为无限序列的定义排除了它。从无限列表中删除一个项目当然是可能的,但你必须知道它是用来删除的,这在某种程度上违背了目的。@9000:关键是你可以将决定推迟到列表的末尾(对于无限列表来说,这是永远不会发生的)然后将该项添加为最后一个元素:因此每次必须枚举时,都要进行相等性检查。从找到这样的元素的那一刻起,您就不再关心向尾部添加元素了。关键是您事先不知道谁将调用您的函数,以及调用什么。@CommuSoft,即使添加了
-- <snipped identical code>
removeElemOrAdd x xs = case ... of
... -> noxs ++ [x | nex]