From 044c971d009b1336e74d92858df376e24dee7cc0 Mon Sep 17 00:00:00 2001 From: Bert Peters Date: Thu, 12 Dec 2024 22:18:32 +0100 Subject: [PATCH] Implement 2024 day 12 --- 2024/src/aoc/days/day12.py | 101 ++++++++++++++++++++++++++++++++++++ 2024/tests/samples/12.1.txt | 4 ++ 2024/tests/samples/12.2.txt | 5 ++ 2024/tests/samples/12.3.txt | 10 ++++ 2024/tests/test_day12.py | 21 ++++++++ 5 files changed, 141 insertions(+) create mode 100644 2024/src/aoc/days/day12.py create mode 100644 2024/tests/samples/12.1.txt create mode 100644 2024/tests/samples/12.2.txt create mode 100644 2024/tests/samples/12.3.txt create mode 100644 2024/tests/test_day12.py diff --git a/2024/src/aoc/days/day12.py b/2024/src/aoc/days/day12.py new file mode 100644 index 0000000..849ec0c --- /dev/null +++ b/2024/src/aoc/days/day12.py @@ -0,0 +1,101 @@ +import numpy + +from . import CombinedRunner + +DIRECTIONS = [ + (-1, 0), + (1, 0), + (0, -1), + (0, 1), +] + + +class DayRunner(CombinedRunner): + @classmethod + def run_both(cls, input: str) -> tuple[int, int]: + grid = numpy.array(list(map(list, input.strip().split("\n")))) + + score = 0 + score2 = 0 + + for y in range(grid.shape[0]): + for x in range(grid.shape[1]): + if grid[y, x] == ".": + continue + + search = grid[y, x] + grid[y, x] = "." + + todo = [(y, x)] + cluster = {(y, x)} + + def enqueue(y, x): + if grid[y, x] == search: + grid[y, x] = "." + todo.append((y, x)) + cluster.add((y, x)) + + while todo: + cy, cx = todo.pop() + + if cx > 0: + enqueue(cy, cx - 1) + if cy > 0: + enqueue(cy - 1, cx) + + if cx < grid.shape[1] - 1: + enqueue(cy, cx + 1) + if cy < grid.shape[0] - 1: + enqueue(cy + 1, cx) + + side_length = sum( + sum((cy + dy, cx + dx) not in cluster for dy, dx in DIRECTIONS) + for cy, cx in cluster + ) + + corners = 0 + + for cy, cx in cluster: + # Outer corners + corners += (cy, cx - 1) not in cluster and ( + cy - 1, + cx, + ) not in cluster + corners += (cy, cx + 1) not in cluster and ( + cy - 1, + cx, + ) not in cluster + corners += (cy, cx - 1) not in cluster and ( + cy + 1, + cx, + ) not in cluster + corners += (cy, cx + 1) not in cluster and ( + cy + 1, + cx, + ) not in cluster + # Inner corners + corners += ( + (cy, cx - 1) in cluster + and (cy - 1, cx) in cluster + and (cy - 1, cx - 1) not in cluster + ) + corners += ( + (cy, cx + 1) in cluster + and (cy - 1, cx) in cluster + and (cy - 1, cx + 1) not in cluster + ) + corners += ( + (cy, cx - 1) in cluster + and (cy + 1, cx) in cluster + and (cy + 1, cx - 1) not in cluster + ) + corners += ( + (cy, cx + 1) in cluster + and (cy + 1, cx) in cluster + and (cy + 1, cx + 1) not in cluster + ) + + score += side_length * len(cluster) + score2 += corners * len(cluster) + + return (score, score2) diff --git a/2024/tests/samples/12.1.txt b/2024/tests/samples/12.1.txt new file mode 100644 index 0000000..b41163a --- /dev/null +++ b/2024/tests/samples/12.1.txt @@ -0,0 +1,4 @@ +AAAA +BBCD +BBCC +EEEC diff --git a/2024/tests/samples/12.2.txt b/2024/tests/samples/12.2.txt new file mode 100644 index 0000000..50a7304 --- /dev/null +++ b/2024/tests/samples/12.2.txt @@ -0,0 +1,5 @@ +OOOOO +OXOXO +OOOOO +OXOXO +OOOOO diff --git a/2024/tests/samples/12.3.txt b/2024/tests/samples/12.3.txt new file mode 100644 index 0000000..85b768f --- /dev/null +++ b/2024/tests/samples/12.3.txt @@ -0,0 +1,10 @@ +RRRRIICCFF +RRRRIICCCF +VVRRRCCFFF +VVRCCCJFFF +VVVVCJJCFE +VVIVCCJJEE +VVIIICJJEE +MIIIIIJJEE +MIIISIJEEE +MMMISSJEEE diff --git a/2024/tests/test_day12.py b/2024/tests/test_day12.py new file mode 100644 index 0000000..b308a0d --- /dev/null +++ b/2024/tests/test_day12.py @@ -0,0 +1,21 @@ +import pytest + +from aoc.days.day12 import DayRunner + +from . import get_data + + +@pytest.mark.parametrize( + "data,result", + [(get_data(12, 1), 140), (get_data(12, 2), 772), (get_data(12, 3), 1930)], +) +def test_sample_part1(data: str, result: int) -> None: + assert DayRunner.part1(data) == result + + +@pytest.mark.parametrize( + "data,result", + [(get_data(12, 1), 80), (get_data(12, 2), 436), (get_data(12, 3), 1206)], +) +def test_sample_part2(data: str, result: int) -> None: + assert DayRunner.part2(data) == result