What I've learned about Python from Advent of Code day 13

Advent of Code puzzles are fun. I use them for learning new python features that I rarely or never use at work. Day 13 was particularly interesting.

WARNING! This article contains spoilers! If you want to solve this puzzle, stop reading, go to the advent of code page and have fun. You can back later :)

The puzzle

You can read the details on the above link, but here is a short summary:

  • We have a list of dots on a transparent paper (a foil).
  • There is a list of folding instructions: fold the paper in half along the given row or column. One half will cover the other one, and as the paper is transparent, this is some kind of a union of the two halves.

The task is to apply these folding instructions (or just one in the first part).

The learnings

Split string into variables

I learned it long ago and I use it from time to time, but it was a big wow-moment for me coming with a java background, so I think it's worth mentioning here.

You can split a string into a list with the str.split method:

coordinates = line.split(',')

It returns a list, but you can unpack it directly into variables:

x, y = line.split(',')

I used it a lot when parsing puzzle input. Of course you have to be sure about how many parts the original string has, or you risk a ValueError at runtime. The split method also has a maxsplit argument that can prevent such an error, but in my opinion input validation is a better idea in a real world scenario.

Partially applied functions

I wanted to try this for so long, I even changed my solution a bit to be able to use it.

I created two functions for the folding operations.

def fold_horizontally(fold_row: int, dots: set[tuple[int, int]]) -> set[tuple[int, int]]:
  ...

def fold_vertically(fold_col: int, dots: set[tuple[int, int]]) -> set[tuple[int, int]]:
  ...

And I parsed the fold instruction strings to a list of partially applied functions.

from functools import partial

instructions = []
while line := f.readline():
  instruction, coord = line.split('=')
  fold_func = fold_horizontally if instruction.endswith('y') else fold_vertically
  instructions.append(partial(fold_func, int(coord)))

What does it do? The partial function returns a callable object for a function and a list of arguments. The remaining arguments will be the arguments of this callable object. In my case the base function is either fold_horizontally or fold_vertically. Those functions have 2 arguments: the row or column to fold along and the list of dots. I fixed the first argument, the fold coordinate to create a partially applied function, and I put it to the instructions list. These functions have one argument (the set of dots) and they can be called like this:

dots = instructions[i](dots)

I think this can be a quite powerful tool, I should use it more frequently.

Unicode character name

The puzzle description uses # characters for the dots which is a good ASCII character for this purpose, but unicode has better options. What about a full block █ character? Or a black large circle ⬤ ? Or a hexagon ⬢ ?

You can copy-paste it into your python code, but there is an escape sequence that I like much better. This is how I used it to print a dot in the grid:

print('\N{FULL BLOCK}' if (col, row) in dots else ' ', end='')

The result looks like this:

██       █     ██  █ █   ██    ██  █  
             █         █         █     
   █       ███      ██   ███     █ █   
 ██     █ █     █   █  █ █       █     
█         █  █ █       █ █    █  █ █   
█       █      ████ █ █         █    █ 

█     ███ █  █   █   █   █     ██   █  
█    █  █ █         █            █    █
█    █ ██    █      █  █         █ █   
█    █    █  █   █    █                
█  █ █  █ █       █ █  █ █       █ █  █
██    ██  █  █ ████  █   ██ █       ██

It's cool, isn't it? :)

I really enjoyed this puzzle, I'm looking forward to my next python discoveries with Advent of Code.

31