【问题标题】:Create Lazy Sequency by Concatenating Collections通过连接集合创建惰性序列
【发布时间】:2017-01-12 15:04:16
【问题描述】:

通过连接集合创建惰性序列。

考虑以下函数:

(defn req []
  (Thread/sleep 1000)
  (repeat 4 (rand-int 10)))

添加睡眠是因为函数最终将是一个http请求,因此它应该模拟一个延迟。

示例输出:

(req)
;; (8 8 8 8)

(req)
;; (4 4 4 4)

我现在正在考虑一个函数,它通过串联后续req 结果创建一个惰性序列。

(take 10 (f req))
;; (3 3 3 3 2 2 2 2 9 9)

这是一种实现方式:

(defn f [g]
  (lazy-seq (concat (g) (f g))))

这是要走的路吗?我以某种方式猜测可能已经有一个可用的抽象。我试过lazy-cat,但这个宏似乎只适用于固定数量的给定序列。


事实证明这是一个有效的抽象:

(take 10 (apply concat (repeatedly req)))

但是,惰性序列的分块看起来会导致req 在这里被调用的频率超过了这里所需的频率,如果它是一个 http 请求,这是不可接受的。

【问题讨论】:

  • 另一种变体:(取10(map (fn [_] (req)) (range)))
  • 这不起作用。它返回列表列表而不是列表。它省略了它的 concat 部分。
  • 对不起--小修正:(取10(mapcat(fn [_](req))(范围)))
  • @DarrylG 这也会导致惰性序列元素的冗余实现
  • @OlegTheCat: 这里提供的示例输出是: (5 5 5 5 9 9 9 9 3 3) 这不是和问题描述一样吗?

标签: clojure lazy-sequences


【解决方案1】:

由于apply needs to know 传递函数应用于的参数数量,正在发生“不需要”的惰性序列元素实现。

快速浏览一下 Clojure 核心库,似乎它没有提供连接一系列序列的功能,同时,以您想要的方式处理惰性(没有多余地实现传递的惰性序列的元素),因此,您需要自己实现它。

以下是可能的解决方案:

(defn apply-concat [xs]
  (lazy-seq
   (when-let [s (seq xs)]
     (concat (first s) (apply-concat (rest s))))))

然后:

user> (defn req []
        (println "--> making request")
        [1 2 3 4])
#'user/req
user> (dorun (take 4 (apply-concat (repeatedly req))))
--> making request
nil
user> (dorun (take 5 (apply-concat (repeatedly req))))
--> making request
--> making request
nil
user> (dorun (take 8 (apply-concat (repeatedly req))))
--> making request
--> making request
nil
user> (dorun (take 9 (apply-concat (repeatedly req))))
--> making request
--> making request
--> making request
nil

这种方法的唯一顾虑是爆栈的危险,因为apply-concat 可能是无限递归的。

更新:

准确地说apply实现了(arity of passed function + 1)传递的惰性序列的元素:

user> (dorun (take 1 (apply (fn [& xs] xs) (repeatedly req))))
--> making request
--> making request
nil
user> (dorun (take 1 (apply (fn [x & xs] xs) (repeatedly req))))
--> making request
--> making request
--> making request
nil
user> (dorun (take 1 (apply (fn [x y & xs] xs) (repeatedly req))))
--> making request
--> making request
--> making request
--> making request
nil

【讨论】:

  • 这里没有堆栈问题。惰性序列的堆栈蹦床效应可以解决所有问题。您可以通过 (last (apply-concat (repeat 1e6 [1]))) 等方式亲自查看。
  • @OlegTheCat 我仍然在为这个问题苦苦挣扎,如果要申请前 4 个项目的话。在我看来,(apply concat (repeatedly req)) 确实采用了 apply (apply ([f args] ..)) 的第一个 fn 定义。你同意吗?
  • 所以我怀疑 apply 会导致这种情况。看看这个:(take 1 (apply (fn [& xs] xs) (repeatedly req))) - 它只计算 req 2 次。
  • @AntonHarald 评估元素的数量取决于传递给 apply 的 arity 函数(正如我在回答中提到的,这是因为 apply 实现了传递函数接受的元素加一)。所以(take 1 (apply (fn [& xs] xs) (repeatedly req))) 实现了两次req(take 1 (apply (fn [x & xs] (cons x xs)) (repeatedly req))) - 三次,(take 1 (apply (fn [x y & xs] (cons x (cons y xs))) (repeatedly req))) - 四次,等等。
  • 好的,我知道了。所以它是 concats [x y & zs] 加一个导致 4 个初始评估。实际上太糟糕了,我想说如果没有这个,将会有更多的情况,可以使用惰性序列的力量。
【解决方案2】:

怎么样

(take 14 
  (mapcat identity (repeatedly req)))

解释:

(defn req [] 
  (print ".") 
  (repeat 4 (rand-int 10)))

(def x 
  (take 80 (mapcat identity (repeatedly req))))
; prints .... = 4x ; this is probably some repl eagerness

; to take 80 items, 20 realizatons (of 4 items) are requrend 
(def y 
  (doall
    (take 80 (mapcat identity (repeatedly req))))) 
 ; prints ..................... = 21x 

编辑:关于这 4 个早期认识:

我认为这是由于apply,我们被mapcat 使用。 给定多个参数,它最多可以实现 4 个参数 [^clojure.lang.IFn f a b c d & args]

【讨论】:

  • 正如我所说,它要么是一个 repl-eagerness 要么是一个 clojure seq 分块 - (clojure 过去以 1 来实现,并且这种行为已经改变以实现 clojure.1.1 中的下 32 个项目,我认为),我现在正在调查确切的原因。
  • @OlegTheCat,我很确定这是由于 apply,我们由 mapcat 使用。它实现了多达 4 个 args [^clojure.lang.IFn f a b c d & args] 给定多个 - 但我可能错了。所以是的,take 0-16 将始终至少预先实现 4 个块,而 take 17 将预先实现 4 次但仅在 16 个项目之后再次实现
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-11-28
  • 2012-08-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-14
  • 1970-01-01
相关资源
最近更新 更多