Add solutions for 2022:7 "No Space Left On Device"

tried to solve this one using only incrementing sums, which
worked fine for the test input but not the actual puzzle input.

By a complete rewrite to actually render the tree as a map,
it worked. The spontanious data store is a mess and cost me
much time to work around, since I wanted to have a list of tuples
with all the sizes for each directory.

Took 2 days to figure this one out. Not proud.
This commit is contained in:
Anders Englöf Ytterström 2022-12-08 21:42:20 +01:00 committed by Anders Englöf Ytterström
parent 67ff3997f1
commit bb708a5e58
2 changed files with 205 additions and 0 deletions

View file

@ -0,0 +1,158 @@
defmodule Aoc.Solution.Day07 do
import Aoc.Utils
@name "Day 7: No Space Left On Device"
@behaviour Solution
@impl Solution
def get_name, do: @name
@impl Solution
def present(solution), do: "The small directories sizes sum is #{solution} (1644735)"
@impl Solution
def present_again(solution), do: "The best dir to remove has a size of #{solution}"
@impl Solution
def parse!(raw) do
raw |> split_lines()
end
@impl Solution
def solve(output) do
output
|> traverse()
|> small_dir_sum()
end
@impl Solution
def solve_again(output) do
output
|> traverse()
|> removal_dir()
end
def traverse(files) when is_list(files) do
Enum.reduce(
files,
%{
pwd: ["/"],
tree: %{
"/" => %{
children: %{},
files: []
}
}
},
&traverse/2
)
end
def traverse("$ cd /", tree) do
%{tree | pwd: ["/"]}
end
def traverse("$ cd ..", tree = %{pwd: path}) do
%{tree | pwd: path |> Enum.drop(1)}
end
def traverse("$ cd " <> dirname, tree = %{pwd: path}) do
%{tree | pwd: [dirname | path]}
end
def traverse("dir " <> dirname, state = %{pwd: pwd, tree: tree}) do
pp = _path(pwd, :children)
node = Map.put(get_in(tree, pp), dirname, %{children: %{}, files: []})
%{state | tree: put_in(tree, pp, node)}
end
def traverse("$ ls", tree) do
tree
end
def traverse(item, %{pwd: pwd, tree: tree}) do
[size, _name] = String.split(item)
pp = _path(pwd, :files)
files = get_in(tree, pp)
%{
pwd: pwd,
tree: put_in(tree, pp, [String.to_integer(size) | files])
}
end
def _path(pwd, pos),
do:
[
pos
| pwd
|> Enum.map(fn s -> [s, :children] end)
]
|> List.flatten()
|> Enum.drop(-1)
|> Enum.reverse()
def small_dir_sum(%{tree: tree}) do
tree
|> dir_sizes()
|> Enum.filter(fn v -> v < 100_000 end)
|> Enum.sum()
end
def removal_dir(%{tree: tree}) do
needed = 30_000_000
disk = 70_000_000
dirs =
tree
|> dir_sizes()
used = Enum.max(dirs)
free = disk - used
dirs
|> Enum.filter(fn s -> free + s > needed end)
|> Enum.min()
end
def dir_sizes(%{"/" => node}) do
dir_sizes("/", node, [])
|> Enum.map(fn {_k, v} -> v end)
end
def dir_sizes(_name, children, seen) when is_list(children) do
sums =
Enum.map(children, fn {name, data} ->
dir_sizes(name, data, seen)
end)
|> List.flatten()
sums ++ seen
end
def dir_sizes(name, data, seen) do
size = dir_size(data)
seen =
case Enum.empty?(Map.get(data, :children)) do
true -> seen
false -> dir_sizes(name, Map.get(data, :children) |> Map.to_list(), seen)
end
[{name, size} | seen]
end
def dir_size(data) do
size = Map.get(data, :files, []) |> Enum.sum()
child_size =
data
|> Map.get(:children)
|> Enum.map(fn {_n, d} ->
dir_size(d)
end)
|> Enum.sum()
size + child_size
end
end

View file

@ -0,0 +1,47 @@
defmodule Day07Test do
use ExUnit.Case
doctest Aoc.Solution.Day07
import Aoc.Solution.Day07
@input ~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
)
test "07: No Space Left On Device, part 1" do
expected = 95437
result = @input |> parse!() |> solve()
assert result == expected
end
test "07: No Space Left On Device, part 2" do
expected = 24_933_642
result = @input |> parse!() |> solve_again()
assert result == expected
end
end