从字符串中删除字符序列

时间:2016-12-24 13:28:20

标签: string haskell

考虑一个函数,它接受一个字符串并返回所有可能情况的列表,其中三个后续的'X'可以从列表中删除。

示例:

"ABXXXDGTJXXXDGXF"应该成为

["ABDGTJXXXDGXF", "ABXXXDGTJDGXF"]

(顺序无关紧要)

这是一个天真的实现:

f :: String -> [String]
f xs =  go [] xs [] where
  go left (a:b:c:right) acc =
    go (left ++ [a]) (b:c:right) y where        -- (1)
    y = if a == 'X' && b == 'X' && c == 'X'
      then (left ++ right) : acc
      else acc
  go _ _ acc = acc

我认为这里的主要问题是标有(1)的行。我正在通过附加它来构建列表的左侧,这通常是昂贵的。

通常这种模式可以解决这个问题:

f [] = []
f (x:xs) = x : f xs

或更明确地说:

f [] = []
f (x:right) = x : left where
  left = f right

现在我在每次递归中都有左右列表。但是,我需要积累它们,我无法弄清楚如何在这里这样做。或者我走错了路?

解决方案

受到Gurkenglas提议的启发,这里有一个更广义的版本:

import Data.Bool

removeOn :: (String -> Bool) -> Int -> String -> [String]
removeOn onF n xs = go xs where
  go xs | length xs >= n =
    bool id (right:) (onF mid) $
    map (head mid:) $
    go (tail xs)
    where
      (mid, right) = splitAt n xs
  go _ = []

removeOn (and . map (=='X')) 3 "ABXXXDGTJXXXDGXF"
--> ["ABDGTJXXXDGXF","ABXXXDGTJDGXF"]

主要想法似乎如下: 从结尾开始遍历列表。利用“预见”机制可以检查列表的下n个元素(因此必须检查,如果当前列表包含那么多元素)。通过这种递归遍历,在以下元素通过真值测试的情况下,正在增强累积的结果列表。无论如何,必须将这些结果添加到列表的当前第一个元素,因为它们来自较短的列表。这可以盲目地完成,因为在结果字符串中添加字符不会改变它们匹配的属性。

1 个答案:

答案 0 :(得分:2)

f :: String -> [String]
f (a:b:c:right)
  = (if a == 'X' && b == 'X' && c == 'X' then (right:) else id)
  $ map (a:) $ f (b:c:right)
f _ = []