繁体   English   中英

主要因素的Clojure尾递归

[英]Clojure Tail Recursion with Prime Factors

我正在尝试自学clojure,我正在使用Prime Factor 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 
    ([n] (primefactors n 2))
    ([n candidate] 
        (cond (<= n 1) (list)
              (= 0 (rem n candidate)) (conj (primefactors (/ n candidate)) candidate)
              :else (primefactors n (inc candidate))
        )
    )
)

这很有效,直到我抛出以下边缘案例测试:

(fact (primefactors 1000001) => (list 101 9901))

我最终得到了堆栈溢出错误。 我知道我需要将其转换为适当的重复循环,但我看到的所有示例似乎都过于简单,仅指向计数器或数值变量作为焦点。 如何进行递归?

谢谢!

这是一个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))))

诀窍是使用累加器参数来存储结果。 请注意,递归结束时的reverse调用是可选的,只要您不关心这些因子是否以相反的顺序列出。

你的第二个递归调用已经在尾部位置,你可以用recur替换它。

(primefactors n (inc candidate))

(recur n (inc candidate))

任何函数重载都会打开一个隐式loop块,因此您无需手动插入。 这应该已经在一定程度上改善了堆栈情况,因为这个分支将更常用。

第一次递归

(primefactors (/ n candidate))

不在尾部位置,因为它的结果传递给了conj 为了把它的尾巴的位置,你需要收集的主要因素,在您的附加蓄能器参数conj从目前的递归级别的结果,然后传递给recur在每次调用。 您需要调整终止条件以返回该累加器。

典型的方法是将累加器包含为函数参数之一。 在函数定义中添加3-arity版本:

(defn primefactors
  ([n] (primefactors n 2 '()))
  ([n candidate acc]
    ...)

然后修改(conj ...)表单以调用(recur ...)并传递(conj acc candidate)作为第三个参数。 确保传递三个参数recur ,即(recur (/ n candidate) 2 (conj acc candidate)) ,这样你就可以调用三primefactors

并且(<= n 1)情况需要返回acc而不是空列表。

如果你不能为自己找出解决方案,我可以详细说明,但我想我应该给你一个尝试先解决问题的机会。

这个函数实际上不应该是尾递归的:它应该构建一个惰性序列。 毕竟,知道4611686018427387902是非素数(它可以被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)))))

以上是您发布的算法的相当缺乏想象力的翻译; 当然存在更好的算法,但这会让你正确和懒惰,并修复堆栈溢出。

尾递归,无累加器,懒惰序列解决方案:

(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)))

嵌入在lazy-seq中的递归调用在迭代序列之前不会被计算,从而消除了堆栈溢出的风险,而无需求助于累加器。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM