Compare commits

..

3 Commits

Author SHA1 Message Date
fcc4341237 Update to use Aoc.pow10 and Aoc.digits10. 2024-12-11 09:17:12 +00:00
2159a5fc5e Performant solution to 2024 day 11.
We notice that we're repeating calculations at each step, so use a
map to ensure we do each stone ID once per step.
2024-12-11 09:16:57 +00:00
8bfe33fece Add pow10, log10i and digits10 functions
These are used by a couple of solutions now.
2024-12-11 09:16:08 +00:00
4 changed files with 70 additions and 52 deletions

View File

@@ -1,20 +1,6 @@
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
(** [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)
@@ -26,7 +12,7 @@ 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
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

View File

@@ -1,47 +1,53 @@
module IntMap = Map.Make (Int)
let load_file fname =
match In_channel.with_open_text fname In_channel.input_line with
| Some x -> x
| None -> failwith "load_file"
let log10i i =
let rec impl acc = function 0 -> acc | x -> impl (acc + 1) (x / 10) in
assert (i > 0);
impl ~-1 i
let digits10 i = 1 + log10i 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
let rec apply_n n fn arg = if n <= 0 then arg else apply_n (n - 1) fn (fn arg)
let update_count n = function None -> Some n | Some x -> Some (x + n)
(*
let print_int_list lst =
List.iter
(fun i ->
print_int i;
print_char ' ')
lst;
print_newline ();
()
*)
let calc n input =
let rec step_rec acc = function
let map_of_ints =
let rec impl acc = function
| [] -> acc
| 0 :: t -> step_rec (1 :: acc) t
| x :: t when digits10 x mod 2 = 0 ->
let pow = pow10 (digits10 x / 2) in
let left = x / pow in
let right = x mod pow in
step_rec (right :: left :: acc) t
| x :: t -> step_rec ((x * 2024) :: acc) t
| h :: t -> impl (IntMap.update h (update_count 1) acc) t
in
apply_n n (step_rec []) input
impl IntMap.empty
let part1 str = Aoc.ints_of_string str |> calc 25 |> List.length
let part2 str = Aoc.ints_of_string str |> calc 75 |> List.length
let _ = Aoc.main load_file [ (string_of_int, part1); (string_of_int, part2) ]
(** [calc_blink_rec acc lst] returns an updated map based off [acc] with the
result of apply a blink step to the stones in [lst]. Entries in [lst] are
pairs of [(stone id, count)]. [acc] and the resulting map have keys which
are stone id, and values which are count. *)
let rec calc_blink_rec acc = function
| [] -> acc
| (0, n) :: t -> calc_blink_rec (IntMap.update 1 (update_count n) acc) t
| (x, n) :: t when Aoc.digits10 x mod 2 = 0 ->
let pow = Aoc.pow10 (Aoc.digits10 x / 2) in
let acc = IntMap.update (x / pow) (update_count n) acc in
let acc = IntMap.update (x mod pow) (update_count n) acc in
calc_blink_rec acc t
| (x, n) :: t ->
calc_blink_rec (IntMap.update (x * 2024) (update_count n) acc) t
(** [calc_blink map] calculates how a collection of stones changes in a blink.
[map] is a map with key of stone ID, and value number of times that stone
appears. The result is a map with similar key, value pairs.
This improves performance because we find that the transformation stones go
through ends up producing repeated numbers. e.g.: 0 -> 1 -> 2024 -> 20 24 ->
2 0 2 4 which has two 2s in it.
We also note that despite the problem description saying stones stay in
order, the result we are asked for (number of stones) does not require them
to be in order. *)
let calc_blink map = IntMap.to_list map |> calc_blink_rec IntMap.empty
(** [part n str] returns the number of stones after [n] blinks, given an initial
string [str] of space seperated stone IDs. *)
let part n str =
let map = Aoc.ints_of_string str |> map_of_ints |> apply_n n calc_blink in
IntMap.fold (fun _ v acc -> v + acc) map 0
let _ =
Aoc.main load_file [ (string_of_int, part 25); (string_of_int, part 75) ]

View File

@@ -66,3 +66,18 @@ module Grid = struct
(String.sub grid.grid (idx + 1) (length grid - idx - 1));
{ grid with grid = Buffer.contents builder }
end
let log10i i =
let rec impl acc = function 0 -> acc | x -> impl (acc + 1) (x / 10) in
assert (i > 0);
impl ~-1 i
let digits10 = function
| 0 -> 1
| n when n > 0 -> 1 + log10i n
| n (* when n < 0 *) -> 1 + log10i (-n)
let pow10 n =
let rec impl acc = function 0 -> acc | x -> impl (acc * 10) (x - 1) in
assert (n >= 0);
impl 1 n

View File

@@ -9,6 +9,17 @@ val strings_of_file : string -> string list
(** [strings_from_file fname] returns a list of strings from the file [fname].
Each string represents a line from the file. *)
val log10i : int -> int
(** [log10i n] returns the floor of [(log10 (float_of_int n))]. [n] must be
positive. *)
val digits10 : int -> int
(** [digits10 n] returns the number of base-10 digits in [n]. *)
val pow10 : int -> int
(** [pow10 n] returns [int_of_float (10. ** float_of_int n)]. [n] must be
non-negative. *)
val main : (string -> 'a) -> (('b -> string) * ('a -> 'b)) list -> unit
(** [main prep parts] executes an advent of code problem. [prep fname] should be
a function that returns the input from [fname]. Each elemet of [parts] is a