Day 8: Haunted Wasteland – Advent of Code 2023
input =
File.stream!("/Users/pw/src/weiland/adventofcode/2023/input/08.txt")
|> Stream.map(&String.trim/1)
test_input = "RL
AAA = (BBB, CCC)
BBB = (DDD, EEE)
CCC = (ZZZ, GGG)
DDD = (DDD, DDD)
EEE = (EEE, EEE)
GGG = (GGG, GGG)
ZZZ = (ZZZ, ZZZ)" |> String.split("\n")
test_input2 = "LLR
AAA = (BBB, BBB)
BBB = (AAA, ZZZ)
ZZZ = (ZZZ, ZZZ)" |> String.split("\n")
test_input3 = "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)" |> String.split("\n")
Parsing
parse = fn input ->
directions =
input
|> Enum.at(0)
|> String.graphemes()
network =
input
|> Stream.drop(2)
|> Stream.map(fn line -> Regex.scan(~r/\w\w\w/, line) |> Enum.map(&Enum.at(&1, 0)) end)
|> Enum.reduce(Map.new(), fn [key | value], map -> Map.put(map, key, value) end)
{directions, network}
end
parse.(test_input) |> elem(1) |> Enum.to_list()
Part One
defmodule PartOne do
def find_index(directions, i) do
max_count = Enum.count(directions)
if i >= max_count, do: rem(i, max_count), else: i
end
def find_next(network, directions, key, i) do
if String.ends_with?(key, "Z") do
i
else
dir = Enum.at(directions, find_index(directions, i))
[l, r] = Map.get(network, key)
next = if dir == "R", do: r, else: l
find_next(network, directions, next, i + 1)
end
end
end
part_one = fn input ->
{directions, network} =
input
|> parse.()
PartOne.find_next(network, directions, "AAA", 0)
end
IO.inspect(
part_one.(test_input) == 2 &&
part_one.(test_input2) == 6,
label: "Smoke test part one"
)
part_one.(input)
Part Two
defmodule MathUtils do
def lcm(curr, nil), do: curr
def lcm(0, 0), do: 0
def lcm(a, b), do: round(a * b / Integer.gcd(a, b))
end
part_two = fn input ->
{directions, network} =
input
|> parse.()
network
|> Map.filter(&String.ends_with?(elem(&1, 0), "A"))
|> Enum.map(&elem(&1, 0))
|> Enum.map(&PartOne.find_next(network, directions, &1, 0))
|> Enum.reduce(&MathUtils.lcm/2)
end
IO.inspect(
part_two.(test_input3) == 6 &&
part_two.(test_input2) == 6 &&
part_two.(test_input) == 2,
label: "Smoke test part two"
)
part_two.(input)