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

Advent of Code 2024/9

2024/9.livemd

Advent of Code 2024/9

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

Section

input = Kino.Input.textarea("input")
defmodule AoC2024_9 do
  def parse(input) do
    Kino.Input.read(input)
    |> String.split("", trim: true)
    |> Enum.map(fn el -> String.to_integer(el) end)
  end

  def defragment(file, []), do: { file, []}
  def defragment(file, free_blocks) do
    block_length = length(file[:blocks])
    usable_free_blocks = Enum.filter(free_blocks, fn block -> block < Enum.max(file[:blocks]) end)
    free_length = length(usable_free_blocks)

    IO.inspect({file[:index], MapSet.new(file[:blocks]), usable_free_blocks})
    
    cond do
      free_length < block_length ->
        {_, blocks_to_keep} = Enum.split(file[:blocks] |> Enum.reverse, free_length)
        IO.inspect({usable_free_blocks, blocks_to_keep})
        { Map.merge(file, %{blocks: usable_free_blocks ++ blocks_to_keep}), [] }
      true ->
        {file_blocks, free_blocks} = Enum.split(usable_free_blocks, block_length)
        { Map.merge(file, %{blocks: file_blocks}), free_blocks }  
    end
  end

  def defragment_file(file, []), do: { file, [] }
  def defragment_file(file, free_blocks) do
    block_length = length(file[:blocks])
    free_blocks = Enum.filter(free_blocks, fn block -> Enum.max(block) < Enum.max(file[:blocks]) end)
    index = Enum.find_index(free_blocks, fn block -> length(block) >= block_length end)
    cond do
      index == nil -> 
        { file, free_blocks }
      index == length(free_blocks) - 1 ->
        {file_blocks, remaining_free_blocks} = Enum.split(Enum.at(free_blocks, index), block_length)
        IO.inspect(remaining_free_blocks)
        {
          Map.merge(file, %{blocks: file_blocks}), 
          Enum.slice(free_blocks, 0..index-1)
          |> Enum.concat([remaining_free_blocks])
          |> Enum.filter(fn el -> length(el) > 0 end)
        } 
      index > 0 ->
        {file_blocks, remaining_free_blocks} = Enum.split(Enum.at(free_blocks, index), block_length)
        {
          Map.merge(file, %{blocks: file_blocks}), 
          Enum.slice(free_blocks, 0..index-1)
          |> Enum.concat([remaining_free_blocks]) 
          |> Enum.concat(Enum.slice(free_blocks, (index + 1)..(length(free_blocks) - 1)))
          |> Enum.filter(fn el -> length(el) > 0 end)
        }
      index == 0 ->
        {file_blocks, remaining_free_blocks} = Enum.split(Enum.at(free_blocks, index), block_length)
        {
          Map.merge(file, %{blocks: file_blocks}), 
          [remaining_free_blocks] 
          |> dbg
          |> Enum.concat(Enum.slice(free_blocks, 1..length(free_blocks) - 1))
          |> Enum.filter(fn el -> length(el) > 0 end)
        }
    end
  end

  def part_1(input) do
    {fat, _} = input
    |> parse
    |> Enum.chunk_every(2,2, [0])
    |> Enum.with_index()
    |> Enum.map(fn {[file, empty], index} -> %{ index: index, file: file, empty: empty } end)
    |> Enum.map_reduce(0, fn file, acc -> { Map.merge(file, %{blocks: Enum.map(0..file[:file] - 1, fn x -> x + acc end)}), acc + file[:file] + file[:empty]} end)
    
    blocks = fat
    |> Enum.flat_map(fn file -> file[:blocks] end)

    free_blocks = Range.to_list(1..Enum.max(blocks)) -- blocks
    
    {defragmented_fat, _} = fat
    |> Enum.reverse
    |> dbg()
    |> Enum.map_reduce(free_blocks, fn file, acc -> defragment(file, acc) end)

    defragmented_fat
    |> Enum.flat_map(fn %{index: index, blocks: blocks} ->
      Enum.map(blocks, fn block -> {block, index} end)
    end)
    |> Enum.sort_by(&amp;(elem(&amp;1,0)))
    |> Enum.map(&amp;(elem(&amp;1,0) * elem(&amp;1,1)))
    |> dbg()
    |> Enum.sum
  end

  def part_2(input) do
    {fat, _} = input
    |> parse
    |> Enum.chunk_every(2,2, [0])
    |> Enum.with_index()
    |> Enum.map(fn {[file, empty], index} -> %{ index: index, file: file, empty: empty } end)
    |> Enum.map_reduce(0, fn file, acc -> { Map.merge(file, %{blocks: Enum.map(0..file[:file] - 1, fn x -> x + acc end)}), acc + file[:file] + file[:empty]} end)
    
    blocks = fat
    |> Enum.flat_map(fn file -> file[:blocks] end)

    free_blocks = 
      (Range.to_list(1..Enum.max(blocks)) -- blocks)
      |> Enum.chunk_every(2,1, [-1])
      |> Enum.chunk_while([], fn [first, second], acc -> 
          cond do
            first + 1 != second ->
              {:cont, acc ++ [first], []}
            true ->
              {:cont, acc ++ [first], }
          end
        end, fn
          [] -> {:cont, []}
          acc -> {:cont, Enum.reverse(acc), []}
        end)

    {defragmented_fat, _} = fat
    |> Enum.reverse
    |> dbg()
    |> Enum.map_reduce(free_blocks, fn file, acc -> defragment_file(file, acc) end)

    defragmented_fat
    |> Enum.flat_map(fn %{index: index, blocks: blocks} ->
      Enum.map(blocks, fn block -> {block, index} end)
    end)
    |> Enum.sort_by(&amp;(elem(&amp;1,0)))
    |> Enum.map(&amp;(elem(&amp;1,0) * elem(&amp;1,1)))
    |> dbg()
    |> Enum.sum
  end
end
AoC2024_9.part_1(input)
AoC2024_9.part_2(input)