# Mailund on the Internet

On Writing, Science, Programming and more

# Advent of Code 2020 — day 24 remade

I wanted to get back to yesterday’s puzzle, and I have a little time before I need to run off…

The use of findall() I refer to in the tweet is this. You can split the input using a regular expression, and then map each input code to a direction. If you combine that with a reduce() you get a very succinct parser:

f = open('/Users/mailund/Projects/adventofcode/2020/24/input.txt')

DIRECTIONS = {
'ne':  1 + 1j,
'e':   2,
'se':  1 - 1j,
'sw': -1 - 1j,
'w':  -2,
'nw': -1 + 1j
}

import re
from functools import reduce

def tile_location(tile):
return reduce(
lambda x, y: x + DIRECTIONS[y],
re.findall(r'(ne|e|se|sw|w|nw)', tile),
0
)


That piece I am shamelessly stealing. It is much nicer than my original code.

To continue solving Puzzle #1 (again), to compute the black tiles, we need the tiles that have been flipped an odd number of times. The way I used a set earlier is smarter than the code below—it is a little easier to read and a lot faster—but this was a fun way to do it:

from collections import Counter

def flip_tiles(tiles):
counts = Counter(tiles)
return { tile for tile in counts if counts[tile] % 2 }


And that is all it takes to solve Puzzle #1:

tiles = flip_tiles(map(tile_location, tiles))
print(f"Puzzle #1: {len(tiles)}")


For Puzzle #2, I had a class for tracking the floor, but since I was only keeping track of the black tiles anyway, I might as well solve everything with set operations. With a few helper functions, we can navigate the floor from the black tiles:

def neighbours(tile):
return { tile + n for n in DIRECTIONS.values() }
def black_neighbours(tile, black):
return len(black & neighbours(tile))

def black_tiles(tiles):
return tiles
def white_tiles(tiles):
return set.union( *({ x for x in neighbours(tile) if x not in tiles }
for tile in tiles) )


When updating the floor, there are black tiles we need to remove and white tiles we need to add (by flipping them to black), and it is a simple case of mapping through the black and white tiles to compute a set of tiles to remove and a set of tiles to add:

def update(tiles):
to_remove = {
b for b in black_tiles(tiles)
if not (1 <= black_neighbours(b, tiles) <= 2)
}
w for w in white_tiles(tiles)
if black_neighbours(w, tiles) == 2
}
tiles.difference_update(to_remove)

def evolve(tiles, n):
for _ in range(n):
update(tiles)
return tiles


And that solves Puzzle #2.

print(f"Puzzle #2: {len(evolve(tiles, 100))}")