2012-02-08 28 views
7

Mam prosty kalkulator liczby pierwszej w clojure (nieefektywny algorytm, ale próbuję po prostu zrozumieć zachowanie powtarzam na teraz). Kod to:Przepełnienie podczas używania recur w clojure

(defn divisible [x,y] (= 0 (mod x y))) 

(defn naive-primes [primes candidates] 
    (if (seq candidates) 
     (recur (conj primes (first candidates)) 
       (remove (fn [x] (divisible x (first candidates))) candidates)) 
     primes) 
) 

Działa tak długo, jak długo nie próbuję znaleźć zbyt wielu liczb. Na przykład:

(print (sort (naive-primes [] (range 2 2000)))) 

działa. W przypadku wszystkiego, co wymaga większej rekurencji, pojawia się błąd przepełnienia.

(print (sort (naive-primes [] (range 2 20000)))) 

nie będzie działać. Zasadniczo, czy używam recur lub call naive-primes ponownie bez próby TCO, nie wydaje się, aby robiło to jakąś różnicę. Dlaczego otrzymuję błędy w przypadku dużych rekursji podczas korzystania z funkcji recur?

+0

Czy recur wymaga pętli, aby uzyskać rekurencję ogona? Nie widzę pętli w twoim kodzie. Uczyniłbym to jako odpowiedź, ale wciąż uczę się Clojure. – octopusgrabbus

+0

Twój kod działa dla mnie w Clojure 1.2.1 i 1.3. Jedyny błąd, jaki w końcu dostaję, to "OutOfMemoryError", gdy znajduję liczby pierwsze do 200 000. –

+0

@octopusgrabbus, no, recur mogą być również używane w ten sposób (również w ramach funkcji). Zobacz http://clojure.org/special_forms#recur. –

Odpowiedz

16

recur zawsze używa rekursji ogonowej, niezależnie od tego, czy powracasz do pętli, czy też do funkcji. Problem dotyczy połączeń z numerem remove. remove dzwoni pod numer , aby uzyskać element z odpowiedniego seq i sprawdza, czy dany element jest prawidłowy. Jeśli bazowe polecenie seq zostało utworzone przez połączenie z numerem remove, otrzymasz kolejne połączenie z numerem first. Jeśli wywołasz remove 20000 razy na tym samym seq, wywołanie first wymaga wywołania first 20000 razy, a żadne z połączeń nie może być rekursywne. W związku z tym błąd przepełnienia stosu.

Zmiana (remove ...) do (doall (remove ...)) rozwiązuje problem, ponieważ zapobiega nieskończoną układanie remove połączeń (każdy dostaje w pełni zastosowane natychmiast i zwraca konkretny nast nie leniwy nast). Myślę, że ta metoda ma tylko jedną listę zapisów w pamięci w jednym czasie, choć nie jestem tego pewien. Jeśli tak, to nie jest to zbyt mało wydajne miejsce, a niektóre testy pokazują, że nie jest to dużo wolniejsze.

+0

Dzięki. Zajęłoby mi to na zawsze zrozumienie tego bez twojej pomocy. Chociaż wydaje mi się to trochę magiczne, to było bardzo pomocne! – DanB

Powiązane problemy