我做错了什么?简单的递归几千次调用深度抛出一个StackOverflowError
。
如果Clojure递归的限制如此之低,我该如何依赖它?
(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)
答案 0 :(得分:72)
这是另一种方式:
(defn factorial [n]
(reduce * (range 1 (inc n))))
这不会导致堆叠,因为range
会返回一个懒惰的seq,而reduce
会在没有抓住头部的情况下穿过seq。
reduce
如果可以的话,会使用chunked seqs,所以这实际上比自己使用recur
更好。使用基于Siddhartha Reddy's recur
的版本和基于reduce
的版本:
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
略有不同。我希望将recur
响铃留给map
和reduce
以及更加可读和明确的朋友,并在内部使用recur
比我可能更优雅一点手工做。有时您需要手动recur
,但根据我的经验,不需要那么多。
答案 1 :(得分:36)
我理解,堆栈大小取决于您使用的JVM以及平台。如果您使用的是Sun JVM,则可以使用-Xss
和-XThreadStackSize
参数来设置堆栈大小。
在Clojure中进行递归的首选方法是使用loop
/ recur
:
(defn fact [x]
(loop [n x f 1]
(if (= n 1)
f
(recur (dec n) (* f n)))))
Clojure会为此进行尾部调用优化;确保您永远不会遇到StackOverflowError
s。
由于defn
implies a loop
binding,您可以省略loop
表达式,并将其参数用作函数参数。要使其成为1参数函数,请使用the multiary
caracteristic of functions:
(defn fact
([n] (fact n 1))
([n f]
(if (<= n 1)
f
(recur (dec n) (* f n)))))
编辑:对于记录,这里是一个Clojure函数,它返回所有阶乘的延迟序列:
(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)
答案 2 :(得分:16)
Clojure有几种方法破坏递归
(defn fact ([x] (trampoline (fact (dec x) x)))
([x a] (if (<= x 1) a #(fact (dec x) (*' x a)))))
(fact 42)
620448401733239439360000N
ps:我没有对我进行复制,所以有人会对陷阱事实函数进行测试吗?
答案 3 :(得分:3)
当我即将发布以下内容时,我发现它与JasonTrue发布的Scheme示例几乎相同......无论如何,这是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
等
答案 4 :(得分:1)
堆栈深度是一个很小的烦恼(但是可配置),但即使是使用像Scheme或F#这样的尾递归的语言,你最终也会用你的代码耗尽堆栈空间。
据我所知,即使在透明支持尾递归的环境中,您的代码也不太可能进行尾递归优化。您可能希望查看延续传递样式以最小化堆栈深度。
这是来自Wikipedia的Scheme中的一个规范示例,它可以被翻译成Clojure,F#或其他函数式语言而不会有太多麻烦:
(define factorial
(lambda (n)
(let fact ([i n] [acc 1])
(if (zero? i)
acc
(fact (- i 1) (* acc i))))))
答案 5 :(得分:1)
正如l0st3d建议的那样,请考虑使用recur或lazy-seq。
此外,尝试使用内置序列形式构建它,使其序列变得懒惰,而不是直接执行。
以下是使用内置序列形式创建惰性Fibonacci序列的示例(来自Programming Clojure一书):
(defn fibo []
(map first (iterate (fn [[a b]] [b (+ a b)]) [0 1])))
=> (take 5 (fibo))
(0 1 1 2 3)
答案 6 :(得分:0)
另一个简单的递归实现很简单:
(defn fac [x]
"Returns the factorial of x"
(if-not (zero? x) (* x (fac (- x 1))) 1))
答案 7 :(得分:0)
要添加Siddhartha Reddy的答案,您还可以借用Factorial函数表Structure And Interpretation of Computer Programs,并进行一些特定于Clojure的调整。即使是非常大的因子计算,这也给了我很好的表现。
(defn fac [n]
((fn [product counter max-count]
(if (> counter max-count)
product
(recur (apply *' [counter product])
(inc counter)
max-count)))
1 1 n))
答案 8 :(得分:-8)
因子数字本质上非常大。我不确定Clojure如何处理这个问题(但我确实看到它适用于java),但任何不使用大数字的实现都会非常快速地溢出。
编辑:这没有考虑到你正在使用递归这一事实,这也可能耗尽资源。
编辑x2:如果实现使用大数字,据我所知,通常是数组,再加上递归(每个函数条目一个大数字副本,由于函数调用总是保存在堆栈上)解释堆栈溢出。尝试在for循环中执行此操作以查看是否存在问题。