aoc 2024 day 16 pt 1
This commit is contained in:
parent
228d965ab1
commit
281c9899fb
1 changed files with 505 additions and 0 deletions
505
elixir/livebook/2024/day16.livemd
Normal file
505
elixir/livebook/2024/day16.livemd
Normal file
|
@ -0,0 +1,505 @@
|
||||||
|
# AOC 2024 Day 16 - Dijkstra's
|
||||||
|
|
||||||
|
## 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 ReindeerMaze do
|
||||||
|
def solve(input) do
|
||||||
|
get_paths(input)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_paths(input) do
|
||||||
|
{map, grid} = parse(input)
|
||||||
|
{start, _} = map |> Enum.find(&(elem(&1, 1) == "S"))
|
||||||
|
{fin, _} = map |> Enum.find(&(elem(&1, 1) == "E"))
|
||||||
|
|
||||||
|
shortest_paths = map
|
||||||
|
|> Map.keys
|
||||||
|
|> Stream.map(&({&1, :infinity}))
|
||||||
|
|> Enum.into(%{})
|
||||||
|
|> Map.put(start, 0)
|
||||||
|
shortest = find_min(
|
||||||
|
map,
|
||||||
|
grid,
|
||||||
|
start,
|
||||||
|
{1, 0},
|
||||||
|
shortest_paths,
|
||||||
|
fin,
|
||||||
|
Map.keys(map)|>MapSet.new|>MapSet.delete(start),
|
||||||
|
%{}
|
||||||
|
)
|
||||||
|
{shortest,
|
||||||
|
fin,
|
||||||
|
shortest[fin]}
|
||||||
|
end
|
||||||
|
|
||||||
|
def cost({x,y}, {xx, yy}, prev_dir) do
|
||||||
|
dir = {x - xx, y - yy}
|
||||||
|
cost = if dir == prev_dir do
|
||||||
|
1
|
||||||
|
else
|
||||||
|
1001
|
||||||
|
end
|
||||||
|
cost
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_min(map, grid, {x,y} = start, facing, paths, fin, unvisited, facing_m) do
|
||||||
|
neighbors = Grid2D.neighbors(start, grid, :straight)
|
||||||
|
|> Stream.reject(&Map.get(map, &1) == "#")
|
||||||
|
|> Stream.filter(&(MapSet.member?(unvisited, &1)))
|
||||||
|
|
||||||
|
{paths, facing_m} =
|
||||||
|
for {point, cost} <- neighbors
|
||||||
|
|> Stream.map(&({&1,paths[start] + cost(&1, start, facing)})), reduce: {paths,facing_m} do
|
||||||
|
{shortest,facing_m} ->
|
||||||
|
if shortest[point] > cost do
|
||||||
|
{xx, yy} = point
|
||||||
|
{
|
||||||
|
shortest |> Map.put(point, cost),
|
||||||
|
facing_m |> Map.put(point, {xx - x, yy - y})
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{shortest, facing_m}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
unvisited = unvisited |> MapSet.delete(start)
|
||||||
|
|
||||||
|
next = unvisited |> Enum.min_by(fn m -> Map.get(paths, m) end)
|
||||||
|
|
||||||
|
if next == nil or unvisited |> MapSet.size() == 0 or unvisited |> Enum.all?(&Map.get(paths, &1) == :infinity) do
|
||||||
|
paths
|
||||||
|
else
|
||||||
|
find_min(map, grid, next, Map.get(facing_m, next), paths, fin, unvisited, facing_m)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse(input) do
|
||||||
|
map =
|
||||||
|
for {line, y} <- input |> String.split("\n", trim: true) |> Stream.with_index(),
|
||||||
|
{c, x} <- line |> String.graphemes() |> Stream.with_index(),
|
||||||
|
into: %{} do
|
||||||
|
{{x, y}, c}
|
||||||
|
end
|
||||||
|
|
||||||
|
max = map |> Map.keys() |> Enum.max()
|
||||||
|
|
||||||
|
grid =
|
||||||
|
Grid2D.new(
|
||||||
|
(max |> elem(0)) + 1,
|
||||||
|
(max |> elem(1)) + 1
|
||||||
|
)
|
||||||
|
|
||||||
|
{map, grid}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
e1 = """
|
||||||
|
###############
|
||||||
|
#.......#....E#
|
||||||
|
#.#.###.#.###.#
|
||||||
|
#.....#.#...#.#
|
||||||
|
#.###.#####.#.#
|
||||||
|
#.#.#.......#.#
|
||||||
|
#.#.#####.###.#
|
||||||
|
#...........#.#
|
||||||
|
###.#.#####.#.#
|
||||||
|
#...#.....#.#.#
|
||||||
|
#.#.#.###.#.#.#
|
||||||
|
#.....#...#.#.#
|
||||||
|
#.###.#.#.#.#.#
|
||||||
|
#S..#.....#...#
|
||||||
|
###############
|
||||||
|
"""
|
||||||
|
|
||||||
|
e2 = """
|
||||||
|
#################
|
||||||
|
#...#...#...#..E#
|
||||||
|
#.#.#.#.#.#.#.#.#
|
||||||
|
#.#.#.#...#...#.#
|
||||||
|
#.#.#.#.###.#.#.#
|
||||||
|
#...#.#.#.....#.#
|
||||||
|
#.#.#.#.#.#####.#
|
||||||
|
#.#...#.#.#.....#
|
||||||
|
#.#.#####.#.###.#
|
||||||
|
#.#.#.......#...#
|
||||||
|
#.#.###.#####.###
|
||||||
|
#.#.#...#.....#.#
|
||||||
|
#.#.#.#####.###.#
|
||||||
|
#.#.#.........#.#
|
||||||
|
#.#.#.#########.#
|
||||||
|
#S#.............#
|
||||||
|
#################
|
||||||
|
"""
|
||||||
|
|
||||||
|
e3 = """
|
||||||
|
########
|
||||||
|
#...####
|
||||||
|
#.#.E..#
|
||||||
|
#.##.#.#
|
||||||
|
#....#.#
|
||||||
|
####.#.#
|
||||||
|
#S.....#
|
||||||
|
########
|
||||||
|
"""
|
||||||
|
|
||||||
|
e4 = """
|
||||||
|
###
|
||||||
|
#E#
|
||||||
|
#.#
|
||||||
|
#.#
|
||||||
|
#.#
|
||||||
|
#.#
|
||||||
|
#.#
|
||||||
|
#.#
|
||||||
|
#.#
|
||||||
|
#S#
|
||||||
|
###
|
||||||
|
"""
|
||||||
|
|
||||||
|
e5 = """
|
||||||
|
##########
|
||||||
|
#S......E#
|
||||||
|
##########
|
||||||
|
"""
|
||||||
|
|
||||||
|
e6 = """
|
||||||
|
###########
|
||||||
|
#........E#
|
||||||
|
#.#########
|
||||||
|
#.........#
|
||||||
|
####..#####
|
||||||
|
#.........#
|
||||||
|
#.####..###
|
||||||
|
#S........#
|
||||||
|
###########
|
||||||
|
"""
|
||||||
|
|
||||||
|
e7 = """
|
||||||
|
##########
|
||||||
|
#E....####
|
||||||
|
#####.####
|
||||||
|
#S....####
|
||||||
|
##########
|
||||||
|
"""
|
||||||
|
|
||||||
|
r1 = """
|
||||||
|
###########################
|
||||||
|
#######################..E#
|
||||||
|
######################..#.#
|
||||||
|
#####################..##.#
|
||||||
|
####################..###.#
|
||||||
|
###################..##...#
|
||||||
|
##################..###.###
|
||||||
|
#################..####...#
|
||||||
|
################..#######.#
|
||||||
|
###############..##.......#
|
||||||
|
##############..###.#######
|
||||||
|
#############..####.......#
|
||||||
|
############..###########.#
|
||||||
|
###########..##...........#
|
||||||
|
##########..###.###########
|
||||||
|
#########..####...........#
|
||||||
|
########..###############.#
|
||||||
|
#######..##...............#
|
||||||
|
######..###.###############
|
||||||
|
#####..####...............#
|
||||||
|
####..###################.#
|
||||||
|
###..##...................#
|
||||||
|
##..###.###################
|
||||||
|
#..####...................#
|
||||||
|
#.#######################.#
|
||||||
|
#S........................#
|
||||||
|
###########################
|
||||||
|
"""
|
||||||
|
|
||||||
|
r2 = """
|
||||||
|
####################################################
|
||||||
|
#......................................#..........E#
|
||||||
|
#......................................#...........#
|
||||||
|
#....................#.................#...........#
|
||||||
|
#....................#.................#...........#
|
||||||
|
#....................#.................#...........#
|
||||||
|
#....................#.................#...........#
|
||||||
|
#....................#.................#...........#
|
||||||
|
#....................#.................#...........#
|
||||||
|
#....................#.................#...........#
|
||||||
|
#....................#.................#...........#
|
||||||
|
#....................#.............................#
|
||||||
|
#S...................#.............................#
|
||||||
|
####################################################
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
{p,_,sol} = ReindeerMaze.solve(e1)
|
||||||
|
sol
|
||||||
|
```
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
defmodule ReindeerMaze2 do
|
||||||
|
def solve(input) do
|
||||||
|
get_paths(input)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_paths(input) do
|
||||||
|
{map, grid} = parse(input)
|
||||||
|
{start, _} = map |> Enum.find(&(elem(&1, 1) == "S"))
|
||||||
|
{fin, _} = map |> Enum.find(&(elem(&1, 1) == "E"))
|
||||||
|
|
||||||
|
shortest_paths = map
|
||||||
|
|> Map.keys
|
||||||
|
|> Stream.map(&({&1, :infinity}))
|
||||||
|
|> Enum.into(%{})
|
||||||
|
|> Map.put(start, 0)
|
||||||
|
{shortest,top} = find_min(
|
||||||
|
map,
|
||||||
|
grid,
|
||||||
|
start,
|
||||||
|
{1, 0},
|
||||||
|
shortest_paths,
|
||||||
|
fin,
|
||||||
|
Map.keys(map)|>MapSet.new|>MapSet.delete(start),
|
||||||
|
%{},
|
||||||
|
%{} |> Map.put(start, MapSet.new([start]))
|
||||||
|
)
|
||||||
|
{
|
||||||
|
shortest,
|
||||||
|
fin,
|
||||||
|
shortest[fin],
|
||||||
|
top
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def cost({x,y}, {xx, yy}, prev_dir) do
|
||||||
|
dir = {x - xx, y - yy}
|
||||||
|
cost = if dir == prev_dir do
|
||||||
|
1
|
||||||
|
else
|
||||||
|
1001
|
||||||
|
end
|
||||||
|
cost
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_min(map, grid, {x,y} = start, facing, paths, fin, unvisited, facing_m, top) do
|
||||||
|
neighbors = Grid2D.neighbors(start, grid, :straight)
|
||||||
|
|> Stream.reject(&Map.get(map, &1) == "#")
|
||||||
|
|> Stream.filter(&(MapSet.member?(unvisited, &1)))
|
||||||
|
|
||||||
|
{paths, facing_m, top} =
|
||||||
|
for {point, cost} <- neighbors
|
||||||
|
|> Stream.map(&({&1,paths[start] + cost(&1, start, facing)})),
|
||||||
|
reduce: {paths,facing_m,top} do
|
||||||
|
|
||||||
|
{shortest,facing_m, top} ->
|
||||||
|
cond do
|
||||||
|
shortest[point] > cost ->
|
||||||
|
{xx, yy} = point
|
||||||
|
{
|
||||||
|
shortest |> Map.put(point, cost),
|
||||||
|
facing_m |> Map.put(point, {xx - x, yy - y}),
|
||||||
|
top |> Map.put(point, MapSet.new([start]))
|
||||||
|
}
|
||||||
|
shortest[point] == cost ->
|
||||||
|
{
|
||||||
|
shortest,
|
||||||
|
facing_m,
|
||||||
|
top |> Map.update!(point, &MapSet.put(&1, start))
|
||||||
|
}
|
||||||
|
true ->
|
||||||
|
{shortest, facing_m, top}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
unvisited = unvisited |> MapSet.delete(start)
|
||||||
|
|
||||||
|
next = unvisited |> Enum.min_by(fn m -> Map.get(paths, m) end)
|
||||||
|
|
||||||
|
if unvisited |> MapSet.size() == 0 or unvisited |> Enum.all?(&Map.get(paths, &1) == :infinity) do
|
||||||
|
{paths,top}
|
||||||
|
else
|
||||||
|
find_min(map, grid, next, Map.get(facing_m, next), paths, fin, unvisited, facing_m, top)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def paths_cheaper_than(_, _, start, _, fin, _, n) when n < 0 and fin != start, do: MapSet.new
|
||||||
|
def paths_cheaper_than(_, _, fin, _, fin, _, n) when n != 0, do: MapSet.new
|
||||||
|
def paths_cheaper_than(_, _, fin, _, fin, visited, 0), do: MapSet.put(visited, fin)
|
||||||
|
def paths_cheaper_than(map, grid, {x, y} = start, direction, fin, visited, budget) do
|
||||||
|
Grid2D.neighbors(start, grid)
|
||||||
|
|> Stream.reject(&(Map.get(map, &1) == "#"))
|
||||||
|
|> Stream.reject(&MapSet.member?(visited, &1))
|
||||||
|
|> Stream.map(&({&1, cost(&1, start, direction)}))
|
||||||
|
|> Stream.filter(&(budget - elem(&1, 1) >= 0))
|
||||||
|
|> Stream.flat_map(
|
||||||
|
fn {{xx, yy} = p, cost} ->
|
||||||
|
new_dir = {xx - x, yy - y}
|
||||||
|
paths_cheaper_than(
|
||||||
|
map,
|
||||||
|
grid,
|
||||||
|
p,
|
||||||
|
new_dir,
|
||||||
|
fin,
|
||||||
|
visited |> MapSet.put(start),
|
||||||
|
budget - cost
|
||||||
|
)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|> Enum.into(MapSet.new)
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse(input) do
|
||||||
|
map =
|
||||||
|
for {line, y} <- input |> String.split("\n", trim: true) |> Stream.with_index(),
|
||||||
|
{c, x} <- line |> String.graphemes() |> Stream.with_index(),
|
||||||
|
into: %{} do
|
||||||
|
{{x, y}, c}
|
||||||
|
end
|
||||||
|
|
||||||
|
max = map |> Map.keys() |> Enum.max()
|
||||||
|
|
||||||
|
grid =
|
||||||
|
Grid2D.new(
|
||||||
|
(max |> elem(0)) + 1,
|
||||||
|
(max |> elem(1)) + 1
|
||||||
|
)
|
||||||
|
|
||||||
|
{map, grid}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
m = r1
|
||||||
|
IO.puts(m)
|
||||||
|
{_,f,p1,top} = ReindeerMaze2.solve(m)
|
||||||
|
IO.puts(p1 |> inspect())
|
||||||
|
IO.puts(top[f] |> inspect())
|
||||||
|
```
|
Loading…
Add table
Reference in a new issue