module EveryBodyCodes2025.QuestOne open System open System.Collections.Generic open System.IO open System.Linq open System.Numerics open System.Threading.Tasks let eni (n:BigInteger) (exp:BigInteger) (m:BigInteger) = let mutable remainders = [] let mutable score = BigInteger 1 for _ in (BigInteger 1)..exp do score <- (score * n) % m remainders <- score::remainders List.rev remainders // Reverse to get correct order (oldest first) // let test1 = eni 2 4 5 |> fun l -> String.Join("", l) |> int64 // let test2 = eni 3 5 16 |> fun l -> String.Join("", l) |> int64 let part1Lines (input:String array) = input |> Array.map (fun line -> line.Split(" ") |> Array.map(fun segment -> let parts = segment.Split("=") let alias = parts[0] let num = parts[1] |> BigInteger.Parse (alias, num) ) |> dict ) let toBigInt (list:'a list) = String.Join("", list) |> BigInteger.Parse let part1 (input: IDictionary array)= input |> Seq.map (fun line -> let a = eni line["A"] line["X"] line["M"] |> toBigInt let b = eni line["B"] line["Y"] line["M"] |> toBigInt let c = eni line["C"] line["Z"] line["M"] |> toBigInt a + b + c ) |> Seq.max |> fun x -> printfn $"{x}" x let part1Answer = File.ReadAllLines "Inputs/Q01_P01.txt" |> part1Lines |> part1 /// Map from (prev, curr) pair to position type PositionMap = Map let rec findCycle (pairToNextPair: PositionMap) startPair currentPair acc = if currentPair = startPair && List.length acc > 0 then Some (List.rev acc) else match Map.tryFind currentPair pairToNextPair with | None -> None | Some nextPair -> findCycle pairToNextPair startPair nextPair (snd currentPair :: acc) let rec eni2' score (n:BigInteger) (exp:BigInteger) (m:BigInteger) (pairMap: PositionMap) (scores:BigInteger list) iter = if iter > exp then scores |> List.rev |> List.skip (max 0 (List.length scores - 5)) |> toBigInt else let newScore = (score * n) % m let key = (score, newScore) match Map.tryFind key pairMap with | Some _ -> match findCycle pairMap key key [] with | Some cycle -> let remaining = int64 (exp - iter) let cycleValues = cycle let cycleLength = List.length cycleValues |> int64 let scoresLength = List.length scores |> int64 let totalLength = scoresLength + 1L + remaining // scores + newScore + remaining let needCount = min 5L totalLength let startPos = max 0L (totalLength - needCount) let scoresReversed = List.rev scores let final5 = [startPos..totalLength - 1L] |> List.map (fun pos -> if pos < scoresLength then // Position is in scores (scores is reversed, so oldest is at end) scoresReversed.[int pos] elif pos = scoresLength then // Position is newScore newScore else let cycleOffset = pos - scoresLength let cyclePos = cycleOffset % cycleLength cycleValues.[int cyclePos] ) // final 5 comes out in reverse order final5 |> List.rev |> toBigInt | None -> eni2' newScore n exp m (Map.add key ((newScore, (newScore * n) % m)) pairMap) (newScore::scores) (iter + BigInteger 1) | None -> let nextPair = (newScore, (newScore * n) % m) eni2' newScore n exp m (Map.add key nextPair pairMap) (newScore::scores) (iter + BigInteger 1) let eni2 (n) (exp) (m) = eni2' (BigInteger 1) n exp m Map.empty [] (BigInteger 1) let part2 (input: IDictionary array)= input |> Array.mapi (fun i line -> //printfn $"""running line {line.AsEnumerable() |> Seq.map(fun kv -> kv.Key + "=" + kv.Value.ToString()) |> fun x -> String.Join(", ", x)}""" let a = eni2 line["A"] line["X"] line["M"] let b = eni2 line["B"] line["Y"] line["M"] let c = eni2 line["C"] line["Z"] line["M"] let ret = a + b + c //printfn $"found {ret}" ret ) |> Seq.max let test2 = let a = eni2 5 6 15 let b = eni2 9 16 15 let c = eni2 7 18 15 let r = a + b + c printfn $"{r}" r let part2Answer = File.ReadAllLines "Inputs/Q01_P02.txt" |> part1Lines |> part2 |> fun x -> printfn $"part2 {x}" x let part2Answer' file = File.ReadAllLines file |> part1Lines |> part2 |> fun x -> printfn $"part2 {x}" x