Monkey in the Middle

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

open System.IO
open System

let mapOperation (parts: string[]) =
    let left = parts[0]
    let sign = parts[1]
    let right = parts[2]

    let operation: (int64 -> int64 -> int64) =
        match sign with
        | "+" -> (+)
        | "*" -> (*)
        | x -> failwith $"Unknown sign {x}"

    if (left = "old") then
        if (right = "old") then
            fun x -> operation x x
        else
            fun x -> operation x (int64 right)
    else if (right = "old") then
        fun x -> (operation (int64 left) x)
    else
        fun _ -> (operation (int64 left) (int64 right))

type Monkey
    (
        items: int64[],
        operation: int64 -> int64,
        divisor: int64,
        tMonkey: int,
        fMonkey: int,
        part: int
    ) =

    let mutable items = items
    let operation = operation
    let divisor = divisor
    let trueTest = tMonkey
    let falseTest = fMonkey

    let mutable inspectedItems: int64 = 0

    member _.HasItem() : bool = items.Length <> 0

    member _.Inspect() =
        inspectedItems <- inspectedItems + 1L
        let mutable result = operation items[0]
        items <- Array.skip 1 items
        result <- result / (if part = 1 then 3L else 1L)

        if result % divisor = int64 0 then
            trueTest, result
        else
            falseTest, result

    member _.Catch(item: int64) = items <- Array.append items [| item |]

    member _.Inspected() = inspectedItems

type Input() =

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

        let mutable monkeys = Array.empty<Monkey>
        let mutable multipliers = 1L

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

            if (line = null) then
                endOfFile <- true
            else
                let items =
                    stream.ReadLine().Substring("  Starting items: ".Length).Split(", ")
                    |> Array.map int64

                let operation =
                    stream.ReadLine().Substring("  Operation: new = ".Length).Split(" ")
                    |> mapOperation

                let divisor = stream.ReadLine().Split(" ") |> Array.last |> int64
                multipliers <- multipliers * divisor

                let trueMonkey = stream.ReadLine().Split(" ") |> Array.last |> int
                let falseMonkey = stream.ReadLine().Split(" ") |> Array.last |> int
                stream.ReadLine() |> ignore

                let monkey = new Monkey(items, operation, divisor, trueMonkey, falseMonkey, part)
                monkeys <- Array.append monkeys [| monkey |]

        let rounds = if part = 1 then 20 else 10000

        for r in 1..rounds do
            for i in 0 .. (monkeys.Length - 1) do
                let currentMonkey = monkeys[i]

                while (currentMonkey.HasItem()) do
                    let (monkey, result) = currentMonkey.Inspect()
                    monkeys[ monkey ].Catch(result % multipliers)

        let lastTwo =
            monkeys
            |> Array.map (fun x -> x.Inspected())
            |> Array.sort
            |> Array.skip (monkeys.Length - 2)

        lastTwo[0] * lastTwo[1]

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

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