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.
This commit is contained in:
2024-12-07 18:13:06 +00:00
parent 799a25464b
commit 54f14c0492

View File

@@ -15,56 +15,63 @@ let pow10 n =
assert (n >= 0); assert (n >= 0);
impl 1 n impl 1 n
(** [is_valid_target1 tgt nums] checks to see if we can reach [tgt] from [nums] (** [check_add tgt v] Check to see if [X + v = tgt] is a valid operation. If not
using multiply and addition only. [nums] should be in reverse order to that returns [None] otherwise returns [Some X]. *)
given in the problem. *) let check_add tgt v = if v > tgt then None else Some (tgt - v)
let rec is_valid_target1 tgt nums =
(** [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: (* We work backwards from the target and note that:
- if we ever go negative then the route we have taken is invalid - 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 - we can only multiply if dividing the tgt by a number results in no
remainder. 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.
*) *)
if tgt < 0 then false let rec impl tgts nums =
else
match nums with match nums with
| [] -> tgt = 0 | [] -> List.exists (( = ) 0) tgts
| h :: t -> | h :: t ->
let sum_valid = is_valid_target1 (tgt - h) t in impl
if tgt mod h = 0 then sum_valid || is_valid_target1 (tgt / h) t (List.map (fun tgt -> List.map (fun op -> op tgt h) ops) tgts
else sum_valid |> List.concat |> List.filter_map Fun.id)
t
in
impl [ tgt ] nums
(** [is_valid_target2 tgt nums] checks to see if we can reach [tgt] from [nums] (** [check_target checkers lst] returns [true] iff the given target can be
using multiply, addition and "merger" only. [nums] should be in reverse reached from its numbers. [checkers] is the list of operations that can be
order to that given in the problem. *) used. *)
let rec is_valid_target2 tgt nums = let check_target checkers = function
(* Same principle as [is_valid_target1], but also noting that merger op is
only possible if the last digits of the current [tgt] are the same as the
number being examined. *)
if tgt < 0 then false
else
match nums with
| [] -> tgt = 0
| h :: t ->
let sum_valid = is_valid_target2 (tgt - h) t in
let p = pow10 (1 + log10i h) in
let merge_valid =
if tgt mod p = h then is_valid_target2 (tgt / p) t else false
in
if tgt mod h = 0 then
sum_valid || merge_valid || is_valid_target2 (tgt / h) t
else sum_valid || merge_valid
let check_target checker = function
| [] -> false | [] -> false
| h :: t -> checker h (List.rev t) | h :: t -> is_valid_target h (List.rev t) checkers
let part checker lst = (** [part checkers lst] Gets the some of the targets in the list [lst] which can
List.filter (check_target checker) lst 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 |> List.map List.hd |> List.fold_left ( + ) 0
let _ = let _ =
Aoc.main ints_of_file Aoc.main ints_of_file
[ [
(string_of_int, part is_valid_target1); (string_of_int, part [ check_add; check_mul ]);
(string_of_int, part is_valid_target2); (string_of_int, part [ check_add; check_mul; check_cat ]);
] ]