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

Day 7

2022/day_07.livemd

Day 7

Mix.install([
  {:kino, "~> 0.7.0"}
])

Common

defmodule Common do
  def parse_input(textarea) do
    root = %{name: "/", size: 0, children: %{}}

    textarea
    |> Kino.Input.read()
    |> String.split("\n", trim: true)
    |> parse_lines(root, [])
  end

  def filter(%{children: children} = dir, filter_fun) do
    filtered_children = Enum.flat_map(children, fn {_key, value} -> filter(value, filter_fun) end)

    if filter_fun.(dir) do
      [dir | filtered_children]
    else
      filtered_children
    end
  end

  def filter(file, filter_fun) do
    if filter_fun.(file) do
      [file]
    else
      []
    end
  end

  defp parse_lines([], tree, _current_path), do: tree

  defp parse_lines(["$ cd /" | rest], tree, _current_path) do
    parse_lines(rest, tree, [])
  end

  defp parse_lines(["$ cd .." | rest], tree, current_path) do
    current_path = Enum.slice(current_path, 0..-3)
    parse_lines(rest, tree, current_path)
  end

  defp parse_lines(["$ cd " <> dir_name | rest], tree, current_path) do
    current_path = current_path ++ [:children, dir_name]
    parse_lines(rest, tree, current_path)
  end

  defp parse_lines(["$ ls" | rest], tree, current_path) do
    parse_lines(rest, tree, current_path)
  end

  defp parse_lines(["dir " <> dir_name | rest], tree, current_path) do
    dir = %{name: dir_name, children: %{}, size: 0}

    tree = add_child(tree, current_path, dir)

    parse_lines(rest, tree, current_path)
  end

  defp parse_lines([file_with_size | rest], tree, current_path) do
    [file_size, file_name] = String.split(file_with_size)

    file = %{name: file_name, size: String.to_integer(file_size)}

    tree = add_child(tree, current_path, file)
    tree = propagate_size(tree, current_path, file.size)

    parse_lines(rest, tree, current_path)
  end

  defp propagate_size(tree, [], size), do: Map.update!(tree, :size, &amp;Kernel.+(&amp;1, size))

  defp propagate_size(tree, current_path, size) do
    tree = update_in(tree, current_path ++ [:size], &amp;(&amp;1 + size))
    propagate_size(tree, Enum.slice(current_path, 0..-3), size)
  end

  defp add_child(tree, current_path, child) do
    update_in(tree, current_path ++ [:children], &amp;Map.put(&amp;1, child.name, child))
  end
end

Input

textarea =
  Kino.Input.textarea("Input:",
    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
    """
  )
input = Common.parse_input(textarea)

Part 1

defmodule Part1 do
  def run(tree) do
    tree
    |> Common.filter(fn node ->
      Map.has_key?(node, :children) &amp;&amp; node.size <= 100_000
    end)
    |> Enum.map(&amp; &amp;1.size)
    |> Enum.sum()
  end
end
Part1.run(input)

Part 2

defmodule Part2 do
  def run(tree) do
    space_total = 70_000_000
    free_space_required = 30_000_000
    free_space = space_total - tree.size
    space_to_free_up = free_space_required - free_space

    tree
    |> Common.filter(fn node ->
      Map.has_key?(node, :children) &amp;&amp; node.size >= space_to_free_up
    end)
    |> Enum.map(&amp; &amp;1.size)
    |> Enum.sort(:asc)
    |> hd()
  end
end
Part2.run(input)