-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathAoC2021_20.py
140 lines (108 loc) Β· 4.06 KB
/
AoC2021_20.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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#! /usr/bin/env python3
#
# Advent of Code 2021 Day 20
#
from prettyprinter import register_pretty, pretty_call
from aoc import my_aocd
import aocd
DARK = '.'
LIGHT = '#'
class ImageEnhancement:
algorithm: str
data: set[tuple[int, int]]
store_dark: bool
def __init__(self, algorithm: str, data: set[tuple[int, int]]):
assert len(algorithm) == 512
self.algorithm = algorithm
self.data = data
self.store_dark = False
def _is_flicker(self) -> bool:
return self.algorithm[0] == LIGHT and self.algorithm[-1] == DARK
def _row_range(self):
rows = {row for row, _ in self.data}
return range(min(rows) - 2, max(rows) + 3)
def _col_range(self):
cols = {col for _, col in self.data}
return range(min(cols) - 2, max(cols) + 3)
def _print(self) -> None:
if not __debug__:
return
row_range = self._row_range()
col_range = self._col_range()
lines = ["".join(LIGHT if (row, col) in self.data else DARK
for col in col_range)
for row in row_range]
[print(line) for line in lines]
def _get_square_around(self, row: int, col: int) -> str:
found, not_found = ('1', '0') \
if not self._is_flicker() or not self.store_dark \
else ('0', '1')
return "".join(found
if (row + dr, col + dc) in self.data
else not_found
for dr in [-1, 0, 1]
for dc in [-1, 0, 1])
def run(self) -> None:
data2 = set[tuple[int, int]]()
row_range = self._row_range()
col_range = self._col_range()
for row in row_range:
for col in col_range:
square = self._get_square_around(row, col)
idx = int(square, 2)
assert 0 <= idx < len(self.algorithm)
light = self.algorithm[idx] == LIGHT
if ((not self._is_flicker() or self.store_dark) and light) or \
(self._is_flicker() and not self.store_dark and not light):
data2.add((row, col))
self.data = data2
self.store_dark = not self.store_dark
def get_lit_pixels(self) -> int:
return len(self.data)
@register_pretty(ImageEnhancement)
def _pretty(value, ctx):
return pretty_call(
ctx,
ImageEnhancement,
algorithm=value.algorithm,
data=value.data
)
def _parse(inputs: tuple[str]) -> int:
blocks = my_aocd.to_blocks(inputs)
algorithm = blocks[0][0]
data = {(r, c)
for r, line in enumerate(blocks[1])
for c, ch in enumerate(line)
if ch == LIGHT}
return ImageEnhancement(algorithm, data)
def _solve(ie: ImageEnhancement, cycles: int) -> int:
for i in range(cycles):
ie.run()
return ie.get_lit_pixels()
def part_1(inputs: tuple[str]) -> int:
ie = _parse(inputs)
return _solve(ie, 2)
def part_2(inputs: tuple[str]) -> int:
ie = _parse(inputs)
return _solve(ie, 50)
TEST = """\
..#.#..#####.#.#.#.###.##.....###.##.#..###.####..#####..#....#..#..##..###..######.###...####..#..#####..##..#.#####...##.#.#..#.##..#.#......#.###.######.###.####...#.##.##..#..#..#####.....#.#....###..#.##......#.....#..#..#..##..#...##.######.####.####.#.#...#.......#..#.#.#...####.##.#......#..#...##.#.##..#...##.#.##..###.#......#.#.......#.#.#.####.###.##...#.....####.#..#..#.##.#....##..#.####....##...##..#...#......#.#.......#.......##..####..#...#.#.#...##..#.#..###..#####........#..####......#..#
#..#.
#....
##..#
..#..
..###
""".splitlines()
def main() -> None:
puzzle = aocd.models.Puzzle(2021, 20)
my_aocd.print_header(puzzle.year, puzzle.day)
assert part_1(TEST) == 35
assert part_2(TEST) == 3351
inputs = my_aocd.get_input(puzzle.year, puzzle.day, 102)
result1 = part_1(inputs)
print(f"Part 1: {result1}")
result2 = part_2(inputs)
print(f"Part 2: {result2}")
my_aocd.check_results(puzzle, result1, result2)
if __name__ == '__main__':
main()