No Space Left On Device

Description: https://adventofcode.com/2022/day/7

open System.IO
open System
open System.Text.RegularExpressions
open System.Collections.Generic

exception Error of string

let (|Regex|_|) pattern input =
    let m = Regex.Match(input, pattern)

    if m.Success then
        Some(List.tail [ for g in m.Groups -> g.Value ])
    else
        None

type Command =
    | CdInit
    | Ls
    | Directory of string
    | File of int
    | CdIn of string
    | CdOut

type Tree() =
    let storageSize: int = 70_000_000
    let updateSize: int = 30_000_000

    let dirs = new Dictionary<string, int>()

    let mutable usedStorage: int = 0
    let mutable currentPath: string[] = Array.empty
    let mutable currentPathString = ""

    let addSize (size: int) : unit =
        if dirs.ContainsKey currentPathString then
            let dirSize = dirs[currentPathString]
            let newSize = dirSize + size

            dirs[currentPathString] <- newSize

    let addPath (name: string) : unit =
        currentPath <- Array.append [| name |] currentPath
        currentPathString <- String.Join(":", currentPath)

    let removePath () : string =
        let headPath = currentPathString
        currentPath <- Array.tail currentPath
        currentPathString <- String.Join(":", currentPath)

        headPath

    member _.Result(part) : int =
        if part = 1 then
            Seq.filter (fun x -> x <= 100_000) dirs.Values |> Seq.sum
        else
            let missingStorage = updateSize - (storageSize - usedStorage)
            Seq.filter (fun x -> x >= missingStorage) dirs.Values |> Seq.min

    member _.Execute(command: Command) : unit =
        match command with
        | CdInit
        | Ls
        | Directory _ -> ()
        | File size ->
            addSize size
            usedStorage <- usedStorage + size
        | CdIn name ->
            addPath name
            dirs.Add(currentPathString, 0)
        | CdOut ->
            let headPath = removePath ()

            if (currentPath.Length <> 0) then
                dirs[currentPathString] <- dirs[currentPathString] + dirs[headPath]

    member _.Parse(line: string) : Command =
        match line with
        | Regex "\$ cd \/" [] -> CdInit
        | Regex "\$ ls" [] -> Ls
        | Regex "dir (.+)" [ name: string ] -> Directory name
        | Regex "(\d+) .+" [ size: string ] -> File(size |> int)
        | Regex "\$ cd (\w+)" [ name: string ] -> CdIn name
        | Regex "\$ cd \.\." [] -> CdOut
        | x -> raise (Error($"Unknown command: {x}"))

type Input() =

    member _.Compute(part: int) =
        use stream = new StreamReader "./input.txt"

        let mutable endOfFile = false
        let tree = new Tree()

        while (not endOfFile) do
            let line = stream.ReadLine()

            if (line = null) then
                endOfFile <- true
            else
                tree.Parse line |> tree.Execute

        tree.Result(part)

let input = Input()
let part = (Environment.GetCommandLineArgs()[1]) |> int

printfn "%d" (input.Compute(part))