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:
@@ -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 ]);
|
||||
]
|
||||
|
Reference in New Issue
Block a user