AoC 2020: day20

Signed-off-by: Ettore Dreucci <ettore.dreucci@gmail.com>
This commit is contained in:
2020-12-25 23:40:39 +01:00
parent f9d0168c2b
commit ef375e534c
3 changed files with 2036 additions and 2 deletions

1728
2020-python/inputs/day_20 Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,306 @@
"""AOC 2020 Day 20"""
import pathlib
import time
import collections
import operator
import itertools
TEST_INPUT = """Tile 2311:
..##.#..#.
##..#.....
#...##..#.
####.#...#
##.##.###.
##...#.###
.#.#.#..##
..#....#..
###...#.#.
..###..###
Tile 1951:
#.##...##.
#.####...#
.....#..##
#...######
.##.#....#
.###.#####
###.##.##.
.###....#.
..#.#..#.#
#...##.#..
Tile 1171:
####...##.
#..##.#..#
##.#..#.#.
.###.####.
..###.####
.##....##.
.#...####.
#.##.####.
####..#...
.....##...
Tile 1427:
###.##.#..
.#..#.##..
.#.##.#..#
#.#.#.##.#
....#...##
...##..##.
...#.#####
.#.####.#.
..#..###.#
..##.#..#.
Tile 1489:
##.#.#....
..##...#..
.##..##...
..#...#...
#####...#.
#..#.#.#.#
...#.#.#..
##.#...##.
..##.##.##
###.##.#..
Tile 2473:
#....####.
#..#.##...
#.##..#...
######.#.#
.#...#.#.#
.#########
.###.#..#.
########.#
##...##.#.
..###.#.#.
Tile 2971:
..#.#....#
#...###...
#.#.###...
##.##..#..
.#####..##
.#..####.#
#..#.#..#.
..####.###
..#.#.###.
...#.#.#.#
Tile 2729:
...#.#.#.#
####.#....
..#.#.....
....#..#.#
.##..##.#.
.#.####...
####.#.#..
##.####...
##..#.##..
#.##...##.
Tile 3079:
#.#.#####.
.#..######
..#.......
######....
####.#..#.
.#...#.##.
#.#####.##
..#.###...
..#.......
..#.###..."""
MONSTER_PATTERN = (
' # ',
'# ## ## ###',
' # # # # # # '
)
def read_input(input_path: str) -> str:
"""take input file path and return a str with the file's content"""
with open(input_path, 'r') as input_file:
input_data = input_file.read().strip()
return input_data
def extract(input_data: str) -> dict:
"""take input data and return the appropriate data structure"""
tiles = dict()
for tile in input_data.split('\n\n'):
parts = tile.split('\n')
tile_id = int(parts[0][5:-1])
tiles[tile_id] = parts[1:]
return tiles
def edge(matrix: list, side: str) -> str:
"""return a side of a matrix"""
if side == 'n':
return matrix[0]
if side == 's':
return matrix[-1]
if side == 'e':
return ''.join(map(operator.itemgetter(-1), matrix))
return ''.join(map(operator.itemgetter(0), matrix))
def get_tiles_corners(tiles: dict) -> dict:
"""return the corners of a set of tiles"""
matching_sides = collections.defaultdict(str)
corners = {}
for id_tile1, id_tile2 in itertools.combinations(tiles, 2):
tile1, tile2 = tiles[id_tile1], tiles[id_tile2]
for side_a in 'nsew':
for side_b in 'nsew':
edge_a, edge_b = edge(tile1, side_a), edge(tile2, side_b)
if edge_a == edge_b or edge_a == edge_b[::-1]:
matching_sides[id_tile1] += side_a
matching_sides[id_tile2] += side_b
for tid, sides in matching_sides.items():
if len(sides) == 2:
corners[tid] = sides
assert len(corners) == 4
return corners
def rotate90(matrix: list) -> tuple:
"""return the matrix rotated by 90"""
return tuple(''.join(column)[::-1] for column in zip(*matrix))
def possible_orientations(matrix: list) -> list:
"""return all possible orientations of a given matrix"""
orientations = [matrix]
for _ in range(3):
matrix = rotate90(matrix)
orientations.append(matrix)
return orientations
def possible_arrangements(matrix: list) -> list:
"""return all possible arrangements of a given matrix"""
arrangements = possible_orientations(matrix)
arrangements.extend(possible_orientations(matrix[::-1]))
return arrangements
def strip_edges(matrix: list) -> list:
"""return a matrix without it's edges"""
return [row[1:-1] for row in matrix[1:-1]]
def matching_tile(tile: list, tiles: dict, side_a: str, side_b: str) -> list:
"""return the adjacent tile of a given one"""
edge_a = edge(tile, side_a)
for other_id, other_tile in tiles.items():
if tile is other_tile:
continue
for other_arr in possible_arrangements(other_tile):
if edge_a == edge(other_arr, side_b):
tiles.pop(other_id)
return other_arr
def matching_row(prev: list, tiles: dict, tiles_per_row: int) -> list:
"""return matching tiles in the row of a given one"""
matching_tiles = [prev]
tile = prev
for _ in range(tiles_per_row - 1):
tile = matching_tile(tile, tiles, 'e', 'w')
matching_tiles.append(tile)
return matching_tiles
def build_image(top_left_tile: list, tiles: dict, image_dimension: int) -> list:
"""build an image of a set of tiles"""
first = top_left_tile
image = []
while 1:
image_row = matching_row(first, tiles, image_dimension)
image_row = map(strip_edges, image_row)
image.extend(map(''.join, zip(*image_row)))
if not tiles:
break
first = matching_tile(first, tiles, 's', 'n')
return image
def count_pattern(image: list, pattern: tuple) -> int:
"""return the number of times a given pattern appears in an image"""
pattern_h, pattern_w = len(pattern), len(pattern[0])
image_sz = len(image)
deltas = []
for row_index, row in enumerate(pattern):
for cell_index, cell in enumerate(row):
if cell == '#':
deltas.append((row_index, cell_index))
for img in possible_arrangements(image):
appearances = 0
for row_index in range(image_sz - pattern_h):
for cell_index in range(image_sz - pattern_w):
if all(img[row_index + dr][cell_index + dc] == '#' for dr, dc in deltas):
appearances += 1
if appearances != 0:
return appearances
def part1(entries: dict) -> int:
"""part1 solver"""
corners = get_tiles_corners(entries)
corners_prod = 1
for tile_id in corners:
corners_prod *= tile_id
return corners_prod
def part2(entries: dict) -> int:
"""part2 solver"""
corners = get_tiles_corners(entries)
top_left_id, matching_sides = corners.popitem()
top_left = entries[top_left_id]
if matching_sides in ('ne', 'en'):
top_left = rotate90(top_left)
elif matching_sides in ('nw', 'wn'):
top_left = rotate90(rotate90(top_left))
elif matching_sides in ('sw', 'ws'):
top_left = rotate90(rotate90(rotate90(top_left)))
image_dimension = int(len(entries) ** 0.5)
entries.pop(top_left_id)
image = build_image(top_left, entries, image_dimension)
monster_cells = sum(row.count('#') for row in MONSTER_PATTERN)
water_cells = sum(row.count('#') for row in image)
n_monsters = count_pattern(image, MONSTER_PATTERN)
return water_cells - n_monsters * monster_cells
def test_input_day_20():
"""pytest testing function"""
entries = extract(TEST_INPUT)
assert part1(entries) == 20899048083289
assert part2(entries) == 273
def test_bench_day_20(benchmark):
"""pytest-benchmark function"""
benchmark(main)
def main():
"""main function"""
input_path = str(pathlib.Path(__file__).resolve().parent.parent) + "/inputs/" + str(pathlib.Path(__file__).stem)
start_time = time.time()
input_data = read_input(input_path)
entries = extract(input_data)
print("Part 1: %d" % part1(entries))
print("Part 2: %d" % part2(entries))
end_time = time.time()
print("Execution time: %f" % (end_time-start_time))
if __name__ == "__main__":
main()

View File

@@ -19,7 +19,7 @@
| [Day 7](https://adventofcode.com/2020/day/7) | [1.689ms](./2020-python/solutions/day_07.py) | [1.565ms](./2020-python/solutions/day_07.py) |
| [Day 8](https://adventofcode.com/2020/day/8) | [6.313ms](./2020-python/solutions/day_08.py) | [794.417µs](./2020-python/solutions/day_08.py) |
| [Day 9](https://adventofcode.com/2020/day/9) | [7.220ms](./2020-python/solutions/day_09.py) | [4.783ms](./2020-python/solutions/day_09.py) |
| [Day 10](https://adventofcode.com/2020/day/10) | [139.110us](./2020-python/solutions/day_10.py) | [98.594µs](./2020-python/solutions/day_10.py) |
| [Day 10](https://adventofcode.com/2020/day/10) | [139.110µs](./2020-python/solutions/day_10.py) | [98.594µs](./2020-python/solutions/day_10.py) |
| [Day 11](https://adventofcode.com/2020/day/11) | [2.879s](./2020-python/solutions/day_11.py) | [419.447ms](./2020-python/solutions/day_11.py) |
| [Day 12](https://adventofcode.com/2020/day/12) | [746.181µs](./2020-python/solutions/day_12.py) | [211.216µs](./2020-python/solutions/day_12.py) |
| [Day 13](https://adventofcode.com/2020/day/13) | [122.107µs](./2020-python/solutions/day_13.py) | [100.671µs](./2020-python/solutions/day_13.py) |
@@ -29,7 +29,7 @@
| [Day 17](https://adventofcode.com/2020/day/17) | [743.804ms](./2020-python/solutions/day_17.py) | [836.255ms](./2020-python/solutions/day_17.py) |
| [Day 18](https://adventofcode.com/2020/day/18) | [10.263ms](./2020-python/solutions/day_18.py) | [2.053ms](./2020-python/solutions/day_18.py) |
| [Day 19](https://adventofcode.com/2020/day/19) | [145.980ms](./2020-python/solutions/day_19.py) | [259.030ms](./2020-python/solutions/day_19.py) |
| [Day 20](https://adventofcode.com/2020/day/19) | | |
| [Day 20](https://adventofcode.com/2020/day/19) | [513.955ms](./2020-python/solutions/day_20.py) | [316.973ms](./2020-python/solutions/day_20.py) |
| [Day 21](https://adventofcode.com/2020/day/21) | [2.177ms](./2020-python/solutions/day_21.py) | [1.578ms](./2020-python/solutions/day_21.py) |
| [Day 22](https://adventofcode.com/2020/day/22) | [1.516s](./2020-python/solutions/day_22.py) | [1.353s](./2020-python/solutions/day_22.py) |
| [Day 23](https://adventofcode.com/2020/day/23) | [14.754s](./2020-python/solutions/day_23.py) | [3.572s](./2020-python/solutions/day_23.py) |