add aoc2024 day 18
This commit is contained in:
parent
9b9b00ed61
commit
7f25d06cc2
1 changed files with 262 additions and 0 deletions
262
elixir/livebook/2024/day18.livemd
Normal file
262
elixir/livebook/2024/day18.livemd
Normal 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
|
||||
```
|
Loading…
Add table
Reference in a new issue