aoc2020/day20.py

218 lines
7.6 KiB
Python

import aoclib
import math
import re
DAY = 20
TEST_SOLUTION_PART1 = 20899048083289
TEST_SOLUTION_PART2 = 273
class Tile:
def __init__(self, raw_input):
self.image_id = int(raw_input[0][5:-1])
self.image = raw_input[1:]
def getPossibleBorderList(self):
return [
self.image[0],
"".join([x[0] for x in self.image]),
"".join([x[-1] for x in self.image]),
self.image[-1],
"".join(reversed(self.image[0])),
"".join(reversed([x[0] for x in self.image])),
"".join(reversed([x[-1] for x in self.image])),
"".join(reversed(self.image[-1])),
]
def getBorderlessImage(self):
return ["".join(x for x in y[1:-1]) for y in self.image[1:-1]]
def getCurrentBorders(self):
# top right bottom left
return [
self.image[0],
"".join([x[-1] for x in self.image]),
self.image[-1],
"".join([x[0] for x in self.image])
]
def rotateRight(self):
self.image = rotateRight(self.image)
def rotateLeft(self):
self.image = rotateLeft(self.image)
def flipHorizontally(self):
self.image = flipHorizontally(self.image)
def flipVertically(self):
self.image = flipVertically(self.image)
def printImage(self):
for x in self.image:
print(x)
def rotateRight(image):
return ["".join([x[line_no] for x in reversed(image)]) for line_no in range(len(image))]
def rotateLeft(image):
return ["".join([x[line_no] for x in image]) for line_no in reversed(range(len(image)))]
def flipHorizontally(image):
return list(reversed(image))
def flipVertically(image):
return ["".join(x for x in reversed(y)) for y in image]
def getAllPossibleBorders(tile_dict):
all_possible_borders = []
for tile_id, tile in tile_dict.items():
all_possible_borders.extend(tile.getPossibleBorderList())
return all_possible_borders
def getCornerPieces(tile_dict, only_get_first_piece=False):
corner_pieces = []
all_borders = getAllPossibleBorders(tile_dict)
for tile_id, tile in tile_dict.items():
single_borders = 0
for border in tile.getPossibleBorderList():
single_borders += all_borders.count(border) - 1
if single_borders == 4: # every border shows up twice (1x straight, 1x reversed)
if only_get_first_piece:
return tile_id
corner_pieces.append(tile_id)
if len(corner_pieces) == 4:
break
return corner_pieces
def part1(test_mode=False):
my_input = aoclib.getMultiLineInputAsArray(day=DAY, test=test_mode)
tile_dict = {}
for tile_input in my_input:
tile = Tile(tile_input)
tile_dict[tile.image_id] = tile
return math.prod(getCornerPieces(tile_dict))
def part2(test_mode=False):
my_input = aoclib.getMultiLineInputAsArray(day=DAY, test=test_mode)
tile_dict = {}
for tile_input in my_input:
tile = Tile(tile_input)
tile_dict[tile.image_id] = tile
borderlen = int(math.sqrt(len(tile_dict)))
all_pieces = list(tile_dict.keys())
corner_piece = getCornerPieces(tile_dict, only_get_first_piece=True)
all_pieces.remove(corner_piece)
# first - find correct orientation of corner piece
possible_borders = getAllPossibleBorders(tile_dict)
transformations = [
tile_dict[corner_piece].flipHorizontally,
tile_dict[corner_piece].flipVertically,
tile_dict[corner_piece].flipHorizontally,
tile_dict[corner_piece].rotateRight,
tile_dict[corner_piece].flipHorizontally,
tile_dict[corner_piece].flipVertically,
tile_dict[corner_piece].flipHorizontally,
]
transformation_counter = 0
found = False
while not found:
if possible_borders.count(tile_dict[corner_piece].getCurrentBorders()[0]) == 1 \
and possible_borders.count(tile_dict[corner_piece].getCurrentBorders()[3]) == 1:
found = True
else:
transformations[transformation_counter]()
transformation_counter += 1
assert found is True
full_image = []
for y in range(borderlen):
full_image.append([])
for x in range(borderlen):
if x == 0 and y == 0:
full_image[y].append(corner_piece)
continue
# find matching piece with correct orientation for the piece to the left (or the top if y > 0 and x == 0)
if x == 0: # check against bottom border of upper piece
check_border = tile_dict[full_image[y - 1][x]].getCurrentBorders()[2]
else:
check_border = tile_dict[full_image[y][x - 1]].getCurrentBorders()[1]
for possible_tile in all_pieces:
if check_border in tile_dict[possible_tile].getPossibleBorderList():
# found my piece ... now orientate it correctly
transformations = [
tile_dict[possible_tile].flipHorizontally,
tile_dict[possible_tile].flipVertically,
tile_dict[possible_tile].flipHorizontally,
tile_dict[possible_tile].rotateRight,
tile_dict[possible_tile].flipHorizontally,
tile_dict[possible_tile].flipVertically,
tile_dict[possible_tile].flipHorizontally,
]
transformation_counter = 0
oriented = False
while not oriented:
if (x == 0 and tile_dict[possible_tile].getCurrentBorders()[0] == check_border) \
or (x != 0 and tile_dict[possible_tile].getCurrentBorders()[3] == check_border):
oriented = True
full_image[y].append(possible_tile)
all_pieces.remove(possible_tile)
else:
transformations[transformation_counter]()
transformation_counter += 1
# now that we have the full picture, assemble the borderless full image
borderless_image = []
full_hash_count = 0
for y in full_image:
for image_line in range(8):
borderless_image.append("".join([tile_dict[x].getBorderlessImage()[image_line] for x in y]))
full_hash_count += borderless_image[-1].count('#')
# and finally scan for our sea monster
# seamonster_line_1 = re.compile(r"..................#.")
seamonster_line_2 = re.compile(r"#....##....##....###")
seamonster_line_3 = re.compile(r".#..#..#..#..#..#...")
seamonster_hashcount = 15 # amount of '#' in the sea monster
seamonster_count = 0
transformations = [
flipHorizontally,
flipVertically,
flipHorizontally,
rotateRight,
flipHorizontally,
flipVertically,
flipHorizontally,
]
transformation_counter = 0
while seamonster_count == 0:
for x in range(len(borderless_image) - 2):
if s_2 := re.split(seamonster_line_2, borderless_image[x+1]):
if s_3 := re.split(seamonster_line_3, borderless_image[x+2]):
# yes, this is somewhat optimistic, but it results in the correct answer
if len(s_2) > 1 and len(s_2) == len(s_3):
seamonster_count += len(s_2) - 1
if seamonster_count == 0:
borderless_image = transformations[transformation_counter](borderless_image)
transformation_counter += 1
return full_hash_count - seamonster_count * seamonster_hashcount