diff --git a/2024/day12/day12.py b/2024/day12/day12.py new file mode 100644 index 0000000..451f182 --- /dev/null +++ b/2024/day12/day12.py @@ -0,0 +1,83 @@ +from dataclasses import dataclass +from collections import defaultdict + + +@dataclass(frozen=True) +class Vec2d: + x: int + y: int + + def __add__(self, other): + return Vec2d(self.x + other.x, self.y + other.y) + + +DIRECTIONS = ( + Vec2d(0, -1), # N + Vec2d(1, 0), # E + Vec2d(0, 1), # S + Vec2d(-1, 0), # W +) + +def in_bounds(grid, pos): + return 0 <= pos.y < len(grid) and 0 <= pos.x < len(grid[0]) + + +def visit_region(grid, root): + region = set() + # BFS + queue = [root] + visited = set() + region.add(root) + visited.add(root) + + while queue != []: + node = queue.pop(0) + for d in DIRECTIONS: + new_pos = node + d + if in_bounds(grid, new_pos) and new_pos not in visited: + visited.add(new_pos) + if grid[root.y][root.x] == grid[new_pos.y][new_pos.x]: + queue.append(new_pos) + region.add(new_pos) + + return region + + +def get_perimeter(grid, region): + perimeter = 0 + for node in region: + for d in DIRECTIONS: + new_pos = node + d + if not in_bounds(grid, new_pos) or grid[new_pos.y][new_pos.x] != grid[node.y][node.x]: + perimeter += 1 + return perimeter + + +def main(grid): + # build list of regions using BFS + regions = [] + visited = set() + for y, row in enumerate(grid): + for x, c in enumerate(row): + pos = Vec2d(x, y) + if pos not in visited: + region = visit_region(grid, pos) + visited.update(region) + regions.append((c, region)) + + total = 0 + for region in regions: + c, nodes = region + area = len(nodes) + perimeter = get_perimeter(grid, nodes) + #print(f"Region {c} area {area} perimeter {perimeter}") + total += area * perimeter + print("Part 1: ", total) + +if __name__ == "__main__": + import sys + infile = sys.argv[1] if 1 < len(sys.argv) else "example.txt" + with open(infile) as f: + grid = [l.rstrip() for l in f.readlines()] + main(grid) +