-
Notifications
You must be signed in to change notification settings - Fork 1
/
day17.py
103 lines (76 loc) · 3.2 KB
/
day17.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
from collections import defaultdict
from itertools import product
from typing import Dict, Iterable, List, Tuple
from utils import elapsed_time, print_results
Point = Tuple[int, ...]
Direction = Tuple[int, ...]
Grid = Dict[Point, str]
ACTIVE = '#'
INACTIVE = '.'
def initialize_nd_grid(filename: str,
num_dimensions: int = 3) -> Tuple[Grid, List[Direction]]:
grid = defaultdict(lambda: INACTIVE)
extra_dimensions = (num_dimensions - 2) * (0,)
with open(filename) as f:
for y, line in enumerate(f):
for x, cube_state in enumerate(line.strip()):
grid[(x, y) + extra_dimensions] = cube_state
directions = make_directions(num_dimensions)
expanded_grid = expand_initial_grid(grid, directions)
return expanded_grid, directions
def make_directions(num_dimensions: int = 3) -> List[Point]:
directions = list(product(*[(-1, 0, 1) for _ in range(num_dimensions)]))
directions.remove(num_dimensions * (0,))
return directions
def expand_initial_grid(grid: Grid, directions: List[Direction]) -> Grid:
initial_points = list(grid.keys())
for point in initial_points:
# visit neighbors to expand the grid
neighbor_states = list(grid[n]
for n in neighboring_points(directions, point))
return grid
def neighboring_points(directions: List[Direction],
point: Point) -> Iterable[Point]:
for direction in directions:
yield tuple(sum(pair) for pair in zip(point, direction))
def count_active_neighbors(grid: Grid,
directions: List[Direction],
point: Point) -> int:
# Grid will expand to include neighbors as they get checked
return sum(grid[point] == ACTIVE
for point in neighboring_points(directions, point))
def next_cube_state(grid: Grid,
directions: List[Direction],
point: Point) -> str:
active_neighbors = count_active_neighbors(grid, directions, point)
cube_state = grid[point]
if active_neighbors == 3:
return ACTIVE
elif active_neighbors == 2 and cube_state == ACTIVE:
return ACTIVE
else:
return INACTIVE
def simulate_cycle(grid: Grid,
directions: List[Direction]) -> Grid:
points = list(grid.keys())
next_grid = {point: next_cube_state(grid, directions, point)
for point in points}
grid.update(next_grid)
return grid
def count_active_cubes(grid: Grid) -> int:
return sum(cube_state == ACTIVE for cube_state in grid.values())
def simulate(filename: str, num_dimensions: int, num_cycles: int) -> Grid:
grid, directions = initialize_nd_grid(filename, num_dimensions)
for _ in range(num_cycles):
grid = simulate_cycle(grid, directions)
return grid
def part1(filename: str) -> int:
grid = simulate(filename, 3, 6)
return count_active_cubes(grid)
def part2(filename: str) -> int:
grid = simulate(filename, 4, 6)
return count_active_cubes(grid)
if __name__ == '__main__':
puzzle_input = 'input_day17.txt'
print_results(elapsed_time(part1, puzzle_input), # 286
elapsed_time(part2, puzzle_input)) # 960