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