【问题标题】:Haskell - Removing adjacent duplicates from a listHaskell - 从列表中删除相邻的重复项
【发布时间】:2018-08-29 08:52:33
【问题描述】:

我正在尝试通过解决一些在线问题和培训练习来学习 Haskell。

现在我正在尝试创建一个从列表中删除相邻重复项的函数。

示例输入

“acvvca”

“1456776541”

“阿巴克”

“aabaabckllm”


预期输出

""

""

"c"

"ckm"

我的第一个想法是创建一个函数,该函数只需删除相邻重复项的第一个实例并恢复列表。

module Test where

removeAdjDups :: (Eq a) => [a] -> [a]
removeAdjDups []           =  []
removeAdjDups [x]          =  [x]
removeAdjDups (x : y : ys)
  | x == y = removeAdjDups ys
  | otherwise = x : removeAdjDups (y : ys)

*Test> removeAdjDups "1233213443"
"122133"

此函数适用于首次找到的对。

所以现在我需要对函数的结果应用相同的函数。

我认为 foldl 可以提供帮助,但我不知道如何实现它。

类似的东西

removeAdjDups' xs = foldl (\acc x -> removeAdjDups x acc) xs

这种方法也是实施解决方案的最佳方法,还是我应该考虑更好的方法?

【问题讨论】:

  • 类似于 Unix uniq 的东西不会更常见吗?

标签: list haskell recursion fold


【解决方案1】:

我也不知道如何使用foldl。这可能是因为,如果你想在这里折叠一些东西,你必须使用foldr

main = mapM_ (print . squeeze) ["acvvca", "1456776541", "abbac", "aabaabckllm"]

-- I like the name in @bipll answer
squeeze = foldr (\ x xs -> if xs /= "" && x == head(xs) then tail(xs) else x:xs) ""

我们来分析一下。这个想法来自@bipll 答案:从右到左。如果f是lambda函数,那么根据foldr的定义:

squeeze "abbac" = f('a' f('b' f('b' f('a' f('c' "")))

根据ff('c' "") = 'c':"" = "c" 的定义,因为xs == ""。右边的下一个字符:f('a' "c") = 'a':"c" = "ac"'a' != head("c") = 'c'f('b' "ac") = "bac" 出于同样的原因。但是f('b' "bac") = tail("bac") = "ac" 因为'b' == head("bac")。等等……

奖励:通过将foldr替换为scanr,您可以看到整个过程:

Prelude> squeeze' = scanr (\ x xs -> if xs /= "" && x == head(xs) then tail(xs) else x:xs) ""
Prelude> zip "abbac" (squeeze' "abbac")
[('a',"c"),('b',"ac"),('b',"bac"),('a',"ac"),('c',"c")]

【讨论】:

    【解决方案2】:

    列表推导经常被忽视。它们当然是语法糖,但有些像我这样上瘾了。首先,字符串本身就是列表。这个函数也可以处理任何列表,也可以处理单例和空列表。您可以通过我们映射来处理一个列表中的多个列表。

    (\l -> [ x  | (x,y) <- zip l $ (tail l) ++ " ", x /= y]) "abcddeeffa"
    

    “abcdefa”

    【讨论】:

      【解决方案3】:

      我不明白foldl 可以用于此目的。 (一般来说,foldl 几乎结合了 foldrfoldl'... 的缺点......那些,或 foldMap,是你通常应该使用的折叠,而不是 foldl。)

      您似乎打算:重复 removeAdjDups,直到不再找到重复项。重复是一项工作

      iterate :: (a -> a) -> a -> [a]
      

      喜欢

      Prelude> iterate removeAdjDups "1233213443"
      ["1233213443","122133","11","","","","","","","","","","","","","","","","","","","","","","","","","","",""...
      

      这是一个不断减少的列表的无限列表。一般来说,它不会收敛到空列表;您需要添加一些终止条件。如果您想根据需要删除尽可能多的副本,那就是 fixpoint;可以通过与您实现 removeAdjDups 的方式非常相似的方式找到它:比较相邻元素,只是这次在 reductions 列表中。

      bipll's suggestion 处理递归重复要好得多,它避免了不必要的比较和一遍又一遍地遍历列表的开头。

      【讨论】:

        【解决方案4】:

        从最后一个顺序开始:首先从尾部删除重复项,然后检查输入的头部是否等于尾部结果的头部(此时,不会有任何重复,所以唯一可能的对是输入的头部与尾部结果的头部):

        main = mapM_ (print . squeeze) ["acvvca", "1456776541", "abbac", "aabaabckllm"]
        
        squeeze :: Eq a => [a] -> [a]
        squeeze (x:xs) = let ys = squeeze xs in case ys of
                                                    (y:ys') | x == y -> ys'
                                                    _ -> x:ys
        squeeze _ = []
        

        输出

        ""
        ""
        "c"
        "ckm"
        

        【讨论】:

        • 这似乎不正确。如果你通过"vacvvca",头部位置的v也应该被省略。
        • 究竟如何?你能给出允许这种解释的“相邻重复”的定义吗?
        • 顺便说一句,“省略”是什么意思?
        • 也许我理解错了,但出于同样的原因,accaacvvca 字符串中的相邻骗子。
        • 啊,我的意思是删除。
        猜你喜欢
        • 1970-01-01
        • 2019-01-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-11-29
        • 2018-11-28
        • 1970-01-01
        相关资源
        最近更新 更多