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

AOC 2022 - Day 07

aoc2022/day07.livemd

AOC 2022 - Day 07

Mix.install([
  {:kino_aoc, "~> 0.1"}
])

AOC Helper

{:ok, puzzle_input} =
  KinoAOC.download_puzzle("2022", "7", System.fetch_env!("LB_AOC_SESSION"))

Part 1

Code

defmodule PartOne do
  def process_file(fs, path, file) do
    [size, _] = file |> String.trim() |> String.split(" ")
    size = String.to_integer(size)

    Enum.map(fs, fn {k, v} ->
      if String.starts_with?(path, k) do
        {k, v + size}
      else
        {k, v}
      end
    end)
  end

  def solve(input) do
    IO.puts("--- Part One ---")
    IO.puts("Result: #{run(input)}")
  end

  def run(input) do
    input
    |> String.split("\n")
    |> Enum.reduce({[{"/", 0}], "/"}, fn line, {fs, current_path} ->
      case String.trim(line) do
        "$ cd /" -> {fs, "/"}
        "$ cd " <> path -> {fs, Path.join(current_path, path) |> Path.expand()}
        "$ ls" -> {fs, current_path}
        "dir " <> dir -> {fs ++ [{Path.join(current_path, dir), 0}], current_path}
        file -> {process_file(fs, current_path, file), current_path}
      end
    end)
    |> elem(0)
    |> Enum.filter(fn {_path, size} -> size <= 100_000 end)
    |> Enum.reduce(0, fn {_path, size}, total -> total + size end)
  end
end

Test

ExUnit.start(autorun: false)

defmodule PartOneTest do
  use ExUnit.Case, async: true
  import PartOne

  @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"
  @expected 95437

  test "part one" do
    actual = run(@input)
    assert actual == @expected
  end
end

ExUnit.run()

Solution

PartOne.solve(puzzle_input)

Part 2

Code

defmodule PartTwo do
  def process_file(fs, path, file) do
    [size, _] = file |> String.trim() |> String.split(" ")
    size = String.to_integer(size)

    Enum.map(fs, fn {k, v} ->
      if String.starts_with?(path, k) do
        {k, v + size}
      else
        {k, v}
      end
    end)
  end

  def find_dirs(fs) do
    total_space = 70_000_000
    required_space = 30_000_000
    used_space = fs |> Enum.find(&amp;(elem(&amp;1, 0) == "/")) |> elem(1)
    available_space = total_space - used_space
    delta_space = required_space - available_space

    fs
    |> Enum.reject(&amp;(elem(&amp;1, 1) < delta_space))
    |> Enum.sort_by(&amp;elem(&amp;1, 1))
    |> List.first()
    |> elem(1)
  end

  def solve(input) do
    IO.puts("--- Part Two ---")
    IO.puts("Result: #{run(input)}")
  end

  def run(input) do
    input
    |> String.split("\n")
    |> Enum.reduce({[{"/", 0}], "/"}, fn line, {fs, current_path} ->
      case String.trim(line) do
        "$ cd /" -> {fs, "/"}
        "$ cd " <> path -> {fs, Path.join(current_path, path) |> Path.expand()}
        "$ ls" -> {fs, current_path}
        "dir " <> dir -> {fs ++ [{Path.join(current_path, dir), 0}], current_path}
        file -> {process_file(fs, current_path, file), current_path}
      end
    end)
    |> elem(0)
    |> find_dirs()
  end
end

Test

ExUnit.start(autorun: false)

defmodule PartTwoTest do
  use ExUnit.Case, async: true
  import PartTwo

  @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"
  @expected 24_933_642

  test "part two" do
    actual = run(@input)
    assert actual == @expected
  end
end

ExUnit.run()

Solution

PartTwo.solve(puzzle_input)