day 20
jurassic jigsaw
approach
each tile is a character grid. rotations yields all eight orientations (four in-place rotations, each also flipped), and every edge is hashed into an integer by reading its # cells as bits. for each tile, the code stores all eight orientations keyed by their (up, right, down, left) edge hashes, and two tiles are adjacent if they share any edge hash.
tiles with exactly two neighbours are corners, which is enough for part 1. to place the full image, reconstruct picks any corner, finds the orientation where it has both a right and a down neighbour, fills the first column by matching down edges and then each row by matching right edges. a comment notes this relies on no two tiles sharing an edge by accident.
part 2 strips the tile borders, stitches the image together, and slides the monster pattern (stored as a list of # offsets) over every orientation of the image, marking matches with O. the answer is the # count from the orientation with the fewest left over.
the trick
hash each edge as a bitmask and precompute all eight orientations, so assembling the image is just looking up the neighbour whose edge hash matches.
def hash_list(l):
return sum(1 << i for i,v in enumerate(l) if v == "#")
def hash_tile(tile):
up = hash_list(tile[0])
down = hash_list(tile[-1])
left = hash_list([tile[i][0] for i in range(len(tile))])
right = hash_list([tile[i][-1] for i in range(len(tile))])
return (up, right, down, left)