DEV Community

Caleb Weeks
Caleb Weeks

Posted on • Originally published at sethcalebweeks.com

Advent of Code Day 7

Links

Highlights

  • This was a big jump up from yesterday's problem. The bulk of the work was in the parsing of the input. I pattern matched on each line, keeping track of the current directory and the filesystem scanned so far. I skipped all lines with the ls command because they did not add any information. I also kept track of the total size of each directory. Every time I came across a file, I added its size to the total size of every parent folder. The resulting data structure from the example input looks like this:
%{ "/" => %{ :files => %{ "b.txt" => 14848514, "c.dat" => 8504156 }, :total_size => 48381165, "a" => %{ :files => %{ "f" => 29116, "g" => 2557, "h.lst" => 62596 }, :total_size => 94853, "e" => %{ files: %{"i" => 584}, total_size: 584 } }, "d" => %{ files: %{ "d.ext" => 5626152, "d.log" => 8033020, "j" => 4060174, "k" => 7214296 }, total_size: 24933642 } } } 
Enter fullscreen mode Exit fullscreen mode
  • After scanning the entire filesystem, each part just required traversing the tree again to determine the answer. Turns out that I didn't really need to keep track of the files at all. Since mapping over the keys of the folder would include the total size and files, I had to remove them to recursively check the other folders.
defmodule Day07 do use AOC @node %{files: %{}, total_size: 0} def parse_filesystem() do input(7) |> String.trim |> String.split("\n") |> Enum.reduce({[], %{"/" => @node}}, &process_line/2) |> elem(1) end def process_line("$ ls", acc), do: acc def process_line("$ cd ..", {[_ | ancestors], fs}), do: {ancestors, fs} def process_line("$ cd " <> dir, {current_dir, fs}), do: {[dir | current_dir], fs} def process_line("dir " <> dir, {current_dir, fs}), do: {current_dir, put_in(fs, Enum.reverse([dir | current_dir]), @node)} def process_line(file, {current_dir, fs}) do [size, filename] = String.split(file) new_fs = update_total_size(fs, current_dir, String.to_integer(size)) |> put_in(Enum.reverse([filename, :files | current_dir]), String.to_integer(size)) {current_dir, new_fs} end def update_total_size(fs, [], _), do: fs def update_total_size(fs, [_ | ancestors] = path, file_size) do get_and_update_in(fs, Enum.reverse([:total_size | path]), fn total_size -> {total_size, total_size + file_size} end) |> (fn {_, fs} -> update_total_size(fs, ancestors, file_size) end).() end def total_size_under_100000(folder) do {total_size, without_size} = Map.pop(folder, :total_size, 0) {_, without_files} = Map.pop(without_size, :files) added_size = if(total_size <= 100000, do: total_size, else: 0) added_size + (Enum.map(without_files, fn {_, v} -> total_size_under_100000(v) end) |> Enum.sum) end def large_enough(folder, space_needed) do {total_size, without_size} = Map.pop(folder, :total_size, 0) {_, without_files} = Map.pop(without_size, :files) cond do total_size > space_needed -> [total_size | Enum.flat_map(without_files, fn {_, v} -> large_enough(v, space_needed) end)] true -> [] end end def part1, do: parse_filesystem() |> (fn %{"/" => root} -> total_size_under_100000(root) end).() def part2 do %{"/" => %{total_size: used_space} = root} = parse_filesystem() space_needed = 30000000 - (70000000 - used_space) large_enough(root, space_needed) |> Enum.min() end end 
Enter fullscreen mode Exit fullscreen mode

Top comments (0)