显然这是一个非常具体的问题。放眼大局通常很有用:这是什么更普遍的问题?显然,在这里,我们正在查看一个列表,并且可能会以零种或多种方式看到我们希望替换的元素。此外,我们希望看看有多少种方法可以进行有限数量的此类替换。因此,让我们先实现一般情况,然后再考虑如何专门解决我们的原始问题:
import Control.Applicative (Alternative, empty, (<|>))
replaceNTimes :: Alternative f => (a -> f a) -> Int -> [a] -> f [a]
replaceNTimes _ 0 xs = pure xs
replaceNTimes _ _ [] = empty
replaceNTimes f n (x:xs) = replaceHere <|> keepLooking
where replaceHere = (:) <$> f x <*> replaceNTimes f (n - 1) xs
keepLooking = (x:) <$> replaceNTimes f n xs
如果我们有零替换的“预算”,我们只需返回列表的其余部分。如果我们有剩余预算但列表为空,我们会中止,因为我们未能进行预期的替换次数。否则,我们会参考我们的替换建议函数来查看哪些替换在当前位置是合法的,然后选择生成其中一个并使用较小的 N 进行递归,或者不生成并使用相同的 N 进行递归。
有了这个工具,最初的问题很简单:我们只是将 N 特化为 1(只替换一次),并提供一个替换函数,只建议用 0 替换 -1:
replaceSingleNegativeOneWithZero :: [Int] -> [[Int]]
replaceSingleNegativeOneWithZero = replaceNTimes go 1
where go (-1) = [0]
go _ = []
并进行测试以确保我们得到预期的输出:
*Main> replaceSingleNegativeOneWithZero [-1,0,0,1,-1,-1,1,1,0]
[ [0,0,0,1,-1,-1,1,1,0]
, [-1,0,0,1,0,-1,1,1,0]
, [-1,0,0,1,-1,0,1,1,0]]