Search code examples
clojureclojurescript

How do I merge the elements of two collections while cycling the first in Clojure?


How would one elegantly take these two inputs:

(def foo [:a 1 :a 1 :a 2])

(def bar [{:hi "there 1"}{:hi "there 2"}{:hi "there 3"}{:hi "there 4"}{:h1 "there 5"}])

and get:

[{:hi "there 1" :a 1}{:hi "there 2" :a 1}{:hi "there 3" :a 2}{:hi "there 4" :a 1}{:hi "there 5" :a 1}]

The first collection cycles at the point the second collection reaches the same number of elements. It would be fine for the first collection to be any of these as it's going to be hard coded:

(def foo [{:a 1} {:a 1} {:a 2}])

(def foo [[:a 1] [:a 1] [:a 2]])

(def foo [1 1 2])

There may be another data structure that would be even better. The 1 1 2 is deliberate as it's not 1 2 3 which would allow range or something like that.

Cycling through the first collection is easy... I'm not sure how to advance through the second collection at the same time. But my approach may not be right in the first place.

As usual, I tend toward weird nested imitations of imperative code but I know there's a better way!


Solution

  • Here's one way to do it:

    You can take the values from foo, cycle through them and partition them in groups of 2 at a time. There's a little secret of vectors of size 2, which is that they can work as a little map (1 key/value pair).

    Once we have two collections of maps, we can merge them together. One collection is infinite but that's OK, map will compute only the values until one collection runs out of elements. mapv is the same as map but it returns a vector instead.

    (def foo [:a 1 :a 1 :a 2])
    
    (def bar [{:hi "there 1"}{:hi "there 2"}{:hi "there 3"}{:hi "there 4"}{:h1 "there 5"}])
    
    (defn cycle-and-zip [xs maps]
      (let [xs-pairs (->> xs cycle (partition 2) (map vec))]
        (mapv merge maps xs-pairs)))
    
    (cycle-and-zip foo bar)
    ;; => [{:hi "there 1", :a 1} {:hi "there 2", :a 1} {:hi "there 3", :a 2} {:hi "there 4", :a 1} {:h1 "there 5", :a 1}]
    

    Update: replaced map with mapv so the output is actually a vector.