使用recur在Clojure中缓存“递归”调用的值

时间:2011-12-31 21:33:27

标签: recursion clojure

我正在做Clojure中的 The Little Schemer The Seasoned Schemer 练习,作为学习Scheme和Clojure的练习 - 尝试将其作为惯用的Clojure写成我可以。在Ch。 14(经验丰富的Schemer )他们定义了函数leftmost,它应该找到第一个“原子”(意思是一个不是列表的实体, Clojure列表中的原子定义。这是我在Clojure中实现的真正的递归版本:

(defn atom? [x]
  (not (coll? x)))

(defn leftmost [l]
  (cond
   (empty? l) []
   (atom? (first l)) (first l)
   :else (let [r (leftmost (first l))]
           (if (atom? r)
             r
             (leftmost (rest l))))))

为了明确它的作用,以下是对它的测试:

(deftest test-leftmost
  (is (= :a (leftmost [:a :b [:c :d]])))
  (is (= :a (leftmost [[:a :b] [:c :d]])))
  (is (= :a (leftmost [[] [] [[:a]] :b [:c :d]])))
  (is (= [] (leftmost-recur [[] [['()]]])))
  (is (= [] (leftmost-recur []))))

练习的关键部分是通过在leftmost (first l)子句中使用let语句来“缓存”:else的调用。

我想使用recur在Clojure中编写它并获得尾调用优化。到目前为止我能做的最好的就是:

(defn leftmost-recur [l]
  (cond
   (empty? l) []
   (atom? (first l)) (first l)
   :else (let [r (leftmost-recur (first l))]
           (if (atom? r)
             r
             (recur (rest l))))))

在:else子句中,我仍然有一个真正的递归,而不是重复,因为recur当然必须出现在尾部调用位置。这个函数通过了测试,但是遇到了真正的递归问题,包括堆栈溢出。

有没有办法在不进行真正递归的情况下“缓存”(let [r (leftmost-recur (first l))]调用?

尝试使用memoize

我试着考虑使用memoize,但我不确定如何记忆自递归函数。这是我的尝试,但我认为它没有按照我的意愿行事:

(defn leftmost-recur-memoize [l]
  (Thread/sleep 100)  ; added to check whether memoize is working    
  (let [memo-leftmost (memoize leftmost-recur-memoize)]
    (cond
     (empty? l) []
     (atom? (first l)) (first l)
     :else (let [r (memo-leftmost (first l))]
             (if (atom? r)
               r
               (memo-leftmost (rest l))
               )))))

...基于测试数字:

(println (time (= :a (leftmost-recur-memoize [:a :b [:c :d]]))))
(println (time (= :a (leftmost-recur-memoize [[] [] [[:a]] :b [:c :d]]))))
(println (time (= [] (leftmost-recur-memoize [[] [['()]]]))))
;; repeat same
(println (time (= :a (leftmost-recur-memoize [:a :b [:c :d]]))))
(println (time (= :a (leftmost-recur-memoize [[] [] [[:a]] :b [:c :d]]))))
(println (time (= [] (leftmost-recur-memoize [[] [['()]]]))))
"Elapsed time: 100.27427 msecs"
true
"Elapsed time: 701.740783 msecs"
true
"Elapsed time: 801.796439 msecs"
true
"Elapsed time: 100.148838 msecs"
true
"Elapsed time: 701.430802 msecs"
true
"Elapsed time: 801.767962 msecs"
true

底线问题

所以,最后(对于冗长的问题),我在两个问题上寻求帮助:

  1. 有没有办法在原始else子句中缓存或记忆该语句,而不是每次都进行真实/实际递归?
  2. 做这类事情的最惯用的Clojure方法是什么? (可能具有更高阶函数或其他方法)

1 个答案:

答案 0 :(得分:3)

  1. 没有。嵌套的列表列表是树,您正在执行深度优先搜索。每次下拉时,都需要跟踪所有以前的列表,以便在重新启动时继续检查它们。因此,堆栈消耗行为实际上不是由于存储返回的值,而是由于遍历树。

  2. (first (flatten coll))