182 lines
4.1 KiB
Markdown
182 lines
4.1 KiB
Markdown
# AoC 2024 Day 10
|
|
|
|
## Section
|
|
|
|
```elixir
|
|
defmodule Grid2D do
|
|
defstruct width: nil, height: nil
|
|
|
|
@directions %{
|
|
straight: [
|
|
{1, 0},
|
|
{-1, 0},
|
|
{0, 1},
|
|
{0, -1}
|
|
],
|
|
diagonal: [
|
|
{1, 1},
|
|
{1, -1},
|
|
{-1, 1},
|
|
{-1, -1}
|
|
]
|
|
}
|
|
|
|
def new(width, height) do
|
|
%Grid2D{width: width, height: height}
|
|
end
|
|
|
|
@doc """
|
|
## Examples
|
|
iex> Grid2D.index_to_coord(Grid2D.new(5,5), 0)
|
|
{0,0}
|
|
|
|
iex> Grid2D.index_to_coord(Grid2D.new(5,5), 5)
|
|
{0,1}
|
|
|
|
iex> Grid2D.index_to_coord(Grid2D.new(5,5), 6)
|
|
{1,1}
|
|
|
|
iex> Grid2D.index_to_coord(Grid2D.new(5,5), 3)
|
|
{3,0}
|
|
|
|
iex> Grid2D.index_to_coord(Grid2D.new(5,5), 24)
|
|
{4, 4}
|
|
"""
|
|
def index_to_coord(%Grid2D{width: w}, index) do
|
|
{rem(index, w), floor(index / w)}
|
|
end
|
|
|
|
@doc """
|
|
## Examples
|
|
iex> Grid2D.coord_to_index( Grid2D.new(5,5), {0,0})
|
|
0
|
|
|
|
iex> Grid2D.coord_to_index( Grid2D.new(5,5), {4,4})
|
|
24
|
|
|
|
iex> Grid2D.coord_to_index( Grid2D.new(5,5), {2,2})
|
|
12
|
|
"""
|
|
def coord_to_index(%Grid2D{width: w}, {x, y}) do
|
|
y * w + x
|
|
end
|
|
|
|
@doc """
|
|
|
|
## Examples
|
|
iex> Grid2D.neighbors({0, 0}, Grid2D.new(5,5)) |> MapSet.new()
|
|
MapSet.new([{0, 1}, {1, 1}, {1,0}])
|
|
|
|
iex> Grid2D.neighbors({2, 2}, Grid2D.new(5,5)) |> MapSet.new()
|
|
[{1, 1}, {1, 2}, {1, 3}, {2, 1}, {2, 3}, {3, 1}, {3, 2}, {3, 3}] |> MapSet.new()
|
|
|
|
iex> Grid2D.neighbors({4, 4}, Grid2D.new(5,5)) |> MapSet.new
|
|
[{3, 3}, {3, 4}, {4, 3}] |> MapSet.new
|
|
"""
|
|
def neighbors(p, g, allowed_directions \\ :all) do
|
|
directions(allowed_directions)
|
|
|> Stream.map(&add(p, &1))
|
|
|> Stream.filter(&is_in_grid?(&1, g))
|
|
end
|
|
|
|
@doc """
|
|
Distance from two points in a grid
|
|
|
|
iex> Grid2D.distance(Grid2D.new(5,5), {0,0}, {0,1}, :radial)
|
|
1
|
|
|
|
iex> Grid2D.distance(Grid2D.new(5,5), {0,0}, {1,1}, :radial)
|
|
1
|
|
|
|
iex> Grid2D.distance(Grid2D.new(5,5), {0,0}, {1,2}, :radial)
|
|
2
|
|
"""
|
|
def distance(grid, p1, p2, type)
|
|
def distance(_, {x, y}, {a, b}, :radial), do: max(abs(x - a), abs(y - b))
|
|
|
|
@doc """
|
|
Add two points together pairwise
|
|
|
|
iex> Grid2D.add({0,0}, {0,1})
|
|
{0,1}
|
|
|
|
iex> Grid2D.add({2,3}, {3,2})
|
|
{5,5}
|
|
"""
|
|
def add({x, y}, {j, k}), do: {x + j, y + k}
|
|
|
|
@doc """
|
|
Test if a point is in a grid
|
|
|
|
iex> Grid2D.is_in_grid?({1, 2}, %Grid2D{width: 3, height: 4})
|
|
true
|
|
|
|
iex> Grid2D.is_in_grid?({2, 2}, %Grid2D{width: 2, height: 4})
|
|
false
|
|
"""
|
|
def is_in_grid?({x, y}, %Grid2D{width: w, height: h}), do: x < w and y < h and x >= 0 and y >= 0
|
|
|
|
@spec directions(allowed :: :all | :straight | :diagonal) :: any()
|
|
def directions(allowed \\ :all)
|
|
def directions(:all), do: directions(:straight) ++ directions(:diagonal)
|
|
def directions(:straight), do: @directions |> Map.get(:straight)
|
|
def directions(:diagonal), do: @directions |> Map.get(:diagonal)
|
|
end
|
|
|
|
```
|
|
|
|
```elixir
|
|
input = """
|
|
"""
|
|
|> String.trim()
|
|
tiles = input |> String.split("\n")
|
|
|> Stream.with_index()
|
|
|> Enum.flat_map(fn {line, l_idx} ->
|
|
line
|
|
|> String.trim()
|
|
|> String.graphemes()
|
|
|> Stream.with_index()
|
|
|> Enum.map(fn {".", idx} -> {{idx, l_idx}, nil}
|
|
{c, idx} -> {{idx, l_idx}, c |> String.to_integer()} end)
|
|
end)
|
|
|> Enum.into(Map.new())
|
|
grid = Grid2D.new(
|
|
input |> String.split("\n") |> Enum.at(0) |> String.trim() |> String.length(),
|
|
input |> String.trim() |> String.split("\n") |> Enum.count()
|
|
)
|
|
```
|
|
|
|
```elixir
|
|
defmodule TrailFinder do
|
|
def find_trails(input, tiles, grid) do
|
|
tiles
|
|
|> Stream.filter(fn {_, v} -> v == 0 end)
|
|
|> Enum.map(fn {k, _} -> {k, find_trails(input, tiles, grid, k, 0, [])} end)
|
|
end
|
|
def find_trails(_, _, _, p, 9, acc), do: [[{p, 9} | acc]]
|
|
def find_trails(input, tiles, grid, start, height, acc) do
|
|
Grid2D.neighbors(start, grid, :straight)
|
|
|> Stream.filter(fn p -> Map.get(tiles, p) == height + 1 end)
|
|
|> Enum.flat_map(fn p ->
|
|
find_trails(input, tiles, grid, p, height + 1, [{start, height} | acc])
|
|
end)
|
|
end
|
|
end
|
|
```
|
|
|
|
```elixir
|
|
trails = TrailFinder.find_trails(input, tiles, grid)
|
|
```
|
|
|
|
```elixir
|
|
trails
|
|
|> Stream.map(fn {s, l} -> {s, for [peak |_] <- l, into: MapSet.new do peak end } end)
|
|
|> Stream.map(fn {_, s} -> MapSet.size(s) end)
|
|
|> Enum.sum()
|
|
```
|
|
|
|
```elixir
|
|
trails
|
|
|> Stream.map(fn {_, l} -> l |> Enum.count end)
|
|
|> Enum.sum()
|
|
```
|