diff --git a/bin/day2413.ml b/bin/day2413.ml index 370696a..a6b0510 100644 --- a/bin/day2413.ml +++ b/bin/day2413.ml @@ -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_pos = Str.regexp {|Prize: X=\([0-9]+\), Y=\([0-9]+\)|} in - let _ = Str.search_forward re_ab line_a 0 in - let a = - ( int_of_string (Str.matched_group 1 line_a), - 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 + let a = get_xy re_ab line_a in + let b = get_xy re_ab line_b in + let prize = get_xy re_pos line_p in (a, b, prize) +(** [parse_machines lst] returns a list of the machines parsed from the input + list of strings. *) let parse_machines = let rec impl acc = function | "" :: t -> impl acc t @@ -27,43 +25,67 @@ let parse_machines = in 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 -(* - We want to solve A * a.x + B * b.x = X - and A * a.y + B * b.y = Y +(** [calc_tokens (a, b p)] calculates how many tokens are needed to get to [p] + by pressing button A (moving [a] amount) and button B (moving [b] amount). + Returns [None] if no solution possible, or [Some t] if the prize can be got + by spending [t] tokens. - which is - - 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 ] -*) + Note the problem says minimize but if there is a solution there is only one + solution. *) 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_d = (ax * by) - (ay * bx) in - Printf.printf - "a = (%d, %d) b = (%d, %d) p = (%d, %d), a_n / a_d = %d / %d, mod = %d\n" - ax ay bx by x y a_n a_d (a_n mod a_d); - if a_n mod a_d <> 0 then None + if a_d = 0 then None + else if a_n mod a_d <> 0 then None else if a_n / a_d <= 0 then None else let a = a_n / a_d in let b = (x - (ax * a)) / bx in - Printf.printf " a = %d b = %d\n" a b; Some ((3 * a) + b) -let part machines = - List.map calc_tokens machines - |> List.filter_map Fun.id |> List.fold_left ( + ) 0 +(** [add_offset offset machine] offsets the prize location for [machine] by + [(offset, offset)]. *) +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) ]