clojure - 为什么程序无休止地运行?
问题描述
为什么程序无休止地运行?
(defn lazycombine
([s] (lazycombine s []))
([s v] (let [a (take 1 s)
b (drop 1 s)]
(if (= a :start)
(lazy-seq (lazycombine b v))
(if (= a :end)
(lazy-seq (cons v (lazycombine b [])))
(lazy-seq (lazycombine b (conj v a))))))))
(def w '(:start 1 2 3 :end :start 7 7 :end))
(lazycombine w)
我需要一个函数,该函数通过从另一个形式为 [: start 1 2: end: start: 5: end] 的序列中获取元素并将以下所有元素组合到一个向量中,从而返回一个惰性元素序列
解决方案
我会这样做,使用take-while
:
(ns tst.demo.core
(:use tupelo.core tupelo.test))
(def data
[:start 1 2 3 :end :start 7 7 :end])
(defn end-tag? [it] (= it :end))
(defn start-tag? [it] (= it :start))
(defn lazy-segments
[data]
(when-not (empty? data)
(let [next-segment (take-while #(not (end-tag? %)) data)
data-next (drop (inc (count next-segment)) data)
segment-result (vec (remove #(start-tag? %) next-segment))]
(cons segment-result
(lazy-seq (lazy-segments data-next))))))
(dotest
(println "result: " (lazy-segments data)))
运行我们得到:
result: ([1 2 3] [7 7])
cons
使用(惰性或非惰性)递归构造序列时请注意合同。您必须返回序列中的下一个值,或者nil
。提供nil
tocons
与提供空序列相同:
(cons 5 nil) => (5)
(cons 5 []) => (5)
因此使用when
表格来测试终止条件很方便(而不是if
在序列必须结束时使用并返回一个空向量)。
假设我们将 写成cons
一个简单的递归:
(cons segment-result
(lazy-segments data-next))
这很好用并产生相同的结果。该部分唯一要做的lazy-seq
就是延迟递归调用发生的时间。因为lazy-seq
是 Clojure 内置的(特殊形式),所以它类似于loop/recur
并且不像普通递归那样消耗堆栈。因此,我们可以在惰性序列中生成数百万(或更多)值,而无需创建StackOverflowError
(在我的计算机上,默认的最大堆栈大小约为 4000)。考虑从 开始的整数的无限惰性序列0
:
(defn intrange
[n]
(cons n (lazy-seq (intrange (inc n)))))
(dotest
(time
(spyx (first (drop 1e6 (intrange 0))))))
删除前一百万个整数并获取下一个成功并且只需要几毫秒:
(first (drop 1000000.0 (intrange 0))) => 1000000
"Elapsed time: 49.5 msecs"