2011-08-20 11 views
9

Estoy trabajando en el problema 14 de Project Euler. Aquí está mi solución.¿Por qué esta expresión de Haskell es tan lenta?

import Data.List 

collatzLength :: Int->Int 
collatzLength 1 = 1 
collatzLength n | odd n = 1 + collatzLength (3 * n + 1) 
       | even n = 1 + collatzLength (n `quot` 2) 

maxTuple :: (Int, Int)->(Int, Int)->Ordering 
maxTuple (x1, x2) (y1, y2) | x1 > y1 = GT 
       | x1 < y1 = LT 
       | otherwise = EQ 

estoy ejecutando el siguiente de GHCi

maximumBy maxTuple [(collatzLength x, x) | x <- [1..1000000]] 

Sé que si Haskell evaluado rigurosamente, el tiempo en esto sería algo así como O (n). Sin embargo, dado que Haskell evalúa perezosamente, parece que debería ser un múltiplo constante de n. Esto ha estado funcionando por casi una hora ahora. Parece muy irrazonable. ¿Alguien tiene alguna idea de por qué?

+3

no veo la razón por la pereza de Haskell debe hacer ninguna diferencia a la complejidad de este algoritmo. – sepp2k

+2

Su función 'collatzLength' no es recursiva de cola. Esto ralentiza la función y causa una asignación innecesaria. Y su 'maxTuple' es lo mismo que' comparando fst'. – fuz

+0

@sepp Realmente no sé cómo se implementan las Comprensiones de listas. Si están usando Map, si Haskell evaluó estrictamente, parece que debería pasar por la lista varias veces. –

Respuesta

22

Suponiendo que la función collatzLength se memorizará. Haskell no hace memoria automática. Tendrás que hacer eso tú mismo. Aquí hay un ejemplo usando el paquete data-memocombinators.

import Data.List 
import Data.Ord 
import qualified Data.MemoCombinators as Memo 

collatzLength :: Integer -> Integer 
collatzLength = Memo.arrayRange (1,1000000) collatzLength' 
    where 
    collatzLength' 1 = 1 
    collatzLength' n | odd n = 1 + collatzLength (3 * n + 1) 
        | even n = 1 + collatzLength (n `quot` 2) 

main = print $ foldl1' max $ [(collatzLength n, n) | n <- [1..1000000]] 

Esto ejecuta en aproximadamente 1 segundo cuando se compila con -O2.

1

Para poder encontrar el máximo de una lista, se debe evaluar toda la lista.

Así que se calculará collatzLength de 1 a 1000000 y collatzLength es recursivo. Lo peor es que su definición de collatzLength ni siquiera es recursiva.

+0

Esta respuesta pierde el sentido. Toda la lista * * necesita ser evaluada, pero al registrar (memorizar) los resultados de 'collatzLength', se puede acelerar mucho * porque * se define recursivamente. Creo que esa es la pepita de sabiduría que se supone que este problema de Euler debe transmitir. –

+0

bien, pero él preguntó acerca de la evaluación perezosa. Y le dije que la evaluación perezosa no lo haría más rápido aquí porque de todos modos se debe evaluar (al menos una vez). ¡Pero estás en lo correcto! El problema no es evaluar todo una vez, el problema es evaluar todo, más de una vez. –

+0

Ah, veo de dónde vienes ahora.Usted también tiene razón: toda la lista debe ser evaluada, por lo tanto, la pereza no ayudará realmente; la confusión del cartel aparentemente vino de no entender claramente la evaluación perezosa, asumiendo que incluía la magia de la memoria que no tiene. –

0

cL es la abreviatura de collatzLength

cL!!n gradas para collatzLength n

cL :: [Int] 
cL = 1 : 1 : [ 1 + (if odd n then cL!!(3*n+1) else cL!!(n `div` 2)) | n <- [2..]] 

Prueba simple:

ghci> cL !! 13 
10 
+0

Básicamente, esto usa una lista que memoriza 'collatzLength' mientras se construye a sí mismo. Pero es bastante alucinante cuando tratas de rastrear el orden de ejecución de 'cL !! 3', que depende de 'cL !! 10', un elemento posterior en la lista que se evaluará antes. –

+0

@ Dan Burton, lo peor es que funciona lentamente, tal vez por (!!). – wenlong

+0

Sí, '!!' agrega una parte de * O (n) * complejidad de tiempo en el corazón del algoritmo (en contraste, una función memorada debe ser * O (1) * para invocar o * O (log n) * en el peor de los casos, para la búsqueda), por lo que para '' n grande, definitivamente verá una gran desaceleración. –

Cuestiones relacionadas