Clojure 使用循环/重现的惰性序列?
我想为一个算法编写一个实现,该算法生成一个无限序列的结果,其中每个元素表示算法的单个迭代的计算。使用惰性序列很方便,因为它将迭代次数逻辑(通过使用Clojure 使用循环/重现的惰性序列?,clojure,Clojure,我想为一个算法编写一个实现,该算法生成一个无限序列的结果,其中每个元素表示算法的单个迭代的计算。使用惰性序列很方便,因为它将迭代次数逻辑(通过使用take)和老化迭代(通过使用drop)与实现分离 下面是两个算法实现的示例,一个生成惰性序列(yadda lazy),另一个不生成惰性序列(yadda loop) 是否有一种方法可以使用与循环/重复相同的样式创建惰性序列?我更喜欢雅达循环,因为: 更明显的是,初始条件是什么,以及算法如何进展到下一次迭代 它不会因为尾部优化而遭受堆栈溢出 你的循环版
take
)和老化迭代(通过使用drop
)与实现分离
下面是两个算法实现的示例,一个生成惰性序列(yadda lazy
),另一个不生成惰性序列(yadda loop
)
是否有一种方法可以使用与循环
/重复
相同的样式创建惰性序列?我更喜欢雅达循环,因为:
- 更明显的是,初始条件是什么,以及算法如何进展到下一次迭代
- 它不会因为尾部优化而遭受堆栈溢出
conj
,这样你的结果与你的yadda lazy
的顺序相同
(defn yadda-loop-2 [len iters]
(let [v1 (cycle (range len))
v2 (map * v1 v1)
v3 (map * v1 v2)
s (map + v1 v2 v3)]
(loop [result [], s s, i 0]
(if (= i iters)
result
(recur (conj result (first s)), (rest s), (inc i))))))
然而,在这一点上,很明显,循环是没有意义的,因为这只是一个循环
(defn yadda-loop-3 [len iters]
(let [v1 (cycle (range len))
v2 (map * v1 v1)
v3 (map * v1 v2)
s (map + v1 v2 v3)]
(into [] (take iters s))))
我们不妨拉出iters
参数,简单地返回s
并从中获取take
(defn yadda-yadda [len]
(let [v1 (cycle (range len))
v2 (map * v1 v1)
v3 (map * v1 v2)]
(map + v1 v2 v3)))
这会产生与您的yadda lazy
相同的结果,也是lazy,并且非常清楚
(take 11 (yadda-yadda 4)) ;=> (0 3 14 39 0 3 14 39 0 3 14)
你也可以,同等地
(defn yadda-yadda [len]
(as-> (range len) s
(cycle s)
(take 3 (iterate (partial map * s) s))
(apply map + s)))
附录
如果您正在寻找一种模式,用于将像您这样的急切循环转换为惰性序列
(循环[acc[]args args]…)
->((fn步骤[args]…)args)
(如果条件(重复…)acc)
->(当条件(延迟序列…)
(重复(conj acc(f…))
->(惰性序列(cons(f…)(step…))
yadda lazy
(defn yadda-lazy-2 [len iters]
(let [base (cycle (range len))]
((fn step [i, v1, v2, v3]
(when (< i iters)
(lazy-seq
(cons (yadda-iter v1 v2 v3)
(step (inc i), (rest v1), (rest v2), (rest v3))))))
0, base, (map #(* %1 %1) base), (map #(* %1 %1 %1) base))))
所以你可以
(take 11 (yadda-lazy-3 4)) ;=> (0 3 14 39 0 3 14 39 0 3 14)
然后你可能会说,嘿,我的yadda iter
只是在第一步应用+
,而步骤
则应用于其余步骤,那么为什么不合并我的v1、v2、v3
,让这更清楚一点呢
(defn yadda-lazy-4 [len]
(let [base (cycle (range len))]
((fn step [vs]
(lazy-seq
(cons (apply + (map first vs))
(step (map rest vs)))))
[base, (map #(* %1 %1) base), (map #(* %1 %1 %1) base)])))
瞧,你们刚刚重新实现了变量映射
(defn yadda-lazy-5 [len]
(let [base (cycle (range len))]
(map + base, (map #(* %1 %1) base), (map #(* %1 %1 %1) base))))
@A.Webb的答案是完美的,但如果您对
循环的热爱克服了他的论点,那么您仍然可以将这两种递归方式结合起来
例如,看一下范围的实现:
(defn range
(...)
([start end step]
(lazy-seq
(let [b (chunk-buffer 32)
comp (cond (or (zero? step) (= start end)) not=
(pos? step) <
(neg? step) >)]
(loop [i start] ;; chunk building through loop/recur
(if (and (< (count b) 32)
(comp i end))
(do
(chunk-append b i)
(recur (+ i step)))
(chunk-cons (chunk b)
(when (comp i end)
(range i end step))))))))) ;; lazy recursive call
有一个新的lazy gen
/yield
功能,它模仿Python中的生成器函数
。它可以从循环结构中的任何点生成一个惰性序列。下面是yadda loop
的一个版本,它显示了lazy gen
&yield
正在运行:
(ns tst.xyz
(:use clojure.test tupelo.test)
(:require [tupelo.core :as t] ))
(defn yadda-lazy-gen
[len iters]
(t/lazy-gen
(let [base (cycle (range len))]
(loop [i 0
v1 base
v2 (map #(* %1 %1) base)
v3 (map #(* %1 %1 %1) base)]
(when (< i iters)
(t/yield (yadda-iter v1 v2 v3))
(recur
(inc i)
(rest v1)
(rest v2)
(rest v3)))))))
Testing tst.clj.core
(take 11 (yadda-lazy 4)) => (0 3 14 39 0 3 14 39 0 3 14)
(yadda-loop 4 11) => (0 3 14 39 0 3 14 39 0 3 14)
(yadda-lazy-gen 4 11) => (0 3 14 39 0 3 14 39 0 3 14)
Ran 1 tests containing 1 assertions.
0 failures, 0 errors.
(ns tst.xyz
(:使用clojure.test tupelo.test)
(:require[tupelo.core:as t]))
(德芬·亚达·杰恩)
[len iters]
(t/gen)
(让[基准(周期(范围len))]
(循环[i 0
v1碱基
v2(地图#(*%1%1)基数)
v3(映射#(*%1%1%1)基)]
(当((0314390314390314)
(雅达环路411)=>(0314390314390314)
(yadda lazy gen 4 11)=>(0 3 14 39 0 3 14 39 0 3 14)
运行了1个包含1个断言的测试。
0次失败,0次错误。
惰性序列无论如何都不会使堆栈溢出。为了使堆栈溢出,您需要不断构建堆栈帧,而惰性序列只返回一个头部带有当前项的序列,尾部带有砰的一声。换句话说,对内部的第一次调用将在下一次调用发生之前返回,因此堆栈将停止在一个恒定的深度上运行。你不会得到堆栈溢出错误,但是如果你抓住头部,你可以得到一个OutOfMemoryError
(你会溢出内存,而不是堆栈)。这并没有真正回答我的问题。但您的代码启发我使用迭代
和映射
来重新编码实现,而不是使用惰性seq
和cons
。部分问题是我在多个项目上反复出现(v1
,v2
,v3
),如何使用iterate
,目前还不清楚,因为iterate
只允许一个元素。这是通过将所有共循环元素包装在一个向量中并解构以从向量中获取单独的共循环元素来解决的。@SamadLotia我不确定当时是否理解了您的问题,但请参见我刚刚编辑的附录。我确实查看了范围
和过滤器
的代码。事实上,我的惰性序列解决方案(yadda lazy
)遵循filter
的形式,这正是我试图避免的。range
看起来太复杂了,无法复制。我同意,range
是一个很好的例子。但是我不明白你为什么喜欢循环
/重复
风格。从你提到的问题来看,堆栈溢出风险已经增加了我看不出初始条件是如何更加明显的——是它们首先被引入的吗?
(defn range
(...)
([start end step]
(lazy-seq
(let [b (chunk-buffer 32)
comp (cond (or (zero? step) (= start end)) not=
(pos? step) <
(neg? step) >)]
(loop [i start] ;; chunk building through loop/recur
(if (and (< (count b) 32)
(comp i end))
(do
(chunk-append b i)
(recur (+ i step)))
(chunk-cons (chunk b)
(when (comp i end)
(range i end step))))))))) ;; lazy recursive call
(defn filter [pred coll]
(letfn [(step [pred coll]
(when-let [[x & more] (seq coll)]
(if (pred x)
(cons x (lazy-seq (step pred more))) ;; lazy recursive call
(recur pred more))))] ;; eager recursive call
(lazy-seq (step pred coll))))
(ns tst.xyz
(:use clojure.test tupelo.test)
(:require [tupelo.core :as t] ))
(defn yadda-lazy-gen
[len iters]
(t/lazy-gen
(let [base (cycle (range len))]
(loop [i 0
v1 base
v2 (map #(* %1 %1) base)
v3 (map #(* %1 %1 %1) base)]
(when (< i iters)
(t/yield (yadda-iter v1 v2 v3))
(recur
(inc i)
(rest v1)
(rest v2)
(rest v3)))))))
Testing tst.clj.core
(take 11 (yadda-lazy 4)) => (0 3 14 39 0 3 14 39 0 3 14)
(yadda-loop 4 11) => (0 3 14 39 0 3 14 39 0 3 14)
(yadda-lazy-gen 4 11) => (0 3 14 39 0 3 14 39 0 3 14)
Ran 1 tests containing 1 assertions.
0 failures, 0 errors.