【问题标题】:Eliminate consecutive duplicates from a string从字符串中消除连续重复
【发布时间】:2022-01-19 10:06:12
【问题描述】:

我想消除像f "aaabbbcccdeefgggg" = "abcdefg"这样的字符串中的连续重复项

这是我的代码

f :: String -> String
f "" = ""
f "_" = "_"
f (x : xs : xss)
    | x == xs   = f (xs : xss)
    | otherwise = x : f (xs : xss)

我得到了错误的非详尽模式,我认为它来自第二行,当它只剩下 1 个字符时,程序不知道如何处理。我该如何解决?

【问题讨论】:

  • f "_" = "_" 应该做什么?
  • 你只处理一个特定的单字符字符串,还有更多。
  • "_" 匹配文字字符串 "_"。我猜你想要的是 [x] 来匹配一个单例列表。
  • 然后改成f [c] = [c]
  • 我相信这个问题最好总结如下:模式"a"是模式['a'](或'a':[])的简写,其中'a'是一个字符文字——这是与[a](或a:[])的模式不同,其中a 是一个变量名。在您的情况下,您有字符文字 '_' 与通配符模式 _,但原理是相同的——它们是不同的结构。

标签: haskell


【解决方案1】:

"_" 模式匹配带有任何字符的字符串,它匹配包含下划线的字符串。

您可以使用[_] 作为单例字符串的模式,所以:

f :: String -> String
f "" = ""
f s@[_] = s
f (x : xs : xss)
    | x == xs   = f (xs : xss)
    | otherwise = x : f (xs : xss)

这里我们使用s@来捕获一个字符为s的字符串。

或者我们可以简化为:

f :: String -> String
f (x : xs : xss)
    | x == xs   = f (xs : xss)
    | otherwise = x : f (xs : xss)
f s = s

【讨论】:

    【解决方案2】:

    我想消除像f "aaabbbcccdeefgggg" = "abcdefg"这样的字符串中的连续重复项

    您可以将相等的字母分组(通过Data.List.group),然后取每个组中的第一个(通过map head,它将head 应用于列表的每个元素并返回列表结果):

    import Data.List (group) -- so we write group instead of Data.List.group
    map head $ group "aaabbbcccdeefgggg"
    

    这可以看作是map head的应用group对输入String的应用。 因此,您的f 可以定义为这两个函数的组合:

    f :: String -> String
    f = map head . group
    

    为了完整起见,由于您似乎是 Haskell 的新手,这里有一些细节:

    • Data.List.group "aaabbbcccdeefgggg" 返回["aaa","bbb","ccc","d","ee","f","gggg"]
    • f $ a b cf (a b c) 相同;
    • . 是合成运算符,它是这样的 (f . g) x == f (g x)

    【讨论】:

      【解决方案3】:

      或者,如果您不处理可以不处理的事情,您可以让它变得更简单:

      f :: String -> String
      f (x:y:xs) | x == y = f (y:xs)
      f (x:xs) = x:f xs
      f _ = ""
      

      【讨论】:

        【解决方案4】:

        为避免不必要地向前看,您不应尝试匹配前两个元素。相反,请跟踪 最近的元素。

        f :: Eq a => [a] -> [a]
        f = start
          where
            start [] = []
            start (x : xs) = x : go x xs
        
            go _old [] = []
            go old (x : xs)
              | x == old
              = go old xs
              | otherwise
              = x : go x xs
        

        如果你愿意,你也可以把它写成一个折叠,在这里你可以用Maybe跟踪你是否见过一个元素:

        f :: Eq a => [a] -> [a]
        f xs = foldr go stop xs Nothing
          where
            stop _ = []
            go x r (Just old)
              | x == old = r (Just old)
            go x r _ = x : r (Just x)
        

        如果重新排列一下,有些人可能会发现折叠更容易阅读。

        f :: Eq a => [a] -> [a]
        f xs = (foldr go stop xs) Nothing
          where
            stop :: Maybe a -> [a]
            stop = \_ -> []
        
            go :: a -> (Maybe a -> [a]) -> (Maybe a -> [a])
            go x r = \acc -> case acc of
              Just old
                | x == old -> r (Just old)
              _ -> x : r (Just x)
        

        【讨论】:

        • @WillNess,我喜欢等号与保护管对齐。我想我已经习惯了阅读 GHC 代码了?
        • @s4seed3sm,我写了一个重新排列的版本。这有帮助吗?如果不是,你能解释一下你觉得困惑的地方吗?您应该尝试将折叠版本“翻译”为它所代表的递归函数。
        • @WillNess,从清晰的角度来看确实如此,但从列表融合的角度来看则不然。像这样保护它,你就不会融合。
        • 当标记为INLINABLE 并使用-O2-O -fspec-constr 编译时,我的代码会正确优化。见this gist。在另一边进行融合需要build 并且可能需要一些INLINE 摆弄。
        • @WillNess,您的代码不会融合,因为drop 根本无法与列表融合一起使用。
        【解决方案5】:

        您也可以使用foldl。逻辑是:将累加器的最后一个元素与当前元素进行比较。

        f :: Eq a => [a] -> [a]
        f xs = foldl (\x y -> if last x == y then x else x++[y] )  [head xs] xs
        

        在这里,我们使用[head x] 启动我们的累加器。

        根据@dfeuer 的提示,我改变了我的解决方案:

        -- with foldl
        f :: Eq a => [a] -> [a]
        f xs = snd $ foldl  opr (Nothing, []) xs
          where
            opr (Just old, acc) n
              | old == n = (Just old, acc)
              | otherwise = (Just n, acc ++ [n])
            opr (Nothing, acc) n = (Just n, acc ++ [n])
        
        -- with foldr
        f2 :: Eq a =>[a] -> [a]
        f2 xs = snd $ foldr opr (Nothing, []) xs
          where
            opr n (Just old, acc)
              | old == n = (Just old, acc)
              | otherwise = (Just n, n:acc)
            opr n (Nothing, acc) = (Just n, n:acc)
        
        

        感谢@dfeuer,我学到了很多新东西。这是基于cmets的第三个版本:

        -- with foldl
        f :: Eq a => [a] -> [a]
        f xs = snd $ foldl opr (Nothing, []) xs
          where
            opr (old, acc) n =
              ( Just n,
                case old of
                  Just o
                    | o == n -> acc
                    | otherwise -> acc ++ [n]
                  Nothing -> acc ++ [n]
              )
        
        -- with foldr
        f :: Eq a => [a] -> [a]
        f xs = snd $ foldr opr (Nothing, []) xs
          where
            opr n (old, acc) =
              ( Just n,
                case old of
                  Just o
                    | o == n -> acc
                    | otherwise -> n : acc
                  Nothing -> n : acc
              )
        

        【讨论】:

        • 你能明白为什么那会是不必要的严格极其低效吗?当你不需要时使用像 headlast 这样的部分函数也是不受欢迎的。对您来说一个有趣的挑战:使用foldr 而不是foldl 编写一个高效惰性 版本。提示:定义适当的gostop 以填补此定义中的空白:f :: Eq a => [a] -> [a]; f xs = foldr _go _stop xs Nothing。是的,将四个参数传递给foldr有意
        • 第二个版本效率低得多,而且更干净。干得好!它仍然过于严格,这将导致对于不是很短的列表的垃圾回收效率低下。
        • 您可以使用结果“形状”始终相同的事实以及在第一种情况下old = n 的事实来适当地延迟您的第二个版本。你能看出来吗?
        • 先试试吧。然后检查这个:gist.github.com/treeowl/a864c00e3ba5b9662c200307413ac918
        • 算了,我觉得修改还不够……
        猜你喜欢
        • 1970-01-01
        • 2018-08-05
        • 2015-01-26
        • 2017-04-03
        • 2019-11-29
        • 1970-01-01
        • 2019-09-02
        • 2018-10-21
        • 1970-01-01
        相关资源
        最近更新 更多