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

Day 07

d07.livemd

Day 07

Solution

defmodule D07 do
  def parse_input(input), do: Enum.reduce(input, {%{}, [], nil}, &parse_line/2) |> elem(0)

  def parse_line("$ cd ..", {dir_sizes, [_curr_dir | path], _state}), do: {dir_sizes, path, nil}
  def parse_line("$ ls", {dir_sizes, path, nil}), do: {dir_sizes, path, :ls}

  def parse_line(<<"$ cd "::binary, target_dir::binary>>, {dir_sizes, path, _state}),
    do: {dir_sizes, [target_dir | path], nil}

  def parse_line(<<"dir "::binary, _dir_name::binary>>, {dir_sizes, path, :ls}),
    do: {dir_sizes, path, :ls}

  def parse_line(file_size_and_name, {dir_sizes, path, :ls}) do
    {file_size, ""} = String.split(file_size_and_name) |> hd() |> Integer.parse()

    {dir_sizes, []} =
      Enum.reduce(
        path,
        {dir_sizes, path},
        fn dir_name, {dir_sizes, [dir_name | rest] = full_path} ->
          full_dir_name = Enum.reverse(full_path) |> Enum.join("/") |> String.replace("//", "/")
          dir_sizes = Map.update(dir_sizes, full_dir_name, file_size, &amp;(&amp;1 + file_size))
          {dir_sizes, rest}
        end
      )

    {dir_sizes, path, :ls}
  end

  @p1_limit 100_000

  def p1(dir_sizes) when is_map(dir_sizes) do
    dir_sizes
    |> Enum.map(fn {_dir_name, size} -> if size <= @p1_limit, do: size, else: 0 end)
    |> Enum.sum()
  end

  @total_disk_space 70_000_000
  @required_free_space 30_000_000

  def p2(dir_sizes) when is_map(dir_sizes) do
    curr_free_space = @total_disk_space - dir_sizes["/"]
    need_to_free = @required_free_space - curr_free_space

    dir_sizes
    |> Enum.filter(fn {_dir_name, dir_size} -> dir_size >= need_to_free end)
    |> Enum.sort_by(fn {_dir_name, dir_size} -> dir_size end, :asc)
    |> hd()
    |> elem(1)
  end
end

Tests

ExUnit.start(autorun: false)

defmodule D07Tests do
  use ExUnit.Case, async: true

  def get_test_input() do
    File.read!("inputs/d07_test") |> String.trim_trailing() |> String.split("\n")
  end

  def get_input() do
    File.read!("inputs/d07") |> String.trim_trailing() |> String.split("\n")
  end

  test "p1" do
    test_dir_sizes = get_test_input() |> D07.parse_input()

    assert test_dir_sizes == %{
             "/" => 48_381_165,
             "/a" => 94853,
             "/a/e" => 584,
             "/d" => 24_933_642
           }

    assert D07.p1(test_dir_sizes) == 95437

    dir_sizes = get_input() |> D07.parse_input()
    assert D07.p1(dir_sizes) == 1_447_046
  end

  test "p2" do
    test_dir_sizes = get_test_input() |> D07.parse_input()
    assert D07.p2(test_dir_sizes) == 24_933_642

    dir_sizes = get_input() |> D07.parse_input()
    assert D07.p2(dir_sizes) == 578_710
  end
end

ExUnit.run()