Clojure:在循环遍历另一个集合时循环遍历一个集合?

4
我有两个集合xy,它们的项数不同。我想循环遍历x并执行有副作用的操作,同时循环遍历y。我不想在循环x时重复y。无论是doseq还是for都会重复y
(for [x (range 5)
      y ["A" "B"]]
  [x y])

这将产生([0 "A"] [0 "B"] [1 "A"] [1 "B"] [2 "A"] [2 "B"] [3 "A"] [3 "B"] [4 "A"] [4 "B"])
我想要的是能够产生:([0 "A"] [1 "B"] [2 "A"] [3 "B"] [4 "A"])
背景:我有一个文件中的行和core.async通道(假设为5),我想把每一行放到我的集合中的下一个通道,类似于:
(defn load-data
  [file chans]
  (with-open [rdr (io/reader file)]
    (go
      (doseq [l (line-seq rdr)
              ch chans]
        (>! ch l)))))
1个回答

7
如果您将多个序列传递给map,它会锁定每个序列并调用映射函数,从每个当前位置的值开始。当其中一个序列耗尽时停止。
user> (map vector (range 5) (cycle ["A" "B"]))
([0 "A"] [1 "B"] [2 "A"] [3 "B"] [4 "A"])

在这种情况下,(cycle ["A" "B"]) 的序列将永远产生 A 和 B,虽然 map 函数在 (range 5) 的序列结束时停止消费它们。每一步都会使用这两个参数调用 vector 函数,并将结果添加到输出序列中。
对于第二个例子,使用 go-loop 是扩展输入序列的一个非常标准的方式。
user> (require '[clojure.core.async :refer [go go-loop <! <!! >!! >! chan close!]])
nil
user> (defn fanout [channels file-lines]
        (go-loop [[ch & chans] (cycle channels)
                  [line & lines] file-lines]
          (if line
            (do
              (>! ch line)
              (recur chans lines))
            (doseq [c channels]
              (close! c)))))
#'user/fanout
user> (def lines ["first" "second" "third" "fourth" "fifth"])
#'user/lines
user> (def test-chans [(chan) (chan) (chan)])  
#'user/test-chans
user> (fanout test-chans lines)
#<ManyToManyChannel clojure.core.async.impl.channels.ManyToManyChannel@3b363fc5>
user> (map <!! test-chans)
("first" "second" "third")
user> (map <!! test-chans)
("fourth" "fifth" nil)

那么,我需要获取它的结果,然后使用doseq将其放入通道中? - or9ob
在我个人看来,我喜欢将数据生成保留在它自己的函数中,这样我就可以独立地测试它,而不受移动数据的机制的影响。 - Arthur Ulfeldt
请注意,您不能将通道操作<!等放入映射函数中,因为Go块无法跨越函数边界。 - Arthur Ulfeldt
使用loop/recur代替doseq应该是接近你所需要的。我会在今天下午提供一个例子。 - Arthur Ulfeldt
非常好的例子,谢谢。我一直在使用line-seq,因为这是一个巨大的文件。这个解决方案不会变得懒惰,对吧? - or9ob
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接