namespace AdventOfCode2023 open System open System.Globalization type Hand = | Five = 6 | Four = 5 | FullHouse = 4 | Three = 3 | TwoPairs = 2 | Pair = 1 | High = 0 type HandContents = { First : byte Second : byte Third : byte Fourth : byte Fifth : byte } [] module Day7 = let inline toByte (adjustJoker : bool) (c : char) : byte = if c <= '9' then byte c - byte '0' elif c = 'T' then 10uy elif c = 'J' then (if adjustJoker then 1uy else 11uy) elif c = 'Q' then 12uy elif c = 'K' then 13uy elif c = 'A' then 14uy else failwithf "could not parse: %c" c let inline private updateState (tallies : ResizeArray<_>) newNum = let mutable isAdded = false for i = 0 to tallies.Count - 1 do if fst tallies.[i] = newNum then tallies.[i] <- (fst tallies.[i], snd tallies.[i] + 1) isAdded <- true if not isAdded then tallies.Add (newNum, 1) type RankedHand = uint64 [] let fifteen = 15uL [] let fifteenFive = fifteen * fifteen * fifteen * fifteen * fifteen [] let fifteenFour = fifteen * fifteen * fifteen * fifteen [] let fifteenThree = fifteen * fifteen * fifteen [] let fifteenTwo = fifteen * fifteen let inline toInt (hand : Hand) (contents : HandContents) : RankedHand = // match hand with // | Hand.Five -> UInt32.MaxValue - 15ul + uint32 contents.First // | _ -> uint64 hand * fifteenFive + uint64 contents.First * fifteenFour + uint64 contents.Second * fifteenThree + uint64 contents.Third * fifteenTwo + uint64 contents.Fourth * fifteen + uint64 contents.Fifth let inline parseHand (tallyBuffer : ResizeArray<_>) (adjustJoker : bool) (s : ReadOnlySpan) : RankedHand = let contents = { First = toByte adjustJoker s.[0] Second = toByte adjustJoker s.[1] Third = toByte adjustJoker s.[2] Fourth = toByte adjustJoker s.[3] Fifth = toByte adjustJoker s.[4] } tallyBuffer.Clear () tallyBuffer.Add (contents.First, 1) updateState tallyBuffer contents.Second updateState tallyBuffer contents.Third updateState tallyBuffer contents.Fourth updateState tallyBuffer contents.Fifth let jokerCount, jokerPos = if not adjustJoker then 0, -1 else let mutable count = 0 let mutable jokerPos = -1 for i = 0 to tallyBuffer.Count - 1 do let card, tally = tallyBuffer.[i] if card = 1uy then count <- tally jokerPos <- i count, jokerPos let hand = if jokerCount > 0 then match tallyBuffer.Count with | 1 -> // Five jokers Hand.Five | 2 -> // Jokers plus one other card type Hand.Five | 3 -> // Jokers plus two other card types. Either full house, or four of a kind if jokerCount >= 2 then // JJABB or JJJAB Hand.Four else if // JAABB or JAAAB jokerPos <> 0 then if snd tallyBuffer.[0] = 2 then Hand.FullHouse else Hand.Four else if snd tallyBuffer.[1] = 2 then Hand.FullHouse else Hand.Four | 4 -> // Jokers plus three other card types, exactly one of which therefore is a two-of. Hand.Three | 5 -> // Five different cards, one of which is a joker. Hand.Pair | _ -> failwith "bad tallyBuffer" elif tallyBuffer.Count = 1 then Hand.Five elif tallyBuffer.Count = 2 then // AAAAB or AAABB if snd tallyBuffer.[0] = 3 || snd tallyBuffer.[0] = 2 then Hand.FullHouse else Hand.Four elif tallyBuffer.Count = 3 then // AAABC or AABBC if snd tallyBuffer.[0] = 3 then Hand.Three elif snd tallyBuffer.[0] = 2 then Hand.TwoPairs elif snd tallyBuffer.[1] = 3 then Hand.Three elif snd tallyBuffer.[1] = 2 then Hand.TwoPairs else Hand.Three elif tallyBuffer.Count = 4 then Hand.Pair else Hand.High toInt hand contents type RankedHandAndBid = uint64 let inline toRankedHandAndBid (r : RankedHand) (bid : uint64) : RankedHandAndBid = 1001uL * r + bid let inline getBid (r : RankedHandAndBid) : uint64 = uint64 (r % 1001uL) let parse (adjustJoker : bool) (s : string) : ResizeArray = use mutable lines = StringSplitEnumerator.make '\n' s let result = ResizeArray () let tallies = ResizeArray 5 while lines.MoveNext () do if not lines.Current.IsEmpty then use mutable line = StringSplitEnumerator.make' ' ' lines.Current line.MoveNext () |> ignore let rankedHand = parseHand tallies adjustJoker line.Current line.MoveNext () |> ignore let bid = UInt64.Parse (line.Current, NumberStyles.Integer, CultureInfo.InvariantCulture) result.Add (toRankedHandAndBid rankedHand bid) result let part1 (s : string) = let arr = parse false s arr.Sort () let mutable answer = 0uL for i = 0 to arr.Count - 1 do answer <- answer + getBid arr.[i] * (uint64 i + 1uL) answer let part2 (s : string) = let arr = parse true s arr.Sort () let mutable answer = 0uL for i = 0 to arr.Count - 1 do answer <- answer + getBid arr.[i] * (uint64 i + 1uL) answer