From 932b2c926cdd0dcc6d8d80320dd31b44ecc06898 Mon Sep 17 00:00:00 2001 From: Matthew Gretton-Dann Date: Thu, 12 Dec 2024 10:23:08 +0000 Subject: [PATCH] Tidy up code for 2024 day 12. This still has some mutable state. --- bin/day2412.ml | 69 +++++++++++++++++++++++++++----------------------- lib/aoc.ml | 8 ++++-- lib/aoc.mli | 4 +++ 3 files changed, 48 insertions(+), 33 deletions(-) diff --git a/bin/day2412.ml b/bin/day2412.ml index 0d50fc0..9d5ba21 100644 --- a/bin/day2412.ml +++ b/bin/day2412.ml @@ -1,65 +1,72 @@ -let get_by_pos_opt grid pos = - if Aoc.Grid.pos_is_valid grid pos then Some (Aoc.Grid.get_by_pos grid pos) - else None - +(** [perimeter grid pos] returns the number of sides of [pos] that contribute to + the perimeter of the region that [pos] is part of in [grid]. *) let perimeter grid (x, y) = let region = Aoc.Grid.get_by_pos grid (x, y) in - 0 - + (if get_by_pos_opt grid (x - 1, y) = Some region then 1 else 0) - + (if get_by_pos_opt grid (x + 1, y) = Some region then 1 else 0) - + (if get_by_pos_opt grid (x, y + 1) = Some region then 1 else 0) - + if get_by_pos_opt grid (x, y - 1) = Some region then 1 else 0 + let check (dx, dy) = + Aoc.Grid.get_by_pos_opt grid (x + dx, y + dy) = Some region + in + [ (-1, 0); (1, 0); (0, 1); (0, -1) ] + |> List.map check |> List.map Bool.to_int |> List.fold_left ( + ) 0 +(** [is_corner grid pos dir] returns true if there is a region corner on the + [dir] side of [pos] in grid. *) let is_corner grid (x, y) (dx, dy) = let region = Aoc.Grid.get_by_pos grid (x, y) in if - get_by_pos_opt grid (x + dx, y) <> Some region - && get_by_pos_opt grid (x, y + dy) <> Some region + Aoc.Grid.get_by_pos_opt grid (x + dx, y) <> Some region + && Aoc.Grid.get_by_pos_opt grid (x, y + dy) <> Some region then true else if - get_by_pos_opt grid (x + dx, y) = Some region - && get_by_pos_opt grid (x, y + dy) = Some region - && get_by_pos_opt grid (x + dx, y + dy) <> Some region + Aoc.Grid.get_by_pos_opt grid (x + dx, y) = Some region + && Aoc.Grid.get_by_pos_opt grid (x, y + dy) = Some region + && Aoc.Grid.get_by_pos_opt grid (x + dx, y + dy) <> Some region then true else false +(** [corners grid pos] returns the count of the number of corners that [pos] is + on for its region in [grid]. *) let corners grid pos = - (if is_corner grid pos (-1, -1) then 1 else 0) - + (if is_corner grid pos (-1, 1) then 1 else 0) - + (if is_corner grid pos (1, 1) then 1 else 0) - + if is_corner grid pos (1, -1) then 1 else 0 + [ (-1, -1); (-1, 1); (1, 1); (1, -1) ] + |> List.map (is_corner grid pos) + |> List.map Bool.to_int |> List.fold_left ( + ) 0 +(** [find_regions calc grid] finds all the regions in [grid] and returns a list + containing an element for each region. The element is a pair [(p, a)] where + [p] is the sum of calling [calc grid pos] for every element in the region + and [a] is the area of the region. *) let find_regions calc grid = - let working = Array.make (Aoc.Grid.length grid) ~-1 in + let visited = Array.make (Aoc.Grid.length grid) false in let add_pos region pos lst = - if get_by_pos_opt grid pos = Some region then + if Aoc.Grid.get_by_pos_opt grid pos = Some region then Aoc.Grid.idx_of_pos grid pos :: lst else lst in - let rec scan_pos perimeter area id = function + let rec scan_pos perimeter area = function | [] -> (perimeter, area) - | idx :: t when working.(idx) <> -1 -> scan_pos perimeter area id t - | idx :: t -> - working.(idx) <- id; + | idx :: t when visited.(idx) -> scan_pos perimeter area t + | idx :: t (* when not visited.(idx) *) -> + visited.(idx) <- true; let x, y = Aoc.Grid.pos_of_idx grid idx in - let area = succ area in + let area = area + 1 in let perimeter = perimeter + calc grid (x, y) in let region = Aoc.Grid.get_by_idx grid idx in let t = add_pos region (x - 1, y) t in let t = add_pos region (x + 1, y) t in let t = add_pos region (x, y - 1) t in let t = add_pos region (x, y + 1) t in - scan_pos perimeter area id t + scan_pos perimeter area t in - let rec impl acc idx id = + let rec impl acc idx = if idx >= Aoc.Grid.length grid then List.rev acc else - let perimeter, area = scan_pos 0 0 id [ idx ] in - if area = 0 then impl acc (idx + 1) id - else impl ((perimeter, area) :: acc) (idx + 1) (id + 1) + let perimeter, area = scan_pos 0 0 [ idx ] in + if area = 0 then impl acc (idx + 1) + else impl ((perimeter, area) :: acc) (idx + 1) in - impl [] 0 0 + impl [] 0 +(** [part calc grid] returns the result of Part N over [grid] where [calc] is + the perimeter/edge counting function. *) let part calc grid = find_regions calc grid |> List.fold_left (fun acc (p, a) -> acc + (p * a)) 0 diff --git a/lib/aoc.ml b/lib/aoc.ml index 56fb877..02dddd1 100644 --- a/lib/aoc.ml +++ b/lib/aoc.ml @@ -54,12 +54,16 @@ module Grid = struct let length grid = String.length grid.grid let pos_of_idx grid idx = (idx mod grid.width, idx / grid.width) let idx_of_pos grid (x, y) = x + (y * grid.width) - let get_by_idx grid idx = grid.grid.[idx] - let get_by_pos grid pos = get_by_idx grid (idx_of_pos grid pos) let pos_is_valid grid (x, y) = x >= 0 && x < grid.width && y >= 0 && y < grid.height + let get_by_idx grid idx = grid.grid.[idx] + let get_by_pos grid pos = get_by_idx grid (idx_of_pos grid pos) + + let get_by_pos_opt grid pos = + if pos_is_valid grid pos then Some (get_by_pos grid pos) else None + let idx_from_opt grid = String.index_from_opt grid.grid let update_pos grid pos c = diff --git a/lib/aoc.mli b/lib/aoc.mli index 834e4db..c99630c 100644 --- a/lib/aoc.mli +++ b/lib/aoc.mli @@ -114,6 +114,10 @@ module Grid : sig (** [Grid.get_by_pos grid pos] returns the character at position [pos] in [grid]. *) + val get_by_pos_opt : t -> int * int -> char option + (** [Grid.get_by_pos_opt grid pos] returns [Some (get_by_pos grid pos)] if + [pos] is a valid position in [grid], and [None] otherwise. *) + val pos_of_idx : t -> int -> int * int (** [Grid.pos_of_idx grid idx] returns the [(x, y)] position mapped by [idx] in [grid]. *)