Files
ocaml-aoc/bin/day2407.ml
Matthew Gretton-Dann 54f14c0492 Simplify the code for 2024 day 7
Simplify may be in the eye of the beholder here.

This reduces code duplication, as both parts are effectively the same.
Part 2 just has an extra operation that can be carried out.
2024-12-07 18:13:06 +00:00

78 lines
2.9 KiB
OCaml

let ints_of_file fname =
Aoc.strings_of_file fname |> List.map (Aoc.ints_of_string ~sep:"[: ]+")
(** [log10i i] returns the integer part of [log10 i]. [i] must be greater than
zero. *)
let log10i i =
let rec impl acc = function 0 -> acc | x -> impl (acc + 1) (x / 10) in
assert (i > 0);
impl ~-1 i
(** [pow10 n] returns [10] raised to the [n]th power. [n] must be non-negative.
*)
let pow10 n =
let rec impl acc = function 0 -> acc | x -> impl (acc * 10) (x - 1) in
assert (n >= 0);
impl 1 n
(** [check_add tgt v] Check to see if [X + v = tgt] is a valid operation. If not
returns [None] otherwise returns [Some X]. *)
let check_add tgt v = if v > tgt then None else Some (tgt - v)
(** [check_mul tgt v] Check to see if [X * v = tgt] is a valid operation. If not
returns [None] otherwise returns [Some X]. *)
let check_mul tgt v = if tgt mod v = 0 then Some (tgt / v) else None
(** [check_cat tgt v] Check to see if [X || v = tgt] is a valid operation. If
not returns [None] otherwise returns [Some X]. *)
let check_cat tgt v =
let p = pow10 (1 + log10i v) in
if tgt mod p = v then Some (tgt / p) else None
(** [is_valid_target tgt nums ops] returns [true] if we can reach [tgt] from
[nums] using [ops]. [nums] is in reverse order. *)
let is_valid_target tgt nums ops =
(* We work backwards from the target and note that:
- if we ever go negative then the route we have taken is invalid
- we can only multiply if dividing the tgt by a number results in no
remainder.
- We can concat if the last digits of tgt match the number being checked.
The recursion in impl takes in a list of current target values and the list
of numbers. It attempts every op on every potential current target with the
head of the nums list. After filtering out those ops which do not produce
a valid target we recurse to the next number, with a new list of target
numbers.
*)
let rec impl tgts nums =
match nums with
| [] -> List.exists (( = ) 0) tgts
| h :: t ->
impl
(List.map (fun tgt -> List.map (fun op -> op tgt h) ops) tgts
|> List.concat |> List.filter_map Fun.id)
t
in
impl [ tgt ] nums
(** [check_target checkers lst] returns [true] iff the given target can be
reached from its numbers. [checkers] is the list of operations that can be
used. *)
let check_target checkers = function
| [] -> false
| h :: t -> is_valid_target h (List.rev t) checkers
(** [part checkers lst] Gets the some of the targets in the list [lst] which can
be made given the input numbers. [checkers] are the operations that are
valid to be used.*)
let part checkers lst =
List.filter (check_target checkers) lst
|> List.map List.hd |> List.fold_left ( + ) 0
let _ =
Aoc.main ints_of_file
[
(string_of_int, part [ check_add; check_mul ]);
(string_of_int, part [ check_add; check_mul; check_cat ]);
]