He estado jugando con programación dinámica en Haskell. Prácticamente cada tutorial que he visto sobre el tema ofrece el mismo algoritmo muy elegante basado en la memorización y la pereza del tipo Array. Inspirado por esos ejemplos, escribí el siguiente algoritmo como una prueba:¿Cómo se escriben algoritmos de programación dinámica eficientes en Haskell?
-- pascal n returns the nth entry on the main diagonal of pascal's triangle
-- (mod a million for efficiency)
pascal :: Int -> Int
pascal n = p ! (n,n) where
p = listArray ((0,0),(n,n)) [f (i,j) | i <- [0 .. n], j <- [0 .. n]]
f :: (Int,Int) -> Int
f (_,0) = 1
f (0,_) = 1
f (i,j) = (p ! (i, j-1) + p ! (i-1, j)) `mod` 1000000
Mi único problema es la eficiencia. Incluso usando -O2 de GHC, este programa demora 1.6 segundos para calcular pascal 1000
, que es aproximadamente 160 veces más lento que un programa C++ no optimizado equivalente. Y la brecha solo se amplía con entradas más grandes.
Parece que he intentado todas las permutaciones posibles del código anterior, junto con las alternativas sugeridas, como la biblioteca de datacombinators, y todas tuvieron el mismo o peor rendimiento. Lo único que no he intentado es la ST Monad, que estoy seguro se podría hacer para ejecutar el programa solo más ligero más lento que la versión C. Pero me gustaría escribirlo en el idiomático Haskell, y no entiendo por qué la versión idiomática es tan ineficiente. Tengo dos preguntas:
¿Por qué el código anterior es tan ineficiente? Parece una iteración directa a través de una matriz, con una operación aritmética en cada entrada. Claramente, Haskell está haciendo algo detrás de escena que no entiendo.
Hay una manera de hacerlo mucho más eficiente (a lo sumo 10-15 veces el tiempo de ejecución de un programa C) sin sacrificar su formulación recursiva sin estado (en comparación con una implementación que utiliza matrices mutables en el ST Monada)?
Muchas gracias.
Editar: El módulo de matriz utilizado es el estándar Data.Array
uso rem' 'en lugar de' mod' – is7s
Qué módulo array ¿Estas usando? – is7s
¿Cómo se compara el rendimiento si solo usas "f (i, j) = (f (i, j-1) + f (i-1, j))" y zanjas p por completo? No entiendo cómo se supone que pasar por p ayuda, aunque admito que no tengo mucha experiencia con Haskell. – DGH