簡體   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