add 2024 day 15
This commit is contained in:
parent
e4de674377
commit
228d965ab1
1 changed files with 392 additions and 0 deletions
392
elixir/livebook/2024/day15.livemd
Normal file
392
elixir/livebook/2024/day15.livemd
Normal file
|
@ -0,0 +1,392 @@
|
|||
# AOC 2024 Day 15
|
||||
|
||||
## 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
|
||||
input = """
|
||||
"""
|
||||
```
|
||||
|
||||
```elixir
|
||||
[map, instructions] = input |> String.trim() |> String.split("\n\n")
|
||||
|
||||
map = map
|
||||
|> String.trim()
|
||||
|> String.split("\n", trim: true)
|
||||
|> Stream.with_index()
|
||||
|> Stream.flat_map(fn {line, y_idx} ->
|
||||
line
|
||||
|> String.graphemes()
|
||||
|> Stream.with_index()
|
||||
|> Enum.map(fn {c, x_idx} -> {{x_idx, y_idx}, c} end)
|
||||
end)
|
||||
|> Enum.into(%{})
|
||||
|
||||
instructions = instructions |> String.trim() |> String.split("", trim: true) |> Stream.reject(&(&1 == "\n")) |> Enum.map(&(
|
||||
case &1 do
|
||||
"<" -> {-1, 0}
|
||||
">" -> { 1, 0}
|
||||
"^" -> { 0, -1}
|
||||
"v" -> { 0, 1}
|
||||
end
|
||||
))
|
||||
|
||||
```
|
||||
|
||||
```elixir
|
||||
w = map |> Map.keys() |> Enum.max_by(&elem(&1, 0)) |> elem(0)
|
||||
h = map |> Map.keys() |> Enum.max_by(&elem(&1, 1)) |> elem(1)
|
||||
start = map |> Enum.find(fn {_, t} -> t == "@" end) |> elem(0)
|
||||
grid = Grid2D.new(w, h)
|
||||
```
|
||||
|
||||
```elixir
|
||||
defmodule WarehouseWoes do
|
||||
def move(map, grid, direction) do
|
||||
[{coord, _}] = map |> Enum.filter(&(elem(&1, 1) == "@"))
|
||||
move(map, grid, coord, direction)
|
||||
end
|
||||
|
||||
def move(map, _, _, []), do: map
|
||||
def move(map, grid, coord, [dir | rest]) do
|
||||
if can_move?(map, grid, coord, dir) do
|
||||
next = coord |> Grid2D.add(dir)
|
||||
tile = Map.get(map, next)
|
||||
|
||||
(if tile == "O" do
|
||||
move(map, grid, next, [ dir ])
|
||||
else
|
||||
map
|
||||
end)
|
||||
|> Map.put(next, Map.get(map, coord))
|
||||
|> Map.put(coord, ".")
|
||||
|> move(grid, rest)
|
||||
else
|
||||
move(map, grid, coord, rest)
|
||||
end
|
||||
end
|
||||
|
||||
def can_move?(map, grid, coord, direction) do
|
||||
next = coord |> Grid2D.add(direction)
|
||||
in_grid? = next |> Grid2D.is_in_grid?(grid)
|
||||
tile = Map.get(map, next)
|
||||
in_grid? and tile != "#" and (tile != "O" or can_move?(map, grid, next, direction))
|
||||
end
|
||||
|
||||
def print(map, grid) do
|
||||
for y <- 0..(grid.height) do
|
||||
for x <- 0..(grid.width) do
|
||||
IO.write(Map.get(map, {x, y}))
|
||||
if x == grid.width do
|
||||
IO.write("\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
:ok
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
```elixir
|
||||
defmodule WarehouseWoes2 do
|
||||
def parse(input) do
|
||||
[map, instructions] = input |> String.trim() |> String.split("\n\n")
|
||||
|
||||
map = map
|
||||
|> String.trim()
|
||||
|> String.split("\n", trim: true)
|
||||
|> Stream.with_index()
|
||||
|> Stream.flat_map(fn {line, y_idx} ->
|
||||
line
|
||||
|> String.graphemes()
|
||||
|> Stream.with_index()
|
||||
|> Enum.map(fn {c, x_idx} -> {{x_idx, y_idx}, c} end)
|
||||
end)
|
||||
|> Enum.reduce(%{}, fn {{x, y}, tile}, acc ->
|
||||
case tile do
|
||||
"O" -> acc |> Map.put({2*x, y}, "[") |> Map.put({2*x + 1, y}, "]")
|
||||
"." -> acc |> Map.put({2*x, y}, ".") |> Map.put({2*x + 1, y}, ".")
|
||||
"#" -> acc |> Map.put({2*x, y}, "#") |> Map.put({2*x + 1, y}, "#")
|
||||
"@" -> acc |> Map.put({2*x, y}, "@") |> Map.put({2*x + 1, y}, ".")
|
||||
end
|
||||
end)
|
||||
|
||||
instructions = instructions |> String.trim() |> String.split("", trim: true) |> Stream.reject(&(&1 == "\n")) |> Enum.map(&(
|
||||
case &1 do
|
||||
"<" -> {-1, 0}
|
||||
">" -> { 1, 0}
|
||||
"^" -> { 0, -1}
|
||||
"v" -> { 0, 1}
|
||||
end
|
||||
))
|
||||
{map, instructions}
|
||||
end
|
||||
|
||||
def solve(input) do
|
||||
{map, instructions} = parse(input)
|
||||
w = map |> Map.keys() |> Enum.max_by(&elem(&1, 0)) |> elem(0)
|
||||
h = map |> Map.keys() |> Enum.max_by(&elem(&1, 1)) |> elem(1)
|
||||
grid = Grid2D.new(w, h)
|
||||
move(map, grid, instructions)
|
||||
|> Stream.filter(&(elem(&1, 1) == "["))
|
||||
|> Stream.map(&(elem(&1,0)))
|
||||
|> Enum.reduce(0, fn {x, y},acc -> acc + 100*y + x end)
|
||||
end
|
||||
|
||||
def move(map, grid, direction) do
|
||||
[{coord, _}] = map |> Enum.filter(&(elem(&1, 1) == "@"))
|
||||
move(map, grid, coord, direction)
|
||||
end
|
||||
|
||||
@doc "
|
||||
Recursively moves a coordinate on the map.
|
||||
"
|
||||
def move(map, _, _, []), do: map
|
||||
# Only used when the turtle is pushing up on a block
|
||||
# We gather all the coordinates for blocks being pushed in a direction,
|
||||
# then try to move them up as a unit.
|
||||
# The whole unit moves up if:
|
||||
# - The space above all the blocks is empty (".")
|
||||
# - The space above all the blocks is either empty or other blocks and those blocks can be move up.
|
||||
# If the space above the blocks contains any walls ("#") or the blocks above them cannot be moved,
|
||||
# the blocks will not move.
|
||||
#
|
||||
def move(map, grid, coords, [{0, n} = dir]) when is_list(coords) and (n == 1 or n == -1) do
|
||||
nexts = coords |> Enum.flat_map(fn i ->
|
||||
next = Grid2D.add(i, dir)
|
||||
tile = Map.get(map, next)
|
||||
case tile do
|
||||
"[" -> [next, Grid2D.add(next, {1, 0})]
|
||||
"]" -> [Grid2D.add(next, {-1, 0}), next]
|
||||
_ -> [next]
|
||||
end
|
||||
end)
|
||||
|
||||
cond do
|
||||
Enum.any?(nexts, &(Map.get(map, &1) == "#")) -> map
|
||||
Enum.all?(nexts, &(Map.get(map, &1) == ".")) ->
|
||||
coords
|
||||
|> Enum.reduce(map, fn coord, acc ->
|
||||
acc
|
||||
|> Map.put(coord |> Grid2D.add(dir), Map.get(map, coord))
|
||||
|> Map.put(coord, ".")
|
||||
end)
|
||||
true ->
|
||||
moved = move(map, grid, nexts |> Enum.filter(fn c ->
|
||||
tile = Map.get(map, c)
|
||||
tile == "[" or tile == "]"
|
||||
end), [dir])
|
||||
if moved != map do
|
||||
move(moved, grid, coords, [dir])
|
||||
else
|
||||
map
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# This method is only ever actually called when `coord` represents the coord of robot (")
|
||||
def move(map, grid, coord, [{0, n} = dir | rest]) when n == 1 or n == -1 do
|
||||
next = Grid2D.add(coord, dir)
|
||||
next_tile = Map.get(map, next)
|
||||
map = case next_tile do
|
||||
"." -> map
|
||||
|> Map.put(next, Map.get(map, coord))
|
||||
|> Map.put(coord, ".")
|
||||
"#" -> map
|
||||
"]" ->
|
||||
next = [ Grid2D.add(next, {-1, 0}), next]
|
||||
moved = move(map, grid, next, [dir])
|
||||
if moved != map do
|
||||
move(moved, grid, coord, [dir])
|
||||
else
|
||||
moved
|
||||
end
|
||||
"[" ->
|
||||
next = [next, Grid2D.add(next, {1, 0})]
|
||||
moved = move(map, grid, next, [dir])
|
||||
if moved != map do
|
||||
move(moved, grid, coord, [dir])
|
||||
else
|
||||
moved
|
||||
end
|
||||
end
|
||||
map |> move(grid, rest)
|
||||
end
|
||||
|
||||
def move(map, grid, coord, [dir | rest]) do
|
||||
next = Grid2D.add(coord, dir)
|
||||
next_tile = Map.get(map, next)
|
||||
map = case next_tile do
|
||||
"." -> map
|
||||
|> Map.put(next, Map.get(map, coord))
|
||||
|> Map.put(coord, ".")
|
||||
"#" -> map
|
||||
_ ->
|
||||
moved = map |> move(grid, next, [dir])
|
||||
if moved == map do
|
||||
moved
|
||||
else
|
||||
move(moved, grid, coord, [dir])
|
||||
end
|
||||
end
|
||||
map |> move(grid, rest)
|
||||
end
|
||||
|
||||
@doc "
|
||||
Prints the ASCII character for an instruction.
|
||||
"
|
||||
def instruction(i) do
|
||||
case i do
|
||||
{-1, 0} -> "<"
|
||||
{1, 0} -> ">"
|
||||
{0, -1} -> "^"
|
||||
{0, 1} -> "v"
|
||||
end
|
||||
end
|
||||
|
||||
@doc "
|
||||
Returns the current map as a string.
|
||||
"
|
||||
def print(map, grid) do
|
||||
for y <- 0..(grid.height) do
|
||||
for x <- 0..(grid.width) do
|
||||
(Map.get(map, {x, y}) || ".") <>
|
||||
(if x == grid.width do
|
||||
"\n"
|
||||
else
|
||||
""
|
||||
end)
|
||||
end
|
||||
end |> Enum.join("")
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
```elixir
|
||||
WarehouseWoes2.solve(input)
|
||||
```
|
Loading…
Add table
Reference in a new issue