diff --git a/aoc_2024/day_04/__init__.py b/aoc_2024/day_04/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/aoc_2024/day_04/a.py b/aoc_2024/day_04/a.py new file mode 100644 index 0000000..3fec25a --- /dev/null +++ b/aoc_2024/day_04/a.py @@ -0,0 +1,93 @@ +from dataclasses import dataclass +from functools import cached_property +from typing import Sequence +from aoc_2024.day_04.parser import Parser + + +@dataclass +class Day04PartASolver: + input: Sequence[Sequence[str]] + + @property + def solution(self) -> int: + return sum( + [ + self._vertical_count, + self._horizontal_count, + self._lr_diagonal_count, + self._rl_diagonal_count, + ] + ) + + @cached_property + def _forward(self) -> Sequence[str]: + return list("XMAS") + + @cached_property + def _backward(self) -> Sequence[str]: + return self._forward[::-1] + + @cached_property + def _height(self) -> int: + return len(self.input) + + @cached_property + def _width(self) -> int: + return len(self.input[0]) + + @cached_property + def _vertical_count(self) -> int: + output = 0 + for y in range(self._height - 3): + for x in range(self._width): + vals = [self.input[y + i][x] for i in range(4)] + if vals in (self._forward, self._backward): + output += 1 + return output + + @cached_property + def _horizontal_count(self) -> int: + output = 0 + for y in range(self._height): + for x in range(self._width - 3): + vals = [self.input[y][x + i] for i in range(4)] + if vals in (self._forward, self._backward): + output += 1 + return output + + @cached_property + def _lr_diagonal_count(self) -> int: + output = 0 + for y in range(self._height - 3): + for x in range(self._width - 3): + vals = [self.input[y + i][x + i] for i in range(4)] + if vals in (self._forward, self._backward): + output += 1 + return output + + @cached_property + def _rl_diagonal_count(self) -> int: + output = 0 + for y in range(self._height - 3): + for x in range(self._width - 3): + vals = [self.input[y + 3 - i][x + i] for i in range(4)] + if vals in (self._forward, self._backward): + output += 1 + return output + + +def solve(input: str) -> int: + data = Parser.parse(input) + solver = Day04PartASolver(data) + + return solver.solution + + +def get_solution() -> int: + with open("aoc_2024/day_04/input.txt", "r") as f: + input = f.read() + return solve(input) + + +if __name__ == "__main__": + print(get_solution()) diff --git a/aoc_2024/day_04/b.py b/aoc_2024/day_04/b.py new file mode 100644 index 0000000..d3b2837 --- /dev/null +++ b/aoc_2024/day_04/b.py @@ -0,0 +1,54 @@ +from dataclasses import dataclass +from functools import cached_property +from typing import Sequence +from aoc_2024.day_04.parser import Parser + + +@dataclass +class Day04PartBSolver: + input: Sequence[Sequence[str]] + + @property + def solution(self) -> int: + output = 0 + target = (self._forward, self._backward) + for y in range(self._height - 2): + for x in range(self._width - 2): + lr_vals = [self.input[y + i][x + i] for i in range(3)] + rl_vals = [self.input[y + 2 - i][x + i] for i in range(3)] + if lr_vals in target and rl_vals in target: + output += 1 + return output + + @cached_property + def _forward(self) -> Sequence[str]: + return list("MAS") + + @cached_property + def _backward(self) -> Sequence[str]: + return self._forward[::-1] + + @cached_property + def _height(self) -> int: + return len(self.input) + + @cached_property + def _width(self) -> int: + return len(self.input[0]) + + +def solve(input: str) -> int: + data = Parser.parse(input) + solver = Day04PartBSolver(data) + + return solver.solution + + +def get_solution() -> int: + with open("aoc_2024/day_04/input.txt", "r") as f: + input = f.read() + return solve(input) + + +if __name__ == "__main__": + print(get_solution()) diff --git a/aoc_2024/day_04/from_prompt.py b/aoc_2024/day_04/from_prompt.py new file mode 100644 index 0000000..18187f1 Binary files /dev/null and b/aoc_2024/day_04/from_prompt.py differ diff --git a/aoc_2024/day_04/input.txt b/aoc_2024/day_04/input.txt new file mode 100644 index 0000000..8474b27 Binary files /dev/null and b/aoc_2024/day_04/input.txt differ diff --git a/aoc_2024/day_04/parser.py b/aoc_2024/day_04/parser.py new file mode 100644 index 0000000..54c11e8 --- /dev/null +++ b/aoc_2024/day_04/parser.py @@ -0,0 +1,8 @@ +from typing import Sequence + + +class Parser: + @staticmethod + def parse(input: str) -> Sequence[Sequence[str]]: + lines = input.strip().splitlines() + return [list(line) for line in lines] diff --git a/tests/test_day_04/__init__.py b/tests/test_day_04/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_day_04/test_a.py b/tests/test_day_04/test_a.py new file mode 100644 index 0000000..40d6540 --- /dev/null +++ b/tests/test_day_04/test_a.py @@ -0,0 +1,10 @@ +from aoc_2024.day_04.a import get_solution, solve +from aoc_2024.day_04.from_prompt import SAMPLE_DATA, SAMPLE_SOLUTION_A, SOLUTION_A + + +def test_solve(): + assert solve(SAMPLE_DATA) == SAMPLE_SOLUTION_A + + +def test_my_solution(): + assert get_solution() == SOLUTION_A diff --git a/tests/test_day_04/test_b.py b/tests/test_day_04/test_b.py new file mode 100644 index 0000000..6c8d0f1 --- /dev/null +++ b/tests/test_day_04/test_b.py @@ -0,0 +1,13 @@ +import pytest + +from aoc_2024.day_04.b import get_solution, solve +from aoc_2024.day_04.from_prompt import SAMPLE_DATA, SAMPLE_SOLUTION_B, SOLUTION_B + + +def test_solve(): + assert solve(SAMPLE_DATA) == SAMPLE_SOLUTION_B + + +@pytest.mark.skip +def test_my_solution(): + assert get_solution() == SOLUTION_B