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

Day 9

day9.livemd

Day 9

Setup

input =
  System.fetch_env!("LB_AOC_DIR")
  |> Path.join("data/day9.txt")
  |> File.read!()

nil
nil

Solve

defmodule Day9 do
  defp checksum(filesystem) do
    for {current, position} <- Enum.with_index(filesystem), current != nil, reduce: 0 do
      sum ->
        sum + current * position
    end
  end

  @doc ~S"""
  iex> Day9.part1("2333133121414131402")
  1928
  """
  def part1(input) do
    fragmented =
      Enum.flat_map(Enum.with_index(String.codepoints(input) |> Enum.chunk_every(2)), fn
        {[files, "0"], index} ->
          for _i <- 0..(String.to_integer(files) - 1) do
            index
          end

        {[files, free], index} ->
          files =
            for _i <- 0..(String.to_integer(files) - 1) do
              index
            end

          free =
            for _i <- 0..max(0, String.to_integer(free) - 1) do
              nil
            end

          files ++ free

        {[files], index} ->
          for _i <- 0..(String.to_integer(files) - 1) do
            index
          end
      end)

    len = length(fragmented)

    fragmented
    |> Enum.reverse()
    |> Enum.with_index()
    |> Enum.reduce_while(fragmented, fn
      {nil, _index}, acc ->
        {:cont, acc}

      {block, index}, acc ->
        first_empty = Enum.find_index(acc, &amp;is_nil/1)

        if first_empty >= len - index do
          {:halt, acc}
        else
          {:cont,
           acc
           |> List.update_at(len - index - 1, fn ^block -> nil end)
           |> List.update_at(first_empty, fn nil -> block end)}
        end
    end)
    |> Enum.filter(&amp; &amp;1)
    |> checksum()
  end

  @doc ~S"""
  iex> Day9.part2("2333133121414131402")
  2858
  """
  def part2(input) do
    fragged =
      Enum.flat_map(Enum.with_index(String.codepoints(input) |> Enum.chunk_every(2)), fn
        {[files, "0"], index} ->
          [{String.to_integer(files), index}]

        {[files, free], index} ->
          [{String.to_integer(files), index}, {String.to_integer(free), nil}]

        {[files], index} ->
          [{String.to_integer(files), index}]
      end)

    fragged
    |> Enum.reverse()
    |> Enum.reduce_while(fragged, fn
      {_amount, nil}, acc ->
        {:cont, acc}

      {num, _position} = current, acc ->
        case Enum.split_while(
               acc,
               fn
                 {amount, nil} when amount >= num ->
                   false

                 _ ->
                   true
               end
             ) do
          {^acc, []} ->
            {:cont, acc}

          {head, [{num_nil, nil} | tail]} ->
            target =
              if num == num_nil, do: [], else: [{num_nil - num, nil}]

            new_fragged =
              head ++ [current] ++ target ++ tail

            replace_index =
              length(new_fragged) - Enum.find_index(Enum.reverse(new_fragged), &amp;(&amp;1 == current)) -
                1

            {:cont,
             List.update_at(
               new_fragged,
               replace_index,
               fn ^current ->
                 {num, nil}
               end
             )
             |> Enum.filter(&amp; &amp;1)}
        end
    end)
    |> Enum.flat_map(fn
      {amount, value} ->
        for _i <- 0..(amount - 1) do
          value
        end
    end)
    |> checksum()
  end
end
{:module, Day9, <<70, 79, 82, 49, 0, 0, 28, ...>>, {:part2, 1}}