我正在做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
,但我不确定如何记忆自递归函数。这是我的尝试,但我认为它没有按照我的意愿行事:
(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
所以,最后(对于冗长的问题),我在两个问题上寻求帮助:
答案 0 :(得分:3)
没有。嵌套的列表列表是树,您正在执行深度优先搜索。每次下拉时,都需要跟踪所有以前的列表,以便在重新启动时继续检查它们。因此,堆栈消耗行为实际上不是由于存储返回的值,而是由于遍历树。
(first (flatten coll))