2023 Day 08
Mix.install([
{:req, "~> 0.4.5"},
{:vega_lite, "~> 0.1.8"},
{:kino_vega_lite, "~> 0.1.11"}
])
alias VegaLite, as: Vl
defmodule AOC do
@aoc_session System.fetch_env!("LB_AOC_SESSION")
def day(day, year) when day in 1..25 do
headers = [cookie: "session=#{@aoc_session}"]
req = Req.new(base_url: "https://adventofcode.com/#{year}/day/#{day}", headers: headers)
input = Req.get!(req, url: "/input").body
if input =~ "Please don't repeatedly request this endpoint before it unlocks",
do: {:error, "Day #{day} hasn't been unlocked yet"},
else: {:ok, %{req: req, input: input}}
end
def submit(answer, %{req: req}, part \\ :part_1) when part in [:part_1, :part_2] do
part = if part == :part_2, do: 2, else: 1
if !part_already_completed?(part, req) do
body = Req.post!(req, url: "/answer", form: [level: part, answer: answer]).body
cond do
body =~ "That's the right answer" -> {:correct, answer}
body =~ "your answer is too low" -> {:too_low, answer}
body =~ "your answer is too high" -> {:too_high, answer}
:else -> {:incorrect, answer}
end
else
{:already_completed_part, answer}
end
end
defp part_already_completed?(part, req) do
body = Req.get!(req).body
cond do
body =~ "Both parts of this puzzle are complete!" -> true
body =~ "The first half of this puzzle is complete!" -> part == 1
:else -> false
end
end
def nums(str) do
Regex.scan(~r/\d+/, str) |> Enum.map(fn [x] -> String.to_integer(x) end)
end
end
{:ok, day} = AOC.day(8, 2023)
Part 1
test_input_1 = """
RL
AAA = (BBB, CCC)
BBB = (DDD, EEE)
CCC = (ZZZ, GGG)
DDD = (DDD, DDD)
EEE = (EEE, EEE)
GGG = (GGG, GGG)
ZZZ = (ZZZ, ZZZ)
"""
test_input_2 = """
LLR
AAA = (BBB, BBB)
BBB = (AAA, ZZZ)
ZZZ = (ZZZ, ZZZ)
"""
defmodule P1 do
def solve(input) do
dirs = input |> String.split("\n", trim: true) |> hd() |> String.graphemes()
nodes =
input
|> String.split("\n", trim: true)
|> Enum.drop(1)
|> Enum.reduce(%{}, fn str, nodes ->
[_, key, left, right] = Regex.run(~r/^(...) = .(...), (...).$/, str)
Map.put(nodes, key, {left, right})
end)
Enum.reduce_while(Stream.cycle(dirs), {0, "AAA"}, fn
_dir, {steps, "ZZZ"} -> {:halt, steps}
"L", {steps, pos} -> {:cont, {steps + 1, elem(nodes[pos], 0)}}
"R", {steps, pos} -> {:cont, {steps + 1, elem(nodes[pos], 1)}}
end)
end
end
day.input |> P1.solve()
Part 2
test_input = """
LR
11A = (11B, XXX)
11B = (XXX, 11Z)
11Z = (11B, XXX)
22A = (22B, XXX)
22B = (22C, 22C)
22C = (22Z, 22Z)
22Z = (22B, 22B)
XXX = (XXX, XXX)
"""
defmodule P2 do
def solve(input) do
dirs = input |> String.split("\n", trim: true) |> hd() |> String.graphemes()
nodes =
input
|> String.split("\n", trim: true)
|> Enum.drop(1)
|> Enum.reduce(%{}, fn str, nodes ->
[_, key, left, right] = Regex.run(~r/^(...) = .(...), (...).$/, str)
Map.put(nodes, key, {left, right})
end)
nodes
|> Map.keys()
|> Enum.filter(&String.ends_with?(&1, "A"))
|> Enum.map(&steps_to_z(nodes, dirs, &1))
|> Enum.reduce(1, fn a, b -> lcm(a, b) end)
end
def steps_to_z(nodes, dirs, pos) do
Enum.reduce_while(Stream.cycle(dirs), {0, pos}, fn dir, {steps, pos} ->
cond do
String.ends_with?(pos, "Z") -> {:halt, steps}
dir == "L" -> {:cont, {steps + 1, elem(nodes[pos], 0)}}
dir == "R" -> {:cont, {steps + 1, elem(nodes[pos], 1)}}
end
end)
end
def lcm(0, 0), do: 0
def lcm(a, b), do: round(a * b / Integer.gcd(a, b))
end
day.input |> P2.solve()