我究竟做错了什么? 简单而又重复几千电话深抛出一个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)
我究竟做错了什么? 简单而又重复几千电话深抛出一个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)
堆栈大小,我明白了,取决于你所使用的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
秒。
而且由于defn
意味着loop
结合 ,你可以省略loop
的表达,并使用它的参数作为函数的参数。 并使其成为1个参数功能,使用了multiary
的功能caracteristic :
(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)
这里的另一种方式:
(defn factorial [n]
(reduce * (range 1 (inc n))))
这会不会吹堆栈,因为range
返回一个懒惰的序列,并reduce
整个序列散步而不握住头。
reduce
利用分块seqs如果可以的,所以这实际上可以进行比使用更好的recur
自己。 使用悉达多Reddy的 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
手动,但以我的经验不是很多。
Clojure的有破坏递归的几种方法
(defn fact ([x] (trampoline (fact (dec x) x)))
([x a] (if (<= x 1) a #(fact (dec x) (*' x a)))))
(fact 42)
620448401733239439360000N
PS:我没有对我有这样REPL会有人好心的测试解决trapoline事实功能?
当我正要张贴下面,我看到它是几乎一样的方案例如张贴JasonTrue ......总之,这里的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
等等
作为l0st3d建议,可以考虑使用易复发或懒惰序列 。
另外,尽量使你的序列懒使用内置序列的形式作为反对直接做建筑的。
下面是使用内置的序列的形式来创建一个懒惰的斐波那契序列(从编程Clojure的书)的一个例子:
(defn fibo []
(map first (iterate (fn [[a b]] [b (+ a b)]) [0 1])))
=> (take 5 (fibo))
(0 1 1 2 3)
堆栈深度是一个小烦恼(尚未配置),但即使在像计划或F#你最终与你的代码运行的堆栈空间尾递归的语言。
据我所知,你的代码是不可能被尾递归在支持尾递归透明的环境中,即使进行了优化。 你想看看一个延续传递风格,以尽量减少堆栈深度。
下面是计划从典型的例子维基百科 ,这可以转换为Clojure中,F#或没有太多的麻烦另一种功能性的语言:
(define factorial
(lambda (n)
(let fact ([i n] [acc 1])
(if (zero? i)
acc
(fact (- i 1) (* acc i))))))
另外,简单的递归实现简单的可能是这样的:
(defn fac [x]
"Returns the factorial of x"
(if-not (zero? x) (* x (fac (- x 1))) 1))
为了增加悉达多雷迪的答案,你也可以借用阶乘函数形式计算机程序的构造和解释 ,有一些具体的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))
阶乘号码是由它们的性质非常大。 我不知道怎么的Clojure处理这项(但我看到它的工作原理与Java),但不使用大的数字,任何实现将溢出非常快。
编辑:这是没有考虑到你正在使用递归这一点,这也很可能使用了资源的事实。
编辑X2:如果实现是用大的数字,其中,因为据我所知,通常阵列,加上递归(每个函数入口,一直保存在栈上,由于函数调用一个巨大的数字拷贝)将解释堆栈溢出。 尝试做它在一个for循环,看看是否这就是问题所在。