mirror of
https://github.com/Noettore/AdventOfCode.git
synced 2025-10-14 19:26: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 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) |
|
||||
|
Reference in New Issue
Block a user