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

Advent of Code 2024 - Day 05

2024/day-05.livemd

Advent of Code 2024 - Day 05

Mix.install([{:kino, github: "livebook-dev/kino"}])

kino_input = Kino.Input.textarea("Please paste your input file: ")

Part 1

input = Kino.Input.read(kino_input)

[ordering, updates] = input
  |> String.split("\n\n", trim: true)
  |> Enum.map(fn list -> String.split(list, "\n", trim: true) end)
  |> then(fn [ordering_str, updates_str] -> 
    [
      ordering_str
      |> Enum.map(fn pair ->
        pair
        |> String.split("|", trim: true)
        |> Enum.map(&String.to_integer/1)
      end)
      |> Enum.reduce(%{}, fn [key, value], acc ->
        Map.update(acc, key, [value], fn existing -> [value | existing] end)
      end)
      |> Enum.into(%{}),

      updates_str
      |> Enum.map(fn update ->
        update
        |> String.split(",", trim: true)
        |> Enum.map(&String.to_integer/1)
      end)
    ]
  end)

updates
|> Enum.filter(fn update ->
  update
  |> Enum.reduce_while([], fn page, acc ->
    page_ordering = Map.get(ordering, page, [])
    
    case Enum.all?(page_ordering, fn later_page -> later_page not in acc end) do
      true -> {:cont, [page | acc]}
      false -> {:halt, false}
    end
  end)
end)
|> Enum.reduce(0, fn update, acc ->
  middle_page = Enum.at(update, div(length(update), 2))
  
  acc + middle_page
end)

Part 2

input = Kino.Input.read(kino_input)

[ordering, updates] = input
  |> String.split("\n\n", trim: true)
  |> Enum.map(fn list -> String.split(list, "\n", trim: true) end)
  |> then(fn [ordering_str, updates_str] -> 
    [
      ordering_str
      |> Enum.map(fn pair ->
        pair
        |> String.split("|", trim: true)
        |> Enum.map(&String.to_integer/1)
      end)
      |> Enum.reduce(%{}, fn [key, value], acc ->
        Map.update(acc, key, [value], fn existing -> [value | existing] end)
      end)
      |> Enum.into(%{}),

      updates_str
      |> Enum.map(fn update ->
        update
        |> String.split(",", trim: true)
        |> Enum.map(&String.to_integer/1)
      end)
    ]
  end)


proper_updates = updates
  |> Enum.filter(fn update ->
    update
    |> Enum.reduce_while([], fn page, acc ->
      page_ordering = Map.get(ordering, page, [])
      
      case Enum.all?(page_ordering, fn later_page -> later_page not in acc end) do
        true -> {:cont, [page | acc]}
        false -> {:halt, false}
      end
    end)
  end)

updates -- proper_updates
|> Enum.map(fn update ->
  update
  |> Enum.reduce([], fn page, reordered_update ->
    page_ordering = Map.get(ordering, page, [])

    index = Enum.find_index(reordered_update, fn p -> !Enum.member?(page_ordering, p) || p == page end)
    if index != :nil do
      List.insert_at(reordered_update, index, page)
    else
      reordered_update ++ [page]
    end
  end)
  |> Enum.reverse()
end)
|> Enum.reduce(0, fn update, acc ->
  middle_page = Enum.at(update, div(length(update), 2))
  
  acc + middle_page
end)