删除元素的函数(如果存在但添加元素)则不会

时间:2015-12-03 15:15:03

标签: list haskell

我正在寻找一种更简洁的方法来编写一个函数,如果列表中没有包含它,则会向列表添加元素。或者如果列表确实包含它,则以其他方式将其删除,我现在使用if子句并且该功能正在运行。

但是我试图找到更多 haskell-ish 的方法来解决这个问题。

这是我的代码:

removeElemOrAdd :: Eq a => a -> [a] -> [a]
removeElemOrAdd elem list = if (List.elem elem list)
                            then (filter(\x -> x /= elem) list)
                            else (elem:list)

3 个答案:

答案 0 :(得分:5)

注意:您的问题中的一个小歧义是在原始列表中x已经发生多次次时该怎么办。我认为这不会发生,如果发生这种情况,只会删除第一个事件。意味着removeElemOrAdd 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]

或等效实施(见下面的讨论):

removeElemOrAdd :: Eq a => a -> [a] -> [a]
removeElemOrAdd x = reoa
    where reoa (y:ys) | x == y = ys
                      | otherwise = y : reoa ys
          reoa [] = [x]

该函数的工作原理如下:如果我们讨论的是包含至少一个项(y:ys)的列表,我们会将xy进行比较,如果它们相等,我们会返回{ {1}}:在这种情况下,我们删除了元素,我们就完成了。

现在,如果两者不相等,我们会返回一个列表构造ys,其中(:)位于头部,因为我们需要保留y而在尾部,我们会做一个使用yremoveElemOrAdd递归调用x。确实:尾部ys中的x可能会移除ys,如果不发生,我们仍然需要将x添加到列表中。

该子句将以递归方式循环遍历列表。从找到y x == y以来y它将删除removeElemOrAdd x []的那一刻起。但是,我们可能会到达列表的末尾,但仍然没有找到该元素。在这种情况下,我们将调用最后一个条款。这里我们知道列表是空的(我们可以编写x)但是为了使函数定义语法总和,我选择使用下划线。如果我们未能在列表中找到[x],我们只能达到此状态,因此我们会通过返回if-then-else将其添加到列表的尾部。

这种方法优于使用 $ cat test.sh current_time=$(date +%s) cat cocos2dxprefsFile.xml | sed "/timeStamp3/{ s/\(value=\)\"[0-9]*\"/\1\"$current_time\"/ }" $ bash test.sh <?xml version='1.0' encoding='utf-8' standalone='yes' ?> <map> <int name="classic_ufg_b2" value="-10" /> <int name="timeStamp3" value="1449157053" /> <string name="lastName">Albert</string> <int name="classic_ufg_s" value="262" /> </map> 的一个优点是,它可以同时执行所有任务(检查,删除和添加),从而提高效率。

另一个优点是,这可以在&#34; 无限&#34;列表(例如素数列表)。列表是懒惰评估的,所以如果你想取前三项,这个函数只会检查前三项的相等性。

答案 1 :(得分:1)

我喜欢其他方法,但不喜欢它们的行为与规范不同。所以这是一种方法:

  1. 与原版一样,删除所有副本,如果有,则删除
  2. 与原版一样,如果需要,在开头插入新值,但是
  3. 与原版不同,使用基于beautiful folding(以及后续开发)的想法的巧妙技巧,只能传递一次数据。
  4. 基本思想是我们将只有一个值来跟踪到目前为止所有值是否都是不匹配以及生成的过滤列表。 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]
    

答案 2 :(得分:0)

我会使用折叠删除所有副本:

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