add aoc2024 day 18

This commit is contained in:
Caleb Webber 2024-12-19 12:43:47 -05:00
parent 9b9b00ed61
commit 7f25d06cc2

View file

@ -0,0 +1,262 @@
# AOC 2024 Day 18
## Input
```elixir
e1 = """
5,4
4,2
4,5
3,0
2,1
6,3
2,4
1,5
0,6
3,3
2,6
5,1
1,2
5,5
2,5
6,5
1,4
0,4
6,4
1,1
6,1
1,0
0,5
1,6
2,0
"""
```
## 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)
def mul({x, y}, n), do: {n*x, n*y}
end
```
```elixir
defmodule RAMRun do
def parse(input, take, w, h) do
corrupted = input
|> String.split("\n", trim: true)
|> then(fn l -> if take != nil, do: Enum.take(l, take), else: l end)
|> Enum.map(fn l -> l |> String.split(",") |> Enum.map(&String.to_integer/1) |> List.to_tuple() end)
|> Enum.into(MapSet.new)
dist = for x <- 0..(w-1),
y <- 0..(h-1),
!MapSet.member?(corrupted, {x, y}),
reduce: %{}
do
dist -> dist |> Map.put({x, y}, :infinity)
end
dist = dist |> Map.put({0, 0}, 0)
{corrupted, dist, Grid2D.new(w, h)}
end
def draw(p, grid) do
for y <- 0..(grid.height-1) do
for x <- 0..(grid.width-1) do
(if p |> MapSet.member?({x, y}), do: "#", else: ".")
<> (if x == grid.width-1, do: "\n", else: "")
end
end |> Enum.join("")
end
def solve_p1(input) do
{cor, dist, grid} = parse(input, 1024, 71, 71)
{_, len} = find_path(cor, dist, grid)
len
end
def solve_p1(input, take, w, h) do
{cor, dist, grid} = parse(input, take, w, h)
case find_path(cor, dist, grid) do
{:unsolvable, _} -> :unsolvable
{_, len} -> len
end
end
def solve_p2(input) do
min = 1024 # solve_p1(input)
fails? = fn i ->
{cor, dist, grid} = RAMRun.parse(input, i, 71, 71)
match?({:unsolvable, _}, RAMRun.find_path(cor, dist, grid))
end
max = Enum.count(input |> String.split("\n", trim: true))
binary_search(min, max, div(max - min, 2), fails?)
end
def find_path(cor, dist, grid) do
find_path(grid, cor, [{{0,0}, 0}], dist, {grid.width-1, grid.height - 1})
end
def find_path(_, _, [{dest,_} | _], dist, dest), do: {dist, dist[dest]}
def find_path(_, _, [], dist, _), do: {:unsolvable, dist}
def find_path(grid, corrupted, [{next,d} | pq], dist, dest) do
{dist, pq} = for nb <- Grid2D.neighbors(next, grid, :straight),
!MapSet.member?(corrupted, nb),
reduce: {dist, pq} do
{dist, pq} ->
if dist[nb] > d + 1 do
{
dist |> Map.put(nb, d + 1),
[{nb, d + 1} | (pq |> Enum.filter(&(elem(&1, 0) != nb)))]
}
else
{dist, pq}
end
end
find_path(grid, corrupted, pq |> Enum.sort_by(fn {_coord, d} -> d end), dist, dest)
end
def binary_search(x, y, i, p) when y == x + 1 do
if p.(i), do: i, else: i + 1
end
def binary_search(min, max, i, predicate) do
if predicate.(i) do
binary_search(min, i, div(i - min, 2) + min, predicate)
else
binary_search(i, max, div(max - i, 2) + i, predicate)
end
end
end
```
```elixir
RAMRun.solve_p1(input)
```
```elixir
n = RAMRun.solve_p2(input)
input |> String.split("\n") |> Enum.at(n - 1) |> IO.puts
```