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

Advent of Code - Day 5

2024/05.livemd

Advent of Code - Day 5

Mix.install([
  {:kino_aoc, "~> 0.1"}
])

Problem

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

I have yet to complete part 2. I feel it could be done with topological sort of rules, but wanted to find another solution.

{:ok, puzzle_input} =
  KinoAOC.download_puzzle("2024", "5", System.fetch_env!("LB_AOC_SESSION"))
defmodule Day5 do  

  def parse_rules(rules) do
    rules
    |> String.split(["\n", "|"], trim: true)
    |> Enum.map(&String.to_integer/1)
    |> Enum.chunk_every(2)
    |> Enum.reduce(%{}, fn [a, b], map ->
      map
      |> Map.update(a, MapSet.new([b]), &MapSet.put(&1, b))
      |> Map.put_new(b, MapSet.new())
    end)
  end
  
  def parse(str) do
    [rules, updates] = String.split(str, "\n\n")

    updates = String.split(updates, "\n")
      |> Enum.map(fn x -> String.split(x, ",") |> Enum.map(&String.to_integer/1) end)
    
    rules = parse_rules(rules)

    {rules, updates}
  end
    
  def correct_order?([], _), do: true

  def correct_order?([page | pages], rules) do
    rule = Map.fetch!(rules, page)
    Enum.all?(pages, &Enum.member?(rule, &1)) and correct_order?(pages, rules)
  end

  def add_middle_pages(updates) do
    Enum.reduce(updates, 0, fn x, acc ->
      acc + Enum.at(x, div(length(x),2))
    end)
  end

  def solve_part1(str) do
    {rules, updates } = parse(str)
    Enum.filter(updates, fn pages ->
      correct_order?(pages, rules)
    end)
    |> add_middle_pages()
  end

  def sort(list, rules) do
    Enum.reduce_while(list, list, fn _, current_list ->
      sorted = do_sort(current_list, [], rules)

      if sorted == current_list do
        {:halt, sorted}
      else
        {:cont, sorted}
      end
    end)
  end

  def do_sort([], acc, _), do: acc

  def do_sort([head], acc, _), do: acc ++ [head]

  def do_sort([first, second | tail], acc, rules) do
    if correct_order?([first, second], rules) do
      do_sort([second | tail], acc ++ [first], rules)
    else
      do_sort([first | tail], acc ++ [second], rules)
    end
  end

  def solve_part2(str) do
    { rules, updates } = parse(str)
    updates
    |> Enum.reject(&correct_order?(&1, rules))
    |> Enum.map(&sort(&1, rules))
    |> add_middle_pages()
  end
end
example_input = "47|53
97|13
97|61
97|47
75|29
61|13
75|53
29|13
97|29
53|29
61|53
97|53
61|29
47|13
75|47
97|75
47|61
75|61
47|29
75|13
53|13

75,47,61,53,29
97,61,53,29,13
75,29,13
75,97,47,61,53
61,13,29
97,13,75,29,47"
Day5.solve_part1(puzzle_input)
Day5.solve_part2(puzzle_input)