changeset 53:52ad18a532ca

Day 08 Part 2
author Lewin Bormann <lbo@spheniscida.de>
date Thu, 14 Dec 2023 14:27:40 +0100
parents 0a0ff94175b5
children a8d3b517a0fe
files 2023/day08.ml
diffstat 1 files changed, 87 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/2023/day08.ml	Thu Dec 14 13:10:22 2023 +0100
+++ b/2023/day08.ml	Thu Dec 14 14:27:40 2023 +0100
@@ -27,7 +27,7 @@
 module Parse = struct
   let directionP = char 'L' *> return L <|> char 'R' *> return R
   let instructionsP = many1 directionP <* char '\n'
-  let pointerP = take_while Char.is_uppercase
+  let pointerP = take_while (fun c -> Char.is_uppercase c || Char.is_digit c)
 
   let nodeP =
     let open Angstrom.Let_syntax in
@@ -58,7 +58,7 @@
 end
 
 module Part1 = struct
-  let rec traverse_once network current count instrs =
+  let rec traverse_once network count current instrs =
     match current with
     | "ZZZ" -> (current, count)
     | _ -> (
@@ -67,13 +67,13 @@
         | [] -> (current, count)
         | x :: xs -> (
             match x with
-            | L -> traverse_once network node.left (count + 1) xs
-            | R -> traverse_once network node.right (count + 1) xs))
+            | L -> traverse_once network (count + 1) node.left xs
+            | R -> traverse_once network (count + 1) node.right xs))
 
   let initial_node : pointer = "AAA"
 
   let traverse network instructions =
-    let rec do_it from count0 =
+    let rec do_it count0 from =
       match traverse_once network from count0 instructions with
       | "ZZZ", count -> count
       | from', count -> do_it from' count
@@ -81,8 +81,88 @@
     do_it initial_node 0
 end
 
+module Part2 = struct
+  type state = pointer Array.t * int Array.t
+
+  let find_start_ptrs network =
+    let keys = Hashtbl.keys network in
+    let f = String.is_suffix ~suffix:"A" in
+    Array.of_list (List.filter ~f keys)
+
+  let is_end_ptr = String.is_suffix ~suffix:"Z"
+
+  let rec all ~f ?(ix = 0) a =
+    if Int.(ix = Array.length a) then true
+    else if not (f a.(ix)) then false
+    else all ~ix:(ix + 1) ~f a
+
+  let print_initial ((st, _) : state) =
+    let f ix e = Out_channel.printf "Path %d => %s\n" ix e in
+    Array.iteri ~f st
+
+  let print_period count ((st, _) : state) =
+    let f ix e =
+      if is_end_ptr e then Out_channel.printf "%d => %d\n" ix count else ()
+    in
+    Array.iteri ~f st
+
+  let update_periods ((st, periods) : state) count =
+    let f ix e =
+      if is_end_ptr e && Int.(periods.(ix) = 0) then periods.(ix) <- count
+      else ()
+    in
+    Array.iteri ~f st
+
+  let periods_complete = all ~f:(fun x -> Int.(x <> 0))
+  let is_finished ((st, _) : state) = all ~f:is_end_ptr st
+
+  let next network ptr =
+    let node = Hashtbl.find_exn network ptr in
+    function L -> node.left | R -> node.right
+
+  let rec gcd a b = if b = 0 then a else gcd b (a mod b)
+  let lcm a b = Int.abs (a * b) / gcd a b
+  let rec multi_lcm = function [] -> 1 | x :: xs -> lcm x (multi_lcm xs)
+
+  let rec traverse_once network count ((st, periods) as state : state) instrs =
+    if periods_complete periods then (count, state, true)
+    else
+      let f instr p = next network p instr in
+      match instrs with
+      | [] -> (count, state, false)
+      | x :: xs ->
+          update_periods state count;
+          Array.map_inplace ~f:(f x) st;
+          traverse_once network (count + 1) state xs
+
+  (* Same algorithm as above, but now we also track the period of each
+     path. Each path is determined by its initial node. The period is set once the
+     a destination node (ending in Z) has been reached. Once all periods are discovered,
+     the number of turns it takes to finish all paths is the lowest common multiple of
+     all periods. *)
+  let traverse network instrs =
+    let initial_ptrs = find_start_ptrs network in
+    let periods = Array.create ~len:(Array.length initial_ptrs) 0 in
+    let rec do_it count st =
+      match traverse_once network count st instrs with
+      | count', st', false -> do_it count' st'
+      | count', _, true -> count'
+    in
+    let state = (initial_ptrs, periods) in
+    print_initial state;
+    ignore @@ do_it 0 state;
+    periods
+
+  let solve network instrs =
+    let periods = traverse network instrs in
+    multi_lcm (Array.to_list periods)
+end
+
 let () =
   let network, instructions = Parse.parse_all In_channel.stdin in
+  Out_channel.output_string Out_channel.stdout (string_of_network network);
+  Out_channel.output_string Out_channel.stdout "\n";
   let count = Part1.traverse network instructions in
-  Out_channel.output_string Out_channel.stdout (string_of_network network);
-  Out_channel.printf "Took %d turns\n" count
+  Out_channel.printf "1: Took %d turns\n" count;
+  let count2 = Part2.solve network instructions in
+  Out_channel.printf "2: Would take %d turns\n" count2