do
リスト内包表記の代わりに、リストモナドの -notation を使用して再帰呼び出しを使用できます。
エッジケースを正しく処理する必要があるため、少しトリッキーです。少し最適化することができました。
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 のみを返す必要があります (最初のケース) - と同じ長さの 0 のリストを返しますds
- 残りの合計が ではない
0
のに が残っていないd
場合、解決策がないため問題が発生します (2 番目のケース) - 解決策がないのは単なる空のリストであることに注意してください
- 他のすべてのケースでは、非ゼロ
n
(残りの合計) といくつかのds
残り (3 番目のケース) があります。
- 次に、選択できる最大数を探します
x
( )上限はであるべきですが、整数のみに関心があるmaxN
ことを覚えておいてください (つまり です)x * d^2
<= n
n / d^2
floor
x
から まで0
のすべてを試すmaxN
x
これを残りの合計で使用する場合、残りの合計のすべてのソリューションを探し、ds
それらのいずれかを選択しますxs
- と組み合わせ
x
てxs
、現在の部分問題の解を与える
リストモナドのバインドが残りを処理します;)
例
λ> 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 よりも実際に数学的です)。