W rozwiązywaniu problemu projecteuler.net # 31 [SPOILERS AHEAD] (licząc liczbę sposobów na 2 £ z brytyjskimi monetami), chciałem użyć programowania dynamicznego. Zacząłem od OCaml i napisałem krótkie i bardzo wydajne następujące programowanie:Korzystanie z programowania dynamicznego w Haskell? [Ostrzeżenie: rozwiązanie ProjectEuler 31 w środku]
open Num
let make_dyn_table amount coins =
let t = Array.make_matrix (Array.length coins) (amount+1) (Int 1) in
for i = 1 to (Array.length t) - 1 do
for j = 0 to amount do
if j < coins.(i) then
t.(i).(j) <- t.(i-1).(j)
else
t.(i).(j) <- t.(i-1).(j) +/ t.(i).(j - coins.(i))
done
done;
t
let _ =
let t = make_dyn_table 200 [|1;2;5;10;20;50;100;200|] in
let last_row = Array.length t - 1 in
let last_col = Array.length t.(last_row) - 1 in
Printf.printf "%s\n" (string_of_num (t.(last_row).(last_col)))
Wykonuje się w ~ 8ms na moim laptopie. Jeśli zwiększę kwotę z 200 pensów do miliona, program nadal znajdzie odpowiedź w mniej niż dwie sekundy.
Przetłumaczyłem program na Haskell (który z pewnością nie był zabawny sam w sobie), i chociaż kończy się prawidłową odpowiedzią za 200 pensów, jeśli zwiększę tę liczbę do 10000, mój laptop zatrzymuje się w pisku (wiele lanie). Oto kod:
import Data.Array
createDynTable :: Int -> Array Int Int -> Array (Int, Int) Int
createDynTable amount coins =
let numCoins = (snd . bounds) coins
t = array ((0, 0), (numCoins, amount))
[((i, j), 1) | i <- [0 .. numCoins], j <- [0 .. amount]]
in t
populateDynTable :: Array (Int, Int) Int -> Array Int Int -> Array (Int, Int) Int
populateDynTable t coins =
go t 1 0
where go t i j
| i > maxX = t
| j > maxY = go t (i+1) 0
| j < coins ! i = go (t // [((i, j), t ! (i-1, j))]) i (j+1)
| otherwise = go (t // [((i, j), t!(i-1,j) + t!(i, j - coins!i))]) i (j+1)
((_, _), (maxX, maxY)) = bounds t
changeCombinations amount coins =
let coinsArray = listArray (0, length coins - 1) coins
dynTable = createDynTable amount coinsArray
dynTable' = populateDynTable dynTable coinsArray
((_, _), (i, j)) = bounds dynTable
in
dynTable' ! (i, j)
main =
print $ changeCombinations 200 [1,2,5,10,20,50,100,200]
Chciałbym usłyszeć od kogoś, kto dobrze zna Haskella, dlaczego wydajność tego rozwiązania jest tak zła.
FWIW, jakbym pisanie to od podstaw w Haskell, bym zrobił coś znacznie bliżej użytkownika @ augustss odpowiedź niż Daniel. – luqui
można to było zrobić bardziej efektywnie, używając tylko list i prawych fałd. zobacz http://stackoverflow.com/questions/36699695 –