mirror of
https://github.com/Noettore/AdventOfCode.git
synced 2025-10-15 03:36:39 +02:00
AoC 2020: day20
Signed-off-by: Ettore Dreucci <ettore.dreucci@gmail.com>
This commit is contained in:
1728
2020-python/inputs/day_20
Normal file
1728
2020-python/inputs/day_20
Normal file
File diff suppressed because it is too large
Load Diff
306
2020-python/solutions/day_20.py
Normal file
306
2020-python/solutions/day_20.py
Normal 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()
|
@@ -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 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 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 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 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 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) |
|
| [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 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 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 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 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 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) |
|
| [Day 23](https://adventofcode.com/2020/day/23) | [14.754s](./2020-python/solutions/day_23.py) | [3.572s](./2020-python/solutions/day_23.py) |
|
||||||
|
Reference in New Issue
Block a user