Day 18
Mix.install([
{:req, "~> 0.4.5"},
{:kino, "~> 0.11.3"},
{:nx, "~> 0.6.4"}
])
Input
input =
Req.get!(
"https://adventofcode.com/2023/day/18/input",
headers: [{"Cookie", ~s"session=#{System.fetch_env!("LB_AOC_SESSION")}"}]
).body
Kino.Text.new(input, terminal: true)
Part 1
defmodule Part1 do
@deltas %{
"U" => {-1, 0},
"D" => {1, 0},
"L" => {0, -1},
"R" => {0, 1}
}
def dig(input) do
{_, map} =
for line <- String.split(input, "\n", trim: true),
reduce: {{0, 0}, MapSet.new()} do
{curr, acc} ->
[dir, amt, _] = String.split(line)
amt = String.to_integer(amt)
{dy, dx} = @deltas[dir]
{next, acc} =
for _ <- 0..(amt - 1), reduce: {curr, acc} do
{{y, x}, acc} ->
next = {y + dy, x + dx}
acc = MapSet.put(acc, next)
{next, acc}
end
{next, acc}
end
map
end
def bounds(map) do
{{y0, _}, {y1, _}} = Enum.min_max(map)
{{_, x0}, {_, x1}} = Enum.min_max_by(map, fn {_, x} -> x end)
{{y0, x0}, {y1, x1}}
end
def print(map) do
{{y0, x0}, {y1, x1}} = bounds(map)
for y <- y0..y1 do
for x <- x0..x1 do
char = if MapSet.member?(map, {y, x}), do: "#", else: "."
IO.write(char)
end
IO.puts("")
end
end
def interior(map) do
{{y0, x0}, {y1, x1}} = bounds(map)
{{y0, x0}, {y1, x1}} = bbox = {{y0 - 1, x0 - 1}, {y1 + 1, x1 + 1}}
exterior = flood(map, bbox)
(y1 - y0 + 1) * (x1 - x0 + 1) - MapSet.size(exterior)
end
def flood(map, {start, _} = bbox), do: flood(map, bbox, [start], MapSet.new())
def flood(_, _, [], explored), do: explored
def flood(map, {{y0, x0}, {y1, x1}} = bbox, [{y, x} = next | frontier], explored) do
explored = MapSet.put(explored, next)
neighbors =
@deltas
|> Map.values()
|> Enum.map(fn {dy, dx} -> {y + dy, x + dx} end)
|> Enum.reject(fn {y, x} = neighbor ->
y < y0 or y > y1 or x < x0 or x > x1 or
MapSet.member?(explored, neighbor) or
MapSet.member?(map, neighbor)
end)
frontier = neighbors ++ frontier
flood(map, bbox, frontier, explored)
end
end
Part1.dig(input)
|> Part1.interior()
Part 2
defmodule Part2 do
@deltas %{
"0" => {0, 1},
"1" => {1, 0},
"2" => {0, -1},
"3" => {-1, 0}
}
def dig(input) do
origin = [0, 0]
{points, _} =
input
|> String.split("\n", trim: true)
|> Enum.map_reduce(origin, fn line, [y, x] ->
[_, <>] =
String.split(line, ~r/[()#]/, trim: true)
distance = String.to_integer(distance, 16)
{dy, dx} = @deltas[direction]
next = [y + distance * dy, x + distance * dx]
{next, next}
end)
segments =
[origin | points]
|> Enum.chunk_every(2, 1, :discard)
area =
segments
|> Enum.map(fn [[y1, x1], [y2, x2]] -> y1 * x2 - x1 * y2 end)
|> Enum.sum()
|> abs()
|> div(2)
perimeter =
segments
|> Enum.map(fn [[y1, x1], [y2, x2]] -> abs(y2 - y1) + abs(x2 - x1) end)
|> Enum.sum()
|> div(2)
answer = area + perimeter + 1
tensor =
segments
|> Nx.tensor(type: :f64)
area_nx =
tensor
|> Nx.LinAlg.determinant()
|> Nx.sum()
|> Nx.abs()
|> Nx.divide(2)
perimeter_nx =
Nx.abs(Nx.subtract(tensor[[.., 0, 0]], tensor[[.., 1, 0]]))
|> Nx.add(Nx.abs(Nx.subtract(tensor[[.., 0, 1]], tensor[[.., 1, 1]])))
|> Nx.sum()
|> Nx.divide(2)
|> Nx.add(1)
answer_nx =
area_nx
|> Nx.add(perimeter_nx)
|> Nx.as_type(:u64)
|> Nx.to_number()
{answer, answer_nx}
end
end
t = Part2.dig(input)