Powered by AppSignal & Oban Pro

Day 07

books/aoc/2022/07.livemd

Day 07

Mix.install([
  {:kino, "~> 0.8.0"},
  {:kino_aoc, git: "https://github.com/ljgago/kino_aoc"}
])
:ok

Input

{:ok, result} = KinoAOC.download_puzzle("2022", "7", System.fetch_env!("LB_AOC_SESSION"))
input = result
"$ cd /\n$ ls\ndir bqc\ndir mwmlf\ndir ngn\n143562 nrwjb\n78449 qqvdcclf\ndir qrnm\ndir smfzmmhc\n116085 tvrms\ndir vrdrsj\n$ cd bqc\n$ ls\n5693 qqvdcclf\n$ cd ..\n$ cd mwmlf\n$ ls\ndir cmfphpc\ndir lqqshq\ndir mwmlf\ndir rlf\ndir smfzmmhc\n$ cd cmfphpc\n$ ls\n235620 tprth.gjn\n82743 vrdrsj.fbl\n$ cd ..\n$ cd lqqshq\n$ ls\n94188 crswqlvd.nsj\ndir dttthls\n60078 lbsfsspm\ndir lqp\n74624 nrwjb\n247709 tjhcqw.wrq\n267693 tvrms\ndir zshrcgfn\n$ cd dttthls\n$ ls\n109072 nrwjb\n31512 qqvdcclf\n$ cd ..\n$ cd lqp\n$ ls\n237917 nrwjb\n45489 vrdrsj.ntw\n$ cd ..\n$ cd zshrcgfn\n$ ls\n185533 smfzmmhc.zzd\n$ cd ..\n$ cd ..\n$ cd mwmlf\n$ ls\ndir bbvhpmsj\ndir mwmlf\n83589 qqvdcclf\n$ cd bbvhpmsj\n$ ls\n232123 hrvlgmjb.wrv\n137190 rlf\n21785 tghvbj.mfr\n25344 vrdrsj.swp\n$ cd ..\n$ cd mwmlf\n$ ls\ndir wnn\n$ cd wnn\n$ ls\ndir thd\n$ cd thd\n$ ls\n36125 lgt\n$ cd ..\n$ cd ..\n$ cd ..\n$ cd ..\n$ cd rlf\n$ ls\ndir fps\n128272 lggpfj.gnr\n146745 lrdq.zgm\ndir mwmlf\n266383 mwmlf.wnp\ndir ncrlvggp\ndir qpgmrj\n$ cd fps\n$ ls\ndir rqjt\ndir wpqvmn\n$ cd rqjt\n$ ls\n175784 pcdtdblc.znj\n185424 wmstvdt.lhn\n$ cd ..\n$ cd wpqvmn\n$ ls\n141743 ljslzhjl\n$ cd ..\n$ cd ..\n$ cd mwmlf\n$ ls\n169015 rlf\n$ cd ..\n$ cd ncrlvggp\n$ ls\n81450 fqtrpm.mqr\n146399 jtbr\ndir nnqgqqj\n288302 qqvdcclf\n$ cd nnqgqqj\n$ ls\ndir fvbhh\n$ cd fvbhh\n$ ls\n121112 zmn.fjz\n$ cd ..\n$ cd ..\n$ cd ..\n$ cd qpgmrj\n$ ls\ndir hrvlgmjb\ndir pbc\n$ cd hrvlgmjb\n$ ls\n7858 czvpvwz\n$ cd ..\n$ cd pbc\n$ ls\n150816 tprth.gjn\n$ cd ..\n$ cd ..\n$ cd ..\n$ cd smfzmmhc\n$ ls\ndir jwvw\n184385 spbjrlv.spt\ndir trpdgl\n201578 zzhz\n$ cd jwvw\n$ ls\n47747 nrwjb\n$ cd ..\n$ cd trpdgl\n$ ls\n136682 mwmlf\n$ cd ..\n$ cd ..\n$ cd ..\n$ cd ngn\n$ ls\ndir fqbph\n282363 lscdth.lbj\ndir mwmlf\n273281 qbpmcqs\n7547 qdffbp\n223960 tprth.gjn\n$ cd fqbph\n$ ls\ndir bgtbmzj\ndir lzjf\n12361 nrwjb\ndir pdsdttz\n150218 pfvtwr.pbf\ndir rlf\n158227 sfhsszcq.tzv\n183244 smfzmmhc.drh\n$ cd bgtbmzj\n$ ls\ndir gvqhtfg\ndir jmvzv\n194008 ldpfs.qhr\ndir mtztr\n11099 nfqmvqcm.ftd\ndir szp\n153379 zrtsd.jwg\n$ cd gvqhtfg\n$ ls\ndir ntq\ndir rlf\n62076 rlf.nlc\n$ cd ntq\n$ ls\ndir wdtzhz\n$ cd wdtzhz\n$ ls\ndir tlqcbp\n$ cd tlqcbp\n$ ls\n102930 smfzmmhc\n$ cd ..\n$ cd ..\n$ cd ..\n$ cd rlf\n$ ls\n185079 tjhcqw.wrq\n$ cd ..\n$ cd ..\n$ cd jmvzv\n$ ls\ndir hfmtzbhf\n61556 jzbvmc\ndir scjpjp\n237487 sgh\ndir smfzmmhc\n$ cd hfmtzbhf\n$ ls\ndir ldt\n136126 tvrms\n$ cd ldt\n$ ls\ndir smfzmmhc\ndir wrhpzrfm\ndir zbcfv\n$ cd smfzmmhc\n$ ls\n86537 tjhcqw.wrq\n$ cd ..\n$ cd wrhpzrfm\n$ ls\n263122 smfzmmhc.shv\n73772 tvrms\n$ cd ..\n$ cd zbcfv\n$ ls\ndir vrdrsj\n$ cd vrdrsj\n$ ls\n220800 zwdbh\n$ cd ..\n$ cd ..\n$ cd ..\n$ cd ..\n$ cd scjpjp\n$ ls\n38282 rlf\n93306 rlf.blz\n$ cd ..\n$ cd smfzmmhc\n$ ls\ndir gqms\ndir mwmlf\n21587 nrwjb\n184621 zvq.lvr\n$ cd gqms\n$ ls\ndir frbs\ndir rlf\n$ cd frbs\n$ ls\ndir mpdll\n82901 zdmnm.wlc\n$ cd mpdll\n$ ls\ndir mgqd\ndir mrljj\n$ cd mgqd\n$ ls\n75558 vrdrsj.zwc\n$ cd ..\n$ cd mrljj\n$ ls\ndir qmsgj\n128171 qqvdcclf\n245942 rlf.hgt\ndir zzvbt\n$ cd qmsgj\n$ ls\n7382 mwmlf\n$ cd ..\n$ cd zzvbt\n$ ls\n285714 zltjmn\n$ cd ..\n$ cd ..\n$ cd ..\n$ cd ..\n$ cd rlf\n$ ls\n43731 gttgwfgf.cpd\ndir hvtn\n112018 jdhb.ssj\n195239 rlf.gvd\n200476 spsstb\n$ cd hvtn\n$ ls\ndir zzsfgzc\n$ cd zzsfgzc\n$ ls\n116544 vrdrsj\n$ cd ..\n$ cd ..\n$ cd ..\n$ cd ..\n$ cd mwmlf\n$ ls\ndir hmpvn\ndir shlnfcpz\n$ cd hmpvn\n$ ls\n55892 qqvdcclf\n$ cd ..\n$ cd shlnfcpz\n$ ls\n144766 fnrhmbbm.rnp\n2622 tjhcqw.wrq\n219884 tprth.gjn\n$ cd ..\n$ cd ..\n$ cd ..\n$ cd ..\n$ cd mtztr\n$ ls\ndir plngmzl\ndir qbwzhr\n$ cd plngmzl\n$ ls\n153434 dhjv.pfn\n$ cd ..\n$ cd qbwzhr\n$ ls\n26553 mcb.djg\n$ cd ..\n$ cd ..\n$ cd szp\n$ ls\n37741 qqvdcclf\ndir vrdrsj\n$ cd vrdrsj\n$ ls\ndir hznmsclb\n13218 mwmlf.vhs\n102602 rggt\n228549 rlnf.qrt\n$ cd hznmsclb\n$ ls\n17957 mwmlf.lsv\n$ cd ..\n$ cd ..\n$ cd ..\n$ cd ..\n$ cd lzjf\n$ ls\n40821 lfgh.jwg\ndir rplfgb\n$ cd rplfgb\n$ ls\n301639 zmrpm.pqn\n$ cd ..\n$ cd ..\n$ cd pdsdttz\n$ ls\n294779 tprth.gjn\n$ cd ..\n$ cd rlf\n$ ls\n37853 qst.zgc\n$ cd ..\n$ cd ..\n$ cd mwmlf\n$ ls\ndir dqvhgnj\ndir fvjtnl\ndir jfsgp\n230725 tjhcqw.wrq\n2872 tvrms\ndir zszcsbl\n$ cd dqvhgnj\n$ ls\n214328 bph\n138045 tjhcqw.wrq\n$ cd ..\n$ cd fvjtnl\n$ ls\n62568 sllvfsf\n286839 tjhcqw.wrq\n241004 zvqrg.bsg\n$ cd ..\n$ cd jfsgp\n$ ls\n116677 tprth.gjn\n151347 vtbt\n$ cd ..\n$ cd zszcsbl\n$ ls\n97786 hrvlgmjb.wzz\n$ cd ..\n$ cd ..\n$ cd ..\n$ cd qrnm\n$ ls\n70534 nrwjb\n292701 qqvdc" <> ...

Part 1

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

  def run(input) do
    input
    |> get_dir_sizes()
    |> Enum.filter(fn val -> val <= 100_000 end)
    |> Enum.sum()
  end

  defp get_dir_sizes(input) do
    input
    |> process_input()
    |> walk_the_tree()
    |> Map.values()
  end

  defp walk_the_tree(commands, dir_map \\ %{}) do
    new_map =
      commands
      |> Enum.reduce(dir_map, fn command, acc -> process_command(command, acc) end)
      |> Map.drop([:cwd])

    tbds = tbd_count(new_map)

    if tbds == 0 do
      new_map
    else
      walk_the_tree(commands, new_map)
    end
  end

  defp tbd_count(dir_map) do
    Map.values(dir_map)
    |> Enum.count(fn val -> val == :tbd end)
  end

  defp process_command({:cd, ".."}, acc) do
    cwd = Map.get(acc, :cwd)

    new_cwd =
      String.split(cwd, "/")
      |> Enum.drop(-2)
      |> Enum.join("/")
      |> Kernel.<>("/")

    Map.put(acc, :cwd, new_cwd)
  end

  defp process_command({:cd, "/"}, acc), do: Map.put(acc, :cwd, "/")

  defp process_command({:cd, dest}, acc) do
    cwd = Map.get(acc, :cwd)
    new_cwd = "#{cwd}#{dest}/"

    Map.put(acc, :cwd, new_cwd)
  end

  defp process_command({:ls, contents}, acc) do
    cwd = Map.get(acc, :cwd)

    if is_integer(Map.get(acc, cwd)) do
      acc
    else
      new_contents = process_contents(contents, acc)
      values = Keyword.values(new_contents)

      if Enum.all?(values, fn val -> is_integer(val) end) do
        Map.put(acc, cwd, Enum.sum(values))
      else
        Map.put(acc, cwd, :tbd)
      end
    end
  end

  defp process_contents(contents, current_map) do
    Enum.reduce(contents, [], fn content, acc ->
      acc ++ [process_content(content, current_map)]
    end)
  end

  defp process_content({:dir, dir}, current_map) do
    cwd = Map.get(current_map, :cwd)
    fqdir = "#{cwd}#{dir}/"

    case Map.get(current_map, fqdir) do
      nil -> {:dir, fqdir}
      :tbd -> {:dir, fqdir}
      size -> {:file, size}
    end
  end

  defp process_content({file, size}, _), do: {file, size}

  defp process_input(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.join(" ")
    |> String.split("$ ")
    |> Enum.map(&String.trim/1)
    |> Enum.reject(fn line -> line == "" end)
    |> Enum.map(&process_code/1)
  end

  defp process_code(code) do
    case String.slice(code, 0, 2) do
      "cd" -> process_cd(code)
      "ls" -> process_ls(code)
    end
  end

  defp process_cd(code) do
    [_, dest] = String.split(code, ~r{\bcd })
    {:cd, dest}
  end

  defp process_ls(code) do
    [_, contents] = String.split(code, ~r{\bls })

    {:ls,
     contents
     |> String.split()
     |> Enum.chunk_every(2)
     |> Enum.map(&process_ls_unit/1)}
  end

  defp process_ls_unit(["dir", dir_name]), do: {:dir, dir_name}
  defp process_ls_unit([size, _]), do: {:file, String.to_integer(size)}
end
{:module, PartOne, <<70, 79, 82, 49, 0, 0, 29, ...>>, {:process_ls_unit, 1}}
input
|> PartOne.resolve()
--- Part One ---
Result: 1325919
:ok
ExUnit.start(autorun: false)

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

  test "part one" do
    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"
    result = run(input)
    assert result == 95437
  end
end

ExUnit.run()
.
Finished in 0.00 seconds (0.00s async, 0.00s sync)
1 test, 0 failures

Randomized with seed 645353
%{excluded: 0, failures: 0, skipped: 0, total: 1}

Part 2

defmodule PartTwo do
  @disk_size 70_000_000
  @space_needed 30_000_000

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

  def run(input) do
    input
    |> get_dir_sizes()
    |> Enum.sort()
    |> find_best_dir()
  end

  defp get_dir_sizes(input) do
    input
    |> process_input()
    |> walk_the_tree()
    |> Map.values()
  end

  defp walk_the_tree(commands, dir_map \\ %{}) do
    new_map =
      commands
      |> Enum.reduce(dir_map, fn command, acc -> process_command(command, acc) end)
      |> Map.drop([:cwd])

    tbds = tbd_count(new_map)

    if tbds == 0 do
      new_map
    else
      walk_the_tree(commands, new_map)
    end
  end

  defp tbd_count(dir_map) do
    Map.values(dir_map)
    |> Enum.count(fn val -> val == :tbd end)
  end

  defp process_command({:cd, ".."}, acc) do
    cwd = Map.get(acc, :cwd)

    new_cwd =
      String.split(cwd, "/")
      |> Enum.drop(-2)
      |> Enum.join("/")
      |> Kernel.<>("/")

    Map.put(acc, :cwd, new_cwd)
  end

  defp process_command({:cd, "/"}, acc), do: Map.put(acc, :cwd, "/")

  defp process_command({:cd, dest}, acc) do
    cwd = Map.get(acc, :cwd)
    new_cwd = "#{cwd}#{dest}/"

    Map.put(acc, :cwd, new_cwd)
  end

  defp process_command({:ls, contents}, acc) do
    cwd = Map.get(acc, :cwd)

    if is_integer(Map.get(acc, cwd)) do
      acc
    else
      new_contents = process_contents(contents, acc)
      values = Keyword.values(new_contents)

      if Enum.all?(values, fn val -> is_integer(val) end) do
        Map.put(acc, cwd, Enum.sum(values))
      else
        Map.put(acc, cwd, :tbd)
      end
    end
  end

  defp process_contents(contents, current_map) do
    Enum.reduce(contents, [], fn content, acc ->
      acc ++ [process_content(content, current_map)]
    end)
  end

  defp process_content({:dir, dir}, current_map) do
    cwd = Map.get(current_map, :cwd)
    fqdir = "#{cwd}#{dir}/"

    case Map.get(current_map, fqdir) do
      nil -> {:dir, fqdir}
      :tbd -> {:dir, fqdir}
      size -> {:file, size}
    end
  end

  defp process_content({file, size}, _), do: {file, size}

  defp min_size_needed(sizes) do
    root_dir = sizes |> Enum.take(-1) |> List.first()
    @space_needed - (@disk_size - root_dir)
  end

  defp find_best_dir(sizes) do
    Enum.find(sizes, fn size -> size >= min_size_needed(sizes) end)
  end

  defp process_input(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.join(" ")
    |> String.split("$ ")
    |> Enum.map(&String.trim/1)
    |> Enum.reject(fn line -> line == "" end)
    |> Enum.map(&process_code/1)
  end

  defp process_code(code) do
    case String.slice(code, 0, 2) do
      "cd" -> process_cd(code)
      "ls" -> process_ls(code)
    end
  end

  defp process_cd(code) do
    [_, dest] = String.split(code, ~r{\bcd })
    {:cd, dest}
  end

  defp process_ls(code) do
    [_, contents] = String.split(code, ~r{\bls })

    {:ls,
     contents
     |> String.split()
     |> Enum.chunk_every(2)
     |> Enum.map(&process_ls_unit/1)}
  end

  defp process_ls_unit(["dir", dir_name]), do: {:dir, dir_name}
  defp process_ls_unit([size, _]), do: {:file, String.to_integer(size)}
end
{:module, PartTwo, <<70, 79, 82, 49, 0, 0, 31, ...>>, {:process_ls_unit, 1}}
input
|> PartTwo.resolve()
--- Part One ---
Result: 2050735
:ok
ExUnit.start(autorun: false)

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

  test "part two" do
    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"
    result = run(input)
    assert result == 24_933_642
  end
end

ExUnit.run()
.
Finished in 0.00 seconds (0.00s async, 0.00s sync)
1 test, 0 failures

Randomized with seed 645353
%{excluded: 0, failures: 0, skipped: 0, total: 1}