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 |