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

Day 7

2022/day_7.livemd

Day 7

Mix.install(
  [
    {:advent_of_code_utils, "~> 3.1"}
  ],
  config: [
    advent_of_code_utils: [session: System.fetch_env!("LB_AOC_TOKEN")]
  ]
)

Mix.Tasks.Aoc.Get.run(["--year", "2022", "--day", "7"])

Inputs

# Override the example
example_input =
  "$ cd /\n$ ls\ndir a\n14848514 b.txt\n8504156 c.dat\ndir d\n$ cd a\n$ ls\ndir e\n29116 f\n2557 g\n62596 h.lst\n$ cd e\n$ ls\n584 i\n$ cd ..\n$ cd ..\n$ cd d\n$ ls\n4060174 j\n8033020 d.log\n5626152 d.ext\n7214296 k"
input = AOC.input_string(2022, 7)

Parser

defmodule Parser do
  def parse_ls_output_item("dir " <> name) do
    %{
      type: :dir,
      name: name
    }
  end

  def parse_ls_output_item(file) do
    [size, name] = String.split(file, " ")

    %{
      type: :file,
      name: name,
      size: String.to_integer(size)
    }
  end

  def parse(input) do
    input
    |> String.split("$ ", trim: true)
    |> Enum.map(fn raw_cmd ->
      %{
        :cmd =>
          raw_cmd
          |> String.graphemes()
          |> Enum.take_while(&amp;(&amp;1 not in [" ", "\n"]))
          |> Enum.join()
          |> String.to_atom(),
        :args =>
          raw_cmd
          |> String.split("\n", trim: true)
          |> hd()
          |> String.split(" ")
          |> tl(),
        :output =>
          raw_cmd
          |> String.split("\n", trim: true)
          |> tl()
          |> Enum.map(&amp;parse_ls_output_item/1)
      }
    end)
  end
end
Parser.parse(example_input)

Filesystem

defmodule Filesystem do
  def new(commands) do
    build(
      commands,
      %{
        fs: %{},
        current_path: []
      }
    )
  end

  def build([], state), do: state.fs

  def build([command | remaining_commands], state) do
    build(remaining_commands, command(command.cmd, command, state))
  end

  def command(:cd, command, state) do
    new_current_path = hd(command.args)

    if new_current_path == ".." do
      # If the cd arg is `..`, just pop the last directory off current path
      {new_path, _last_element} = Enum.split(state.current_path, -1)

      state
      |> Map.put(:current_path, new_path)
    else
      # If the cd arg is a new directory, add it to the current path
      # and add a map for it in the fs
      new_path = state.current_path ++ [new_current_path]

      state
      |> Map.put(:current_path, new_path)
      |> put_in([:fs] ++ new_path, %{type: :dir})
    end
  end

  def command(:ls, command, state) do
    new_fs =
      for output <- command.output, output.type == :file, reduce: state.fs do
        acc ->
          put_in(
            acc,
            state.current_path ++ [output.name],
            %{type: :file, size: output.size, name: output.name}
          )
      end

    state
    |> Map.put(:fs, new_fs)
  end

  def calculate_directory_sizes(fs) do
    fs
  end
end

example_input
|> Parser.parse()
|> Filesystem.new()
|> Filesystem.calculate_directory_sizes()

Part 1

example_input
|> Parser.parse()
|> Filesystem.new()
input
|> Parser.parse()
|> Filesystem.new()

Part 2

example_input
input