2011-08-20 18 views
9

Pracuję nad projektem Euler Problem 14. Oto moje rozwiązanie.Dlaczego to wyrażenie Haskella jest takie wolne?

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 

biegnę następujące z GHCi

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

wiem, że jeśli Haskell ocenia surowo, czas na to byłoby coś podobnego O (n). Ponieważ Haskell ocenia leniwie, wydaje się, że powinno to być ciągłe wielokrotne n. To trwa już prawie godzinę. Wydaje się bardzo nierozsądne. Czy ktoś ma jakiś pomysł, dlaczego?

+3

nie widzę dlaczego lenistwo Haskell powinna żadnej różnicy na złożoność tego algorytm. – sepp2k

+2

Twoja funkcja 'collatzLength' nie jest rekursywna. Spowalnia to funkcję i powoduje niepotrzebne przydzielanie. A twoje 'maxTuple' jest takie samo jak' porównywanie fst'. – fuz

+0

@sepp Naprawdę nie wiem, w jaki sposób implementuje się rozumienie list. Jeśli używają mapy, jeśli Haskell jest oceniany ściśle, wydaje się, że musiałby przejść przez listę wiele razy. –

Odpowiedz

22

Zakłada się, że funkcja collatzLength zostanie zapamiętana. Haskell nie wykonuje automatycznej zapamiętywania. Musisz to zrobić sam. Oto przykład użycia pakietu 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]] 

To trwa około 1 sekundy, gdy skompilowano z -O2.

1

Aby można było znaleźć maksymalną listę, należy dokonać oceny całej listy.

Więc to obliczy collatzLength od 1 do 1000000 i collatzLength jest rekurencyjne. Najgorsze jest to, że twoja definicja collatzLength nie jest nawet rekurencyjna.

+0

Ta odpowiedź nie ma znaczenia. Cała lista * nie * musi być oceniona, ale przez zapisanie (zapamiętywanie) wyników 'collatzLength', może być znacznie przyspieszona właśnie * ponieważ * jest rekurencyjnie zdefiniowana. Myślę, że jest to bryłka mądrości, którą ma przekazać ten problem Eulera. –

+0

w porządku, ale zapytał o leniwe oceny. Powiedziałem, że leniwy wynik nie przyspieszy tutaj, ponieważ wszystko musi zostać ocenione (przynajmniej raz) w każdym razie. Ale masz rację! Problem nie ocenia wszystkiego raz, problemem jest ocena wszystkiego _ więcej niż raz. –

+0

Ach, widzę, skąd przybywasz.Ty też masz rację: cała lista musi zostać oceniona, dlatego lenistwo tak naprawdę nie pomoże; Zamieszanie plakatu najwyraźniej wynikało z niezrozumienia leniwej oceny, zakładając, że zawiera magię memoizacji, której nie ma. –

0

cL jest skrótem collatzLength

cL!!n podpórek collatzLength n

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

prosty test:

ghci> cL !! 13 
10 
+0

Zasadniczo używa to listy, która zapamiętuje 'collatzLength' podczas jej konstruowania. Ale to raczej zadziwiające, kiedy próbujesz prześledzić kolejność wykonywania dla 'cL !! 3', który zależy od 'cL !! 10', późniejszy element na liście, który zostanie oceniony wcześniej! –

+0

@Dan Burton, najgorsze jest to, że działa wolno, może spowodować (!!). – wenlong

+0

Tak, '!!' dodaje część złożoności * O (n) * do serca algorytmu (przeciwnie, funkcja zapamiętana powinna być * O (1) *, aby wywołać lub * O (log n) * w najgorszym przypadku dla wyszukiwania), więc dla dużego 'n' na pewno zobaczysz duże spowolnienie. –

Powiązane problemy