python(day20): Optimize and tidy up

This commit is contained in:
Sijmen 2020-12-24 17:48:55 +01:00
parent c1c649ed05
commit 6ff43c5335
Signed by: vijfhoek
GPG Key ID: DAF7821E067D9C48
1 changed files with 151 additions and 183 deletions

View File

@ -2,243 +2,211 @@ import fileinput
from pprint import pprint from pprint import pprint
import itertools import itertools
from copy import deepcopy from copy import deepcopy
from typing import List, Tuple, Optional, Dict
N = 10 N = 10
SNEK_POSITIONS = (
(0, 0),
(1, 1),
(4, 1),
(5, 0),
(6, 0),
(7, 1),
(10, 1),
(11, 0),
(12, 0),
(13, 1),
(16, 1),
(17, 0),
(18, 0),
(18, -1),
(19, 0),
)
# Tile = List[List[bool]]
# Parse the input Position = Tuple[int, int]
# Extremes = Tuple[int, int, int, int]
current_tile = None
tiles = []
for line in fileinput.input():
line = line.strip()
if not line:
continue
if line.startswith("Tile "):
if current_tile:
assert len(current_tile) == N
tiles.append((current_id, current_tile))
current_id = int(line[5:-1])
current_tile = []
else:
assert len(line) == N
current_tile.append(list(line))
assert len(current_tile) == N
tiles.append((current_id, current_tile))
def aligns_right(left, right): def parse() -> List[Tuple[int, Tile]]:
return all(left[y][-1] == right[y][0] for y in range(len(left))) current_tile = None
tiles = []
for line in fileinput.input():
line = line.strip()
if not line:
continue
if line.startswith("Tile "):
if current_tile:
assert len(current_tile) == N
tiles.append((current_id, current_tile))
current_id = int(line[5:-1])
current_tile = []
else:
assert len(line) == N
current_tile.append([c == "#" for c in line])
assert len(current_tile) == N
tiles.append((current_id, current_tile))
return tiles
def aligns_bottom(top, bottom): def aligns_right(left: Tile, right: Tile) -> bool:
return all(top[-1][x] == bottom[0][x] for x in range(len(top))) return all(left_row[-1] == right_row[0] for (left_row, right_row) in zip(left, right))
def aligns(apos, b): def aligns_bottom(top: Tile, bottom: Tile) -> bool:
ax, ay = apos return top[-1] == bottom[0]
_, a = kek[(ax, ay)]
if aligns_right(a, b):
return 1, 0 def aligns(a: Tile, b: Tile) -> Optional[Position]:
if aligns_right(b, a):
return -1, 0
if aligns_bottom(a, b): if aligns_bottom(a, b):
return 0, 1 return 0, 1
if aligns_bottom(b, a): if aligns_bottom(b, a):
return 0, -1 return 0, -1
if aligns_right(a, b):
return 1, 0
if aligns_right(b, a):
return -1, 0
return None return None
def rotate(a): def rotate(tile: Tile) -> Tile:
output = deepcopy(a) output = list(reversed(tile))
for y in range(len(a)): for y in range(len(tile)):
for x in range(len(a)): for x in range(y):
output[-1 - x][y] = a[y][x] output[y][x], output[x][y] = output[x][y], output[y][x]
return output return output
def flip(a): def flip(a: Tile) -> Tile:
output = [None] * len(a) return list(reversed(a))
for y in range(len(a)):
output[y] = list(reversed(a[y]))
return output
def rotate_align(a_pos, b): def rotate_align(a: Tile, b: Tile) -> Optional[Tuple[Tile, Position]]:
""" """
Rotates and flips a and checks if it aligns for every possible orientation. Rotates and flips a and checks if it aligns for every possible orientation.
""" """
if d := aligns(a_pos, b): for _ in range(4):
return b, d if pos := aligns(a, b):
bf = flip(b) return b, pos
if d := aligns(a_pos, bf): bf = flip(b)
return bf, d
b = rotate(b) if pos := aligns(a, bf):
if d := aligns(a_pos, b): return bf, pos
return b, d b = rotate(b)
bf = flip(b) return None
if d := aligns(a_pos, bf):
return bf, d
b = rotate(b)
if d := aligns(a_pos, b):
return b, d
bf = flip(b)
if d := aligns(a_pos, bf):
return bf, d
b = rotate(b)
if d := aligns(a_pos, b):
return b, d
bf = flip(b)
if d := aligns(a_pos, bf):
return bf, d
return None, None
positions = {tiles[0][0]: ((0, 0), tiles[0][1])} def part1(
kek = {(0, 0): tiles[0]} tiles: List[Tuple[int, Tile]]
) -> Tuple[Dict[Position, Tuple[int, Tile]], Extremes]:
tile_positions = {tiles[0][0]: (0, 0)}
position_tiles = {(0, 0): tiles[0]}
while len(positions) != len(tiles): while len(tile_positions) != len(tiles):
for a_id, _ in tiles: for a_id, _ in tiles:
((a_x, a_y), a_tile) = positions.get(a_id, ((None, None), None)) try:
if not a_tile: (a_x, a_y) = a_pos = tile_positions[a_id]
continue _, a_tile = position_tiles[a_pos]
except KeyError:
for b_id, b_tile in tiles:
if b_id in positions or a_id == b_id:
continue continue
transformed, dpos = rotate_align((a_x, a_y), b_tile) for b_id, b_tile in tiles:
if dpos is not None: if b_id in tile_positions or a_id == b_id:
dx, dy = dpos continue
b_x = a_x + dx
b_y = a_y + dy
positions[b_id] = (b_x, b_y), transformed
kek[(b_x, b_y)] = b_id, transformed
min_y = min(y for ((_, y), _) in positions.values()) aligned = rotate_align(a_tile, b_tile)
max_y = max(y for ((_, y), _) in positions.values()) if aligned is not None:
min_x = min(x for ((x, _), _) in positions.values()) transformed, b_pos = aligned
max_x = max(x for ((x, _), _) in positions.values()) dx, dy = b_pos
b_x = a_x + dx
b_y = a_y + dy
tile_positions[b_id] = (b_x, b_y)
position_tiles[(b_x, b_y)] = b_id, transformed
tilemap = {pos: (id, tile) for id, (pos, tile) in positions.items()} min_y = min(y for (_, y) in tile_positions.values())
bl, _ = tilemap[(min_x, min_y)] max_y = max(y for (_, y) in tile_positions.values())
br, _ = tilemap[(max_x, min_y)] min_x = min(x for (x, _) in tile_positions.values())
tl, _ = tilemap[(min_x, max_y)] max_x = max(x for (x, _) in tile_positions.values())
tr, _ = tilemap[(max_x, max_y)]
print("Part 1:", tl * tr * bl * br) bl, _ = position_tiles[(min_x, min_y)]
br, _ = position_tiles[(max_x, min_y)]
tl, _ = position_tiles[(min_x, max_y)]
tr, _ = position_tiles[(max_x, max_y)]
print("Part 1:", tl * tr * bl * br)
return position_tiles, (min_x, max_x, min_y, max_y)
def is_snek(b, x, y): def is_snek(image: Tile, start_x: int, start_y: int) -> bool:
for (x_, y_) in ( for (x, y) in SNEK_POSITIONS:
(0, 0),
(1, 1),
(4, 1),
(5, 0),
(6, 0),
(7, 1),
(10, 1),
(11, 0),
(12, 0),
(13, 1),
(16, 1),
(17, 0),
(18, 0),
(18, -1),
(19, 0),
):
try: try:
if b[y_ + y][x_ + x] == ".": if not image[y + start_y][x + start_x]:
return False return False
except: except:
return False return False
return True return True
def find_sneks(b): def find_sneks(b: Tile) -> List[Position]:
sneks = [] sneks = []
for (x, y) in itertools.product(range(8 * 12), repeat=2): for (x, y) in itertools.product(range(8 * 12), repeat=2):
if is_snek(b, x, y): if is_snek(b, x, y):
print(x, y, "is snek")
sneks.append((x, y)) sneks.append((x, y))
return sneks return sneks
def rotate_find_sneks(b): def rotate_find_sneks(b: Tile) -> Tuple[Tile, List[Position]]:
if d := find_sneks(b): for _ in range(4):
return b, d if position := find_sneks(b):
bf = flip(b) return b, position
if d := find_sneks(bf): bf = flip(b)
return bf, d
b = rotate(b) if position := find_sneks(bf):
if d := find_sneks(b): return bf, position
return b, d b = rotate(b)
bf = flip(b) raise RuntimeError("no sneks found")
if d := find_sneks(bf):
return bf, d
b = rotate(b)
if d := find_sneks(b):
return b, d
bf = flip(b)
if d := find_sneks(bf):
return bf, d
b = rotate(b)
if d := find_sneks(b):
return b, d
bf = flip(b)
if d := find_sneks(bf):
return bf, d
def remove_snek(snek): def remove_snek(image: Tile, snek_position: Position) -> None:
# :( # :(
(x, y) = snek (x, y) = snek_position
for (x_, y_) in ( for (x_, y_) in SNEK_POSITIONS:
(0, 0), image[y + y_][x + x_] = False
(1, 1),
(4, 1),
(5, 0),
(6, 0),
(7, 1),
(10, 1),
(11, 0),
(12, 0),
(13, 1),
(16, 1),
(17, 0),
(18, 0),
(18, -1),
(19, 0),
):
big_chungus[y + y_][x + x_] = '.'
chungus = [["."] * 8 * (max_x - min_x + 1) for _ in range(8 * (max_y - min_y + 1))] def part2(
for (x, y), (id, tile) in tilemap.items(): position_tiles: Dict[Position, Tuple[int, Tile]],
x_ = (x - min_x) * 8 extremes: Tuple[int, int, int, int],
y_ = (y - min_y) * 8 ) -> None:
for i, row in enumerate(tile[1:-1]): min_x, max_x, min_y, max_y = extremes
for j, c in enumerate(row[1:-1]): image = [[False] * 8 * (max_x - min_x + 1) for _ in range(8 * (max_y - min_y + 1))]
chungus[i + y_][j + x_] = c for (x, y), (_, tile) in position_tiles.items():
x_ = (x - min_x) * 8
y_ = (y - min_y) * 8
for i, row in enumerate(tile[1:-1]):
for j, c in enumerate(row[1:-1]):
image[i + y_][j + x_] = c
# for y, row in enumerate(chungus): rotated_image, snek_positions = rotate_find_sneks(image)
# print("".join(row)) for snek_position in snek_positions:
remove_snek(rotated_image, snek_position)
big_chungus, sneks = rotate_find_sneks(chungus) part2 = 0
for snek in sneks: for row in rotated_image:
remove_snek(snek) for c in row:
if c:
part2 += 1
part2 = 0 print("Part 2:", part2)
for row in big_chungus:
for c in row:
if c == "#": def main() -> None:
part2 += 1 tiles = parse()
print("Part 2:", part2) position_tiles, extremes = part1(tiles)
part2(position_tiles, extremes)
if __name__ == "__main__":
main()