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

Day 7

2022/day_7.livemd

Day 7

Setup

Mix.install([:kino])
input =
  Kino.Input.textarea("Paste input here",
    default: """
    $ cd /
    $ ls
    dir a
    14848514 b.txt
    8504156 c.dat
    dir d
    $ cd a
    $ ls
    dir e
    29116 f
    2557 g
    62596 h.lst
    $ cd e
    $ ls
    584 i
    $ cd ..
    $ cd ..
    $ cd d
    $ ls
    4060174 j
    8033020 d.log
    5626152 d.ext
    7214296 k
    """
  )
lines = Kino.Input.read(input)
defmodule FileSystem do
  @moduledoc """
  Parse a list of commands and produce a list of the full path of 
  each directory and its respective filesize.
  """
  def parse_commands(lines) do
    initial = {"/", %{}}

    lines
    |> String.splitter("\n", trim: true)
    |> Enum.reduce(initial, fn
      "$ cd ..", {current_dir, filesystem} ->
        {move_out(current_dir), filesystem}

      "$ cd /", {_current_dir, filesystem} ->
        {"/", filesystem}

      "$ cd " <> target, {current_dir, filesystem} ->
        {move_in(current_dir, target), filesystem}

      "$ ls", acc ->
        acc

      "dir " <> _dir_name, acc ->
        acc

      file_name_and_size, {current_dir, filesystem} ->
        # when the result is a file, add its size to the current dir and all parent dirs
        size = parse_file_size(file_name_and_size)

        {current_dir, update_filesizes(current_dir, filesystem, size)}
    end)
  end

  defp update_filesizes("/", filesystem, size), do: update_directory_size(filesystem, "/", size)

  defp update_filesizes(current_dir, filesystem, size) do
    update_filesizes(
      move_out(current_dir),
      update_directory_size(filesystem, current_dir, size),
      size
    )
  end

  defp update_directory_size(filesystem, dir, size) do
    Map.update(filesystem, dir, size, &amp;(&amp;1 + size))
  end

  defp parse_file_size(file_name_and_size) do
    [size, _name] = String.split(file_name_and_size, " ", trim: true)

    String.to_integer(size)
  end

  defp move_out("/"), do: "/"

  defp move_out(path) do
    [_ | rest] = path |> String.splitter("/", trim: true) |> Enum.reverse()

    "/" <> (rest |> Enum.reverse() |> Enum.join("/"))
  end

  defp move_in("/", target_dir), do: "/#{target_dir}"
  defp move_in(current_dir, target_dir), do: "#{current_dir}/#{target_dir}"
end
{_, filesystem} = FileSystem.parse_commands(lines)

Part 1

filesystem
|> Stream.map(fn {_, size} -> size end)
|> Stream.filter(fn size -> size <= 100_000 end)
|> Enum.sum()

Part 2

total_space = 70_000_000
free_space = total_space - filesystem["/"]

target_space = 30_000_000
needed_to_delete = target_space - free_space

filesystem
|> Enum.reduce(total_space, fn
  {_, size}, candidate when size < candidate and size >= needed_to_delete -> size
  _, candidate -> candidate
end)