Advent of Code 2023
Mix.install([
{:req, "~> 0.3.2"}
])
Day 18
input =
"https://adventofcode.com/2023/day/18/input"
|> Req.get!(headers: [cookie: "session=#{System.get_env("AOC_COOKIE")}"])
|> Map.get(:body)
sample = """
R 6 (#70c710)
D 5 (#0dc571)
L 2 (#5713f0)
D 2 (#d2c081)
R 2 (#59c680)
D 2 (#411b91)
L 5 (#8ceee2)
U 2 (#caa173)
L 1 (#1b58a2)
U 2 (#caa171)
R 2 (#7807d2)
U 3 (#a77fa3)
L 2 (#015232)
U 2 (#7a21e3)
"""
defmodule A do
def parse(input) do
input
|> String.split("\n", trim: true)
|> Enum.map(&String.split(&1, " "))
|> Enum.map(fn [dir, n, c] ->
[_, n2, dir2] = Regex.run(~r/^\(#(.*)(.)\)$/, c)
{n2, ""} = Integer.parse(n2, 16)
dir2 =
case dir2 do
"0" -> "R"
"1" -> "D"
"2" -> "L"
"3" -> "U"
end
{dir, String.to_integer(n), {dir2, n2}}
end)
end
# We need the vertices in anti-clockwise order for the shoelace algorithm.
# Building the list naturally in Elixir does that for us.
defp construct_vertices(instructions) do
instructions
|> Enum.map(fn
{"R", n} -> {n, 0}
{"L", n} -> {-n, 0}
{"D", n} -> {0, n}
{"U", n} -> {0, -n}
end)
|> Enum.reduce({{0, 0}, []}, fn {dx, dy}, {{x, y}, vertices} ->
p = {x + dx, y + dy}
{p, [p | vertices]}
end)
|> elem(1)
end
defp area(vertices) do
# we need this to wrap around
next_vertices = tl(vertices) ++ [hd(vertices)]
Enum.zip(vertices, next_vertices)
|> Enum.map(fn {{x1, y1}, {x2, y2}} ->
x1 * y2 - y1 * x2
end)
|> Enum.sum()
|> abs()
|> div(2)
end
# https://www.101computing.net/the-shoelace-algorithm/
defp shoelace(instructions) do
instructions
|> construct_vertices()
|> area()
end
defp perimeter(instructions) do
instructions
|> Enum.map(fn {_d, n} -> n end)
|> Enum.sum()
end
# Pick's theorem -> A = i + b/2 - 1
# where: A = area, i = interior points, b = perimeter
#
# to get the number of points, rearrange to solve for i:
# i = A - b/2 + 1
# then add b (interior points + boundary points)
# i + b = A + b/2 + 1
defp picks_theorem(instructions) do
area =
instructions
|> shoelace()
perimeter =
instructions
|> perimeter()
area + div(perimeter, 2) + 1
end
def part1(input) do
input
|> parse()
|> then(&Enum.map(&1, fn {d, n, _c} -> {d, n} end))
|> picks_theorem()
end
def part2(input) do
input
|> parse()
|> then(&Enum.map(&1, fn {_d, _n, c} -> c end))
|> picks_theorem()
end
end
Part 1
input
|> A.part1()
Part 2
input
|> A.part2()