Day 13: Claw Contraption
Megathread guidelines
- Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
- You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL
FAQ
- What is this?: Here is a post with a large amount of details: https://programming.dev/post/6637268
- Where do I participate?: https://adventofcode.com/
- Is there a leaderboard for the community?: We have a programming.dev leaderboard with the info on how to join in this post: https://programming.dev/post/6631465
Uiua
Pretty much just a transcription of my Dart solution.
Data ← ⊜(⊜(⊜⋕⊸∈"0123456789")⊸≠@\n)⊸(¬⦷"\n\n")"Button A: X+94, Y+34\nButton B: X+22, Y+67\nPrize: X=8400, Y=5400\n\nButton A: X+26, Y+66\nButton B: X+67, Y+21\nPrize: X=12748, Y=12176\n\nButton A: X+17, Y+86\nButton B: X+84, Y+37\nPrize: X=7870, Y=6450\n\nButton A: X+69, Y+23\nButton B: X+27, Y+71\nPrize: X=18641, Y=10279"
IsInt ← <0.00001⌵-⁅.
AB ← ÷°⊂≡(/-×⇌°⊟)⊏[0_1 2_1 0_2]
Cost ← /+×IsInt.×3_1AB
&p /+≡Cost Data
&p /+≡(Cost⍜(⊡2|+1e13))Data
Welp, got frustrated again with part one because there kept being something wrong with my totally-not-ugly loop and so came here again. I did have to change IsInt
(and thus also Cost
to account for different handling) for part two though because I kept getting wrong results for my input.
I’m guessing it’s because uiua didn’t see the difference between rounded and non-rounded number anymore.
Here’s the updated, slightly messier version of the two functions that worked out for me in the end :D
IsInt ← ≍°⊟⍉⍜(⊙(⍉≡↙₂))(/+×)⊙⍉⁅
Cost ← /+×3_1×⟜IsInt⊸AB
Could have been done better but I’m lacking the patience for that now
Nim
I’m embarrasingly bad with math. Couldn’t have solved this one without looking up the solution. =C
type Vec2 = tuple[x,y: int64]
const
PriceA = 3
PriceB = 1
ErrorDelta = 10_000_000_000_000
proc isInteger(n: float): bool = n.round().almostEqual(n)
proc `+`(a: Vec2, b: int): Vec2 = (a.x + b, a.y + b)
proc solveEquation(a, b, prize: Vec2): int =
let res_a = (prize.x*b.y - prize.y*b.x) / (a.x*b.y - a.y*b.x)
let res_b = (a.x*prize.y - a.y*prize.x) / (a.x*b.y - a.y*b.x)
if res_a.isInteger and res_b.isInteger:
res_a.int * PriceA + res_b.int * PriceB
else: 0
proc solve(input: string): AOCSolution[int, int] =
let chunks = input.split("\n\n")
for chunk in chunks:
let lines = chunk.splitLines()
let partsA = lines[0].split({' ', ',', '+'})
let partsB = lines[1].split({' ', ',', '+'})
let partsC = lines[2].split({' ', ',', '='})
let a = (parseBiggestInt(partsA[3]), parseBiggestInt(partsA[6]))
let b = (parseBiggestInt(partsB[3]), parseBiggestInt(partsB[6]))
let c = (parseBiggestInt(partsC[2]), parseBiggestInt(partsC[5]))
result.part1 += solveEquation(a,b,c)
result.part2 += solveEquation(a,b,c+ErrorDelta)
Rust
Hardest part was parsing the input, i somehow forgot how regexes work and wasted hours.
Learning how to do matrix stuff in rust was a nice detour as well.
#[cfg(test)]
mod tests {
use nalgebra::{Matrix2, Vector2};
use regex::Regex;
fn play_game(ax: i128, ay: i128, bx: i128, by: i128, gx: i128, gy: i128) -> i128 {
for a_press in 0..100 {
let rx = gx - ax * a_press;
let ry = gy - ay * a_press;
if rx % bx == 0 && ry % by == 0 && rx / bx == ry / by {
return a_press * 3 + ry / by;
}
}
0
}
fn play_game2(ax: i128, ay: i128, bx: i128, by: i128, gx: i128, gy: i128) -> i128 {
// m * p = g
// p = m' * g
// |ax bx|.|a_press| = |gx|
// |ay by| |b_press| |gy|
let m = Matrix2::new(ax as f64, bx as f64, ay as f64, by as f64);
match m.try_inverse() {
None => return 0,
Some(m_inv) => {
let g = Vector2::new(gx as f64, gy as f64);
let p = m_inv * g;
let pa = p[0].round() as i128;
let pb = p[1].round() as i128;
if pa * ax + pb * bx == gx && pa * ay + pb * by == gy {
return pa * 3 + pb;
}
}
};
0
}
#[test]
fn day13_part1_test() {
let input = std::fs::read_to_string("src/input/day_13.txt").unwrap();
let re = Regex::new(r"[0-9]+").unwrap();
let games = input
.trim()
.split("\n\n")
.map(|line| {
re.captures_iter(line)
.map(|x| {
let first = x.get(0).unwrap().as_str();
first.parse::<i128>().unwrap()
})
.collect::<Vec<i128>>()
})
.collect::<Vec<Vec<i128>>>();
let mut total = 0;
for game in games {
let cost = play_game2(game[0], game[1], game[2], game[3], game[4], game[5]);
total += cost;
}
// 36870
println!("{}", total);
}
#[test]
fn day12_part2_test() {
let input = std::fs::read_to_string("src/input/day_13.txt").unwrap();
let re = Regex::new(r"[0-9]+").unwrap();
let games = input
.trim()
.split("\n\n")
.map(|line| {
re.captures_iter(line)
.map(|x| {
let first = x.get(0).unwrap().as_str();
first.parse::<i128>().unwrap()
})
.collect::<Vec<i128>>()
})
.collect::<Vec<Vec<i128>>>();
let mut total = 0;
for game in games {
let cost = play_game2(
game[0],
game[1],
game[2],
game[3],
game[4] + 10000000000000,
game[5] + 10000000000000,
);
total += cost;
}
println!("{}", total);
}
}
J
I think this puzzle is a bit of a missed opportunity. They could have provided inputs with no solution or with a line of solutions, so that the cost optimization becomes meaningful. As it is, you just have to carry out Cramer’s rule in extended precision rational arithmetic.
load 'regex'
data_file_name =: '13.data'
raw =: cutopen fread data_file_name
NB. a b sublist y gives elements [a..b) of y
sublist =: ({~(+i.)/)~"1 _
parse_button =: monad define
match =. 'X\+([[:digit:]]+), Y\+([[:digit:]]+)' rxmatch y
". (}. match) sublist y
)
parse_prize =: monad define
match =. 'X=([[:digit:]]+), Y=([[:digit:]]+)' rxmatch y
". (}. match) sublist y
)
parse_machine =: monad define
3 2 $ (parse_button >0{y), (parse_button >1{y), (parse_prize >2{y)
)
NB. x: converts to extended precision, which gives us rational arithmetic
machines =: x: (parse_machine"1) _3 ]\ raw
NB. A machine is represented by an array 3 2 $ ax ay bx by tx ty, where button
NB. A moves the claw by ax ay, button B by bx by, and the target is at tx ty.
NB. We are looking for nonnegative integer solutions to ax*a + bx*b = tx,
NB. ay*a + by*b = ty; if there is more than one, we want the least by the cost
NB. function 3*a + b.
solution_rank =: monad define
if. 0 ~: -/ . * }: y do. 0 NB. system is nonsingular
elseif. */ (=/"1) 2 ]\ ({. % {:) |: y do. 1 NB. one equation is a multiple of the other
else. _1 end.
)
NB. solve0 yields the cost of solving a machine of solution rank 0
solve0 =: monad define
d =. -/ . * }: y
a =. (-/ . * 2 1 { y) % d
b =. (-/ . * 0 2 { y) % d
if. (a >: 0) * (a = <. a) * (b >: 0) * (b = <. b) do. b + 3 * a else. 0 end.
)
NB. there are actually no machines of solution rank _1 or 1 in the test set
result1 =: +/ solve0"_1 machines
machines2 =: machines (+"2) 3 2 $ 0 0 0 0 10000000000000 10000000000000
NB. there are no machines of solution rank _1 or 1 in the modified set either
result2 =: +/ solve0"_1 machines2
C
“The cheapest way” “the fewest tokens”, that evil chap!
I’m on a weekend trip and thought to do the puzzle in the 3h train ride but I got silly stumped on 2D line intersection*, was too stubborn to look it up, and fell asleep 🤡
When I woke up, so did the little nugget of elementary algebra somewhere far in the back of my mind. Tonight I finally got to implementing, which was smooth sailing except for this lesson I learnt:
int64 friends don’t let int64 friends play with float32s.
*) on two parts:
- how can you capture a two-dimensional problem in a linear equation (ans: use slopes), and
- what unknown was I supposed to be finding? (ans: either x or y of intersection will do)
Code
#include "common.h"
static int64_t
score(int ax, int ay, int bx, int by, int64_t px, int64_t py)
{
int64_t a,b, x;
double as,bs;
as = (double)ay / ax;
bs = (double)by / bx;
/* intersection between a (from start) and b (from end) */
x = (int64_t)round((px*bs - py) / (bs-as));
a = x / ax;
b = (px-x) / bx;
return
a*ax + b*bx == px &&
a*ay + b*by == py ? a*3 + b : 0;
}
int
main(int argc, char **argv)
{
int ax,ay, bx,by;
int64_t p1=0,p2=0, px,py;
if (argc > 1)
DISCARD(freopen(argv[1], "r", stdin));
while (scanf(
" Button A: X+%d, Y+%d"
" Button B: X+%d, Y+%d"
" Prize: X=%"SCNd64", Y=%"SCNd64,
&ax, &ay, &bx, &by, &px, &py) == 6) {
p1 += score(ax,ay, bx,by, px,py);
p2 += score(ax,ay, bx,by,
px + 10000000000000LL,
py + 10000000000000LL);
}
printf("13: %"PRId64" %"PRId64"\n", p1, p2);
return 0;
}