Skip to content

Commit c301ac1

Browse files
committed
Day 13
1 parent 32e0c06 commit c301ac1

File tree

4 files changed

+215
-0
lines changed

4 files changed

+215
-0
lines changed

Cargo.lock

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ edition = "2021"
99
clap = { version = "4.4", features = ["derive"] }
1010
itertools = "0.14.0"
1111
rayon = "1.8"
12+
static_assertions = "1.1.0"
1213
tailcall = "1.0.1"
14+
thiserror = "2.0.12"

src/day13.rs

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
use std::{iter::from_fn, num::ParseIntError, str::FromStr};
2+
use thiserror::Error;
3+
4+
type StoreInt = u64;
5+
type CalcInt = i128;
6+
7+
const PART2_SHIFT: StoreInt = 10000000000000;
8+
9+
const_assert!(u64::MAX > 2 * PART2_SHIFT);
10+
const_assert!(i128::MAX > 4 * PART2_SHIFT as CalcInt * PART2_SHIFT as CalcInt);
11+
12+
pub fn day13(input: &str) -> (usize, usize) {
13+
let machines = Machine::parse_all(input)
14+
.filter_map(|m| m.ok())
15+
.collect::<Vec<_>>();
16+
17+
(part1(&machines), part2(&machines))
18+
}
19+
20+
fn part1(machines: &[Machine]) -> usize {
21+
machines
22+
.iter()
23+
.filter_map(|m| m.solve())
24+
.map(|s| s.cost())
25+
.sum()
26+
}
27+
28+
fn part2(machines: &[Machine]) -> usize {
29+
machines
30+
.iter()
31+
.map(|m| Machine {
32+
prize: m.prize.map(|x| x + PART2_SHIFT),
33+
a_action: m.a_action,
34+
b_action: m.b_action,
35+
})
36+
.filter_map(|m| m.solve())
37+
.map(|s| s.cost())
38+
.sum()
39+
}
40+
41+
#[derive(Debug, PartialEq, Eq)]
42+
struct Machine {
43+
prize: [StoreInt; 2],
44+
a_action: [StoreInt; 2],
45+
b_action: [StoreInt; 2],
46+
}
47+
48+
#[derive(Error, Debug)]
49+
enum MachineParseError {
50+
#[error("Wrong Format")]
51+
WrongNumberFormat(#[from] ParseIntError),
52+
#[error("Wrong Format")]
53+
WrongFormat,
54+
#[error("Not enough Data")]
55+
NotEnoughData,
56+
}
57+
58+
impl Machine {
59+
fn solve(&self) -> Option<Solution> {
60+
// prize = a_presses * a_action + b_presses * b_action
61+
// is a 2x2 LSE, use Cramer's rule to solve
62+
let [p1, p2] = self.prize.map(|x| x as CalcInt);
63+
let [a1, a2] = self.a_action.map(|x| x as CalcInt);
64+
let [b1, b2] = self.b_action.map(|x| x as CalcInt);
65+
66+
let det = a1 * b2 - a2 * b1;
67+
68+
if det == 0 {
69+
return None;
70+
}
71+
72+
let (a_presses, a_rem) = divmod(b2 * p1 - b1 * p2, det);
73+
let (b_presses, b_rem) = divmod(a1 * p2 - a2 * p1, det);
74+
75+
if a_presses < 0 || a_rem != 0 || b_presses < 0 || b_rem != 0 {
76+
return None;
77+
}
78+
79+
return StoreInt::try_from(a_presses)
80+
.ok()
81+
.zip_with(StoreInt::try_from(b_presses).ok(), Solution::new);
82+
83+
fn divmod(x: CalcInt, y: CalcInt) -> (CalcInt, CalcInt) {
84+
(x / y, x % y)
85+
}
86+
}
87+
}
88+
89+
impl FromStr for Machine {
90+
type Err = MachineParseError;
91+
92+
fn from_str(s: &str) -> Result<Self, Self::Err> {
93+
fn parse_button(line: &str, name: &str) -> Result<(StoreInt, StoreInt), MachineParseError> {
94+
line.strip_prefix(&format!("Button {}: X+", name))
95+
.and_then(|rest| rest.split_once(", Y+"))
96+
.ok_or(MachineParseError::WrongFormat) // Would want WrongFormat, but then the typechecker gets confused
97+
.and_then(|(x, y)| Ok((x.parse()?, y.parse()?)))
98+
}
99+
100+
fn parse_prize(line: &str) -> Result<(StoreInt, StoreInt), MachineParseError> {
101+
line.strip_prefix("Prize: X=")
102+
.and_then(|rest| rest.split_once(", Y="))
103+
.ok_or(MachineParseError::WrongFormat)
104+
.and_then(|(x, y)| Ok((x.parse()?, y.parse()?)))
105+
}
106+
107+
let mut lines = s.lines();
108+
let a_line = lines.next().ok_or(MachineParseError::NotEnoughData)?;
109+
let b_line = lines.next().ok_or(MachineParseError::NotEnoughData)?;
110+
let p_line = lines.next().ok_or(MachineParseError::NotEnoughData)?;
111+
112+
let (a_x, a_y) = parse_button(a_line, "A")?;
113+
let (b_x, b_y) = parse_button(b_line, "B")?;
114+
let (p_x, p_y) = parse_prize(p_line)?;
115+
116+
Ok(Machine {
117+
prize: [p_x, p_y],
118+
a_action: [a_x, a_y],
119+
b_action: [b_x, b_y],
120+
})
121+
}
122+
}
123+
124+
trait ParseIncrementally: FromStr {
125+
const NUM_LINES: usize;
126+
127+
fn incremental_parse(s: &str) -> (Result<Self, Self::Err>, &str) {
128+
fn split_n_newlines(n: usize, s: &str) -> (&str, &str) {
129+
if n == 0 {
130+
return ("", &s);
131+
}
132+
let newlines: Vec<_> = s.match_indices('\n').take(n).collect();
133+
if newlines.len() == n {
134+
let split_pos = newlines[n - 1].0;
135+
(&s[..split_pos], &s[split_pos + 1..])
136+
} else {
137+
(&s, "")
138+
}
139+
}
140+
141+
let (start, rest) = split_n_newlines(Self::NUM_LINES, s);
142+
(start.parse(), rest)
143+
}
144+
145+
fn parse_all(s: &str) -> impl Iterator<Item = Result<Self, Self::Err>> {
146+
let mut rem = s;
147+
from_fn(move || {
148+
if rem.is_empty() {
149+
return None;
150+
}
151+
let (result, rest) = Self::incremental_parse(rem);
152+
rem = rest;
153+
Some(result)
154+
})
155+
}
156+
}
157+
158+
impl ParseIncrementally for Machine {
159+
const NUM_LINES: usize = 4;
160+
}
161+
162+
#[derive(Debug, PartialEq, Eq)]
163+
struct Solution {
164+
a_presses: StoreInt,
165+
b_presses: StoreInt,
166+
}
167+
168+
impl Solution {
169+
fn new(a_presses: StoreInt, b_presses: StoreInt) -> Self {
170+
Self {
171+
a_presses,
172+
b_presses,
173+
}
174+
}
175+
176+
fn cost(&self) -> usize {
177+
self.a_presses as usize * 3 + self.b_presses as usize * 1
178+
}
179+
}

src/main.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
#![feature(cmp_minmax)]
2+
#![feature(option_zip)]
3+
4+
#[macro_use]
5+
extern crate static_assertions;
26

37
use std::{fmt::Debug, fs, time::Instant};
48

@@ -16,6 +20,7 @@ mod day09;
1620
mod day10;
1721
mod day11;
1822
mod day12;
23+
mod day13;
1924

2025
#[derive(Parser)]
2126
struct Args {
@@ -77,6 +82,7 @@ fn main() {
7782
(day10::day10, "day10"),
7883
(day11::day11, "day11"),
7984
(day12::day12, "day12"),
85+
(day13::day13, "day13"),
8086
];
8187

8288
// underflow is fine

0 commit comments

Comments
 (0)