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

Advent of Code: Day 07

2022/Day07.livemd

Advent of Code: Day 07

Helpers

defmodule Helpers do
  @spec read_file_contents(String.t()) :: binary()
  def read_file_contents(filename) do
    file_path = "/Users/charlie/github.com/charlieroth/advent-of-code/2022/#{filename}"

    case File.read(file_path) do
      {:ok, contents} -> contents
      {:error, _} -> raise("Failed to read file contents")
    end
  end
end
defmodule Tree do
  defstruct dir?: false, size: 0, children: %{}

  def dir(), do: %__MODULE__{dir?: true}
  def file(size), do: %__MODULE__{dir?: false, size: size}

  def add!(%__MODULE__{dir?: true} = cwd, name, %__MODULE__{} = child) do
    %{cwd | size: cwd.size + child.size, children: Map.put(cwd.children, name, child)}
  end

  def add!(%__MODULE__{dir?: false}, _, _) do
    raise ArgumentError, "Can't add children to a file"
  end

  def remove!(%__MODULE__{dir?: true} = cwd, path) do
    child = Map.fetch!(cwd.children, path)
    {child, %{cwd | size: cwd.size - child.size, children: Map.delete(cwd.children, path)}}
  end

  def remove!(%__MODULE__{dir?: false}, _name) do
    raise ArgumentError, "File has no children"
  end
end
defmodule Shell do
  def from_tree(node), do: {node, []}

  def to_tree({node, _}), do: node
  def to_tree(node), do: node |> cd("..") |> to_tree()

  def cd({%Tree{dir?: true} = cwd, [{parent, name} | t]}, "..") do
    {Tree.add!(parent, name, cwd), t}
  end

  def cd({%Tree{dir?: true} = cwd, trail}, name) do
    {%Tree{dir?: true} = child, cwd} = Tree.remove!(cwd, name)
    {child, [{cwd, name} | trail]}
  end

  def add({%Tree{dir?: true} = cwd, trail}, name, %Tree{} = node) do
    {Tree.add!(cwd, name, node), trail}
  end
end
defmodule Parser do
  def parse(cmds) do
    Tree.dir()
    |> Tree.add!("/", Tree.dir())
    |> Shell.from_tree()
    |> parse(cmds)
    |> Shell.to_tree()
  end

  def parse(shell, []), do: shell

  def parse(shell, [cmd | rest]) do
    case parse_line(cmd) do
      {:dir, name} ->
        shell
        |> Shell.add(name, Tree.dir())
        |> parse(rest)

      {:file, name, size} ->
        shell
        |> Shell.add(name, Tree.file(size))
        |> parse(rest)

      {:cd, path} ->
        shell
        |> Shell.cd(path)
        |> parse(rest)

      _ ->
        parse(shell, rest)
    end
  end

  defp parse_line("$ cd " <> path), do: {:cd, path}
  defp parse_line("$ ls"), do: {:ls}
  defp parse_line("dir " <> name), do: {:dir, name}

  defp parse_line(<> = line) when char in ?1..?9 do
    [size, name] = String.split(line, " ", parts: 2, trim: true)
    {:file, name, String.to_integer(size)}
  end

  defp parse_line(_), do: {:garbage}
end

Part 01

The device the Elves gave you has problems with more than just its communication system. You try to run a system update:

$ system-update --please --pretty-please-with-sugar-on-top
Error: No space left on device

Perhaps you can delete some files to make space for the update?

You browse around the filesystem to assess the situation and save the resulting terminal output (your puzzle input):

$ 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

Given the commands and output in the example above, you can determine that the filesystem looks visually like this:

- / (dir)
  - a (dir)
    - e (dir)
      - i (file, size=584)
    - f (file, size=29116)
    - g (file, size=2557)
    - h.lst (file, size=62596)
  - b.txt (file, size=14848514)
  - c.dat (file, size=8504156)
  - d (dir)
    - j (file, size=4060174)
    - d.log (file, size=8033020)
    - d.ext (file, size=5626152)
    - k (file, size=7214296)

Since the disk is full, your first step should probably be to find directories that are good candidates for deletion. To do this, you need to determine the total size of each directory. The total size of a directory is the sum of the sizes of the files it contains, directly or indirectly. (Directories themselves do not count as having any intrinsic size.)

To begin, find all of the directories with a total size of at most 100000, then calculate the sum of their total sizes. In the example above, these directories are a and e; the sum of their total sizes is 95437 (94853 + 584). (As in this example, this process can count files more than once!)

Find all of the directories with a total size of at most 100000

What is the sum of the total sizes of those directories?

defmodule PartOne do
  @cap 100_000
  def solution(input) do
    input
    |> String.split("\n", trim: true)
    |> Parser.parse()
    |> filter([])
  end

  defp filter(%Tree{dir?: true, size: size, children: children}, acc) when size > @cap do
    children
    |> Map.values()
    |> Enum.filter(&amp; &amp;1.dir?)
    |> Enum.reduce(acc, &amp;filter/2)
  end

  defp filter(%Tree{dir?: true} = node, acc) do
    node.children
    |> Map.values()
    |> Enum.filter(&amp; &amp;1.dir?)
    |> Enum.reduce([node | acc], &amp;filter/2)
  end

  defp filter(%Tree{dir?: false}, acc), do: acc
end
~S"""
$ 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
"""
|> PartOne.solution()

Part 02

aasdf