考虑一个函数,它接受一个字符串并返回所有可能情况的列表,其中三个后续的'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个元素(因此必须检查,如果当前列表包含那么多元素)。通过这种递归遍历,在以下元素通过真值测试的情况下,正在增强累积的结果列表。无论如何,必须将这些结果添加到列表的当前第一个元素,因为它们来自较短的列表。这可以盲目地完成,因为在结果字符串中添加字符不会改变它们匹配的属性。
答案 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 _ = []