diff --git a/src/advent_of_code_2023/days/day17/README.md b/src/advent_of_code_2023/days/day17/README.md new file mode 100644 index 0000000..bfd55da --- /dev/null +++ b/src/advent_of_code_2023/days/day17/README.md @@ -0,0 +1,5 @@ +### Part 1 + + +### Part 2 + diff --git a/src/advent_of_code_2023/days/day17/__init__.py b/src/advent_of_code_2023/days/day17/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/advent_of_code_2023/days/day17/solution.py b/src/advent_of_code_2023/days/day17/solution.py new file mode 100644 index 0000000..d1e1c6c --- /dev/null +++ b/src/advent_of_code_2023/days/day17/solution.py @@ -0,0 +1,49 @@ +""" +""" + +import heapq + +import numpy as np + +from advent_of_code_2023.utils import Solution + + +class DaySolution(Solution): + def __init__(self: "DaySolution", day: int = 17, year: int = 2023) -> None: + super().__init__(day, year) + + def _parse_data(self: "DaySolution", input_data: str) -> np.ndarray: + """ """ + return np.array([[int(x) for x in line] for line in input_data.splitlines()]) + + def min_heat_loss( + self: "DaySolution", edges: np.ndarray, least: int = 1, most: int = 3 + ) -> int: + queue = [(0, 0, 0, 0, 0)] + seen = set() + while queue: + heat, x, y, px, py = heapq.heappop(queue) + if (x, y) == (edges.shape[0] - 1, edges.shape[1] - 1): + return heat + if (x, y, px, py) in seen: + continue + seen.add((x, y, px, py)) + # calculate turns only + for dx, dy in {(1, 0), (0, 1), (-1, 0), (0, -1)} - {(px, py), (-px, -py)}: + a, b, h = x, y, heat + # enter 4-10 moves in the chosen direction + for i in range(1, most + 1): + a, b = a + dx, b + dy + if 0 <= a < edges.shape[0] and 0 <= b < edges.shape[1]: + h += edges[a, b] + if i >= least: + heapq.heappush(queue, (h, a, b, dx, dy)) + return 0 + + def _solve_part1(self: "DaySolution", parsed_data: np.ndarray) -> str: + """ """ + return str(self.min_heat_loss(parsed_data)) + + def _solve_part2(self: "DaySolution", parsed_data: np.ndarray) -> str: + """ """ + return str(self.min_heat_loss(parsed_data, 4, 10)) diff --git a/src/advent_of_code_2023/days/day18/README.md b/src/advent_of_code_2023/days/day18/README.md new file mode 100644 index 0000000..bfd55da --- /dev/null +++ b/src/advent_of_code_2023/days/day18/README.md @@ -0,0 +1,5 @@ +### Part 1 + + +### Part 2 + diff --git a/src/advent_of_code_2023/days/day18/__init__.py b/src/advent_of_code_2023/days/day18/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/advent_of_code_2023/days/day18/solution.py b/src/advent_of_code_2023/days/day18/solution.py new file mode 100644 index 0000000..a196f10 --- /dev/null +++ b/src/advent_of_code_2023/days/day18/solution.py @@ -0,0 +1,71 @@ +""" +""" + +import numpy as np + +from advent_of_code_2023.utils import Solution + + +def shoelace(x_y: list[tuple[int, int]]) -> float: + x_y_arr = np.array(x_y) + x_y_arr = x_y_arr.reshape(-1, 2) + + x = x_y_arr[:, 0] + y = x_y_arr[:, 1] + + S1 = np.sum(x * np.roll(y, -1)) + S2 = np.sum(y * np.roll(x, -1)) + + area = 0.5 * np.absolute(S1 - S2) + + return area + + +class DaySolution(Solution): + def __init__(self: "DaySolution", day: int = 18, year: int = 2023) -> None: + super().__init__(day, year) + + def _parse_data(self: "DaySolution", input_data: str) -> list[list[str]]: + """ """ + return [ + line.replace("(", "").replace(")", "").split(" ", 3) + for line in input_data.splitlines() + ] + + def _solve_part1(self: "DaySolution", parsed_data: list[list[str]]) -> str: + """ """ + polygon = [(0, 0)] + bound_length = 0 + for line in reversed(parsed_data): + dr, lngth, _ = line + bound_length += int(lngth) + match dr: + case "R": + polygon.append((polygon[-1][0], polygon[-1][1] + int(lngth))) + case "U": + polygon.append((polygon[-1][0] - int(lngth), polygon[-1][1])) + case "D": + polygon.append((polygon[-1][0] + int(lngth), polygon[-1][1])) + case "L": + polygon.append((polygon[-1][0], polygon[-1][1] - int(lngth))) + return str(int(shoelace(polygon) + bound_length // 2 + 1)) + + def _solve_part2(self: "DaySolution", parsed_data: list[list[str]]) -> str: + """ """ + polygon = [(0, 0)] + bound_length = 0 + nr_to_dr = {"0": "R", "1": "D", "2": "L", "3": "U"} + for line in reversed(parsed_data): + _, _, code = line + lngth = int(code[1:-1], 16) + bound_length += int(lngth) + match nr_to_dr[code[-1]]: + case "R": + polygon.append((polygon[-1][0], polygon[-1][1] + int(lngth))) + case "U": + polygon.append((polygon[-1][0] - int(lngth), polygon[-1][1])) + case "D": + polygon.append((polygon[-1][0] + int(lngth), polygon[-1][1])) + case "L": + polygon.append((polygon[-1][0], polygon[-1][1] - int(lngth))) + return str(int(shoelace(polygon) + bound_length // 2 + 1)) diff --git a/tests/test_day17.py b/tests/test_day17.py new file mode 100644 index 0000000..ccc1cbe --- /dev/null +++ b/tests/test_day17.py @@ -0,0 +1,36 @@ +import pytest + +from advent_of_code_2023.days.day17.solution import DaySolution # type: ignore + + +@pytest.fixture +def day_testdata() -> str: + return """\ +2413432311323 +3215453535623 +3255245654254 +3446585845452 +4546657867536 +1438598798454 +4457876987766 +3637877979653 +4654967986887 +4564679986453 +1224686865563 +2546548887735 +4322674655533\ +""" + + +def test_part1(day_testdata: str) -> None: + sol = DaySolution() + parsed_data = sol._parse_data(day_testdata) + result = sol._solve_part1(parsed_data) + assert result == "102" + + +def test_part2(day_testdata: str) -> None: + sol = DaySolution() + parsed_data = sol._parse_data(day_testdata) + result = sol._solve_part2(parsed_data) + assert result == "94" diff --git a/tests/test_day18.py b/tests/test_day18.py new file mode 100644 index 0000000..a4c4c46 --- /dev/null +++ b/tests/test_day18.py @@ -0,0 +1,37 @@ +import pytest + +from advent_of_code_2023.days.day18.solution import DaySolution # type: ignore + + +@pytest.fixture +def day_testdata() -> str: + return """\ +R 6 (#70c710) +D 5 (#0dc571) +L 2 (#5713f0) +D 2 (#d2c081) +R 2 (#59c680) +D 2 (#411b91) +L 5 (#8ceee2) +U 2 (#caa173) +L 1 (#1b58a2) +U 2 (#caa171) +R 2 (#7807d2) +U 3 (#a77fa3) +L 2 (#015232) +U 2 (#7a21e3)\ +""" + + +def test_part1(day_testdata: str) -> None: + sol = DaySolution() + parsed_data = sol._parse_data(day_testdata) + result = sol._solve_part1(parsed_data) + assert result == "62" + + +def test_part2(day_testdata: str) -> None: + sol = DaySolution() + parsed_data = sol._parse_data(day_testdata) + result = sol._solve_part2(parsed_data) + assert result == "952408144115"