Combining lists into tuples

Here’s how you can zip up multiple lists into a single list of pairs or tuples using (map vector ).

(def letters [:a :b :c])
(def numbers [1 2 3])
(def fruits ["peach" "tomato" "orange"])

(map vector letters numbers)
; => ([:a 1] [:b 2] [:c 3])

(map vector letters fruits numbers)
; => ([:a "peach" 1] [:b "tomato" 2] [:c "orange" 3])

Cool trick, but how is this useful, you ask?

Say we’ve got 3 sales reps:

(def sales-reps ["Alice" "Bob" "Carl"])

and 8 states/territories in Australia to cover:

(def au-states ["ACT" "NSW" "NT" "Qld" "SA" "Tas" "Vic" "WA"])

We don’t really care who’s assigned where, as long as each state has someone assigned to it.

(map vector sales-reps au-states)
; => (["Alice" "ACT"] ["Bob" "NSW"] ["Carl" "NT"])

Almost. You see, (map ) bombs out as soon as any of the lists runs out (in our case sales-reps).

We’ll need to enlist the help of (cycle ).

(cycle sales-reps)
; => ("Alice" "Bob" "Carl" "Alice" "Bob" "Carl" "Alice" ...)

(take 4 (cycle sales-reps))
; => ("Alice" "Bob" "Carl" "Alice")

Let’s try again:

(def assignments
  (map vector (cycle sales-reps) au-states))
; => (["Alice" "ACT"] ["Bob" "NSW"] ["Carl" "NT"] ["Alice" "QLD"]
;     ["Bob" "SA"] ["Carl" "TAS"] ["Alice" "VIC"] ["Bob" "WA"])

Nice. This time, (cycle ) returns an infinitely cycling list, so (map ) stops when au-states runs out.

So which states were assigned to Alice?

(defn is-alice? [tuple] (= (first tuple) "Alice"))

(def alice-tuples
  (filter is-alice? assignments))
; => (["Alice" "ACT"] ["Alice" "Qld"] ["Alice" "Vic"])

(defn just-states [tuple] (second tuple))

(map just-states alice-tuples)
; => ("ACT" "Qld" "Vic")

See all the states grouped by their respective sales reps:

(def grouped-by-reps (group-by first assignments))
; => {"Alice" [["Alice" "ACT"] ["Alice" "Qld"] ["Alice" "Vic"]],
;     "Bob"   [["Bob" "NSW"] ["Bob" "SA"] ["Bob" "WA"]],
;     "Carl"  [["Carl" "NT"] ["Carl" "Tas"]]}

Clean it up a little:

(map (fn [assignment]
       [(first assignment) (map just-states (second assignment))])

; => (["Alice" ("ACT" "Qld" "Vic")]
;     ["Bob"   ("NSW" "SA" "WA")]
;     ["Carl"  ("NT" "Tas")])

With this last one, we’re mapping over grouped-by-reps, each assignment looks like this:

["Bob" [["Bob" "NSW"] ["Bob" "SA"] ["Bob" "WA"]]]

Breaking it down:

(first assignment)  ; =>  "Bob"
(second assignment) ; => [["Bob" "NSW"] ["Bob" "SA"] ["Bob" "WA"]]
(map just-states (second assignment)) ; => ("ACT" "Qld" "Vic")

; Hence
[(first assignment) (map just-states (second assignment))]
; => ["Bob" ("ACT" "Qld" "Vic")]

VoilĂ !