namespace AdventOfCode2023 open System [] module Day13 = let rowToInt (row : ReadOnlySpan) : uint32 = let mutable mult = 1ul let mutable answer = 0ul for c = row.Length - 1 downto 0 do if row.[c] = '#' then answer <- answer + mult mult <- mult * 2ul answer let colToInt (grid : ReadOnlySpan) (rowLength : int) (colNum : int) = let mutable mult = 1ul let mutable answer = 0ul for i = grid.Count '\n' - 1 downto 0 do if grid.[i * (rowLength + 1) + colNum] = '#' then answer <- answer + mult mult <- mult * 2ul answer let verifyHorizontalReflection (group : ResizeArray<'a>) (smaller : int) (bigger : int) : bool = let midPoint = (smaller + bigger) / 2 let rec isOkWithin (curr : int) = if smaller + curr > midPoint then true else if group.[smaller + curr] = group.[bigger - curr] then isOkWithin (curr + 1) else false if not (isOkWithin 0) then false else smaller = 0 || bigger = group.Count - 1 /// Find reflection among rows [] let rec findRow (rows : ResizeArray<'a>) (currentLine : int) = if currentLine = rows.Count - 1 then None else let mutable answer = UInt32.MaxValue let mutable i = currentLine while i < rows.Count - 1 do i <- i + 1 if currentLine % 2 <> i % 2 then if rows.[i] = rows.[currentLine] then if verifyHorizontalReflection rows currentLine i then answer <- uint32 (((currentLine + i) / 2) + 1) i <- Int32.MaxValue if answer < UInt32.MaxValue then Some answer else findRow rows (currentLine + 1) let part1 (s : string) = let mutable s = s.AsSpan () use lines = StringSplitEnumerator.make' '\n' s let rows = ResizeArray () let cols = ResizeArray () let mutable answer = 0ul while not s.IsEmpty do rows.Clear () cols.Clear () let index = s.IndexOf "\n\n" let group = if index < 0 then // last group s else s.Slice (0, index + 1) let lineLength = s.IndexOf '\n' cols.EnsureCapacity lineLength |> ignore for col = 0 to lineLength - 1 do cols.Add (colToInt group lineLength col) for row in StringSplitEnumerator.make' '\n' group do if not row.IsEmpty then rows.Add (rowToInt row) match findRow rows 0 with | Some rowIndex -> answer <- answer + 100ul * rowIndex | None -> let colIndex = Option.get (findRow cols 0) answer <- answer + colIndex if index < 0 then s <- ReadOnlySpan.Empty else s <- s.Slice (index + 2) answer let part2 (s : string) = use mutable lines = StringSplitEnumerator.make '\n' s let mutable answer = 0uL for line in lines do if not line.IsEmpty then () answer