In a previous post I said I would come back to explain a few somewhat opaque lines in my matrix-based solution to Advent of Code 2022, day 2. This is what I’m going to do here.
There’s three smallish things I thought it was worth pointing out. None is going to surprise veteran python coders, but they might be of interest to some.
Building matrices
So the first line I want to look at is:
B = np.array(list("012" * 3)).astype(int).reshape(3, 3)
This builds the matrix $B$ from the previous post as a numpy array. Reminder that matrix $B$ looks like this:
So this line of code is throwing together a bunch of small things that we should unpack.
First, in the middle there, we have a string "123"
,
which we then multiply by 3.
Multiplying a string by $n$ means repeating it $n$ times.
So "012" * 3
is the same as "012012012"
.
Next we turn that string into a list.
That is, we treat the string as a list of single character strings:
list("012" * 3)
is:
["0", "1", "2", "0", "1", "2", "0", "1", "2"]
.
We take that list, and turn it into a numpy array.
np.array(list("012"*3))
is a one-dimensional array of strings.
>>> np.array(list("012"*3))
array(['0', '1', '2', '0', '1', '2', '0', '1', '2'], dtype='<U1')
That dtype='<U1'
is telling us that numpy is treating the data type of the array
as unicode strings, which is not what we want.
.astype(int)
turns it into a one-dimensional array of ints
(which works because each string can be cast to an int).
>>> np.array(list("012"*3)).astype(int)
array([0, 1, 2, 0, 1, 2, 0, 1, 2])
>>> np.array(list("012"*3)).astype(int).dtype
dtype('int64')
And finally, .reshape(3,3)
reshapes the array into a 3 by 3 array.
>>> np.array(list("012" * 3)).astype(int).reshape(3, 3)
array([[0, 1, 2],
[0, 1, 2],
[0, 1, 2]])
Matching vectors
The second thing I wanted to explain is _get_vec
:
def _get_vec(in_val, values):
return (np.array(list(values)) == in_val).astype(int)
Recall that for the AoC problem, the input was a string,
with moves represented by letters – “A”, “B” or “C” for their move, “X”, “Y”, or “Z” for my move –
and what we wanted to get out, for the matrix solution
was a unit vector with a “1” in the position corresponding to the move.
That is, if their move was a “B”, we wanted to get out $(0,1,0)$.
This is precisely what the above function does:
put in “B” and “ABC” as the two arguments and it returns np.array([0,1,0])
.
So what is it doing?
Starting from the inside again, we have list(values)
which is doing the same trick we had before:
take a string, and return a list of characters.
Then we turn that list into a numpy array.
We then compare that numpy array of characters to the in_val
argument, using the ==
comparison operator.
This gives us a boolean array:
with False
where the character in that position doesn’t match in_val
and True
where we do have a match.
So np.array(list("ABC")) == "B"
is np.array([False, True, False])
.
The final step is to turn that boolean array into an array of integers:
False
maps to 0
and True
maps to 1
.
This gives us np.array([0,1,0])
as desired.
Matrix multiplication
The last thing I wanted to mention is the quickest, and possibly the most surprising.
Python has a matrix multiplication infix operator!
Or rather, there’s a PEP spec for one, and numpy implements it.
Use A @ B
to multiply matrices A
and B
together.
That’s neat, and not something I was aware of until I went looking for it.
Will I get much use out of it?
Probably not.
But it’s nice to know it’s there.