# 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() ```