您可以使用带有do
-notation 的递归调用来代替 list-comprehension 列表单子。
这有点棘手,因为您必须正确处理边缘情况,我允许自己进行一些优化:
solve :: Integer -> [Integer] -> [[Integer]]
solve 0 ds = [replicate (length ds) 0]
solve _ [] = []
solve n (d:ds) = do
let maxN = floor $ fromIntegral n / fromIntegral (d^2)
x <- [0..maxN]
xs <- solve (n - x * d^2) ds
return (x:xs)
它是这样工作的:
- 它跟踪第一个参数中的剩余总和
- 当这个总和
0
显然已经完成并且只需要返回 0(第一种情况)时 - 它会返回一个长度与ds
- 如果剩余的总和没有
0
,但没有d
剩下的,我们就有麻烦了,因为没有解决方案(第二种情况) - 请注意,没有解决方案只是空列表
- 在其他所有情况下,我们都有一个非零
n
(剩余总和)和一些ds
左(第三种情况):
- 现在寻找您可以为
x
( maxN
) 选择的最大数字,记住x * d^2
应该是<= n
上限,n / d^2
但我们只对整数感兴趣(所以它是floor
)
x
从0
到尝试所有maxN
x
将其与剩余的一起使用时寻找剩余总和的所有解决方案ds
并选择其中一个xs
- 结合
x
以xs
给出当前子问题的解决方案
list-monad 的绑定将为您处理其余部分;)
例子
λ> solve 12 [1,2,3]
[[0,3,0],[3,0,1],[4,2,0],[8,1,0],[12,0,0]]
λ> solve 37 [2,3,4,6]
[[3,1,1,0],[7,1,0,0]]
评论
这在处理负数时会失败——如果你需要那些你将不得不介绍更多的案例——我相信你会弄清楚它们(在这一点上它真的比 Haskell 更数学)