Tidy up code for 2024 day 12.

This still has some mutable state.
This commit is contained in:
2024-12-12 10:23:08 +00:00
parent 7e0e6d3770
commit 932b2c926c
3 changed files with 48 additions and 33 deletions

View File

@@ -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