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);
impl 1 n
(** [is_valid_target1 tgt nums] checks to see if we can reach [tgt] from [nums]
using multiply and addition only. [nums] should be in reverse order to that
given in the problem. *)
let rec is_valid_target1 tgt nums =
(** [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.
*)
if tgt < 0 then false
else
let rec impl tgts nums =
match nums with
| [] -> tgt = 0
| [] -> List.exists (( = ) 0) tgts
| h :: t ->
let sum_valid = is_valid_target1 (tgt - h) t in
if tgt mod h = 0 then sum_valid || is_valid_target1 (tgt / h) t
else sum_valid
(** [is_valid_target2 tgt nums] checks to see if we can reach [tgt] from [nums]
using multiply, addition and "merger" only. [nums] should be in reverse
order to that given in the problem. *)
let rec is_valid_target2 tgt nums =
(* 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
impl
(List.map (fun tgt -> List.map (fun op -> op tgt h) ops) tgts
|> List.concat |> List.filter_map Fun.id)
t
in
if tgt mod h = 0 then
sum_valid || merge_valid || is_valid_target2 (tgt / h) t
else sum_valid || merge_valid
impl [ tgt ] nums
let check_target checker = function
(** [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 -> checker h (List.rev t)
| h :: t -> is_valid_target h (List.rev t) checkers
let part checker lst =
List.filter (check_target checker) lst
(** [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 is_valid_target1);
(string_of_int, part is_valid_target2);
(string_of_int, part [ check_add; check_mul ]);
(string_of_int, part [ check_add; check_mul; check_cat ]);
]