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

Advent 2024 - Day 9

day9.livemd

Advent 2024 - Day 9

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

Setup

input = Kino.Input.textarea("Please paste your input file")
input = input
  |> Kino.Input.read()
  |> String.graphemes()
  |> Enum.map(&(String.to_integer/1))

Part 1

disk_in_chunks = input
|> Enum.with_index()
|> Enum.map(fn {num, index} ->
  value = if rem(index, 2) == 0 do
    div((index), 2)
  else
    nil
  end

  List.duplicate(value, num)
end)
|> List.flatten()
defmodule GParted do
  def defrag(disk) do
    defrag(disk, 0, length(disk) - 1)
  end

  def defrag(disk, current_index, max_index) when current_index > max_index do
    disk
  end

  def defrag(disk, current_index, max_index) do
    if Enum.at(disk, current_index) == nil do
      {val, disk} = List.pop_at(disk, max_index)

      if val == nil do
        defrag(disk, current_index, max_index - 1)
      else
        disk = disk |> List.replace_at(current_index, val)
        defrag(disk, current_index + 1, max_index - 1)
      end
    else
      defrag(disk, current_index + 1, max_index)
    end
  end
end
GParted.defrag(disk_in_chunks)
|> Enum.with_index()
|> Enum.map(fn {pos, index} -> pos * index end)
|> Enum.sum()

Part 2

disk_in_files = input
|> Enum.with_index()
|> Enum.map(fn {num, index} ->
  id = div((index), 2)
  if rem(index, 2) == 0 do
    {:file, id, num}
  else
    {:empty, num}
  end
end)
|> List.flatten()
defmodule FDisk do
  def display(disk) do
    for entity <- disk do
      case entity do
        {:file, id, size} -> String.duplicate(Integer.to_string(id), size)
        {:empty, size} -> String.duplicate(".", size)
      end
    end
    |> Enum.join()
  end

  def manipulate(disk) do
    to_move = disk
      |> Enum.reverse()
      |> Enum.filter(fn
        {:file, _, _} -> true
        _ -> false
      end)

    manipulate(disk, to_move)
  end

  def manipulate(disk, to_move) when length(to_move) == 0 do
    disk
  end

  def manipulate(disk, [{:file, id, size} = file | rest]) do
    # IO.puts(disk |> display())

    {_, file_index} = disk
      |> Enum.with_index()
      |> Enum.find(nil, fn
        {{:file, file_id, _size}, _} when file_id == id -> true
        _ -> false
      end)
  
    empty_space = disk
      |> Enum.with_index()
      |> Enum.find(nil, fn
        {{:empty, gap}, index} when gap >= size and index < file_index -> true
        _ -> false
      end)

    if empty_space == nil do
      manipulate(disk, rest)
    else
      {{:empty, gap}, gap_index} = empty_space

      disk = disk
        |> List.replace_at(file_index, {:empty, size})
        |> List.replace_at(gap_index, file)

      disk = if gap == size do
        disk
      else
        disk |> List.insert_at(gap_index + 1, {:empty, gap - size})
      end

      manipulate(disk, rest)
    end
  end
end
FDisk.manipulate(disk_in_files)
|> Enum.reduce({0, 0}, fn
  {:file, id, size}, {total, index} ->
    total = total + (for x <- index..(index + (size - 1)) do
      IO.inspect({id, x})
      id * x
    end
    |> Enum.sum())
  
    {total, index + size}
  {:empty, size}, {total, index} ->
    {total, index + size}
end)
|> elem(0)