let ints_of_file fname = Aoc.strings_of_file fname |> List.map (Aoc.ints_of_string ~sep:"[: ]+") (** [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 = Aoc.pow10 (Aoc.digits10 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.filter_map (fun op -> op tgt h) ops) tgts |> List.concat) 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 ]); ]