Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure: Cycle through a collection while looping through another collection?

I have two collections x and y, both having different number of items. I want to loop through x and do something with a side-effect, while cycling through y. I don't want to repeat y while looping through x. Both doseq and for repeats y:

(for [x (range 5)
      y ["A" "B"]]
  [x y])

This produces ([0 "A"] [0 "B"] [1 "A"] [1 "B"] [2 "A"] [2 "B"] [3 "A"] [3 "B"] [4 "A"] [4 "B"]).

What I want is something that will produce: ([0 "A"] [1 "B"] [2 "A"] [3 "B"] [4 "A"]).

Background, I have lines from a file and core.async channels (say 5) and I want to put each line to the next channel in my collection, something like:

(defn load-data
  [file chans]
  (with-open [rdr (io/reader file)]
    (go
      (doseq [l (line-seq rdr)
              ch chans]
        (>! ch l)))))
like image 330
arnab Avatar asked Mar 17 '23 18:03

arnab


1 Answers

If you pass multiple sequences to map it steps through each of them in lock step calling the mapped function with the value from the current position in each. Stopping when one of the sequences runs out.

user> (map vector (range 5) (cycle ["A" "B"]))
([0 "A"] [1 "B"] [2 "A"] [3 "B"] [4 "A"])

In this case the sequence from (cycle ["A" "B"]) will keep producing As and Bs forever though map will stop consuming them when the sequence from (range 5) ends. each step then calls the vector function with these two arguments and adds the result to the output sequence.

and for the second example using a go-loop is a fairly standard way of fanning out an input sequence:

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)
like image 122
Arthur Ulfeldt Avatar answered May 16 '23 07:05

Arthur Ulfeldt