Advent of Code 2020 day 24
Advent of Code 2020 day 24. Two fun days in a row! Kinda wish I went a bit more object-orientated with this one - the result looks a bit messy and “scripty”.
I vaguely remembered a previous Advent of Code puzzle that used a hexagonal grid, and after a little poke around I used the cube coordinates system described by Red Blob Games.
Spent a while wondering why I wasn’t getting the correct answers for the part two examples, before I realised that I’d need to somehow cater for tiles beyond those I’d already looked at, as the puzzle clearly stated that they exist and default to white. pad_floor
was therefore born.
from pathlib import Path
import re
import unittest
DIRECTIONS = {
'e': [1, -1, 0],
'se': [0, -1, 1],
'sw': [-1, 0, 1],
'w': [-1, 1, 0],
'nw': [0, 1, -1],
'ne': [1, 0, -1]
}
def get_raw_input():
return (Path(__file__).parent / 'day_24_input.txt').read_text()
def parse_raw_input(raw_input):
regexp = re.compile(r'e|se|sw|w|nw|ne')
return [
[direction for direction in re.findall(regexp, line.strip())]
for line in raw_input.strip().splitlines()
]
def build_floor(parsed_input):
tiles = {}
# 0/None: white, 1: black
for line in parsed_input:
x, y, z = 0, 0, 0
for direction in line:
x += DIRECTIONS[direction][0]
y += DIRECTIONS[direction][1]
z += DIRECTIONS[direction][2]
if tiles.get((x, y, z), 0) == 0:
tiles[(x, y, z)] = 1
else:
tiles[(x, y, z)] = 0
return tiles
def pad_floor(floor):
new_tiles = {}
for tile in floor:
for adjacent_direction in DIRECTIONS.values():
k = (
tile[0] + adjacent_direction[0],
tile[1] + adjacent_direction[1],
tile[2] + adjacent_direction[2]
)
if k not in floor:
new_tiles[k] = 0
floor.update(new_tiles)
return floor
def part_one(parsed_input):
return len([tile for tile in build_floor(parsed_input).values() if tile == 1])
def adjacent_tile_colours(floor, tile):
x, y, z = tile
return [
floor.get((x + 1, y - 1, z), 0),
floor.get((x, y - 1, z + 1), 0),
floor.get((x - 1, y, z + 1), 0),
floor.get((x - 1, y + 1, z), 0),
floor.get((x, y + 1, z - 1), 0),
floor.get((x + 1, y, z - 1), 0)
]
def part_two(parsed_input):
floor = build_floor(parsed_input)
for day in range(100):
floor = pad_floor(floor)
changes = {}
for position, colour in floor.items():
adjacent_black_tiles = len([
adjacent_colour
for adjacent_colour
in adjacent_tile_colours(floor, position)
if adjacent_colour == 1
])
if colour == 1 and (adjacent_black_tiles == 0 or adjacent_black_tiles > 2):
changes[position] = 0
elif colour == 0 and adjacent_black_tiles == 2:
changes[position] = 1
floor.update(changes)
return len([tile for tile in floor.values() if tile == 1])
class TestExamples(unittest.TestCase):
def setUp(self):
self.example_input = """sesenwnenenewseeswwswswwnenewsewsw
neeenesenwnwwswnenewnwwsewnenwseswesw
seswneswswsenwwnwse
nwnwneseeswswnenewneswwnewseswneseene
swweswneswnenwsewnwneneseenw
eesenwseswswnenwswnwnwsewwnwsene
sewnenenenesenwsewnenwwwse
wenwwweseeeweswwwnwwe
wsweesenenewnwwnwsenewsenwwsesesenwne
neeswseenwwswnwswswnw
nenwswwsewswnenenewsenwsenwnesesenew
enewnwewneswsewnwswenweswnenwsenwsw
sweneswneswneneenwnewenewwneswswnese
swwesenesewenwneswnwwneseswwne
enesenwswwswneneswsenwnewswseenwsese
wnwnesenesenenwwnenwsewesewsesesew
nenewswnwewswnenesenwnesewesw
eneswnwswnwsenenwnwnwwseeswneewsenese
neswnwewnwnwseenwseesewsenwsweewe
wseweeenwnesenwwwswnew"""
def test_part_one_example(self):
self.assertEqual(part_one(parse_raw_input(self.example_input)), 10)
def test_part_two_example(self):
self.assertEqual(part_two(parse_raw_input(self.example_input)), 2208)
class TestPuzzleInput(unittest.TestCase):
def test_part_one(self):
self.assertEqual(part_one(parse_raw_input(get_raw_input())), 465)
if __name__ == '__main__':
print(f"Part one: {part_one(parse_raw_input(get_raw_input()))}")
print(f"Part two: {part_two(parse_raw_input(get_raw_input()))}")