Clojure: proste czynniki czynnikowe powodują przepełnienie stosu
Co robię źle? Prosta rekurencja kilka tysięcy wywołań głęboko rzuca StackOverflowError
.
Jeśli granica rekurencji Clojure jest tak niska, jak Mogę na niej polegać?
(defn fact[x]
(if (<= x 1) 1 (* x (fact (- x 1)) )))
user=> (fact 2)
2
user=> (fact 4)
24
user=> (fact 4000)
java.lang.StackOverflowError (NO_SOURCE_FILE:0)
9 answers
Rozmiar stosu, jak rozumiem, zależy od JVM, którego używasz, jak również od platformy. Jeśli używasz JVM Sun, możesz użyć parametrów -Xss
i -XThreadStackSize
, Aby ustawić rozmiar stosu.
Preferowanym sposobem wykonywania rekurencji w Clojure jest użycie loop
/recur
:
(defn fact [x]
(loop [n x f 1]
(if (= n 1)
f
(recur (dec n) (* f n)))))
Clojure zrobi dla tego optymalizację połączeń ogonowych, co zapewni, że nigdy nie napotkasz StackOverflowError
s.
I due defn
implikuje loop
Wiązanie , można pominąć loop
wyrażenie i używać jego argumentów jako argumentu funkcji. Aby uczynić z niej funkcję 1-argumentową, Użyj funkcji multiary
:
(defn fact
([n] (fact n 1))
([n f]
(if (<= n 1)
f
(recur (dec n) (* f n)))))
Edit: dla przypomnienia, oto funkcja Clojure, która zwraca leniwy ciąg wszystkich faktorii:
(defn factorials []
(letfn [(factorial-seq [n fact]
(lazy-seq
(cons fact (factorial-seq (inc n) (* (inc n) fact)))))]
(factorial-seq 1 1)))
(take 5 (factorials)) ; will return (1 2 6 24 120)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-09 14:51:35
Oto inny sposób:
(defn factorial [n]
(reduce * (range 1 (inc n))))
To nie rozwali stosu, ponieważ range
zwraca leniwy seq i reduce
przechodzi przez seq bez trzymania się głowy.
reduce
korzysta z chunked seqs, jeśli może, więc może to działać lepiej niż używanie recur
samodzielnie. Siddhartha Reddy ' s recur
-wersja bazowa i Ta reduce
-wersja bazowa:
user> (time (do (factorial-recur 20000) nil))
"Elapsed time: 2905.910426 msecs"
nil
user> (time (do (factorial-reduce 20000) nil))
"Elapsed time: 2647.277182 msecs"
nil
Tylko drobna różnica. Lubię zostawiać mój recur
pierścień map
i reduce
i przyjaciołom, które są bardziej czytelne i wyraźne, i używać recur
wewnętrznie nieco bardziej elegancko, niż prawdopodobnie zrobię to ręcznie. Są chwile, kiedy trzeba ręcznie, ale nie tak wiele z mojego doświadczenia.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:26
Clojure ma kilka sposobów przerywania rekurencji
(defn fact ([x] (trampoline (fact (dec x) x)))
([x a] (if (<= x 1) a #(fact (dec x) (*' x a)))))
(fact 42)
620448401733239439360000N
Ps: nie mam przy sobie repl, więc czy ktoś mógłby przetestować-naprawić funkcję fakt trapoline?
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-10 21:20:00
Jak już miałem pisać poniżej, widzę, że jest prawie taki sam jak Przykład schematu zamieszczony przez JasonTrue... W każdym razie, oto implementacja w Clojure:
user=> (defn fact[x]
((fn [n so_far]
(if (<= n 1)
so_far
(recur (dec n) (* so_far n)))) x 1))
#'user/fact
user=> (fact 0)
1
user=> (fact 1)
1
user=> (fact 2)
2
user=> (fact 3)
6
user=> (fact 4)
24
user=> (fact 5)
120
Itd.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-02 17:24:33
Jak zasugerował l0st3d, rozważ użycie recur lub lazy-seq .
Staraj się również, aby Twoja Sekwencja była leniwa, budując ją za pomocą wbudowanych formularzy sekwencyjnych, w przeciwieństwie do robienia tego bezpośrednio.
Oto przykład użycia wbudowanych formularzy sekwencji do stworzenia leniwej sekwencji Fibonacciego (z książki Clojure programowania):(defn fibo []
(map first (iterate (fn [[a b]] [b (+ a b)]) [0 1])))
=> (take 5 (fibo))
(0 1 1 2 3)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-02 18:10:01
Głębokość stosu jest małą irytacją (jeszcze konfigurowalną), ale nawet w języku z rekurencją ogonową, takim jak Scheme lub F#, W końcu zabraknie miejsca na stosie z kodem.
Z tego co wiem, jest mało prawdopodobne, aby Twój kod był zoptymalizowany nawet w środowisku, które obsługuje rekurencję ogonową. Warto przyjrzeć się stylowi kontynuacji przechodzenia, aby zminimalizować głębokość stosu.
Oto kanoniczny przykład w schemacie z Wikipedii , która może być przetłumaczone na Clojure, F # lub inny język funkcjonalny bez większych problemów: {]}
(define factorial
(lambda (n)
(let fact ([i n] [acc 1])
(if (zero? i)
acc
(fact (- i 1) (* acc i))))))
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-02 18:54:23
Inna, prosta rekurencyjna implementacja prosta może być następująca:
(defn fac [x]
"Returns the factorial of x"
(if-not (zero? x) (* x (fac (- x 1))) 1))
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-10 20:03:35
Aby dodać do odpowiedzi Siddharthy Reddy ' ego, możesz również pożyczyć formę funkcji czynnikowej Struktura i interpretacja programów komputerowych, z pewnymi poprawkami specyficznymi dla Clojure. To dało mi całkiem dobre wyniki nawet dla bardzo dużych obliczeń czynnikowych.
(defn fac [n]
((fn [product counter max-count]
(if (> counter max-count)
product
(recur (apply *' [counter product])
(inc counter)
max-count)))
1 1 n))
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-05 11:31:45
Liczby czynnikowe są z natury bardzo duże. Nie jestem pewien, jak Clojure sobie z tym radzi (ale widzę, że działa z Javą), ale każda implementacja, która nie używa dużych liczb, przepełni się bardzo szybko.
Edit: jest to bez uwzględnienia faktu, że używasz do tego rekurencji, która może również zużywać zasoby.
Edit x2: jeśli implementacja używa dużych liczb, które, o ile wiem, są zwykle tablicami, sprzężonymi z rekurencją (jeden kopiowanie dużej liczby na wpis funkcji, zawsze zapisywane na stosie ze względu na wywołania funkcji) wyjaśniałoby przepełnienie stosu. Spróbuj zrobić to w pętli for, aby zobaczyć, czy to jest problem.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-02 16:54:01