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