170 lines
3.8 KiB
Python
170 lines
3.8 KiB
Python
from collections import defaultdict
|
|
from lib import get_data, ints
|
|
from math import isqrt
|
|
|
|
|
|
data = get_data(__file__)
|
|
|
|
|
|
def top(rows):
|
|
return tuple(rows[0])
|
|
|
|
|
|
def bottom(rows):
|
|
return tuple(rows[-1])
|
|
|
|
|
|
def left(rows):
|
|
return tuple(tuple(row[0] for row in rows))
|
|
|
|
|
|
def right(rows):
|
|
return tuple(tuple(row[-1] for row in rows))
|
|
|
|
|
|
def fliph(rows):
|
|
return tuple(tuple(row[::-1] for row in rows))
|
|
|
|
|
|
def flipv(rows):
|
|
return tuple(rows[::-1])
|
|
|
|
|
|
def rot90(rows):
|
|
return tuple(tuple(row) for row in zip(*rows[::-1]))
|
|
|
|
|
|
def rot180(rows):
|
|
return rot90(rot90(rows))
|
|
|
|
|
|
def rot270(rows):
|
|
return rot90(rot90(rot90(rows)))
|
|
|
|
|
|
TOP = 0
|
|
RIGHT = 1
|
|
BOTTOM = 2
|
|
LEFT = 3
|
|
|
|
|
|
def all(rows) -> list:
|
|
return [
|
|
rows,
|
|
rot90(rows),
|
|
rot180(rows),
|
|
rot270(rows),
|
|
fliph(rows),
|
|
flipv(rows),
|
|
rot90(fliph(rows)),
|
|
rot90(flipv(rows)),
|
|
# rot180(fliph(rows)),
|
|
# rot180(flipv(rows)),
|
|
# rot270(fliph(rows)),
|
|
# rot270(flipv(rows)),
|
|
]
|
|
|
|
|
|
tiles = []
|
|
for p in data.strip().split("\n\n"):
|
|
lines = p.splitlines()
|
|
(id,) = ints(lines[0])
|
|
rowst = tuple(map(tuple, lines[1:]))
|
|
tiles.append((id, all(rowst)))
|
|
|
|
|
|
rights = defaultdict(list)
|
|
bottoms = defaultdict(list)
|
|
|
|
for id, variants in tiles:
|
|
for variant in variants:
|
|
rights[left(variant)].append((id, variant))
|
|
bottoms[top(variant)].append((id, variant))
|
|
|
|
num_tiles = len(tiles)
|
|
rows = isqrt(len(tiles))
|
|
|
|
|
|
def dfs(id_set, id_list, tiles_used):
|
|
if len(tiles_used) == num_tiles:
|
|
return id_list, tiles_used
|
|
|
|
i = len(tiles_used)
|
|
id_variants = None
|
|
if i < rows: # first row
|
|
id_variants = rights[right(tiles_used[i - 1])]
|
|
elif i % rows == 0: # first tile in row
|
|
id_variants = bottoms[bottom(tiles_used[i - rows])]
|
|
else:
|
|
id_variants = set(bottoms[bottom(tiles_used[i - rows])])
|
|
id_variants &= set(rights[right(tiles_used[i - 1])])
|
|
id_variants = list(id_variants)
|
|
|
|
assert id_variants is not None
|
|
|
|
for id, variant in id_variants:
|
|
if id in id_set:
|
|
continue
|
|
id_set.add(id)
|
|
id_list.append(id)
|
|
tiles_used.append(variant)
|
|
r = dfs(id_set, id_list, tiles_used)
|
|
if r is not False:
|
|
return r
|
|
tiles_used.pop()
|
|
id_set.remove(id)
|
|
id_list.pop()
|
|
return False, None
|
|
|
|
|
|
tiles_used = None
|
|
for id, tile_variants in tiles:
|
|
for tile in tile_variants:
|
|
r, tiles_used = dfs(set([id]), [id], [tile])
|
|
if type(r) is list:
|
|
print(r[0] * r[-1] * r[rows - 1] * r[-rows])
|
|
break
|
|
if tiles_used is not None:
|
|
break
|
|
else:
|
|
print("no result")
|
|
exit(0)
|
|
|
|
tiles_used = [[tile[x][1:-1] for x in range(1, len(tile) - 1)] for tile in tiles_used]
|
|
|
|
|
|
merged_tiles = []
|
|
for i in range(0, rows * rows, rows):
|
|
for row in range(len(tiles_used[0])):
|
|
merged_row = []
|
|
for j in range(rows):
|
|
merged_row.extend(tiles_used[i + j][row])
|
|
merged_tiles.append(merged_row)
|
|
|
|
seemonster = """ #
|
|
# ## ## ###
|
|
# # # # # # """
|
|
|
|
offsets: list[tuple[int, int]] = []
|
|
for row, line in enumerate(seemonster.splitlines()):
|
|
for col, c in enumerate(line):
|
|
if c == "#":
|
|
offsets.append((row, col))
|
|
|
|
total_hashes = sum([row.count("#") for row in merged_tiles])
|
|
seemonster_hashes = len(offsets)
|
|
|
|
max_seemonster_count = 0
|
|
for field in all(merged_tiles):
|
|
seemonster_count = 0
|
|
for ri in range(len(field) - 2):
|
|
for ci in range(len(field[0]) - 19):
|
|
for ro, co in offsets:
|
|
if field[ri + ro][ci + co] != "#":
|
|
break
|
|
else:
|
|
seemonster_count += 1
|
|
max_seemonster_count = max(max_seemonster_count, seemonster_count)
|
|
|
|
print(total_hashes - max_seemonster_count * seemonster_hashes)
|