From 0a5d25abb676cf44ca10dc6faddd0636bbdd1d45 Mon Sep 17 00:00:00 2001 From: sloane Date: Sat, 21 Dec 2024 09:59:59 -0500 Subject: [PATCH] add more grid helper functions to prelude --- 2024/lib/2024/10.ex | 7 ---- 2024/lib/2024/12.ex | 5 --- 2024/lib/aoc/prelude.ex | 78 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 76 insertions(+), 14 deletions(-) diff --git a/2024/lib/2024/10.ex b/2024/lib/2024/10.ex index caaaadd..a68bdcc 100644 --- a/2024/lib/2024/10.ex +++ b/2024/lib/2024/10.ex @@ -64,13 +64,6 @@ aoc 2024, 10 do end end - ## shared - - defp n({x, y}), do: {x, y + 1} - defp s({x, y}), do: {x, y - 1} - defp e({x, y}), do: {x + 1, y} - defp w({x, y}), do: {x - 1, y} - ## input def read_map(input) do diff --git a/2024/lib/2024/12.ex b/2024/lib/2024/12.ex index 26167d0..f9dcf04 100644 --- a/2024/lib/2024/12.ex +++ b/2024/lib/2024/12.ex @@ -64,11 +64,6 @@ aoc 2024, 12 do end) end - def n({x, y}), do: {x, y + 1} - def s({x, y}), do: {x, y - 1} - def e({x, y}), do: {x + 1, y} - def w({x, y}), do: {x - 1, y} - defp count_sides(plot) do # find exterior faces exterior_faces = diff --git a/2024/lib/aoc/prelude.ex b/2024/lib/aoc/prelude.ex index bdf708b..a3494de 100644 --- a/2024/lib/aoc/prelude.ex +++ b/2024/lib/aoc/prelude.ex @@ -28,7 +28,9 @@ defmodule AOC.Prelude do |> Enum.map(&String.to_integer/1) end - @type map_grid(a) :: %{{x :: non_neg_integer(), y :: non_neg_integer()} => a} + @type coord() :: {x :: integer(), y :: integer()} + @type coord(t) :: {x :: t, y :: t} + @type map_grid(a) :: %{coord(non_neg_integer()) => a} @doc """ Reads an Advent of Code-style grid of characters into a map of x, y positions @@ -45,6 +47,78 @@ defmodule AOC.Prelude do Returns `true` if the given position tuple is within the bounds of the map_grid. """ - @spec in_bounds?(map_grid(any()), {x :: integer(), y :: integer()}) :: boolean() + @spec in_bounds?(map_grid(any()), coord()) :: boolean() def in_bounds?(grid, pos), do: Map.has_key?(grid, pos) + + @type direction() :: :up | :down | :left | :right | :n | :ne | :e | :se | :s | :sw | :w | :nw + + @doc """ + Applies a single movement to a coordindate pair + + There are also single function shorthands: + """ + @spec move_coord(coord(), direction()) :: coord() + def move_coord({x, y}, dir) when dir in [:up, :n], do: {x, y - 1} + def move_coord({x, y}, dir) when dir in [:down, :s], do: {x, y + 1} + def move_coord({x, y}, dir) when dir in [:left, :w], do: {x - 1, y} + def move_coord({x, y}, dir) when dir in [:right, :e], do: {x + 1, y} + def move_coord(pos, :ne), do: Enum.reduce([:n, :e], pos, &move_coord/2) + def move_coord(pos, :se), do: Enum.reduce([:s, :e], pos, &move_coord/2) + def move_coord(pos, :sw), do: Enum.reduce([:s, :w], pos, &move_coord/2) + def move_coord(pos, :nw), do: Enum.reduce([:n, :w], pos, &move_coord/2) + + directions = ~w[up down left right n ne e se s sw w nw]a + + for dir <- directions do + @doc """ + A shorthand for `move_coord(pos, #{inspect(dir)})` + """ + @spec unquote(dir)(coord()) :: coord() + def unquote(dir)(coord), do: move_coord(coord, unquote(dir)) + end + + @type neighborhood_opt() :: {:include_center, boolean()} + @type neighborhood_opts() :: list(neighborhood_opt()) + + @doc """ + For a given coordinate position, returns the [Von Neumann neighborhood][1]. + + The Von Neumann neighborhood consists of the cells to the north, south, east, + and west of the center cell. + + ## Options + + - `:include_center`, whether or not to include the passed position. Defaults + to `false` + + [1]: https://en.wikipedia.org/wiki/Von_Neumann_neighborhood + """ + @spec von_neumann_neighborhood(coord()) :: nonempty_list(coord()) + @spec von_neumann_neighborhood(coord(), opts :: neighborhood_opts()) :: nonempty_list(coord()) + def von_neumann_neighborhood(pos, opts \\ []) do + include_center = Keyword.get(opts, :include_center, false) + cells = Enum.map(~w[n e s w]a, &move_coord(pos, &1)) + maybe_center = if include_center, do: [pos], else: [] + maybe_center ++ cells + end + + @doc """ + For a given coordinate position, returns the [Moore neighborhood][1]. + + The Moore neighborhood consists of the cells to the north, northeast, east, + southeast, south, southwest, west, and northwest of the center cell. + + ## Options + + - `:include_center`, whether or not to include the passed position. Defaults + to `false` + + [1]: https://en.wikipedia.org/wiki/Moore_neighborhood + """ + def moore_neighborhood(pos, opts \\ []) do + include_center = Keyword.get(opts, :include_center, false) + cells = Enum.map(~w[n ne e se s sw w nw]a, &move_coord(pos, &1)) + maybe_center = if include_center, do: [pos], else: [] + maybe_center ++ cells + end end