python(day20): Optimize and tidy up
This commit is contained in:
parent
c1c649ed05
commit
6ff43c5335
1 changed files with 151 additions and 183 deletions
334
python/day20.py
334
python/day20.py
|
@ -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()
|
||||||
|
|
Loading…
Reference in a new issue