174 lines
3.9 KiB
Text
174 lines
3.9 KiB
Text
|
# AOC 2022 - Day 9
|
||
|
|
||
|
```elixir
|
||
|
Mix.install([
|
||
|
{:req, "~> 0.3.3"},
|
||
|
{:vega_lite, "~> 0.1.6"},
|
||
|
{:kino_vega_lite, "~> 0.1.7"}
|
||
|
])
|
||
|
|
||
|
alias VegaLite, as: Vl
|
||
|
```
|
||
|
|
||
|
## Puzzle description
|
||
|
|
||
|
[Day 9: Rope Bridge](https://adventofcode.com/2022/day/9).
|
||
|
|
||
|
## Input
|
||
|
|
||
|
```elixir
|
||
|
defmodule Load do
|
||
|
def input do
|
||
|
aoc_session = System.fetch_env!("LB_AOC_SESSION")
|
||
|
input_url = "https://adventofcode.com/2022/day/9/input"
|
||
|
Req.get!(input_url, headers: [cookie: "session=#{aoc_session}"]).body
|
||
|
end
|
||
|
end
|
||
|
```
|
||
|
|
||
|
## Solution
|
||
|
|
||
|
```elixir
|
||
|
defmodule Util do
|
||
|
def process(input) do
|
||
|
input
|
||
|
|> String.split()
|
||
|
|> Enum.chunk_every(2)
|
||
|
|> Enum.map(&parse_motion/1)
|
||
|
end
|
||
|
|
||
|
defp parse_motion(["U", amount]), do: {{0, 1}, String.to_integer(amount)}
|
||
|
defp parse_motion(["D", amount]), do: {{0, -1}, String.to_integer(amount)}
|
||
|
defp parse_motion(["L", amount]), do: {{-1, 0}, String.to_integer(amount)}
|
||
|
defp parse_motion(["R", amount]), do: {{1, 0}, String.to_integer(amount)}
|
||
|
|
||
|
def plot_locations(positions) do
|
||
|
data =
|
||
|
positions
|
||
|
|> Enum.map(fn {x, y} -> %{x: x, y: y} end)
|
||
|
|
||
|
chart =
|
||
|
Vl.new(width: 500, height: 400)
|
||
|
|> Vl.mark(:square)
|
||
|
|> Vl.encode_field(:x, "x", type: :ordinal, title: "X-coordinate", axis: [label_angle: 0])
|
||
|
|> Vl.encode_field(:y, "y", type: :ordinal, title: "Y-coordinate", sort: :descending)
|
||
|
|> Vl.config(view: [stroke: nil])
|
||
|
|> Kino.VegaLite.new()
|
||
|
|> Kino.render()
|
||
|
|
||
|
for position <- data do
|
||
|
Kino.VegaLite.push(chart, position)
|
||
|
Process.sleep(1)
|
||
|
end
|
||
|
|
||
|
positions
|
||
|
end
|
||
|
end
|
||
|
```
|
||
|
|
||
|
```elixir
|
||
|
defmodule RopeSimulator do
|
||
|
def simulate(instructions, knots \\ 2) do
|
||
|
{_final_pos, trail} =
|
||
|
for i <- instructions, reduce: {{0, 0}, []} do
|
||
|
{pos, trail} ->
|
||
|
new_trail = move(pos, i, [])
|
||
|
new_pos = hd(new_trail)
|
||
|
{new_pos, new_trail ++ trail}
|
||
|
end
|
||
|
|
||
|
trail = Enum.reverse(trail)
|
||
|
|
||
|
for _ <- 2..knots, reduce: trail do
|
||
|
trail -> Enum.scan(trail, {0, 0}, fn head, tail -> move_tail(head, tail) end)
|
||
|
end
|
||
|
|> MapSet.new()
|
||
|
end
|
||
|
|
||
|
def move({x, y}, {_dir, 0}, trail), do: [{x, y} | trail]
|
||
|
|
||
|
def move(pos, {dir, n}, trail) do
|
||
|
new_pos = translate(pos, dir)
|
||
|
move(new_pos, {dir, n - 1}, [new_pos | trail])
|
||
|
end
|
||
|
|
||
|
# overlap, do nothing
|
||
|
def move_tail({x, y}, {x, y}), do: {x, y}
|
||
|
# on same row
|
||
|
def move_tail({hx, y}, {tx, y}) when abs(hx - tx) == 1, do: {tx, y}
|
||
|
def move_tail({hx, y}, {tx, y}) when hx > tx, do: {tx + 1, y}
|
||
|
def move_tail({hx, y}, {tx, y}) when hx < tx, do: {tx - 1, y}
|
||
|
# on same column
|
||
|
def move_tail({x, hy}, {x, ty}) when abs(hy - ty) == 1, do: {x, ty}
|
||
|
def move_tail({x, hy}, {x, ty}) when hy > ty, do: {x, ty + 1}
|
||
|
def move_tail({x, hy}, {x, ty}) when hy < ty, do: {x, ty - 1}
|
||
|
# somewhere diagonally => do nothing
|
||
|
def move_tail({hx, hy}, {tx, ty}) when abs(hx - tx) + abs(hy - ty) == 2, do: {tx, ty}
|
||
|
|
||
|
# move tail closer diagonally
|
||
|
def move_tail({hx, hy}, {tx, ty}) do
|
||
|
{dx, dy} = {hx - tx, hy - ty}
|
||
|
translate({tx, ty}, {round(dx / abs(dx)), round(dy / abs(dy))})
|
||
|
end
|
||
|
|
||
|
defp translate({x, y}, {dx, dy}), do: {x + dx, y + dy}
|
||
|
end
|
||
|
```
|
||
|
|
||
|
```elixir
|
||
|
defmodule Part1 do
|
||
|
def run(input) do
|
||
|
input
|
||
|
|> Util.process()
|
||
|
|> RopeSimulator.simulate()
|
||
|
|> Enum.count()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
defmodule Part2 do
|
||
|
def run(input) do
|
||
|
input
|
||
|
|> Util.process()
|
||
|
|> RopeSimulator.simulate(10)
|
||
|
|> Enum.count()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
ExUnit.start(autorun: false)
|
||
|
|
||
|
defmodule Test do
|
||
|
use ExUnit.Case, async: true
|
||
|
@example_input ~s(R 5
|
||
|
U 8
|
||
|
L 8
|
||
|
D 3
|
||
|
R 17
|
||
|
D 10
|
||
|
L 25
|
||
|
U 20)
|
||
|
@input Load.input()
|
||
|
|
||
|
test "it loads the input" do
|
||
|
assert String.length(@input) > 0
|
||
|
end
|
||
|
|
||
|
test "part 1 example" do
|
||
|
assert Part1.run(@example_input) === 88
|
||
|
end
|
||
|
|
||
|
test "part 1" do
|
||
|
assert Part1.run(@input) === 5683
|
||
|
end
|
||
|
|
||
|
test "part 2 example" do
|
||
|
assert Part2.run(@example_input) === 36
|
||
|
end
|
||
|
|
||
|
test "part 2" do
|
||
|
assert Part2.run(@input) === 2372
|
||
|
end
|
||
|
end
|
||
|
|
||
|
ExUnit.run()
|
||
|
```
|