About 15 days ago, I gave up on Advent of Code for this year. Partly because I wanted to focus on finishing a comp sci certificate and prepare for a data science bootcamp. But also because they were getting hard. Now that it's Christmas, I'm letting myself look at solutions.
SPOILER ALERT: This post includes solutions to Advent of Code 2019 Day 02.
To add some learning value to this, I'll be writing some blog posts on lessons I learn from those solutions. I'll start with Day 02. I actually solved this one myself, so I'll be comparing aspects of my solution with that by Joel Grus.
One quickly apparent difference is that he used tuple unpacking to simplify a portion of the code.
Tuple unnpacking works like this:
In [1]: a, b, c = 3, 4, 5
In [2]: a
Out[2]: 3
In [3]: b
Out[3]: 4
In [4]: c
Out[4]: 5
As you can see, the code in line In [1]:
assigns 3
to a
, 4
to b
, and 5
to c
. But why did that work?
Let's back up. A tuple is a fundamental data structure in python. The syntax to create a tuple is like this:
In [7]: my_tuple = 3, 4, 5, 'foo', 'bar'
In [8]: my_tuple
Out[8]: (3, 4, 5, 'foo', 'bar')
Note also that you will some times see things like my_tuple = (3, 4, 5, 'foo', 'bar')
, but these parentheses are not strictly required.
Tuples in python are like lists, except that they are immutable; once created they cannot be changed.
Like lists, tuples can contain different data types; in this case we saw int
and str
.
On the line a, b, c = 3, 4, 5
, a few steps happen in the background. (3, 4, 5)
gets packaged as a tuple. Then python attempts to store this tuple to the variable on the left side of the equation. Finding not one variable, but the tuple of variables (a, b, c)
, it unpacks both (3, 4, 5)
and (a, b, c)
, matching up the numberes and variables in order.
Note that this process is also called multiple assignment because, well, it assigns multiple variables in one line.
What is this useful for? Here's one use case from Joel's solution.
But first I'll show a (out of context) portion of my (rather ugly) solution. Don't worry too much about what is happening, just note the convoluted syntax. That's a lot of info packed onto one line!
elif tape[current_index] == 1:
tape[tape[current_index + 3]] = tape[tape[current_index + 1]] + tape[tape[current_index + 2]]
That's a long, intricate line. It would be nice if we could shorten it.
Joel does this by specifying opcode = program[pos]
, loc1 = program[pos + 1]
, loc2 = program[pos + 2]
, loc3 = program[pos + 3]
, leading to code that looks like this:
if opcode == 1:
program[loc3] = program[loc1] + program[loc2]
The code to assing opcode
, loc1
, loc2
and loc31
could go on four separate lines, but here's where tuple unpacking becomes relevant. Joel does all of those assignments in one line.
opcode, loc1, loc2, loc3 = program[pos], program[pos + 1], program[pos + 2], program[pos + 3]
That code (on the second line of this sample) creates a tuple of integers on the right ride and passes it to the left side, where it gets unpacked into opcode
, loc1
, loc2
, and loc3
.
What's the advantage? It saves 3 lines of code. Maybe that's a small gain for a whole blog post, but tuple unpacking gets used in a wide variety of situations in Python.
And there's tuple unpacking applied to assignment, all wrapped up for Christmas!
Top comments (4)
I don't like this sort of thing for assignments. I think readability suffers too much.
It makes it harder to see at a glance that
loc2
andprogram[pos + 2]
are the matching things where the assignment is happening. It gets worse when the names are long (like in this example) and should be wrapped across the next line.It also makes it harder to merge changes if things aren't kept to one assignment per line.
Interesting point. Would you reccomend doing the assignment on four lines? I do see the readability advantage of that.
As I look back on this, assigning the loc variables does more for readability then the tuple assignment does.
I think it's worth adding that this is something Joel Grus hacked to gether, so I don't know whether he would write code this way in all situations.
Given the data is in a list, and sequential, it would be clearer to just directly unpack the list, rather than using an intermediate tuple:
opcode, loc1, loc2, loc3 = program[pos:pos+4]
I think that's readable. But if there isn't an existing relationship between the values, assigning across multiple lines is definitely better for the reasons Ben suggested.
Thanks.
That's clever, and I agree it's readable.