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

Day 5

day5.livemd

Day 5

Mix.install([
  {:kino, "~> 0.14.2"}
])

Part 1

https://adventofcode.com/2024/day/5

[rules, updates] = Kino.FS.file_path("day5_input.txt")
  |> File.read!()
  |> String.trim()
  |> String.split("\n\n")
  |> Enum.map(fn section ->
    String.trim(section)
    |> String.split("\n")
  end)

rules = Enum.map(rules, fn pair ->
  String.split(pair, "|") 
  |> Enum.map(&String.to_integer/1)
  |> List.to_tuple()
end)

updates = Enum.map(updates, fn group ->
  String.split(group, ",")
  |> Enum.map(&String.to_integer/1)
end)
defmodule UpdateValidator do
  @doc """
  Check if all {x before y} rules pass for the given update.
  """
  def is_valid_update?(update, rules) do
    not Enum.any?(rules, fn {x, y} -> not is_before?(x, y, update) end)
  end

  @doc """
  Check if x occurs before y in the given list
  """
  def is_before?(x, y, list = [first | rest]) do
    if x in list and y in list do
      if y == first, do: false, else: is_before?(x, y, rest)
    else
      true
    end
  end
end

start = System.monotonic_time(:microsecond)

sum_middle_numbers = fn updates -> updates
  |> Enum.map(& {length(&1) |> Integer.floor_div(2), &1})  # Get index of middle num.
  |> Enum.map(fn {middle_index, update} ->
      Enum.at(update, middle_index)
    end)  # Extract value at index
  |> Enum.sum()
end

valid_updates = Enum.filter(updates, fn update ->
  UpdateValidator.is_valid_update?(update, rules)
end)

result = sum_middle_numbers.(valid_updates)
IO.puts("Result: #{result}")

elapsed = System.monotonic_time(:microsecond) - start
IO.puts "Finished in #{elapsed / 1000}ms"

Part 2

defmodule UpdateFixer do
  import UpdateValidator

  @doc """
  Recursively applies fixes until no more
  modifications are made (some fixes break other rules).
  """
  def fix_update(update = [ _ | _ ], rules) do
    fix_update({true, update}, rules)
  end
  def fix_update({true, update}, rules) do
    do_fix_update(update, rules) |> fix_update(rules)
  end
  def fix_update({false, update}, _), do: update

  # Applies fixes for all rules by swapping values.
  defp do_fix_update(update, rules, modified? \\ false)
  defp do_fix_update(update, [], modified?), do: {modified?, update}
  defp do_fix_update(update, [{x, y} | rules], modified?) do
    {update_, modified?} = if not is_before?(x, y, update) do
      # Swap values if this rule failed validation.
      update_ = update
        |> Enum.map(& {&1})  # Pack into tuple to make compat. with keyreplace
        |> List.keyreplace(x, 0, {y}) 
        |> List.keyreplace(y, 0, {x})
        |> Enum.map(fn {val} -> val end)
      {update_, true}
    else
      # Otherwise, pass update as-is.
      {update, modified?}
    end
    # Check next rule.
    do_fix_update(update_, rules, modified?)
  end
end

start = System.monotonic_time(:microsecond)

result = updates -- valid_updates  # Invalid updates
  |> Enum.map(fn update -> UpdateFixer.fix_update(update, rules) end)
  |> sum_middle_numbers.()

IO.puts("Result: #{result}")

elapsed = System.monotonic_time(:microsecond) - start
IO.puts "Finished in #{elapsed / 1000}ms"