2012-02-08 9 views
7

Ho un semplice calcolatore di numeri primi in clojure (un algoritmo inefficiente, ma sto solo cercando di capire il comportamento di recur per ora). Il codice è:Overflow durante l'utilizzo ricorrente in 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) 
) 

Questo funziona fintanto che non sto cercando di trovare troppi numeri. Ad esempio

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

opere. Per qualsiasi cosa richieda più ricorsione, ottengo un errore di overflow.

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

non funziona. In generale, se ricorrere o chiamare di nuovo i primati naive senza il tentativo di TCO non sembra fare alcuna differenza. Perché ricevo errori per ricorsioni di grandi dimensioni durante l'utilizzo di ricorrenza?

+0

È necessario ripetere il ciclo per ottenere la ricorsione della coda? Non vedo il ciclo nel tuo codice. Farei una risposta, ma sto ancora imparando Clojure. – octopusgrabbus

+0

Il tuo codice funziona per me in Clojure 1.2.1 e 1.3. L'unico errore che ottengo è un 'OutOfMemoryError' quando si trovano i primi fino a 200.000. –

+0

@octopusgrabbus, no, recur può essere usato in questo modo (solo all'interno di un corpo di una funzione). Vedi http://clojure.org/special_forms#recur. –

risposta

16

recur utilizza sempre la ricorsione della coda, indipendentemente dal fatto che si stia ricorrendo a un ciclo oa una testa di funzione. Il problema sono le chiamate a remove. remove chiama first per ottenere l'elemento dal seq sottostante e controlla se quell'elemento è valido. Se il seq sottostante è stato creato da una chiamata a remove, si ottiene un'altra chiamata a first. Se si chiama remove 20000 volte sullo stesso seq, chiamare first richiede chiamare first 20000 volte e nessuna delle chiamate può essere ricorsiva in coda. Quindi, l'errore di overflow dello stack.

Modifica (remove ...) a (doall (remove ...)) risolve il problema, in quanto impedisce l'impilamento infinito di remove chiamate (ognuno viene completamente applicata immediatamente e restituisce una seq concreto, non un seq pigro). Penso che questo metodo mantenga sempre una lista di candidati in memoria in una sola volta, anche se non ne sono certo. Se è così, non è troppo poco efficiente, e un po 'di test mostra che non è in realtà molto più lento.

+0

Grazie. Mi ci sarebbe voluto un giorno per capirlo senza il tuo aiuto. Anche se doall sembra un po 'magico per me, questo è stato estremamente utile! – DanB