Recursion 带素因子的Clojure尾递归
我试着教自己clojure,我用基本因子Kata和TDD的原理来做到这一点 通过以下一系列Midje测试:Recursion 带素因子的Clojure尾递归,recursion,clojure,tail-recursion,prime-factoring,Recursion,Clojure,Tail Recursion,Prime Factoring,我试着教自己clojure,我用基本因子Kata和TDD的原理来做到这一点 通过以下一系列Midje测试: (fact (primefactors 1) => (list)) (fact (primefactors 2) => (list 2)) (fact (primefactors 3) => (list 3)) (fact (primefactors 4) => (list 2 2)) 我能够创建以下函数: (defn primefactors (
(fact (primefactors 1) => (list))
(fact (primefactors 2) => (list 2))
(fact (primefactors 3) => (list 3))
(fact (primefactors 4) => (list 2 2))
我能够创建以下函数:
(defn primefactors
([n] (primefactors n 2))
([n candidate]
(cond (<= n 1) (list)
(= 0 (rem n candidate)) (conj (primefactors (/ n candidate)) candidate)
:else (primefactors n (inc candidate))
)
)
)
最后出现了堆栈溢出错误。我知道我需要将其转化为一个适当的递归循环,但我看到的所有示例似乎都过于简单,只指向一个计数器或数值变量作为焦点。如何使其递归
谢谢 您的第二个递归调用已经处于尾部位置,您可以将其替换为
recur
(primefactors n (inc candidate))
变成
(recur n (inc candidate))
任何函数重载都会打开一个隐式循环
块,因此不需要手动插入该块。这应该已经在一定程度上改善了堆栈情况,因为这一分支将更常见
第一递归
(primefactors (/ n candidate))
不在尾部位置,因为其结果被传递到
conj
。要将其置于尾部位置,您需要在一个附加的累加器参数中收集基本因子,然后在每次调用时将当前递归级别的结果传递到该参数上。您需要调整终止条件以返回累加器。典型的方法是将累加器作为函数参数之一。在函数定义中添加3-arity版本:
(defn primefactors
([n] (primefactors n 2 '()))
([n candidate acc]
...)
然后修改(conj…
表单以调用(recur…
,并将(conj acc candidate)
作为第三个参数传递。确保将三个参数传递给recur
,即(recur(/n候选者)2(conj acc候选者))
,以便调用primefactors的三元版本
而(这是primefactors
过程的尾部递归实现,它应该在不引发堆栈溢出错误的情况下工作:
(defn primefactors
([n]
(primefactors n 2 '()))
([n candidate acc]
(cond (<= n 1) (reverse acc)
(zero? (rem n candidate)) (recur (/ n candidate) candidate (cons candidate acc))
:else (recur n (inc candidate) acc))))
(定义参数)
([n]
(素数2’())
([n候选人acc]
(cond(尾部递归、无累加器、惰性序列解决方案:
(defn prime-factors [n]
(letfn [(step [n div]
(when (< 1 n)
(let [q (quot n div)]
(cond
(< q div) (cons n nil)
(zero? (rem n div)) (cons div (lazy-step q div))
:else (recur n (inc div))))))
(lazy-step [n div]
(lazy-seq
(step n div)))]
(lazy-step n 2)))
(定义基本因子[n]
(letfn[(步骤[n部分]
(当(<1 n)
(让[q(quot n div)]
(续)
(
嵌入在lazy seq
中的递归调用在对序列进行迭代之前不会进行评估,从而消除了堆栈溢出的风险,而无需使用累加器。此函数实际上不应该是尾部递归的:它应该构建一个延迟序列。毕竟,知道461168601842738790不是很好吗2是非素数(它可以被2整除),而不必计算数字并发现它的另一个素数因子是2305843009213693951
(defn prime-factors
([n] (prime-factors n 2))
([n candidate]
(cond (<= n 1) ()
(zero? (rem n candidate)) (cons candidate (lazy-seq (prime-factors (/ n candidate)
candidate)))
:else (recur n (inc candidate)))))
(定义基本因子)
([n](素因子n2))
([n候选人]
(cond)(谢谢,这太棒了,这是我需要的解释。在Clojure中,“递归然后反转”是一种反模式:我们有向量,它们在右边附加得很便宜,所以最好从正确的顺序开始构建列表(如果你需要一个列表,而不是最后的向量,只需seq
it,这比反向更便宜)。但实际上,惰性解决方案比尾部递归解决方案更可取:请看我的答案,看一个简单的例子。哇。这是我第一次看到编写Lisp的人真正给出自己的行:P
(defn prime-factors
([n] (prime-factors n 2))
([n candidate]
(cond (<= n 1) ()
(zero? (rem n candidate)) (cons candidate (lazy-seq (prime-factors (/ n candidate)
candidate)))
:else (recur n (inc candidate)))))