Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Advent of Code 2024 - Day 05

2024/05.livemd

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|