StyleInCode

RSS

 

Day 12 - Advent of Code 2024

12 December 2024

Working solution for the day 12 puzzles.

Part One and Part Two

""" day_12_01_02.py """

# usage: python3 day_12_01_02.py < input

import sys
from collections import Counter
from collections import deque
from collections import namedtuple


Point = namedtuple('Point2D', 'x, y')


def box_vertices(ul):
    """ vertices of box given upperleft """
    return {ul, Point(ul.x + 1, ul.y), Point(ul.x + 1, ul.y + 1),
            Point(ul.x, ul.y + 1)}


def group(upperlefts):
    """ calculate zones of connected boxes from upperlefts """
    deltas = [(0, -1), (1, 0), (0, 1), (-1, 0)]
    uls = upperlefts[:]
    zones = []

    while uls:
        ul = uls.pop()
        zone = [box_vertices(ul)]
        queue = deque([ul])
        while queue:
            ul = queue.popleft()
            for dx, dy in deltas:
                adjacent = Point(ul.x + dx, ul.y + dy)
                if adjacent in uls:
                    uls.remove(adjacent)
                    zone.append(box_vertices(adjacent))
                    queue.append(adjacent)

        zones.append(zone)

    return zones


def edge(zone):
    """ how many times do two boxes touch diagonally only """
    vertices = Counter(vertex for box in zone for vertex in box)
    count = 0
    for i in [vertex for vertex, freq in vertices.items() if freq == 2]:
        box = box_vertices(Point(i.x - 1, i.y - 1))
        if box in zone:
            box = box_vertices(i)
            if box in zone:
                count += 1
        else:
            box = box_vertices(Point(i.x, i.y - 1))
            if box in zone:
                box = box_vertices(Point(i.x - 1, i.y))
                if box in zone:
                    count += 1
    return count


def perimeter(zone):
    """ calculate perimeter given boxes in zone """
    vertices = Counter(vertex for box in zone for vertex in box)
    return len([vertex for vertex, freq in vertices.items()
                if freq < 4]) + edge(zone)


def sides(zone):
    """ calculate sides given boxes in zone """
    vertices = Counter(vertex for box in zone for vertex in box)
    return len([vertex for vertex, freq in vertices.items()
                if freq in [1, 3]]) + 2 * edge(zone)


plots = {}
with sys.stdin as infile:
    point = Point(0, 0)
    for line in infile:
        for plot in line.strip():
            sites = plots.get(plot, [])
            sites.append(point)
            plots[plot] = sites
            point = Point(point.x + 1, point.y)
        point = Point(0, point.y + 1)

perimeter_cost = 0
sides_cost = 0

for _, sites in plots.items():
    for region in group(sites):
        region_area = len(region)
        perimeter_cost += region_area * perimeter(region)
        sides_cost += region_area * sides(region)

print(perimeter_cost)
print(sides_cost)

Categories

Links