Skip to content

Commit 78598a2

Browse files
committed
[2024] Cleanup day 13
1 parent c67f958 commit 78598a2

File tree

3 files changed

+55
-81
lines changed

3 files changed

+55
-81
lines changed

aoc_2024/src/day_13.rs

Lines changed: 44 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,56 @@
1-
use std::collections::{HashMap, VecDeque};
2-
1+
use aoc_lib::vector::AsTuple2;
32
use common::{solution, Answer};
43
use nd_vec::{vector, Vec2};
54

65
solution!("Claw Contraption", 13);
76

87
fn part_a(input: &str) -> Answer {
9-
let problem = Problem::parse(input);
10-
problem
11-
.cases
12-
.iter()
13-
.map(|x| x.cheapest())
14-
.filter(|&x| x != u64::MAX)
15-
.sum::<u64>()
16-
.into()
8+
Problem::parse(input).solve().into()
179
}
1810

1911
fn part_b(input: &str) -> Answer {
20-
/* i used mathematica for p2
21-
22-
out = 0;
23-
Do[
24-
x = input[[i]];
25-
solve =
26-
Solve[x[[1]][[1]] == x[[2]][[1]]*a + x[[3]][[1]]*b &&
27-
x[[1]][[2]] == x[[2]][[2]]*a + x[[3]][[2]]*b, {a, b}, Integers];
28-
result = 3*solve[[All, 1, 2]] + solve[[All, 2, 2]];
29-
out += If[Length[result] == 0, {0}, result];
30-
, {i, 1, Length[input]}];
31-
out
32-
33-
*/
34-
35-
Answer::Unimplemented
12+
Problem::parse(input).part_b().solve().into()
3613
}
3714

38-
#[derive(Debug)]
3915
struct Problem {
4016
cases: Vec<Case>,
4117
}
4218

43-
#[derive(Debug)]
4419
struct Case {
4520
a_button: Vec2<u64>,
4621
b_button: Vec2<u64>,
4722
goal: Vec2<u64>,
4823
}
4924

50-
fn parse_button(input: &str) -> Vec2<u64> {
51-
let (_, parts) = input.rsplit_once(": ").unwrap();
52-
let (x, y) = parts.split_once(", ").unwrap();
53-
vector!(x[1..].parse().unwrap(), y[1..].parse().unwrap())
25+
impl Case {
26+
fn cheapest(&self) -> u64 {
27+
let cast = |x: Vec2<u64>| x.try_cast::<i64>().unwrap().as_tuple();
28+
let ((gx, gy), (ax, ay), (bx, by)) =
29+
(cast(self.goal), cast(self.a_button), cast(self.b_button));
30+
31+
// The best a and b values for a case are the solutions to the following
32+
// system of equations, where g is the goal position, a/b are the number
33+
// of times you press the corespondent buttons (what we are solving
34+
// for), and (a|b)(x|y) are the offsets applied by each button press.
35+
//
36+
// gx = ax * a + bx * b
37+
// gy = ay * a + by * b
38+
//
39+
// By plugging those into Wolfram Alpha, I got the below equation for a,
40+
// then used that to find the equation of b. Because this is integer
41+
// math, we need to verify it by making sure a and b are greater than
42+
// zero and checking if the solution actually solves the system. If it
43+
// does, we return 3a + b, the price.
44+
45+
let a = (by * gx - bx * gy) / (ax * by - ay * bx);
46+
let b = (gx - ax * a) / bx;
47+
48+
if a <= 0 || b <= 0 || self.goal != self.a_button * a as u64 + self.b_button * b as u64 {
49+
return 0;
50+
}
51+
52+
a as u64 * 3 + b as u64
53+
}
5454
}
5555

5656
impl Problem {
@@ -76,59 +76,22 @@ impl Problem {
7676
Self { cases }
7777
}
7878

79-
fn part_b(mut self) -> Self {
80-
for case in self.cases.iter_mut() {
81-
case.goal += vector!(10000000000000, 10000000000000);
82-
}
79+
fn solve(&self) -> u64 {
80+
self.cases.iter().map(|x| x.cheapest()).sum::<u64>().into()
81+
}
8382

83+
fn part_b(mut self) -> Self {
84+
self.cases
85+
.iter_mut()
86+
.for_each(|case| case.goal += vector!(10000000000000, 10000000000000));
8487
self
8588
}
8689
}
8790

88-
impl Case {
89-
fn cheapest(&self) -> u64 {
90-
// a->3, b->1
91-
fn inner(
92-
case: &Case,
93-
memo: &mut HashMap<(Vec2<u64>, (u64, u64)), u64>,
94-
pos: Vec2<u64>,
95-
counts: (u64, u64),
96-
price: u64,
97-
) -> u64 {
98-
if let Some(&cache) = memo.get(&(pos, counts)) {
99-
return cache;
100-
}
101-
102-
if pos == case.goal {
103-
return price;
104-
}
105-
106-
if counts.0 > 100 || counts.1 > 100 {
107-
return u64::MAX;
108-
}
109-
110-
let min = inner(
111-
case,
112-
memo,
113-
pos + case.a_button,
114-
(counts.0 + 1, counts.1),
115-
price + 3,
116-
)
117-
.min(inner(
118-
case,
119-
memo,
120-
pos + case.b_button,
121-
(counts.0, counts.1 + 1),
122-
price + 1,
123-
));
124-
125-
memo.insert((pos, counts), min);
126-
127-
min
128-
}
129-
130-
inner(self, &mut HashMap::new(), vector!(0, 0), (0, 0), 0)
131-
}
91+
fn parse_button(input: &str) -> Vec2<u64> {
92+
let (_, parts) = input.rsplit_once(": ").unwrap();
93+
let (x, y) = parts.split_once(", ").unwrap();
94+
vector!(x[1..].parse().unwrap(), y[1..].parse().unwrap())
13295
}
13396

13497
#[cfg(test)]
@@ -160,6 +123,6 @@ mod test {
160123

161124
#[test]
162125
fn part_b() {
163-
assert_eq!(super::part_b(CASE), ().into());
126+
assert_eq!(super::part_b(CASE), 875318608908_u64.into());
164127
}
165128
}

aoc_lib/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ pub mod regex;
33
pub mod direction;
44
pub mod math;
55
pub mod matrix;
6+
pub mod vector;

aoc_lib/src/vector.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
use nd_vec::Vec2;
2+
3+
pub trait AsTuple2<T> {
4+
fn as_tuple(self) -> (T, T);
5+
}
6+
impl<T: Copy> AsTuple2<T> for Vec2<T> {
7+
fn as_tuple(self) -> (T, T) {
8+
(self.x(), self.y())
9+
}
10+
}

0 commit comments

Comments
 (0)