let ints_of_file fname = Aoc.strings_of_file fname |> List.map (Aoc.ints_of_string ~sep:"[: ]+") (** [log10i i] returns the integer part of [log10 i]. [i] must be greater than zero. *) let log10i i = let rec impl acc = function 0 -> acc | x -> impl (acc + 1) (x / 10) in assert (i > 0); impl ~-1 i (** [pow10 n] returns [10] raised to the [n]th power. [n] must be non-negative. *) let pow10 n = let rec impl acc = function 0 -> acc | x -> impl (acc * 10) (x - 1) in 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 = (* 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. *) if tgt < 0 then false else match nums with | [] -> tgt = 0 | 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 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 | h :: t -> checker h (List.rev t) let part checker lst = List.filter (check_target checker) 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); ]