input = ARGF.read.lines(chomp: true)

Dir = Struct.new(:parent, :name, :children) do
  def size = children.sum(&:size)

  def each
    return enum_for(__method__) unless block_given?

    yield self

    children.each do |child|
      case child
      when Dir
        child.each { yield _1 }
      when File
        yield child
      else
        fail child.inspect
      end
    end
  end
end

File = Struct.new(:parent, :name, :size)

root = Dir.new(nil, ?/, [])
pwd = root

input.each do |line|
  case line
  when /\$ cd \//
    # no-op
  when /\$ cd \.\./
    pwd = pwd.parent
  when /\$ cd (.+)/
    pwd = pwd.children.find { _1.name == $1 }
  when /\$ ls/
    # no-op
  when /dir (.+)/
    pwd.children << Dir.new(pwd, $1, [])
  when /(\d+) (.+)/
    pwd.children << File.new(pwd, $2, $1.to_i)
  else
    fail line
  end
end

# part 1
p root.each
  .select { Dir === _1 }
  .map(&:size)
  .select { _1 < 100_000 }
  .sum

# part 2
p root.each
  .select { Dir === _1 }
  .map(&:size)
  .sort
  .find { _1 >= 30_000_000 - (70_000_000 - root.size) }