London Clojurians - October 2013 Clojure Dojo
October 30, 2013
I was at the London Clojure Dojo last night, and worked on 4clojure problem #146. I'm not going to post my code - that feels like a violation of the spirit of 4clojure - but I am going to talk about a cool feature of the Clojure 'for' macro that I discovered while playing with this.
Let's say - as a different problem that shows off the same feature - that you have a map where the keys are integers, and the values are vectors of integers.
user=> (def example-data-2 {1 [2 3 4 5] 6 [7 8 9 0]}) #'user/example-data-2
Let's say that you want - for some reason - to turn this into a sequence of each key multiplied by each element of its value.
user=> (def target (list (* 1 2) (* 1 3) (* 1 4) (* 1 5) (* 6 7) (* 6 8) (* 6 9) (* 6 0))) #'user/target user=> target (2 3 4 5 42 48 54 0)
My first approach was to do a nested for loop - one to handle the outer list and one to handle the inner list. This produced a list of two lists, which between them contained the right values.
user=> (for [[x subvector] example-data-2] (for [y subvector] (* x y))) ((2 3 4 5) (42 48 54 0))
I was able to join these lists with 'mapcat identity', though a fellow dojoist pointed out that 'apply concat' also works:
user=> (mapcat identity (for [[x subvector] example-data-2] (for [y subvector] (* x y)))) (2 3 4 5 42 48 54 0) user=> (apply concat (for [[x subvector] example-data-2] (for [y subvector] (* x y)))) (2 3 4 5 42 48 54 0)
There's a cleaner solution, though. 'for' can take multiple bindings, and iterates through them together, producing a combinatorial result:
user=> (for [x [1 2 3 4] y [1 2 3 4]] [x y]) ([1 1] [1 2] [1 3] [1 4] [2 1] [2 2] [2 3] [2 4] [3 1] [3 2] [3 3] [3 4] [4 1] [4 2] [4 3] [4 4])
You can even use this to loop over something you destructured earlier within the same 'for':
user=> (for [[x subvector] example-data-2 y subvector] (* x y)) (2 3 4 5 42 48 54 0)
In this example, x initially becomes 1 and subvector becomes
[2 3 4 5]
, then y becomes each element of that in turn, so (* x y)
is
(* 1 2)
, (* 1 3)
, and so on until (* 6 0)
- matching the original definition of target
.
This kind of multiple-level iteration is a pretty cool feature - it'll be interesting to see if it's applicable to real problems, not just 4clojure