|
3 | 3 | # Advent of Code 2024 Day 13 |
4 | 4 | # |
5 | 5 |
|
| 6 | +from __future__ import annotations |
| 7 | + |
6 | 8 | import sys |
| 9 | +from typing import NamedTuple |
7 | 10 |
|
8 | 11 | from aoc import my_aocd |
9 | 12 | from aoc.common import InputData |
10 | 13 | from aoc.common import SolutionBase |
11 | 14 | from aoc.common import aoc_samples |
12 | 15 |
|
13 | | -Input = list[tuple[tuple[int, int], tuple[int, int], tuple[int, int]]] |
14 | | -Output1 = int |
15 | | -Output2 = int |
16 | | - |
17 | | - |
18 | 16 | TEST = """\ |
19 | 17 | Button A: X+94, Y+34 |
20 | 18 | Button B: X+22, Y+67 |
|
34 | 32 | """ |
35 | 33 |
|
36 | 34 |
|
| 35 | +class Machine(NamedTuple): |
| 36 | + ax: int |
| 37 | + bx: int |
| 38 | + ay: int |
| 39 | + by: int |
| 40 | + px: int |
| 41 | + py: int |
| 42 | + |
| 43 | + @classmethod |
| 44 | + def from_input(cls, block: list[str]) -> Machine: |
| 45 | + a, b = ((int(block[i][12:14]), int(block[i][18:20])) for i in range(2)) |
| 46 | + sp = block[2].split(", ") |
| 47 | + px, py = int(sp[0].split("=")[1]), int(sp[1][2:]) |
| 48 | + return Machine(a[0], b[0], a[1], b[1], px, py) |
| 49 | + |
| 50 | + |
| 51 | +Input = list[Machine] |
| 52 | +Output1 = int |
| 53 | +Output2 = int |
| 54 | + |
| 55 | + |
37 | 56 | class Solution(SolutionBase[Input, Output1, Output2]): |
38 | 57 | def parse_input(self, input_data: InputData) -> Input: |
39 | | - machines = [] |
40 | | - for block in my_aocd.to_blocks(input_data): |
41 | | - a = (int(block[0][12:14]), int(block[0][18:20])) |
42 | | - b = (int(block[1][12:14]), int(block[1][18:20])) |
43 | | - sp = block[2].split(", ") |
44 | | - px = int(sp[0].split("=")[1]) |
45 | | - py = int(sp[1][2:]) |
46 | | - machines.append((a, b, (px, py))) |
47 | | - return machines |
48 | | - |
49 | | - def guess( |
50 | | - self, ax: int, bx: int, ay: int, by: int, px: int, py: int |
51 | | - ) -> int | None: |
52 | | - # best = sys.maxsize |
53 | | - div = bx * ay - ax * by |
54 | | - ans_a = (py * bx - px * by) / div |
55 | | - ans_b = (px * ay - py * ax) / div |
56 | | - if int(ans_a) == ans_a: |
57 | | - return int(ans_a) * 3 + int(ans_b) |
58 | | - # for ans_a in range(100, 0, -1): |
59 | | - # for ans_b in range(1, 101): |
60 | | - # if ( |
61 | | - # ans_a * ax + ans_b * bx == px |
62 | | - # and ans_a * ay + ans_b * by == py |
63 | | - # ): |
64 | | - # best = min(best, ans_a * 3 + ans_b) |
65 | | - # if best < sys.maxsize: |
66 | | - # return best |
67 | | - else: |
68 | | - return None |
| 58 | + return [ |
| 59 | + Machine.from_input(block) |
| 60 | + for block in my_aocd.to_blocks(input_data) |
| 61 | + ] |
| 62 | + |
| 63 | + def solve(self, machines: list[Machine], offset: int = 0) -> int: |
| 64 | + def calc_tokens(machine: Machine, offset: int) -> int | None: |
| 65 | + px, py = machine.px + offset, machine.py + offset |
| 66 | + div = machine.bx * machine.ay - machine.ax * machine.by |
| 67 | + ans_a = (py * machine.bx - px * machine.by) / div |
| 68 | + ans_b = (px * machine.ay - py * machine.ax) / div |
| 69 | + if int(ans_a) == ans_a and int(ans_b) == ans_b: |
| 70 | + return int(ans_a) * 3 + int(ans_b) |
| 71 | + else: |
| 72 | + return None |
| 73 | + |
| 74 | + return sum( |
| 75 | + tokens |
| 76 | + for tokens in (calc_tokens(m, offset) for m in machines) |
| 77 | + if tokens is not None |
| 78 | + ) |
69 | 79 |
|
70 | 80 | def part_1(self, machines: Input) -> Output1: |
71 | | - ans = 0 |
72 | | - for a, b, p in machines: |
73 | | - ax, ay = a |
74 | | - bx, by = b |
75 | | - px, py = p |
76 | | - g = self.guess(ax, bx, ay, by, px, py) |
77 | | - if g is not None: |
78 | | - ans += g |
79 | | - return ans |
| 81 | + return self.solve(machines) |
80 | 82 |
|
81 | 83 | def part_2(self, machines: Input) -> Output2: |
82 | | - MOD = 10_000_000_000_000 |
83 | | - ans = 0 |
84 | | - for a, b, p in machines: |
85 | | - ax, ay = a |
86 | | - bx, by = b |
87 | | - px, py = p |
88 | | - g = self.guess(ax, bx, ay, by, MOD + px, MOD + py) |
89 | | - if g is not None: |
90 | | - ans += g |
91 | | - return ans |
92 | | - |
93 | | - @aoc_samples( |
94 | | - ( |
95 | | - ("part_1", TEST, 480), |
96 | | - ) |
97 | | - ) |
| 84 | + return self.solve(machines, offset=10_000_000_000_000) |
| 85 | + |
| 86 | + @aoc_samples((("part_1", TEST, 480),)) |
98 | 87 | def samples(self) -> None: |
99 | 88 | pass |
100 | 89 |
|
|
0 commit comments