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