http://projecteuler.net/index.php?section=problems&id=31
素直に書くと次のようになるでしょうか。
let coins = [ 1; 2; 5; 10; 20; 50; 100; 200 ] // nポンドをm番目までのコインを使って何通りの表し方があるか let rec num_ways n m = if n = 0 || m = 0 then 1 else Seq.sum (Seq.map (fun x -> num_ways x (m - 1)) (Seq.takeWhile (fun x -> x >= 0) (Seq.map (fun k -> n - coins.[m] * k) (seq { 0..n })))) printfn "%d" (num_ways 200 7)
このコードで瞬間的に解が得られます。しかし、このコードは同じ計算を何度もしています。こういうときはメモ化を使うのが定番です。
Mapは次のように使います。
let m = Map.empty let m2 = m.Add (1, 2) let m3 = m2.Add (3, 4) printfn "%d" (m3.Item 1) // 2 printfn "%b" (m3.ContainsKey 3) // true
Addすると新たなMapができます。
再帰的な方法もありますが、ここでは使いにくいので、mutableにします。
let mutable m = Map.empty m <- m.Add (1, 2) m <- m.Add (3, 4) printfn "%d" (m.Item 1) printfn "%b" (m.ContainsKey 3)
これでメモ化できます。200ポンドを1000ポンドにすると、最初のコードはなかなか返ってきませんでしたが、メモ化を使うとすぐに返ってきました。
let coins = [ 1; 2; 5; 10; 20; 50; 100; 200 ] let mutable dic = Map.empty let rec num_ways n m = let t = (n, m) if dic.ContainsKey t then dic.Item t else if n = 0 || m = 0 then 1 else let v = Seq.sum (Seq.map (fun x -> num_ways x (m - 1)) (Seq.takeWhile (fun x -> x >= 0) (Seq.map (fun k -> n - coins.[m] * k) (seq { 0..n })))) dic <- dic.Add (t, v) v printfn "%d" (num_ways 1000 7)