Advent of Code 2024 - Day 05
Mix.install([
{:req, "~> 0.5"},
{:benchee, "~> 1.3"}
])
Input
opts = [headers: [{"cookie", "session=#{System.fetch_env!("LB_AOC_SESSION")}"}]]
puzzle_input = Req.get!("https://adventofcode.com/2024/day/5/input", opts).body
[rules, pages] = String.split(puzzle_input, "\n\n")
rules =
rules
|> String.split("\n", trim: true)
|> Enum.map(fn line ->
line
|> String.split("|")
|> Enum.map(&String.to_integer/1)
|> List.to_tuple()
end)
pages =
pages
|> String.split("\n", trim: true)
|> Enum.map(fn line ->
line
|> String.split(",")
|> Enum.map(&String.to_integer/1)
end)
Helpers
rules_by_second = Enum.group_by(rules, &elem(&1, 1))
valid_page? = fn page ->
range = 0..(length(page) - 1)
Enum.reduce_while(range, {page, :valid}, fn _index, {[head | tail], _status} ->
case Map.get(rules_by_second, head, []) do
[] ->
{:cont, {tail, :valid}}
applicable_rules ->
rule_numbers = Enum.map(applicable_rules, &elem(&1, 0)) |> MapSet.new()
remaining = MapSet.new(tail)
if MapSet.disjoint?(rule_numbers, remaining) do
{:cont, {tail, :valid}}
else
{:halt, {tail, :invalid}}
end
end
end)
|> elem(1)
|> Kernel.==(:valid)
end
get_middle_page_number = fn page ->
Enum.at(page, div(length(page), 2))
end
Section
puzzle_1 = fn ->
Enum.reduce(pages, 0, fn el, acc ->
add = valid_page?.(el) && get_middle_page_number.(el) || 0
add + acc
end)
end
puzzle_1.()
Puzzle 2
sort_by_rules = fn pages, rules ->
Enum.sort(pages, fn a, b ->
# if rule {a, b} exists we need to return false to put a before b
not Enum.member?(rules, {a, b})
end)
end
puzzle_2 = fn ->
Enum.reduce(pages, 0, fn el, acc ->
if valid_page?.(el) do
acc
else
sorted_mid = sort_by_rules.(el, rules) |> get_middle_page_number.()
acc + sorted_mid
end
end)
end
puzzle_2.()
Benchmarks
Benchee.run(%{
"puzzle_1" => fn -> puzzle_1.() end,
"puzzle_2" => fn -> puzzle_2.() end
})
|Name | ips| average| deviation| median| 99th %| ——–| —– | ——–| ——–| ——– |——–| |puzzle_1| 27.26| 36.68 ms| ±7.41%| 35.79 ms| 53.45 ms| |puzzle_2| 20.68| 48.35 ms| ±0.68%| 48.25 ms| 49.55 ms|