使用循环/重复的懒惰序列?

时间:2014-02-11 20:18:06

标签: clojure

我想为生成无限结果序列的算法编写一个实现,其中每个元素表示算法的单个迭代的计算。使用延迟序列很方便,因为它解耦了实现的迭代次数(通过使用take)和 burn-in 迭代(通过使用drop)的逻辑

以下是两个算法实现的示例,一个生成惰性序列(yadda-lazy),另一个不生成(yadda-loop)。

(defn yadda-iter
  [v1 v2 v3]

  (+ (first v1)
     (first v2)
     (first v3)))

(defn yadda-lazy
  [len]

  (letfn [(inner [v1 v2 v3]
            (cons (yadda-iter v1 v2 v3)
                  (lazy-seq (inner (rest v1)
                                   (rest v2)
                                   (rest v3)))))]
    (let [base (cycle (range len))]
      (inner base
             (map #(* %1 %1) base)
             (map #(* %1 %1 %1) base)))))

(defn yadda-loop
  [len iters]

  (let [base (cycle (range len))]
    (loop [result nil
           i 0
           v1 base
           v2 (map #(* %1 %1) base)
           v3 (map #(* %1 %1 %1) base)]
      (if (= i iters)
        result
        (recur (cons (yadda-iter v1 v2 v3) result)
               (inc i)
               (rest v1)
               (rest v2)
               (rest v3))))))

(prn (take 11 (yadda-lazy 4)))
(prn (yadda-loop 4 11))

有没有办法使用与loop / recur相同的样式创建延迟序列?我更喜欢yadda-loop,因为:

  • 更明显的是初始条件是什么以及算法如何进展到下一次迭代。
  • 由于尾部优化,它不会受到堆栈溢出的影响。

3 个答案:

答案 0 :(得分:28)

你的循环版本会更好地写入(1)从循环中拉出加法,这样你就不必重复这么多的序列,并且(2)在向量累加器上使用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参数,只返回stake

(defn yadda-yadda [len]
   (let [v1 (cycle (range len))
         v2 (map * v1 v1)
         v3 (map * v1 v2)]
     (map + v1 v2 v3)))

这会产生与yadda-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)))

<强>附录

如果您正在寻找一种模式,用于将像您这样的热切循环转换为惰性序列

  1. (loop [acc [] args args] ...) - &gt; ((fn step [args] ...) args)
  2. (if condition (recur ...) acc) - &gt; (when condition (lazy-seq ...)
  3. (recur (conj acc (f ...)) ...) - &gt; (lazy-seq (cons (f ...) (step ...)))
  4. 将此应用于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))))
    

    此时你可能想要取出iters

    (defn yadda-lazy-3 [len]
      (let [base (cycle (range len))]
        ((fn step [v1, v2, v3]
            (lazy-seq 
              (cons (yadda-iter v1 v2 v3)
                (step (rest v1), (rest v2), (rest v3)))))
          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只是在第一个+上应用step而其余部分都应用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))))
    

    瞧,你刚刚重新实现了可变图谱

    {{1}}

答案 1 :(得分:5)

@ A.Webb的答案是完美的,但是如果你对loop / recur的爱情克服了他的论点,那么你知道你仍然可以结合两种递归方式。

例如,看看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

这是另一个例子,filter的替代实现:

(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))))

答案 2 :(得分:0)

The Tupelo library有一个新的lazy-gen / yield功能,可以在Python中模仿generator functions。它可以从循环结构中的任何点生成惰性序列。以下是显示yadda-loop&amp;的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.