diff --git a/2022-elixir/lib/solutions/day_07.ex b/2022-elixir/lib/solutions/day_07.ex new file mode 100644 index 0000000..bcc9278 --- /dev/null +++ b/2022-elixir/lib/solutions/day_07.ex @@ -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 diff --git a/2022-elixir/test/solutions/day_07_test.exs b/2022-elixir/test/solutions/day_07_test.exs new file mode 100644 index 0000000..ca83626 --- /dev/null +++ b/2022-elixir/test/solutions/day_07_test.exs @@ -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