Transposition
tl;dr (apply map vector v)
transposes a vector in Clojure.
Say I’ve got a vector of vectors.
[[1 2 3]
[4 5 6]]
And I want its transpose. Transposing the previous example looks like this:
[[1 4]
[2 5]
[3 6]]
One way to do that in Clojure is with the following incantation:
(apply map vector [[1 2 3] [4 5 6]])
([1 4] [2 5] [3 6])
I’ve always been delighted by that little fragment of code. If I had to come up with an explanation, perhaps it’s the terse but expressive combination of Clojure’s functional-programming primitives.
I’m going to break down the code snippet, though I fear that, like explaining why a joke is funny, a little of the magic will be lost in translation.
Map
At the core of the thing is a call to good ol’ map
.
map
takes the form (map f coll)
,
where you get a collection of calling f
on every element in coll
.
A simple example:
(map inc [1 2 3])
(2 3 4)
One interesting thing about Clojure’s map
(and maybe other Lisps as well?)
is that if you pass multiple collections,
successive elements of each will be passed into f
.
(map (fn [a b] (* a b)) [4 5 6] [7 8 9])1
(28 40 54)
Apply
The other major piece in the snippet is apply
,
a function that takes a function and a collection, and calls the function with collection as arguments.
For example:
(apply (fn [a b c] (+ a b c)) [1 2 3])2
(28 40 54)
Putting It Together
Let’s look at the whole thing again:
(apply map vector v)
.
Say v
is [[1 2 3] [4 5 6]]
.
If we break down what apply
is doing here,
it’s equivalent to this:
(map vector [1 2 3] [4 5 6])
.
That is,
we’ve wound up with the multiple-argument version of map
.
There’s not much more to the story.
vector
is a function that takes any number of arguments,
and returns a vector containing those arguments.
For example,
(vector 1 2 3)
yields [1 2 3]
.
Thus (map vector a b)
takes the first element from a
and the first element from b
,
then returns a new two-element vector of the two.
Then it proceeds to the second elements of a
and b
,
and so on.
We wind up with the transpose of the original vector.
(I would be remiss if I didn’t note that this formulation actually returns a lazy sequence of the transpose;
if I really need the transpose to be a vector,
I can use mapv
.
Again, I don’t know precisely what it is that tickles my fancy about this little piece of code. But it’s really cool to see how atomic ideas can plug together into larger molecules of functionality.
Questions? Comments? Contact me!
Tools Used
- Clojure CLI
- 1.10.1.492