80 lines
2.0 KiB
Python
80 lines
2.0 KiB
Python
import math
|
|
from dataclasses import dataclass
|
|
from typing import Any, Iterator
|
|
|
|
import parse # pyright: ignore[reportMissingTypeStubs]
|
|
|
|
from ..base import BaseSolver
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Machine:
|
|
prize: tuple[int, int]
|
|
button_a: tuple[int, int]
|
|
button_b: tuple[int, int]
|
|
|
|
|
|
def read_machine(block: str) -> Machine:
|
|
ba = parse.parse( # type: ignore
|
|
"""Button A: X{bax:d}, Y{bay:d}
|
|
Button B: X{bbx:d}, Y{bby:d}
|
|
Prize: X={px:d}, Y={py:d}""",
|
|
block,
|
|
)
|
|
return Machine(
|
|
prize=(ba["px"], ba["py"]), # type: ignore
|
|
button_a=(ba["bax"], ba["bay"]), # type: ignore
|
|
button_b=(ba["bbx"], ba["bby"]), # type: ignore
|
|
)
|
|
|
|
|
|
def diophantine(a: int, b: int, c: int) -> tuple[int, int]:
|
|
q, r = divmod(a, b)
|
|
if r == 0:
|
|
return (0, c // b)
|
|
else:
|
|
u, v = diophantine(b, r, c)
|
|
return (v, u - q * v)
|
|
|
|
|
|
def solve(machine: Machine) -> int:
|
|
(ax, ay), (bx, by), (px, py) = machine.button_a, machine.button_b, machine.prize
|
|
dx, dy = math.gcd(ax, bx), math.gcd(ay, by)
|
|
|
|
if px % dx != 0 or py % dy != 0:
|
|
return 0
|
|
|
|
xa, xb = diophantine(ax, bx, px)
|
|
ya, yb = diophantine(ay, by, py)
|
|
|
|
# expr (x): xa - kx * bx / dx, xb + kx * ax / dx
|
|
# expr (y): ya - ky * by / dy, yb + ky * ay / dy
|
|
|
|
num = ay * (ya - xa) + by * (yb - xb)
|
|
den = (ax * by - ay * bx) // dx
|
|
|
|
if num % den != 0:
|
|
return 0
|
|
|
|
kx = num // den
|
|
pa, pb = xa - kx * bx // dx, xb + kx * ax // dx
|
|
return 3 * pa + pb
|
|
|
|
|
|
class Solver(BaseSolver):
|
|
def solve(self, input: str) -> Iterator[Any]:
|
|
machines = [read_machine(block) for block in input.split("\n\n")]
|
|
|
|
yield sum(map(solve, machines))
|
|
|
|
shift = 10000000000000
|
|
machines = [
|
|
Machine(
|
|
prize=(shift + m.prize[0], shift + m.prize[1]),
|
|
button_a=m.button_a,
|
|
button_b=m.button_b,
|
|
)
|
|
for m in machines
|
|
]
|
|
yield sum(map(solve, machines))
|