2024 day 13 part 2

This commit is contained in:
2024-12-13 18:43:09 +00:00
parent 8d7c14a707
commit 75f9ac6975

View File

@@ -1,23 +1,21 @@
let parse_machine line_a line_b prize_pos = (** [parse_machine line_a line_b line_p] parse the machine description and
returns a triple [(a, b, p)] describing the actions of the 'A' and 'B'
buttons along with the location of the prize. *)
let parse_machine line_a line_b line_p =
let get_xy re s =
let _ = Str.search_forward re s 0 in
( int_of_string (Str.matched_group 1 s),
int_of_string (Str.matched_group 2 s) )
in
let re_ab = Str.regexp {|Button [AB]: X\+\([0-9]+\), Y\+\([0-9]+\)|} in let re_ab = Str.regexp {|Button [AB]: X\+\([0-9]+\), Y\+\([0-9]+\)|} in
let re_pos = Str.regexp {|Prize: X=\([0-9]+\), Y=\([0-9]+\)|} in let re_pos = Str.regexp {|Prize: X=\([0-9]+\), Y=\([0-9]+\)|} in
let _ = Str.search_forward re_ab line_a 0 in let a = get_xy re_ab line_a in
let a = let b = get_xy re_ab line_b in
( int_of_string (Str.matched_group 1 line_a), let prize = get_xy re_pos line_p in
int_of_string (Str.matched_group 2 line_a) )
in
let _ = Str.search_forward re_ab line_b 0 in
let b =
( int_of_string (Str.matched_group 1 line_b),
int_of_string (Str.matched_group 2 line_b) )
in
let _ = Str.search_forward re_pos prize_pos 0 in
let prize =
( int_of_string (Str.matched_group 1 prize_pos),
int_of_string (Str.matched_group 2 prize_pos) )
in
(a, b, prize) (a, b, prize)
(** [parse_machines lst] returns a list of the machines parsed from the input
list of strings. *)
let parse_machines = let parse_machines =
let rec impl acc = function let rec impl acc = function
| "" :: t -> impl acc t | "" :: t -> impl acc t
@@ -27,43 +25,67 @@ let parse_machines =
in in
impl [] impl []
(** [machines_of_file fname] returns the list of machines described in the file
[fname]. *)
let machines_of_file fname = Aoc.strings_of_file fname |> parse_machines let machines_of_file fname = Aoc.strings_of_file fname |> parse_machines
(* (** [calc_tokens (a, b p)] calculates how many tokens are needed to get to [p]
We want to solve A * a.x + B * b.x = X by pressing button A (moving [a] amount) and button B (moving [b] amount).
and A * a.y + B * b.y = Y Returns [None] if no solution possible, or [Some t] if the prize can be got
by spending [t] tokens.
which is Note the problem says minimize but if there is a solution there is only one
solution. *)
A * a.x / b.x + B = X / b.x
A * a.y / b.y + B = Y / b.y
A * (a.x / b.x - a.y / b.y) = X / b.x - Y / b.y
A * (a.x * b.y - a.y * b.x) / b.x / b.y = X / b.x - Y / b.y
A * (a.x * b.y - a.y * b.x) = X * b.y - Y * b.x
A = (X * b.y - Y * b.x) / (a.x * by.y - a.y * b.x)
(7870 * 37 - 6450 * 84) / (17 * 37 - 86 * 84)
80 - (8400 * 67 - 5400 * 22) / (94 * 67 - 34 * 22)
a = (94, 34) b = (22, 67) p = (8400, 5400)
[ a.x b.x ] [ A ] = [ X ]
[ a.y b.y ] [ B ] [ Y ]
*)
let calc_tokens ((ax, ay), (bx, by), (x, y)) = let calc_tokens ((ax, ay), (bx, by), (x, y)) =
(* Solve as a sequence of linear equations:
We want to find A & B in:
A * ax + B * bx = X (1)
A * ay + B * by = Y (2)
dividing (1) by bx and (2) by by gives us:
A * ax / bx + B = X / bx (3)
A * ay / by + B = Y / by (4)
(3) - (4) gives:
A * (ax / bx - ay / by) = X / bx - Y / by.
Multiplying through by (bx * by) gives:
A * (ax * by - ay * bx) = X * by - Y * bx.
Dividing by (ax * by - ay * by) gives:
A = (X * by - Y * bx) / (ax * by - ay * by)
If ax * by - ay * by is 0 we have an infinite number of answers, and we'll
deal with that if that becomes an issue (spoiler alert: it doesn't).
A needs to be a whole number so (X * by - Y * bx) mod (ax * by - ay * by)
must be 0, otherwise there is no solution. *)
let a_n = (x * by) - (y * bx) in let a_n = (x * by) - (y * bx) in
let a_d = (ax * by) - (ay * bx) in let a_d = (ax * by) - (ay * bx) in
Printf.printf if a_d = 0 then None
"a = (%d, %d) b = (%d, %d) p = (%d, %d), a_n / a_d = %d / %d, mod = %d\n" else if a_n mod a_d <> 0 then None
ax ay bx by x y a_n a_d (a_n mod a_d);
if a_n mod a_d <> 0 then None
else if a_n / a_d <= 0 then None else if a_n / a_d <= 0 then None
else else
let a = a_n / a_d in let a = a_n / a_d in
let b = (x - (ax * a)) / bx in let b = (x - (ax * a)) / bx in
Printf.printf " a = %d b = %d\n" a b;
Some ((3 * a) + b) Some ((3 * a) + b)
let part machines = (** [add_offset offset machine] offsets the prize location for [machine] by
List.map calc_tokens machines [(offset, offset)]. *)
|> List.filter_map Fun.id |> List.fold_left ( + ) 0 let add_offset offset (a, b, (x, y)) = (a, b, (x + offset, y + offset))
let _ = Aoc.main machines_of_file [ (string_of_int, part) ] (** [part offset machines] calculates the number of tokens needed to win as many
prizes as possible from [machines]. All machines prizes are offset by
[(offset, offset)]. *)
let part offset machines =
List.map (add_offset offset) machines
|> List.filter_map calc_tokens
|> List.fold_left ( + ) 0
let _ =
Aoc.main machines_of_file
[ (string_of_int, part 0); (string_of_int, part 10000000000000) ]