changeset 72:039e082065a4

Day 15 Part 2
author Lewin Bormann <lbo@spheniscida.de>
date Thu, 28 Dec 2023 20:09:08 +0100
parents 936b17a8e4ff
children 2c6477929e58
files 2023/day15.ml
diffstat 1 files changed, 111 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/2023/day15.ml	Thu Dec 28 19:10:41 2023 +0100
+++ b/2023/day15.ml	Thu Dec 28 20:09:08 2023 +0100
@@ -5,7 +5,11 @@
 type initseq = string list
 
 module Parse = struct
-  let parse_initseq s : initseq = String.split s ~on:','
+  let chomp s = String.chop_suffix_if_exists ~suffix:"\n" s
+
+  let parse_initseq s : initseq =
+    let s = chomp s in
+    String.split s ~on:','
 end
 
 module Part1 = struct
@@ -15,15 +19,116 @@
     List.fold l ~init:0 ~f:step
 
   let hash_all steps = List.map steps ~f:hash
+  let hash_initseq s = Parse.chomp s |> Parse.parse_initseq |> hash_all
+  let hash_sum s = hash_initseq s |> List.fold ~init:0 ~f:( + )
+end
 
-  let hash_initseq s =
-    String.chop_suffix_if_exists ~suffix:"\n" s
-    |> Parse.parse_initseq |> hash_all
+module Part2 = struct
+  (* A lens has a label and a focal length *)
+  type lens = { label : string; f : int } [@@deriving show]
+
+  let equal_lens_label { label; _ } { label = label2; _ } =
+    String.(label = label2)
+
+  (* An op is insertion or removal of a lens *)
+  type op = Remove of string | Insert of lens [@@deriving show]
+
+  (* A hash map is an array of buckets *)
+  type bucket = lens list [@@deriving show]
+  type hm = bucket array
+
+  (* Make a hash map *)
+  let make_hm () : hm = Array.create ~len:256 []
+
+  (* Get a bucket by lens label. *)
+  let get_hm hm (lbl : string) =
+    let ix = Part1.hash lbl in
+    Array.get hm ix
+
+  (* Set a bucket by lens label. *)
+  let set_hm hm lbl list =
+    let ix = Part1.hash lbl in
+    Array.set hm ix list
+
+  (* Remove a lens by label. *)
+  let remove_hm hm lbl =
+    let l = get_hm hm lbl in
+    let l' = List.filter l ~f:(fun l2 -> not String.(lbl = l2.label)) in
+    set_hm hm lbl l'
+
+  (* Update a list of lenses: either replace an existing lens or add a new one *)
+  let update_list lbl lens =
+    let replace (ok, a) e =
+      if (not ok) && equal_lens_label e lens then (true, lens :: a)
+      else (ok, e :: a)
+    in
+    let ok, l' = List.fold ~f:replace ~init:(false, []) lbl in
+    let l_new = List.rev (if ok then l' else lens :: l') in
+    l_new
+
+  (* Insert or replace a lens into a hash map *)
+  let insert_hm hm lens =
+    let l = get_hm hm lens.label in
+    let l' = update_list l lens in
+    set_hm hm lens.label l'
 
-  let hash_sum s = hash_initseq s |> List.fold ~init:0 ~f:( + )
+  (* Parsing for Part 2 *)
+  module Parse = struct
+    let parse_digit =
+      satisfy Char.is_digit >>| fun c -> String.of_char c |> Int.of_string
+
+    let parse_op =
+      let open Angstrom in
+      let label = take_while1 Char.is_alpha in
+      let remove = label <* char '-' >>| fun l -> Remove l in
+      let insert =
+        label <* char '=' >>= fun l ->
+        parse_digit >>= fun f -> return (Insert { label = l; f })
+      in
+      remove <|> insert
+
+    let parse_ops s =
+      let open Angstrom in
+      let parse s =
+        parse_string ~consume:All parse_op s |> Result.ok_or_failwith
+      in
+      let parts = Parse.parse_initseq s in
+      let ops = List.map parts ~f:parse in
+      ops
+  end
+
+  (* Process a single op *)
+  let process_one hm = function
+    | Remove label -> remove_hm hm label
+    | Insert l -> insert_hm hm l
+
+  (* Process a list of ops *)
+  let process_all hm ops = List.iter ops ~f:(process_one hm)
+
+  (* Assign a score to a bucket *)
+  let score_bucket ix l =
+    let a = ix + 1 and f i acc l = acc + ((i + 1) * l.f) in
+    let bc = List.foldi l ~init:0 ~f in
+    let score = a * bc in
+    score
+
+  (* Assign a score to the whole hash map *)
+  let score hm =
+    let f ix acc l = acc + score_bucket ix l in
+    Array.foldi hm ~init:0 ~f
+
+  (* Process an input string *)
+  let process s =
+    let ops = Parse.parse_ops s in
+    let hm = make_hm () in
+    process_all hm ops;
+    let score = score hm in
+    score
 end
 
 let () =
   let inp = In_channel.(input_all stdin) in
   let s = Part1.hash_sum inp in
-  printf "Part 1: %d\n" s
+  printf "Part 1: %d\n" s;
+  let score = Part2.process inp in
+  printf "Part 2: %d\n" score