🎄 Year 2024 🔔 Day 09
Mix.install([
  {:arrays, "~> 2.1"}
])
Setup
elems =
  File.read!("#{__DIR__}/../../../inputs/2024/day09.txt")
  # "2333133121414131402"
  |> String.trim()
  |> String.graphemes()
  |> Enum.with_index(fn e, i ->
    asdf = if rem(i, 2) == 0, do: :file, else: :free
    {String.to_integer(e), asdf}
  end)
  |> Enum.flat_map_reduce(0, fn
    {count, :file}, acc ->
      {List.duplicate(acc, count), acc + 1}
    {count, :free}, acc ->
      {List.duplicate(nil, count), acc}
  end)
  |> elem(0)
{file_indexes, max_i} =
  File.read!("#{__DIR__}/../../../inputs/2024/day09.txt")
  # "2333133121414131402"
  |> String.trim()
  |> String.graphemes()
  |> Enum.with_index(fn e, i ->
    file_n = if rem(i, 2) == 0, do: div(i, 2), else: nil
    {String.to_integer(e), file_n}
  end)
  |> Enum.map_reduce(0, fn {count, file_n}, start_i ->
    {{file_n, start_i, count}, start_i + count}
  end)
  |> then(fn {elems, max_i} -> {elems, max_i - 1} end)
Part 1
defmodule Helper do
  def solve(elems) do
    elems = Enum.with_index(elems)
    reversed = elems |> Enum.reverse()
    solve(elems, reversed, [])
  end
  defp solve([], _, result), do: Enum.reverse(result)
  defp solve(alist = [{a, ai} | atail], blist = [{b, bi} | btail], result) do
    cond do
      b == nil -> solve(alist, btail, result)
      bi < ai -> solve(atail, blist, [nil | result])
      a != nil -> solve(atail, blist, [a | result])
      a == nil -> solve(atail, btail, [b | result])
    end
  end
end
Helper.solve(elems)
|> Enum.filter(fn e -> e != nil end)
|> Enum.with_index(fn e, i -> e * i end)
|> Enum.sum()
Part 2
defmodule Helper2 do
  def step(elem_array, nil_indexes, to_move)
  def step(elem_array, [{_, _, _count = 0} | nil_indexes], to_move) do
    step(elem_array, nil_indexes, to_move)
  end
  def step(elem_array, nil_indexes, {file_n, start_i, move_count}) do
    case find_nil_index(nil_indexes, 0, move_count, start_i) do
      {{nil, nil_start_i, nil_size}, nil_list_i} ->
        new_nil_indexes =
          List.update_at(
            nil_indexes,
            nil_list_i,
            fn _ -> {nil, nil_start_i + move_count, nil_size - move_count} end
          )
        nil_end_i = nil_start_i + move_count - 1
        nil_range = nil_start_i..nil_end_i
        end_i = start_i + move_count - 1
        move_range = start_i..end_i
        new_elem_array =
          elem_array
          |> then(
            &Enum.reduce(nil_range, &1, fn ui, elem_array ->
              put_in(elem_array[ui], file_n)
            end)
          )
          |> then(
            &Enum.reduce(move_range, &1, fn ui, elem_array ->
              put_in(elem_array[ui], nil)
            end)
          )
        {new_elem_array, new_nil_indexes}
      nil ->
        {elem_array, nil_indexes}
    end
  end
  defp find_nil_index(nil_indexes, i, min_size, max_i) do
    [nil_index = {nil, start_i, size} | tail] = nil_indexes
    cond do
      start_i > max_i ->
        nil
      size >= min_size ->
        {nil_index, i}
      true ->
        find_nil_index(tail, i + 1, min_size, max_i)
    end
  end
end
elem_array = elems |> Enum.into(Arrays.new())
nil_indexes = Enum.take_every(Enum.drop(file_indexes, 1), 2)
file_indexes = Enum.take_every(file_indexes, 2) |> Enum.reverse()
file_indexes
|> Enum.reduce(
  {elem_array, nil_indexes},
  fn file_loc, {elem_array, nil_indexes} ->
    Helper2.step(elem_array, nil_indexes, file_loc)
  end
)
|> elem(0)
|> Enum.with_index(fn file_n, i -> {file_n, i} end)
|> Enum.filter(fn {char, _} -> char != nil end)
|> Enum.map(fn {char, i} -> char * i end)
|> Enum.sum()