3.9 KiB
3.9 KiB
AOC 2022 - Day 9
Mix.install([
{:req, "~> 0.3.3"},
{:vega_lite, "~> 0.1.6"},
{:kino_vega_lite, "~> 0.1.7"}
])
alias VegaLite, as: Vl
Puzzle description
Input
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
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
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
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()