diff --git a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj index 9f5941f..187ea7c 100644 --- a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj +++ b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj @@ -1,7 +1,7 @@  - net8.0 + netstandard2.0;net8.0;net9.0 $(AssemblyBaseName) true true @@ -21,7 +21,8 @@ - + + @@ -40,4 +41,10 @@ + + + <_Parameter1>FSharp.Collections.Immutable.Tests + + + diff --git a/src/FSharp.Collections.Immutable/FlatList.fs b/src/FSharp.Collections.Immutable/FlatList.fs index 82bcce7..a98405f 100644 --- a/src/FSharp.Collections.Immutable/FlatList.fs +++ b/src/FSharp.Collections.Immutable/FlatList.fs @@ -1,9 +1,19 @@ +// This file contains F# bindings to ImmutableArray from System.Collections.Immutable. +// It provides a flat list implementation that is optimized for performance and memory usage. +// The FlatList type is a wrapper around ImmutableArray, providing a more convenient API for working with immutable lists. +// FlatList code is designed to perform operations without allocating new arrays unnecessarily, making it suitable for high-performance applications. #if INTERACTIVE namespace global #else namespace FSharp.Collections.Immutable #endif +open System +open System.Buffers +open System.Collections.Generic +open System.Collections.Immutable +open System.Linq + // The FlatList name comes from a similar construct seen in the official F# source code type FlatList<'T> = System.Collections.Immutable.ImmutableArray<'T> @@ -19,575 +29,1256 @@ module FlatList = if list.IsDefault then invalidArg argName "Uninstantiated ImmutableArray/FlatList" - let inline internal check (list : FlatList<'T>) = checkNotDefault (nameof list) list + let inline internal indexNotFound () = + raise + <| System.Collections.Generic.KeyNotFoundException ("An item with the specified key was not found.") + + let inline internal sequenceNotFound () = + raise + <| System.InvalidOperationException ("Sequence contains no matching element.") ////////// Creating ////////// - let inline empty<'T> : FlatList<_> = FlatListFactory.Create<'T> () - let inline singleton<'T> (item : 'T) : FlatList<'T> = FlatListFactory.Create<'T> (item) + [] + let inline builderWith capacity : FlatList<'T>.Builder = FlatListFactory.CreateBuilder (capacity) - let inline ofSeq source = FlatListFactory.CreateRange source + [] + let moveFromBuilder (builder : FlatList<_>.Builder) : FlatList<_> = + checkNotNull (nameof builder) builder // Keep check for null builder, not default FlatList + builder.MoveToImmutable () + + [] + let inline empty<'T> : FlatList<'T> = FlatListFactory.Create<'T> () + + [] let inline ofArray (source : _ array) = FlatListFactory.CreateRange source + [] + let inline ofSeq source = FlatListFactory.CreateRange source + + [] + let inline ofList (source : 'T list) = FlatListFactory.CreateRange source + + [] + let inline singleton<'T> (item : 'T) : FlatList<'T> = FlatListFactory.Create<'T> (item) + + [] + let ofOption (option : 'T option) : FlatList<'T> = + match option with + | Some x -> singleton x + | None -> empty + + [] + let ofValueOption (voption : 'T voption) : FlatList<'T> = + match voption with + | ValueSome x -> singleton x + | ValueNone -> empty + + [] + let init count (initializer : int -> 'T) = + if count < 0 then + invalidArg (nameof count) ErrorStrings.InputMustBeNonNegative + + if count = 0 then + empty + else + // Create a builder with exact capacity needed + let builder = FlatListFactory.CreateBuilder<'T> count + // Resize the internal array to ensure all indices are valid + builder.Count <- count + + // Use Parallel.For to initialize elements in parallel + System.Threading.Tasks.Parallel.For (0, count, fun i -> builder.[i] <- initializer i) + |> ignore + + builder.MoveToImmutable () + + [] + let create count (value : 'T) = init count (fun _ -> value) + + [] + let replicate count initial = create count initial + + [] + let zeroCreate<'T> (count : int) : FlatList<'T> = + if count < 0 then + invalidArg (nameof count) ErrorStrings.InputMustBeNonNegative + if count = 0 then + empty + else + let arr = Array.zeroCreate<'T> count + FlatListFactory.CreateRange (arr) + + [] let inline toSeq (flatList : FlatList<_>) = flatList :> seq<_> - let inline toArray (list : FlatList<_>) = - check list - Seq.toArray list + [] + let inline toArray (list : FlatList<_>) = list.ToArray () - ////////// Building ////////// + [] + let toList (list : FlatList<'T>) : 'T list = + if list.IsDefaultOrEmpty then + [] + else + let len = list.Length + let mutable result = [] + for i = len - 1 downto 0 do + result <- list.[i] :: result + result - let moveFromBuilder (builder : FlatList<_>.Builder) : FlatList<_> = - checkNotNull (nameof builder) builder - builder.MoveToImmutable () + [] + let toOption (list : FlatList<'T>) : 'T option = if list.Length = 1 then Some list.[0] else None + + [] + let toValueOption (list : FlatList<'T>) : 'T voption = if list.Length = 1 then ValueSome list.[0] else ValueNone + + [] + let copy (list : FlatList<'T>) : FlatList<'T> = list.ToImmutableArray () + + ////////// Building ////////// + [] let ofBuilder (builder : FlatList<_>.Builder) : FlatList<_> = - checkNotNull (nameof builder) builder + checkNotNull (nameof builder) builder // Keep check for null builder builder.ToImmutable () + [] let inline builder () : FlatList<'T>.Builder = FlatListFactory.CreateBuilder () - let inline builderWith capacity : FlatList<'T>.Builder = FlatListFactory.CreateBuilder (capacity) - let toBuilder list : FlatList<_>.Builder = - check list - list.ToBuilder () + [] + let toBuilder (list : FlatList<'T>) : FlatList<'T>.Builder = list.ToBuilder () module Builder = + let inline private check (builder : FlatList<'T>.Builder) = checkNotNull (nameof builder) builder - let add item builder = + [] + let add (item : 'T) (builder : FlatList<'T>.Builder) : FlatList<'T>.Builder = check builder - builder.Add (item) - - let inline internal indexNotFound () = raise <| System.Collections.Generic.KeyNotFoundException () + builder.Add item + builder + [] let isEmpty (list : FlatList<_>) = list.IsEmpty + + [] let isDefault (list : FlatList<_>) = list.IsDefault + + [] let isDefaultOrEmpty (list : FlatList<_>) = list.IsDefaultOrEmpty ////////// IReadOnly* ////////// - let length list = - check list - list.Length + [] + let length (list : FlatList<'T>) = list.Length - let item index list = - check list - list.[index] + [] + let item index (list : FlatList<'T>) = list.[index] - let append list1 list2 : FlatList<'T> = - checkNotDefault (nameof list1) list1 - checkNotDefault (nameof list2) list2 - list1.AddRange (list2 : FlatList<_>) + [] + let append (list1 : FlatList<'T>) (list2 : FlatList<'T>) : FlatList<'T> = + list1.AddRange (list2 :> System.Collections.Generic.IEnumerable<'T>) - /// Searches for the specified object and returns the zero-based index of the first occurrence within the range - /// of elements in the list that starts at the specified index and - /// contains the specified number of elements. - let indexRangeWith comparer index count item list = - check list - list.IndexOf (item, index, count, comparer) + [] + let indexRangeWith comparer index count item (list : FlatList<'T>) = list.IndexOf (item, index, count, comparer) + [] let indexRange index count item list = indexRangeWith HashIdentity.Structural index count item list - let indexFromWith comparer index item list = indexRangeWith comparer index (length list - index) item + + [] + let indexFromWith comparer index item list = indexRangeWith comparer index (length list - index) item list + + [] let indexFrom index item list = indexFromWith HashIdentity.Structural index item list + + [] let indexWith comparer item list = indexFromWith comparer 0 item list - let index item list = indexWith HashIdentity.Structural item list - /// Searches for the specified object and returns the zero-based index of the last occurrence within the - /// range of elements in the list that contains the specified number - /// of elements and ends at the specified index. - let lastIndexRangeWith comparer index count item list = - check list - list.LastIndexOf (item, index, count, comparer) + [] + let index item (list : FlatList<'T>) = + let idx = list.IndexOf (item) + if idx = -1 then indexNotFound () else idx + + [] + let lastIndexRangeWith comparer index count item (list : FlatList<'T>) = list.LastIndexOf (item, index, count, comparer) + [] let lastIndexRange index count item list = lastIndexRangeWith HashIdentity.Structural index count item list + + [] let lastIndexFromWith comparer index item list = lastIndexRangeWith comparer index (index + 1) item list + + [] let lastIndexFrom index item list = lastIndexFromWith HashIdentity.Structural index item list + + [] let lastIndexWith comparer item list = lastIndexFromWith comparer (length list - 1) item list - let lastIndex item list = lastIndexWith HashIdentity.Structural item list - /// Removes the specified objects from the list with the given comparer. - let removeAllWith (comparer : System.Collections.Generic.IEqualityComparer<'T>) (items : 'T seq) list : FlatList<_> = - check list - list.RemoveRange (items, comparer) + [] + let lastIndex item (list : FlatList<'T>) = + let idx = list.LastIndexOf (item) + if idx = -1 then indexNotFound () else idx - /// Removes the specified objects from the list. - let removeAll items list = removeAllWith HashIdentity.Structural items list + [] + let removeAllWith comparer (items : 'T seq) (list : FlatList<'T>) : FlatList<'T> = + let itemsToRemove = HashSet (items, comparer) + list.RemoveAll (System.Predicate (fun x -> itemsToRemove.Contains x)) - /// Removes all the elements that do not match the conditions defined by the specified predicate. - let filter predicate list : FlatList<_> = - check list - System.Predicate (not << predicate) |> list.RemoveAll + [] + let removeAll items (list : FlatList<'T>) = removeAllWith HashIdentity.Structural items list - /// Removes all the elements that do not match the conditions defined by the specified predicate. - let where predicate list = filter predicate list + [] + let filter (predicate : 'T -> bool) (list : FlatList<'T>) : FlatList<'T> = + list.RemoveAll (System.Predicate (not << predicate)) - /// Removes a range of elements from the list. - let removeRange index (count : int) list : FlatList<_> = - check list - list.RemoveRange (index, count) + [] + let where (predicate : 'T -> bool) (list : FlatList<'T>) : FlatList<'T> = filter predicate list - let blit source sourceIndex (destination : 'T[]) destinationIndex count = - checkNotDefault (nameof source) source + [] + let removeRange index (count : int) (list : FlatList<'T>) : FlatList<'T> = list.RemoveRange (index, count) - try - source.CopyTo (sourceIndex, destination, destinationIndex, count) - with exn -> - raise exn // throw same exception with the correct stack trace. Update exception code + [] + let blit (source : FlatList<'T>) sourceIndex (destination : 'T[]) destinationIndex count = + source.CopyTo (sourceIndex, destination, destinationIndex, count) - let sortRangeWithComparer comparer index count list = - check list - list.Sort (index, count, comparer) + [] + let sortRangeWithComparer comparer index count (list : FlatList<'T>) = list.Sort (index, count, comparer) + [] let sortRangeWith comparer index count list = sortRangeWithComparer (ComparisonIdentity.FromFunction comparer) index count list + [] let sortRange index count list = sortRangeWithComparer ComparisonIdentity.Structural index count list - let sortWithComparer (comparer : System.Collections.Generic.IComparer<_>) list = - check list - list.Sort (comparer) + [] + let sortWithComparer (comparer : System.Collections.Generic.IComparer<'T>) (list : FlatList<'T>) = list.Sort (comparer) + [] let sortWith comparer list = sortWithComparer (ComparisonIdentity.FromFunction comparer) list - let sort list = - check list - list.Sort () - - ////////// Loop-based ////////// - - let inline private builderWithLengthOf list = builderWith <| length list - - let init count initializer = - if count < 0 then - invalidArg (nameof count) ErrorStrings.InputMustBeNonNegative + [] + let sort (list : FlatList<'T>) = list.Sort () - let builder = builderWith count - - for i = 0 to count - 1 do - builder.Add <| initializer i + [] + let rev (list : FlatList<'T>) : FlatList<'T> = + if list.IsDefaultOrEmpty then + list + else + let len = list.Length + let builder = FlatListFactory.CreateBuilder<'T> len + for i = len - 1 downto 0 do + builder.Add list.[i] + builder.MoveToImmutable () + + [] + let inline sortDescending (list : FlatList<'T>) : FlatList<'T> when 'T : comparison = sortWith (fun x y -> compare y x) list + + [] + let inline sortByDescending (projection : 'T -> 'Key) (list : FlatList<'T>) : FlatList<'T> when 'Key : comparison = + sortWith (fun x y -> compare (projection y) (projection x)) list + + [] + let sortBy (projection : 'T -> 'Key) (list : FlatList<'T>) : FlatList<'T> when 'Key : comparison = + if list.IsDefaultOrEmpty then + list + else + let items = list.ToArray () // Work with a mutable array for sorting + System.Array.Sort (items, fun x y -> compare (projection x) (projection y)) + FlatListFactory.CreateRange (items) + + ////////// Loop-based (now LINQ-based where applicable) ////////// + + [] + let concat (arrs : FlatList>) = + let totalLength = Seq.sumBy (fun (innerList : FlatList<'T>) -> innerList.Length) arrs + let builder = FlatListFactory.CreateBuilder<'T> (totalLength) + for i = 0 to arrs.Length - 1 do + let arr = arrs.[i] + for j = 0 to arr.Length - 1 do + builder.Add (arr.[j]) + builder.MoveToImmutable () - moveFromBuilder builder + [] + let inline map (mapping : 'T -> 'U) (list : FlatList<'T>) : FlatList<'U> = list.Select(mapping).ToImmutableArray () - let rec private concatAddLengths (arrs : FlatList>) i acc = - if i >= length arrs then - acc - else - concatAddLengths arrs (i + 1) (acc + arrs.[i].Length) + [] + let mapi (mapping : int -> 'T -> 'U) (list : FlatList<'T>) : FlatList<'U> = + list.Select(fun x i -> mapping i x).ToImmutableArray () - let concat (arrs : FlatList>) = // consider generalizing - let result : FlatList<'T>.Builder = builderWith <| concatAddLengths arrs 0 0 + [] + let mapi2 (mapping : int -> 'T1 -> 'T2 -> 'U) (list1 : FlatList<'T1>) (list2 : FlatList<'T2>) : FlatList<'U> = + let len1 = list1.Length + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - for i = 0 to length arrs - 1 do - result.AddRange (arrs.[i] : FlatList<'T>) + Enumerable.Range(0, len1).Select(fun i -> mapping i list1.[i] list2.[i]).ToImmutableArray () - moveFromBuilder result + [] + let mapi3 + (mapping : int -> 'T1 -> 'T2 -> 'T3 -> 'U) + (list1 : FlatList<'T1>) + (list2 : FlatList<'T2>) + (list3 : FlatList<'T3>) + : FlatList<'U> = + let len1 = list1.Length + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + if len1 <> list3.Length then + invalidArg (nameof list3) ErrorStrings.ListsHaveDifferentLengths - let inline map mapping list = - check list - let builder = builderWithLengthOf list + Enumerable.Range(0, len1).Select(fun i -> mapping i list1.[i] list2.[i] list3.[i]).ToImmutableArray () - for i = 0 to length list - 1 do - builder.Add (mapping list.[i]) + [] + let map2 (mapping : 'T1 -> 'T2 -> 'U) (list1 : FlatList<'T1>) (list2 : FlatList<'T2>) : FlatList<'U> = + let len1 = list1.Length + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - moveFromBuilder builder + Enumerable.Range(0, len1).Select(fun i -> mapping list1.[i] list2.[i]).ToImmutableArray () - let countBy projection list = - check list - // need struct box optimization - let dict = new System.Collections.Generic.Dictionary<'Key, int> (HashIdentity.Structural) + [] + let map3 + (mapping : 'T1 -> 'T2 -> 'T3 -> 'U) + (list1 : FlatList<'T1>) + (list2 : FlatList<'T2>) + (list3 : FlatList<'T3>) + : FlatList<'U> = + let len1 = list1.Length + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + if len1 <> list3.Length then + invalidArg (nameof list3) ErrorStrings.ListsHaveDifferentLengths - // Build the groupings - for v in list do - let key = projection v - let mutable prev = Unchecked.defaultof<_> + Enumerable.Range(0, len1).Select(fun i -> mapping list1.[i] list2.[i] list3.[i]).ToImmutableArray () - if dict.TryGetValue (key, &prev) then - dict.[key] <- prev + 1 - else - dict.[key] <- 1 + [] + let mapFold<'T, 'State, 'Result> + (mapping : 'State -> 'T -> 'Result * 'State) + (state : 'State) + (list : FlatList<'T>) + : FlatList<'Result> * 'State = + checkNotDefault (nameof list) list + let len = list.Length - let res = builderWith dict.Count - let mutable i = 0 + if len = 0 then + empty, state + else + let resultBuilder = FlatListFactory.CreateBuilder<'Result> (len) + resultBuilder.Count <- len + + let mutable currentState = state + + for i = 0 to len - 1 do + let item = list.[i] + let result, newState = mapping currentState item + resultBuilder.[i] <- result + currentState <- newState + + resultBuilder.MoveToImmutable (), currentState + + [] + let mapFoldBack<'T, 'State, 'Result> + (mapping : 'T -> 'State -> 'Result * 'State) + (list : FlatList<'T>) + (state : 'State) + : FlatList<'Result> * 'State = + checkNotDefault (nameof list) list + let len = list.Length - for group in dict do - res.Add (group.Key, group.Value) - i <- i + 1 + if len = 0 then + empty, state + else + let resultBuilder = FlatListFactory.CreateBuilder<'Result> (len) + resultBuilder.Count <- len - moveFromBuilder res + let mutable currentState = state - let indexed list = - check list - let builder = builderWithLengthOf list + for i = len - 1 downto 0 do + let item = list.[i] + let result, newState = mapping item currentState + resultBuilder.[i] <- result + currentState <- newState - for i = 0 to length list - 1 do - builder.Add (i, list.[i]) + resultBuilder.MoveToImmutable (), currentState - moveFromBuilder builder + [] + let countBy (projection : 'T -> 'Key) (list : FlatList<'T>) = + list.GroupBy(projection).Select(fun group -> struct (group.Key, Seq.length group)).ToImmutableArray () - let inline iter action list = - check list + [] + let indexed (list : FlatList<'T>) = list.Select(fun item index -> struct (index, item)).ToImmutableArray () - for i = 0 to length list - 1 do - action list.[i] + [] + let inline iter (action : 'T -> unit) (list : FlatList<'T>) = + for item in list do + action item - let iter2 action list1 list2 = - checkNotDefault (nameof list1) list1 - checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<'T, 'U, unit>.Adapt (action) - let len = length list1 + [] + let iteri action (list : FlatList<'T>) = + for i = 0 to list.Length - 1 do + do action i list.[i] - if len <> length list2 then + [] + let iter2 (action : 'T1 -> 'T2 -> unit) (list1 : FlatList<'T1>) (list2 : FlatList<'T2>) = + let len = list1.Length + if len <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - for i = 0 to len - 1 do - f.Invoke (list1.[i], list2.[i]) - - let distinctBy projection (list : FlatList<'T>) = - let builder : FlatList<'T>.Builder = builderWith <| length list - let set = System.Collections.Generic.HashSet<'Key> (HashIdentity.Structural) - let mutable outputIndex = 0 - - for i = 0 to length list - 1 do - let item = list.[i] - - if set.Add <| projection item then - outputIndex <- outputIndex + 1 - Builder.add item builder - - ofBuilder builder + do action list1.[i] list2.[i] - let map2 mapping list1 list2 = - checkNotDefault (nameof list1) list1 - checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (mapping) + [] + let iter3 (action : 'T1 -> 'T2 -> 'T3 -> unit) (list1 : FlatList<'T1>) (list2 : FlatList<'T2>) (list3 : FlatList<'T3>) = let len1 = list1.Length - if len1 <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - - let res = builderWith len1 - - for i = 0 to len1 - 1 do - res.Add <| f.Invoke (list1.[i], list2.[i]) - - moveFromBuilder res - - let map3 mapping list1 list2 list3 = - checkNotDefault (nameof list1) list1 - checkNotDefault (nameof list2) list2 - checkNotDefault (nameof list3) list3 - let f = OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt (mapping) - let len1 = list1.Length - - if not (len1 = list2.Length) then - invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - - if not (len1 = list3.Length) then + if len1 <> list3.Length then invalidArg (nameof list3) ErrorStrings.ListsHaveDifferentLengths - - let res = builderWith len1 - for i = 0 to len1 - 1 do - res.Add <| f.Invoke (list1.[i], list2.[i], list3.[i]) + action list1.[i] list2.[i] list3.[i] - moveFromBuilder res - - let mapi2 mapping list1 list2 = - checkNotDefault (nameof list1) list1 - checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt (mapping) + [] + let iteri2 (action : int -> 'T1 -> 'T2 -> unit) (list1 : FlatList<'T1>) (list2 : FlatList<'T2>) = let len1 = list1.Length - if len1 <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - - let res = builderWith len1 - for i = 0 to len1 - 1 do - res.Add <| f.Invoke (i, list1.[i], list2.[i]) - - moveFromBuilder res - - let iteri action list = - check list - let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (action) - let len = list.Length - - for i = 0 to len - 1 do - f.Invoke (i, list.[i]) - - let iteri2 action list1 list2 = - checkNotDefault (nameof list1) list1 - checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt (action) + action i list1.[i] list2.[i] + + [] + let iteri3 + (action : int -> 'T1 -> 'T2 -> 'T3 -> unit) + (list1 : FlatList<'T1>) + (list2 : FlatList<'T2>) + (list3 : FlatList<'T3>) + = let len1 = list1.Length - if len1 <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - + if len1 <> list3.Length then + invalidArg (nameof list3) ErrorStrings.ListsHaveDifferentLengths for i = 0 to len1 - 1 do - f.Invoke (i, list1.[i], list2.[i]) - - let mapi mapping list = - check list - let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (mapping) - let len = list.Length - let res = builderWithLengthOf list - - for i = 0 to len - 1 do - res.Add <| f.Invoke (i, list.[i]) + action i list1.[i] list2.[i] list3.[i] - moveFromBuilder res + [] + let exists (predicate : 'T -> bool) (list : FlatList<'T>) = list.Any (predicate) - let exists predicate list = - check list - let len = list.Length - let rec loop i = i < len && (predicate list.[i] || loop (i + 1)) + [] + let exists2 (predicate : 'T1 -> 'T2 -> bool) (list1 : FlatList<'T1>) (list2 : FlatList<'T2>) = + let len = list1.Length + if len <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + let rec loop i = i < len && (predicate list1.[i] list2.[i] || loop (i + 1)) loop 0 - let inline contains e list = - check list - let mutable state = false - let mutable i = 0 - - while (not state && i < list.Length) do - state <- e = list.[i] - i <- i + 1 - - state - - let exists2 predicate list1 list2 = - checkNotDefault (nameof list1) list1 - checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (predicate) + [] + let exists3 (predicate : 'T1 -> 'T2 -> 'T3 -> bool) (list1 : FlatList<'T1>) (list2 : FlatList<'T2>) (list3 : FlatList<'T3>) = let len1 = list1.Length - if len1 <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - + if len1 <> list3.Length then + invalidArg (nameof list3) ErrorStrings.ListsHaveDifferentLengths let rec loop i = i < len1 - && (f.Invoke (list1.[i], list2.[i]) || loop (i + 1)) - + && (predicate list1.[i] list2.[i] list3.[i] || loop (i + 1)) loop 0 - let forall predicate list = - check list - let len = list.Length - let rec loop i = i >= len || (predicate list.[i] && loop (i + 1)) - loop 0 + [] + let forall (predicate : 'T -> bool) (list : FlatList<'T>) = list.All (predicate) - let forall2 predicate list1 list2 = - checkNotDefault (nameof list1) list1 - checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (predicate) + [] + let forall2 (predicate : 'T1 -> 'T2 -> bool) (list1 : FlatList<'T1>) (list2 : FlatList<'T2>) = let len1 = list1.Length - if len1 <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + let rec loop i = i >= len1 || (predicate list1.[i] list2.[i] && loop (i + 1)) + loop 0 + [] + let forall3 (predicate : 'T1 -> 'T2 -> 'T3 -> bool) (list1 : FlatList<'T1>) (list2 : FlatList<'T2>) (list3 : FlatList<'T3>) = + let len1 = list1.Length + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + if len1 <> list3.Length then + invalidArg (nameof list3) ErrorStrings.ListsHaveDifferentLengths let rec loop i = i >= len1 - || (f.Invoke (list1.[i], list2.[i]) && loop (i + 1)) - + || (predicate list1.[i] list2.[i] list3.[i] && loop (i + 1)) loop 0 - let groupBy projection list = - check list - let dict = new System.Collections.Generic.Dictionary<'Key, ResizeArray<'T>> (HashIdentity.Structural) - - // Build the groupings - for i = 0 to (list.Length - 1) do - let v = list.[i] - let key = projection v - let ok, prev = dict.TryGetValue (key) - - if ok then - prev.Add (v) - else - let prev = new ResizeArray<'T> (1) - dict.[key] <- prev - prev.Add (v) + [] + let inline contains item (list : FlatList<'T>) = list.Contains (item) - // Return the list-of-lists. - let result = builderWith dict.Count - let mutable i = 0 + [] + let partition (predicate : 'T -> bool) (list : FlatList<'T>) = + let res1 = builderWith list.Length + let res2 = builderWith list.Length + for x in list do // Iteration will cause InvalidOperationException if list is default + if predicate x then res1.Add x else res2.Add x + (res1.ToImmutable (), res2.ToImmutable ()) - for group in dict do - result.Add (group.Key, ofSeq group.Value) - i <- i + 1 + [] + let find (predicate : 'T -> bool) (list : FlatList<'T>) = list.First (predicate) - moveFromBuilder result + [] + let tryFind (predicate : 'T -> bool) (list : FlatList<'T>) : 'T voption = list.Where (predicate) |> Seq.vtryHead - let pick chooser list = - check list + [] + let findBack (predicate : 'T -> bool) (list : FlatList<'T>) = list.Last (predicate) - let rec loop i = - if i >= list.Length then - indexNotFound () - else - match chooser list.[i] with - | None -> loop (i + 1) - | Some res -> res + [] + let findLast (predicate : 'T -> bool) (list : FlatList<'T>) = list.Last (predicate) - loop 0 + [] + let tryFindBack (predicate : 'T -> bool) (list : FlatList<'T>) : 'T voption = + let mutable result = ValueNone + for i = 0 to list.Length - 1 do + let item = list.[i] + if predicate item then + result <- ValueSome item + result - let tryPick chooser list = - check list + [] + let findIndexBack (predicate : 'T -> bool) (list : FlatList<'T>) = + let mutable idx = -1 + for i = 0 to list.Length - 1 do + if predicate list.[i] then + idx <- i + if idx >= 0 then idx else sequenceNotFound () + + [] + let tryFindLast (predicate : 'T -> bool) (list : FlatList<'T>) : 'T voption = + let mutable result = ValueNone + for i = list.Length - 1 downto 0 do + let item = list.[i] + if predicate item && ValueOption.isNone result then + result <- ValueSome item + result + + [] + let tryFindIndexBack (predicate : 'T -> bool) (list : FlatList<'T>) : int voption = + if list.IsDefaultOrEmpty then + ValueNone + else + let mutable i = list.Length - 1 + let mutable result = ValueNone + + while i >= 0 && ValueOption.isNone result do + if predicate list.[i] then + result <- ValueSome i + i <- i - 1 + + result + + [] + let findLastIndex (predicate : 'T -> bool) (list : FlatList<'T>) : int = + let mutable found = false + let mutable idx = -1 + for i = list.Length - 1 downto 0 do + if predicate list.[i] && not found then + idx <- i + found <- true + if found then idx else sequenceNotFound () + + [] + let tryFindLastIndex (predicate : 'T -> bool) (list : FlatList<'T>) : int voption = + let mutable result = ValueNone + for i = list.Length - 1 downto 0 do + if predicate list.[i] && ValueOption.isNone result then + result <- ValueSome i + result + + [] + let pick (chooser : 'T -> 'U voption) (list : FlatList<'T>) = + checkNotDefault (nameof list) list + let mutable result = ValueNone + let mutable i = 0 + while i < list.Length && ValueOption.isNone result do + result <- chooser list.[i] + i <- i + 1 + match result with + | ValueSome x -> x + | ValueNone -> sequenceNotFound () + + [] + let tryPick (chooser : 'T -> 'U voption) (list : FlatList<'T>) : 'U voption = + list.Select(chooser).Where(ValueOption.isSome).Select (ValueOption.get) + |> Seq.vtryHead + + [] + let pickBack (chooser : 'T -> 'U voption) (list : FlatList<'T>) : 'U = + let mutable result = ValueNone + for i = list.Length - 1 downto 0 do + let v = chooser list.[i] + if ValueOption.isSome v && ValueOption.isNone result then + result <- v + match result with + | ValueSome x -> x + | ValueNone -> sequenceNotFound () + + [] + let tryPickBack (chooser : 'T -> 'U voption) (list : FlatList<'T>) : 'U voption = + let mutable result = ValueNone + for i = list.Length - 1 downto 0 do + let v = chooser list.[i] + if ValueOption.isSome v && ValueOption.isNone result then + result <- v + result + + [] + let choose (chooser : 'T -> 'U voption) (list : FlatList<'T>) : FlatList<'U> = + list.Select(chooser).Where(ValueOption.isSome).Select(ValueOption.get).ToImmutableArray () + + [] + let chooseBack (chooser : 'T -> 'U voption) (list : FlatList<'T>) : FlatList<'U> = + let builder = FlatListFactory.CreateBuilder<'U> () + for i = list.Length - 1 downto 0 do + match chooser list.[i] with + | ValueSome v -> builder.Add v + | ValueNone -> () + builder.ToImmutable () - let rec loop i = - if i >= list.Length then - None - else - match chooser list.[i] with - | None -> loop (i + 1) - | res -> res + [] + let groupBy (projection : 'T -> 'Key) (list : FlatList<'T>) = + list.GroupBy(projection).Select(fun group -> struct (group.Key, group.ToImmutableArray ())).ToImmutableArray () - loop 0 - - let choose chooser list = - check list - let res = builderWith list.Length + [] + let distinctBy (projection : 'T -> 'Key) (list : FlatList<'T>) = + if list.IsDefaultOrEmpty then + list + else + let setBuilder = ImmutableHashSet.CreateBuilder<'Key> () + let arrayBuilder = ImmutableArray.CreateBuilder<'T> () + for i = 0 to list.Length - 1 do + let item = list.[i] + if setBuilder.Add (projection item) then + arrayBuilder.Add (item) + arrayBuilder.ToImmutable () + + [] + let findDup (list : FlatList<'T>) = + checkNotDefault (nameof list) list + let seen = System.Collections.Generic.HashSet<'T> () + let mutable result = ValueNone + let mutable i = 0 + while i < list.Length && ValueOption.isNone result do + let item = list.[i] + if not (seen.Add (item)) then + result <- ValueSome item + i <- i + 1 + match result with + | ValueSome x -> x + | ValueNone -> indexNotFound () + + [] + let findDupBy (projection : 'T -> 'Key) (list : FlatList<'T>) = + checkNotDefault (nameof list) list + let seen = System.Collections.Generic.HashSet<'Key> () + let mutable result = ValueNone + let mutable i = 0 + while i < list.Length && ValueOption.isNone result do + let item = list.[i] + let key = projection item + if not (seen.Add (key)) then + result <- ValueSome item + i <- i + 1 + match result with + | ValueSome x -> x + | ValueNone -> indexNotFound () - for i = 0 to list.Length - 1 do - match chooser list.[i] with - | None -> () - | Some b -> res.Add (b) + [] + let collect (mapping : 'T -> 'U seq) (list : FlatList<'T>) : FlatList<'U> = list.SelectMany(mapping).ToImmutableArray () - ofBuilder res + [] + let tryItem index (list : FlatList<'T>) : voption<'T> = + // list.Length or list.[index] will throw if list is default before comparison happens + if list.IsDefault then + ValueNone // Explicitly handle default case for tryItem to return ValueNone + elif index >= 0 && index < list.Length then + ValueSome list.[index] + else + ValueNone - let partition predicate list = - check list - let res1 = builderWith list.Length - let res2 = builderWith list.Length + [] + let head (list : FlatList<'T>) = list.First () - for i = 0 to list.Length - 1 do - let x = list.[i] - if predicate x then res1.Add (x) else res2.Add (x) + [] + let tryHead (list : FlatList<'T>) : 'T voption = + if list.IsDefaultOrEmpty then + ValueNone + else + ValueSome list.[0] - ofBuilder res1, ofBuilder res2 + [] + let last (list : FlatList<_>) = list.Last () - let find predicate list = - check list + [] + let tryLast (list : FlatList<'T>) : 'T voption = + if list.IsDefaultOrEmpty then + ValueNone + else + ValueSome list.[list.Length - 1] + + [] + let tail (list : FlatList<'T>) = + if list.IsDefaultOrEmpty then + invalidArg (nameof list) "List must not be empty to get tail." + list.Slice (1, list.Length - 1) + + [] + let tryTail (list : FlatList<'T>) : voption> = + if list.IsDefaultOrEmpty then + ValueNone + else + ValueSome (list.Slice (1, list.Length - 1)) - let rec loop i = - if i >= list.Length then indexNotFound () - else if predicate list.[i] then list.[i] - else loop (i + 1) + [] + let tryHeadAndTail (list : FlatList<'T>) : ('T * FlatList<'T>) voption = + if list.IsDefaultOrEmpty then + ValueNone + else + ValueSome (list.[0], list.Slice (1, list.Length - 1)) - loop 0 + [] + let tryLastAndInit (list : FlatList<'T>) : (FlatList<'T> * 'T) voption = + if list.IsDefaultOrEmpty then + ValueNone + else + ValueSome (list.Slice (0, list.Length - 1), list.[list.Length - 1]) - let tryFind predicate list = - check list + [] + let take (count : int) (list : FlatList<'T>) = + if count < 0 then + invalidArg (nameof count) ErrorStrings.InputMustBeNonNegative + let len = list.Length + if count = 0 then empty + elif count >= len then list + else list.Slice (0, count) - let rec loop i = - if i >= list.Length then None - else if predicate list.[i] then Some list.[i] - else loop (i + 1) + [] + let takeEnd (count : int) (list : FlatList<'T>) : FlatList<'T> = + if count < 0 || count > list.Length then + invalidArg (nameof count) ErrorStrings.InputMustBeNonNegative + if count = 0 then + empty + else + list.Slice (list.Length - count, count) - loop 0 + [] + let takeWhile (predicate : 'T -> bool) (list : FlatList<'T>) = list.TakeWhile(predicate).ToImmutableArray () - let findBack predicate list = - check list + [] + let skip index (list : FlatList<'T>) = + if index < 0 then + invalidArg (nameof index) ErrorStrings.InputMustBeNonNegative + let len = list.Length + if index = 0 then list + elif index >= len then empty + else list.Slice (index, len - index) - let rec loop i = - if i < 0 then indexNotFound () - else if predicate list.[i] then list.[i] - else loop (i - 1) + [] + let skipEnd (count : int) (list : FlatList<'T>) : FlatList<'T> = + if count < 0 || count > list.Length then + invalidArg (nameof count) ErrorStrings.InputMustBeNonNegative + if count = 0 then + list + else + list.Slice (0, list.Length - count) - loop <| length list - 1 + [] + let skipWhile (predicate : 'T -> bool) (list : FlatList<'T>) = list.SkipWhile(predicate).ToImmutableArray () - let tryFindBack predicate list = - check list + [] + let sub start count (list : FlatList<'T>) = list.Slice (start, count) - let rec loop i = - if i < 0 then None - else if predicate list.[i] then Some list.[i] - else loop (i + 1) + [] + let truncate count (list : FlatList<'T>) = if count < list.Length then list.Slice (0, count) else list - loop <| length list - 1 + [] + let splitAt index (list : FlatList<'T>) = (list.Slice (0, index), list.Slice (index, list.Length - index)) - let findIndexBack predicate list = - check list + [] + let chunkBySize chunkSize (list : FlatList<'T>) = + if chunkSize <= 0 then + invalidArg (nameof chunkSize) ErrorStrings.InputMustBeNonNegative + let len = list.Length + if len = 0 then + empty + else + let numChunks = (len + chunkSize - 1) / chunkSize + let builder = FlatListFactory.CreateBuilder> (numChunks) - let rec loop i = - if i < 0 then indexNotFound () - else if predicate list.[i] then i - else loop (i - 1) + for i = 0 to numChunks - 1 do + let start = i * chunkSize + if start < len then + let remaining = len - start + let count = min chunkSize remaining + builder.Add (list.Slice (start, count)) - loop <| length list - 1 + builder.ToImmutable () - let tryFindIndexBack predicate list = - check list + [] + let inline build f = + let builder = builder () + f builder + builder.ToImmutable () - let rec loop i = - if i < 0 then None - else if predicate list.[i] then Some i - else loop (i - 1) + [] + let inline update f (list : FlatList<'T>) = + let builder = toBuilder list + f builder + builder.ToImmutable () - loop <| length list - 1 - // TODO: windowed + [] + let findIndex (predicate : 'T -> bool) (list : FlatList<'T>) = + checkNotDefault (nameof list) list - ////////// Based on other operations ////////// + let mutable index = -1 + let mutable found = false + let len = list.Length - let take count list = removeRange count (length list - count) list + if len = 0 then + sequenceNotFound () - let inline private lengthWhile predicate list = - check list - let mutable count = 0 + let mutable i = 0 + while i < len && not found do + if predicate list.[i] then + index <- i + found <- true + else + i <- i + 1 - while count < list.Length && predicate list.[count] do - count <- count + 1 + if found then index else sequenceNotFound () - count + [] + let tryFindIndex (predicate : 'T -> bool) (list : FlatList<'T>) : int voption = + list.Select (fun item i -> struct (item, i)) + |> Seq.where (fun struct (item, i) -> predicate item) + |> Seq.map (fun struct (item, i) -> i) + |> Seq.vtryHead - let takeWhile predicate list = take (lengthWhile predicate list) list + [] + let windowed windowSize (list : FlatList<'T>) = + if windowSize < 1 then + invalidArg (nameof windowSize) ErrorStrings.InputMustBeNonNegative + let len = list.Length + if windowSize > len then + empty + else + Enumerable.Range(0, len - windowSize + 1).Select(fun i -> list.Slice (i, windowSize)).ToImmutableArray () - let skip index list = removeRange 0 index list + [] + let pairwise (list : FlatList<'T>) = + if list.Length < 2 then + empty + else + Enumerable.Zip(list, list.Skip (1), fun first second -> struct (first, second)).ToImmutableArray () - let skipWhile predicate list = skip (lengthWhile predicate list) list + [] + let splitInto (count : int) (list : FlatList<'T>) : FlatList> = + if count <= 0 then + invalidArg (nameof count) ErrorStrings.InputMustBeNonNegative - let sub start stop list = skip start list |> take (stop - start - 1) + let len = list.Length + if len = 0 then + empty + else + let chunkSize = (len + count - 1) / count + chunkBySize chunkSize list - let truncate count list = if count < length list then take count list else list + [] + let splitIntoN (count : int) (list : FlatList<'T>) : FlatList> = splitInto count list - let splitAt index list = take index list, skip index list + [] + let distinct (list : FlatList<'T>) = + if list.IsDefaultOrEmpty then + list + else + let seen = System.Collections.Generic.HashSet<'T> () + let builder = ImmutableArray.CreateBuilder<'T> () + for item in list do + if seen.Add (item) then + builder.Add (item) + builder.ToImmutable () + + [] + let allPairs (xs : FlatList<'T>) (ys : FlatList<'U>) = xs.SelectMany(fun x -> ys.Select (fun y -> (x, y))).ToImmutableArray () + + [] + let permute indexMap (list : FlatList<'T>) = + let len = list.Length + if len = 0 then + list + else + let builder = FlatListFactory.CreateBuilder<'T> len + builder.Count <- len + let usedSourceIndices = System.Collections.Generic.HashSet () + + for i = 0 to len - 1 do + let sourceIndex = indexMap i + if sourceIndex < 0 || sourceIndex >= len then + invalidArg (nameof indexMap) "Invalid permutation function, source index out of range" + if not (usedSourceIndices.Add (sourceIndex)) then + invalidArg (nameof indexMap) "Invalid permutation function, duplicate source indices mapped" + + builder.[i] <- list.[sourceIndex] + builder.MoveToImmutable () + + [] + let zip (list1 : FlatList<'T>) (list2 : FlatList<'U>) = + let len1 = list1.Length + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + Enumerable.Range(0, len1).Select(fun i -> struct (list1.[i], list2.[i])).ToImmutableArray () - let head list = item 0 list + [] + let zip3 (list1 : FlatList<'T>) (list2 : FlatList<'U>) (list3 : FlatList<'V>) = + let len1 = list1.Length + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + if len1 <> list3.Length then + invalidArg (nameof list3) ErrorStrings.ListsHaveDifferentLengths + Enumerable.Range(0, len1).Select(fun i -> struct (list1.[i], list2.[i], list3.[i])).ToImmutableArray () - let tryItem index list = - if index >= length list || index < 0 then - None + [] + let unzip (list : FlatList) = + if list.IsEmpty then + struct (empty, empty) else - Some (list.[index]) + struct (list.Select(fstv).ToImmutableArray (), list.Select(sndv).ToImmutableArray ()) - let tryHead list = tryItem 0 list + [] + let unzip3 (list : FlatList) = + if list.IsEmpty then + struct (empty, empty, empty) + else + let res1 = list.Select(fun struct (x, _, _) -> x).ToImmutableArray () + let res2 = list.Select(fun struct (_, y, _) -> y).ToImmutableArray () + let res3 = list.Select(fun struct (_, _, z) -> z).ToImmutableArray () + struct (res1, res2, res3) + + [] + let inline average<'T + when 'T : (static member (+) : 'T * 'T -> 'T) + and 'T : (static member DivideByInt : 'T * int -> 'T) + and 'T : (static member Zero : 'T)> + (list : FlatList<'T>) + = + if list.Length = 0 then + invalidArg (nameof list) LanguagePrimitives.ErrorStrings.InputArrayEmptyString + let sum = list.Aggregate ('T.Zero, fun acc x -> Checked.(+) acc x) + 'T.DivideByInt (sum, list.Length) + + [] + let inline averageBy<'T, 'U + when 'U : (static member (+) : 'U * 'U -> 'U) + and 'U : (static member DivideByInt : 'U * int -> 'U) + and 'U : (static member Zero : 'U)> + (projection : 'T -> 'U) + (list : FlatList<'T>) + = + let sum = list.Aggregate ('U.Zero, fun acc x -> Checked.(+) acc (projection x)) + 'U.DivideByInt (sum, list.Length) + + [] + let fold<'T, 'State> (folder : 'State -> 'T -> 'State) (state : 'State) (list : FlatList<'T>) = list.Aggregate (state, folder) + + [] + let fold2<'T1, 'T2, 'State> + (folder : 'State -> 'T1 -> 'T2 -> 'State) + (state : 'State) + (list1 : FlatList<'T1>) + (list2 : FlatList<'T2>) + = + let len1 = list1.Length + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + let mutable acc = state + for i = 0 to len1 - 1 do + acc <- folder acc list1.[i] list2.[i] + acc - let last (list : FlatList<_>) = list.[length list - 1] + [] + let foldi<'T, 'State> (folder : int -> 'State -> 'T -> 'State) (state : 'State) (list : FlatList<'T>) : 'State = + let mutable acc = state + for i = 0 to list.Length - 1 do + acc <- folder i acc list.[i] + acc + + [] + let foldi2<'T1, 'T2, 'State> + (folder : int -> 'State -> 'T1 -> 'T2 -> 'State) + (state : 'State) + (list1 : FlatList<'T1>) + (list2 : FlatList<'T2>) + : 'State = + let len = list1.Length + if len <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + let mutable acc = state + for i = 0 to len - 1 do + acc <- folder i acc list1.[i] list2.[i] + acc + + [] + let foldBack<'T, 'State> (folder : 'T -> 'State -> 'State) (list : FlatList<'T>) (state : 'State) = + checkNotDefault (nameof list) list + let mutable acc = state + for i = list.Length - 1 downto 0 do + acc <- folder list.[i] acc + acc + + [] + let foldBack2<'T1, 'T2, 'State> + (folder : 'T1 -> 'T2 -> 'State -> 'State) + (list1 : FlatList<'T1>) + (list2 : FlatList<'T2>) + (state : 'State) + = + let len1 = list1.Length + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + let mutable acc = state + for i = len1 - 1 downto 0 do + acc <- folder list1.[i] list2.[i] acc + acc + + [] + let foldBacki (folder : int -> 'T -> 'State -> 'State) (list : FlatList<'T>) (state : 'State) : 'State = + let mutable acc = state + for i = list.Length - 1 downto 0 do + acc <- folder i list.[i] acc + acc + + [] + let foldBacki2 + (folder : int -> 'T1 -> 'T2 -> 'State -> 'State) + (list1 : FlatList<'T1>) + (list2 : FlatList<'T2>) + (state : 'State) + : 'State = + let len = list1.Length + if len <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + let mutable acc = state + for i = len - 1 downto 0 do + acc <- folder i list1.[i] list2.[i] acc + acc + + [] + let foldBack3<'T1, 'T2, 'T3, 'State> + (folder : 'T1 -> 'T2 -> 'T3 -> 'State -> 'State) + (list1 : FlatList<'T1>) + (list2 : FlatList<'T2>) + (list3 : FlatList<'T3>) + (state : 'State) + = + let len1 = list1.Length + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + if len1 <> list3.Length then + invalidArg (nameof list3) ErrorStrings.ListsHaveDifferentLengths + let mutable acc = state + for i = len1 - 1 downto 0 do + acc <- folder list1.[i] list2.[i] list3.[i] acc + acc + + [] + let reduce (reduction : 'T -> 'T -> 'T) (list : FlatList<'T>) = + if list.IsDefaultOrEmpty then + invalidArg (nameof list) LanguagePrimitives.ErrorStrings.InputArrayEmptyString + else + list.Aggregate (reduction) - let tryLast list = tryItem (length list - 1) list + [] + let reduceBack (reduction : 'T -> 'T -> 'T) (list : FlatList<'T>) = + if list.IsDefaultOrEmpty then + invalidArg (nameof list) LanguagePrimitives.ErrorStrings.InputArrayEmptyString + else + let len = list.Length + let mutable result = list.[len - 1] + for i = len - 2 downto 0 do + result <- reduction list.[i] result + result + + [] + let scan<'T, 'State> folder (state : 'State) (list : FlatList<'T>) = + let builder = FlatListFactory.CreateBuilder<'State> (list.Length + 1) + builder.Add state + let mutable currentState = state + for item in list do + currentState <- folder currentState item + builder.Add currentState + builder.ToImmutable () - let tail list = removeRange 1 (length list - 1) list + [] + let scanBack<'T, 'State> folder (list : FlatList<'T>) (state : 'State) = + checkNotDefault (nameof list) list + let len = list.Length + let builder = FlatListFactory.CreateBuilder<'State> (len + 1) + builder.Count <- len + 1 - let tryTail list = if isEmpty list then None else Some <| tail list + builder.[len] <- state + let mutable currentState = state + for i = len - 1 downto 0 do + currentState <- folder list.[i] currentState + builder.[i] <- currentState - let create count item = init count <| fun _ -> item // optimize + builder.MoveToImmutable () - let replicate count item = create item count + [] + let exactlyOne (list : FlatList<'T>) = list.Single () - let collect mapping list = concat <| map mapping list + [] + let tryExactlyOne (list : FlatList<'T>) : 'T voption = + if list.IsDefaultOrEmpty || list.Length <> 1 then + ValueNone + else + ValueSome list.[0] + + [] + let except (itemsToExclude : 'T seq) (list : FlatList<'T>) : FlatList<'T> = + let excludeSet = HashSet (itemsToExclude) + filter (fun x -> not (excludeSet.Contains x)) list + + [] + let inline sum (list : FlatList< ^T >) : ^T when ^T : (static member (+) : ^T * ^T -> ^T) and ^T : (static member Zero : ^T) = + list.Aggregate (LanguagePrimitives.GenericZero< ^T>, fun acc x -> acc + x) + + [] + let inline sumBy + (projection : 'T -> 'U) + (list : FlatList<'T>) + : 'U when 'U : (static member (+) : 'U * 'U -> 'U) and 'U : (static member Zero : 'U) = + list.Aggregate (LanguagePrimitives.GenericZero<'U>, fun acc x -> acc + (projection x)) + + [] + let transpose (lists : FlatList>) : FlatList> = + if lists.IsDefaultOrEmpty then + empty + else + let len0 = lists.[0].Length + for i = 1 to lists.Length - 1 do + if lists.[i].Length <> len0 then + invalidArg (nameof lists) "All inner arrays must have the same length." + + Enumerable + .Range(0, len0) + .Select(fun j -> Enumerable.Range(0, lists.Length).Select(fun i -> lists.[i].[j]).ToImmutableArray ()) + .ToImmutableArray () + + [] + let updateAt (index : int) (value : 'T) (list : FlatList<'T>) : FlatList<'T> = list.SetItem (index, value) + + [] + let removeAt (index : int) (list : FlatList<'T>) : FlatList<'T> = list.RemoveAt (index) + + [] + let insertAt (index : int) (value : 'T) (list : FlatList<'T>) : FlatList<'T> = list.Insert (index, value) + + [] + let insertManyAt (index : int) (values : 'T seq) (list : FlatList<'T>) : FlatList<'T> = list.InsertRange (index, values) + + [] + let fill (index : int) (count : int) (value : 'T) (list : FlatList<'T>) : FlatList<'T> = + if index < 0 || count < 0 || index + count > list.Length then + invalidArg (nameof index) ErrorStrings.InputMustBeNonNegative + let builder = list.ToBuilder () + for i = index to index + count - 1 do + builder.[i] <- value + builder.MoveToImmutable () - let inline build f = + [] + let unfold<'T, 'State> (generator : 'State -> struct ('T * 'State) voption) (state : 'State) : FlatList<'T> = let builder = builder () - f builder - moveFromBuilder builder + let mutable currentState = state + let mutable continuing = true - let inline update f list = - let builder = toBuilder list - f builder - moveFromBuilder builder + while continuing do + match generator currentState with + | ValueSome (value, newState) -> + builder.Add (value) + currentState <- newState + | ValueNone -> continuing <- false -////////// + builder.ToImmutable () -module ImmutableArray = FlatList + [] + let compareWith (comparer : 'T -> 'T -> int) (list1 : FlatList<'T>) (list2 : FlatList<'T>) : int = + if list1.IsDefault && list2.IsDefault then + 0 + elif list1.IsDefault then + -1 + elif list2.IsDefault then + 1 + else + let len1 = list1.Length + let len2 = list2.Length + let minLength = min len1 len2 + + let mutable i = 0 + let mutable result = 0 + let mutable continueComparing = true + + while i < minLength && continueComparing do + result <- comparer list1.[i] list2.[i] + if result <> 0 then + continueComparing <- false + else + i <- i + 1 + + if result = 0 then compare len1 len2 else result + + [] + let inline max<'T when 'T : comparison> (list : FlatList<'T>) : 'T = + checkNotDefault (nameof list) list + if list.IsEmpty then + invalidArg (nameof list) LanguagePrimitives.ErrorStrings.InputArrayEmptyString + let mutable acc = list.[0] + for i = 1 to list.Length - 1 do + let curr = list.[i] + if curr > acc then + acc <- curr + acc + + [] + let inline maxBy<'T, 'Key when 'Key : comparison> (projection : 'T -> 'Key) (list : FlatList<'T>) : 'T = + checkNotDefault (nameof list) list + if list.IsEmpty then + invalidArg (nameof list) LanguagePrimitives.ErrorStrings.InputArrayEmptyString + let mutable maxVal = list.[0] + let mutable maxKey = projection maxVal + for i = 1 to list.Length - 1 do + let currVal = list.[i] + let currKey = projection currVal + if currKey > maxKey then + maxVal <- currVal + maxKey <- currKey + maxVal + + [] + let inline min<'T when 'T : comparison> (list : FlatList<'T>) : 'T = + checkNotDefault (nameof list) list + if list.IsEmpty then + invalidArg (nameof list) LanguagePrimitives.ErrorStrings.InputArrayEmptyString + let mutable acc = list.[0] + for i = 1 to list.Length - 1 do + let curr = list.[i] + if curr < acc then + acc <- curr + acc + + [] + let inline minBy<'T, 'Key when 'Key : comparison> (projection : 'T -> 'Key) (list : FlatList<'T>) : 'T = + checkNotDefault (nameof list) list + if list.IsEmpty then + invalidArg (nameof list) LanguagePrimitives.ErrorStrings.InputArrayEmptyString + let mutable minVal = list.[0] + let mutable minKey = projection minVal + for i = 1 to list.Length - 1 do + let currVal = list.[i] + let currKey = projection currVal + if currKey < minKey then + minVal <- currVal + minKey <- currKey + minVal diff --git a/src/FSharp.Collections.Immutable/FlatList.fsi b/src/FSharp.Collections.Immutable/FlatList.fsi new file mode 100644 index 0000000..d560cb1 --- /dev/null +++ b/src/FSharp.Collections.Immutable/FlatList.fsi @@ -0,0 +1,1446 @@ +// This file contains F# bindings to ImmutableArray from System.Collections.Immutable. +// It provides a flat list implementation that is optimized for performance and memory usage. +// The FlatList type is a wrapper around ImmutableArray, providing a more convenient API for working with immutable lists. +// FlatList code is designed to perform operations without allocating new arrays unnecessarily, making it suitable for high-performance applications. +#if INTERACTIVE +namespace global +#else +namespace FSharp.Collections.Immutable +#endif + +open System +open System.Collections.Generic +open System.Collections.Immutable + +// The FlatList name comes from a similar construct seen in the official F# source code +type FlatList<'T> = System.Collections.Immutable.ImmutableArray<'T> + +// based on the F# Array module source +[] +module FlatList = + + ////////// Creating ////////// + + /// Creates a new builder with the specified capacity + /// The initial capacity of the builder + /// An empty builder with the specified capacity + [] + val inline builderWith<'T> : capacity : int -> FlatList<'T>.Builder + + /// Builds a from a builder, moving the elements and leaving the builder empty + /// The builder to build from + /// A containing the elements from the builder + /// Thrown when builder is null + [] + val moveFromBuilder<'T> : builder : FlatList<'T>.Builder -> FlatList<'T> + + /// Returns an empty + /// An empty + /// + /// + /// let emptyList = FlatList.empty<int> + /// printfn "Is empty? %b" (FlatList.isEmpty emptyList) // true + /// + /// + [] + val inline empty<'T> : FlatList<'T> + + /// Builds a from the given + /// The to build the from + /// A containing the elements of the array + [] + val inline ofArray<'T> : source : 'T array -> FlatList<'T> + + /// Builds a from the given + /// The to build the from + /// A containing the elements of the sequence + [] + val inline ofSeq<'T> : source : seq<'T> -> FlatList<'T> + + /// Builds a from the given + /// The to build the from + /// A containing the elements of the sequence + [] + val inline ofList<'T> : source : 'T list -> FlatList<'T> + + /// Creates a list from a value option. + /// The input option. + /// A list of one element if the option is Some, and an empty list if the option is None. + [] + val ofOption<'T> : option : 'T option -> FlatList<'T> + + /// Creates a list from a value option. + /// The input value option. + /// A list of one element if the option is ValueSome, and an empty list if the option is ValueNone. + [] + val ofValueOption<'T> : voption : 'T voption -> FlatList<'T> + + /// Returns a with a single element + /// The item to put into the + /// A containing only the given item + [] + val inline singleton<'T> : item : 'T -> FlatList<'T> + + /// Creates a by initializing each element with the given function + /// The number of elements to create + /// The function to initialize each element + /// A new with the initialized elements + /// Thrown when count is negative + [] + val init<'T> : count : int -> initializer : (int -> 'T) -> FlatList<'T> + + /// Creates a of a given length with all elements set to the given value + /// The length of the to create + /// The value to replicate + /// A of the specified length with all elements equal to the given value + [] + val create<'T> : count : int -> value : 'T -> FlatList<'T> + + /// Replicates a value into a of a given length + /// The length of the to create + /// The value to replicate + /// A of the specified length with all elements equal to the given value + [] + val replicate<'T> : count : int -> initial : 'T -> FlatList<'T> + + /// Creates an of a specified length, with all the elements initialized to the default zero value for the type. + /// The length of the to create. + /// The created . + [] + val zeroCreate<'T> : count : int -> FlatList<'T> + + /// Views the as a + /// The input + /// The containing the elements of the + [] + val inline toSeq<'T> : flatList : FlatList<'T> -> seq<'T> + + /// Builds an from the given + /// The to build the from + /// An containing the elements of the + [] + val inline toArray<'T> : list : FlatList<'T> -> 'T array + + /// Builds an from the given + /// The to build the from + /// An containing the elements of the + [] + val toList<'T> : list : FlatList<'T> -> 'T list + + /// Converts a list to an option. If the list has one element, it returns Some of that element. + /// Otherwise, it returns None. + /// The input list. + /// An option representing the list's single element, or None. + [] + val toOption<'T> : list : FlatList<'T> -> 'T option + + /// Converts a list to a value option. If the list has one element, it returns ValueSome of that element. + /// Otherwise, it returns ValueNone. + /// The input list. + /// A value option representing the list's single element, or ValueNone. + [] + val toValueOption<'T> : list : FlatList<'T> -> 'T voption + + /// Builds a new that contains the elements of the given . + /// The input . + [] + val copy<'T> : list : FlatList<'T> -> FlatList<'T> + + ////////// Building ////////// + + /// Builds a from a builder, copying the elements + /// The builder to build from + /// A containing the elements from the builder + /// Thrown when builder is null + [] + val ofBuilder<'T> : builder : FlatList<'T>.Builder -> FlatList<'T> + + /// Creates a new builder + /// An empty builder + [] + val inline builder<'T> : unit -> FlatList<'T>.Builder + + /// Creates a builder containing the elements of the input + /// The to create the builder from + /// A builder containing the elements of the + [] + val toBuilder<'T> : list : FlatList<'T> -> FlatList<'T>.Builder + + module Builder = + + /// Adds an item to the builder + /// The item to add + /// The builder to add to + [] + val add<'T> : item : 'T -> builder : FlatList<'T>.Builder -> FlatList<'T>.Builder + + /// Checks if the is empty + /// The to check + /// True if the is empty, false otherwise + [] + val isEmpty<'T> : list : FlatList<'T> -> bool + + /// Checks if the is uninstantiated + /// The to check + /// True if the is uninstantiated, false otherwise + [] + val isDefault<'T> : list : FlatList<'T> -> bool + + /// Checks if the is uninstantiated or empty + /// The to check + /// True if the is uninstantiated or empty, false otherwise + [] + val isDefaultOrEmpty<'T> : list : FlatList<'T> -> bool + + ////////// IReadOnly* ////////// + + /// Returns the number of elements in the + /// The input + /// The number of elements in the + [] + val length<'T> : list : FlatList<'T> -> int + + /// Gets the element at the specified index in the + /// The index to retrieve + /// The input + /// The element at the specified index + /// Thrown when the index is out of range + [] + val item<'T> : index : int -> list : FlatList<'T> -> 'T + + /// Appends two s to create a new containing all elements from both s + /// The first + /// The second + /// A new containing all elements from both input s + [] + val append<'T> : list1 : FlatList<'T> -> list2 : FlatList<'T> -> FlatList<'T> + + /// Searches for the specified object and returns the zero-based index of the first occurrence within the range + /// of elements in the that starts at the specified index and + /// contains the specified number of elements. + /// The equality comparer to use + /// The starting index + /// The number of elements to search + /// The item to search for + /// The input + /// The zero-based index of the first occurrence of the item + [] + val indexRangeWith<'T> : + comparer : IEqualityComparer<'T> -> index : int -> count : int -> item : 'T -> list : FlatList<'T> -> int + + /// Searches for the specified object and returns the zero-based index of the first occurrence within the range + /// of elements in the that starts at the specified index and + /// contains the specified number of elements. + /// The starting index + /// The number of elements to search + /// The item to search for + /// The input + /// The zero-based index of the first occurrence of the item + [] + val indexRange<'T when 'T : equality> : index : int -> count : int -> item : 'T -> list : FlatList<'T> -> int + + /// Searches for the specified object and returns the zero-based index of the first occurrence within the range + /// of elements in the that starts at the specified index and + /// contains the specified number of elements. + /// The equality comparer to use + /// The starting index + /// The item to search for + /// The input + /// The zero-based index of the first occurrence of the item + [] + val indexFromWith<'T> : comparer : IEqualityComparer<'T> -> index : int -> item : 'T -> list : FlatList<'T> -> int + + /// Searches for the specified object and returns the zero-based index of the first occurrence within the range + /// of elements in the that starts at the specified index and + /// contains the specified number of elements. + /// The starting index + /// The item to search for + /// The input + /// The zero-based index of the first occurrence of the item + [] + val indexFrom<'T when 'T : equality> : index : int -> item : 'T -> list : FlatList<'T> -> int + + /// Searches for the specified object and returns the zero-based index of the first occurrence within the range + /// of elements in the that starts at the specified index and + /// contains the specified number of elements. + /// The equality comparer to use + /// The item to search for + /// The input + /// The zero-based index of the first occurrence of the item + [] + val indexWith<'T> : comparer : IEqualityComparer<'T> -> item : 'T -> list : FlatList<'T> -> int + + /// Searches for the specified object and returns the zero-based index of the first occurrence within the range + /// of elements in the that starts at the specified index and + /// contains the specified number of elements. + /// The item to search for + /// The input + /// The zero-based index of the first occurrence of the item + [] + val index<'T when 'T : equality> : item : 'T -> list : FlatList<'T> -> int + + /// Searches for the specified object and returns the zero-based index of the last occurrence within the + /// range of elements in the that contains the specified number + /// of elements and ends at the specified index. + /// The equality comparer to use + /// The ending index + /// The number of elements to search + /// The item to search for + /// The input + /// The zero-based index of the last occurrence of the item + [] + val lastIndexRangeWith<'T> : + comparer : IEqualityComparer<'T> -> index : int -> count : int -> item : 'T -> list : FlatList<'T> -> int + + /// Searches for the specified object and returns the zero-based index of the last occurrence within the + /// range of elements in the that contains the specified number + /// of elements and ends at the specified index. + /// The ending index + /// The number of elements to search + /// The item to search for + /// The input + /// The zero-based index of the last occurrence of the item + [] + val lastIndexRange<'T when 'T : equality> : index : int -> count : int -> item : 'T -> list : FlatList<'T> -> int + + /// Searches for the specified object and returns the zero-based index of the last occurrence within the + /// range of elements in the that contains the specified number + /// of elements and ends at the specified index. + /// The equality comparer to use + /// The ending index + /// The item to search for + /// The input + /// The zero-based index of the last occurrence of the item + [] + val lastIndexFromWith<'T> : comparer : IEqualityComparer<'T> -> index : int -> item : 'T -> list : FlatList<'T> -> int + + /// Searches for the specified object and returns the zero-based index of the last occurrence within the + /// range of elements in the that contains the specified number + /// of elements and ends at the specified index. + /// The ending index + /// The item to search for + /// The input + /// The zero-based index of the last occurrence of the item + [] + val lastIndexFrom<'T when 'T : equality> : index : int -> item : 'T -> list : FlatList<'T> -> int + + /// Searches for the specified object and returns the zero-based index of the last occurrence within the + /// range of elements in the that contains the specified number + /// of elements and ends at the specified index. + /// The equality comparer to use + /// The item to search for + /// The input + /// The zero-based index of the last occurrence of the item + [] + val lastIndexWith<'T> : comparer : IEqualityComparer<'T> -> item : 'T -> list : FlatList<'T> -> int + + /// Searches for the specified object and returns the zero-based index of the last occurrence within the + /// range of elements in the that contains the specified number + /// of elements and ends at the specified index. + /// The item to search for + /// The input + /// The zero-based index of the last occurrence of the item + [] + val lastIndex<'T when 'T : equality> : item : 'T -> list : FlatList<'T> -> int + + /// Removes the specified objects from the with the given comparer. + /// The equality comparer to use + /// The items to remove + /// The input + /// A new with the specified items removed + [] + val removeAllWith<'T> : comparer : IEqualityComparer<'T> -> items : 'T seq -> list : FlatList<'T> -> FlatList<'T> + + /// Removes the specified objects from the . + /// The items to remove + /// The input + /// A new with the specified items removed + [] + val removeAll<'T when 'T : equality> : items : 'T seq -> list : FlatList<'T> -> FlatList<'T> + + /// Removes all the elements that do not match the conditions defined by the specified predicate. + /// The predicate to test elements + /// The input + /// A new with elements that match the predicate + /// + /// + /// let numbers = FlatList.ofArray [|1; 2; 3; 4; 5; 6|] + /// let evens = FlatList.filter (fun x -> x % 2 = 0) numbers + /// // evens is [|2; 4; 6|] + /// + /// + [] + val filter<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> FlatList<'T> + + /// Removes all the elements that do not match the conditions defined by the specified predicate. + /// The predicate to test elements + /// The input + /// A new with elements that match the predicate + [] + val where<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> FlatList<'T> + + /// Removes a range of elements from the . + /// The starting index + /// The number of elements to remove + /// The input + /// A new with the specified range of elements removed + [] + val removeRange<'T> : index : int -> count : int -> list : FlatList<'T> -> FlatList<'T> + + /// Fills the elements of a list with a specified value. + /// The starting index in the target list. + /// The number of elements to fill. + /// The value to fill with. + /// The input list. + /// A new list with the specified range filled with the value. + [] + val fill<'T> : index : int -> count : int -> value : 'T -> list : FlatList<'T> -> FlatList<'T> + + /// Copies a range of elements from the source to the destination array + /// The source + /// The starting index in the source + /// The destination array + /// The starting index in the destination array + /// The number of elements to copy + /// Thrown when the range is invalid + [] + val blit<'T> : + source : FlatList<'T> -> sourceIndex : int -> destination : 'T[] -> destinationIndex : int -> count : int -> unit + + /// Sorts a range of elements in the using the specified comparer + /// The comparer to use + /// The starting index + /// The number of elements to sort + /// The input + /// A new with the specified range of elements sorted + [] + val sortRangeWithComparer<'T> : comparer : IComparer<'T> -> index : int -> count : int -> list : FlatList<'T> -> FlatList<'T> + + /// Sorts a range of elements in the using the specified comparison function + /// The comparison function to use + /// The starting index + /// The number of elements to sort + /// The input + /// A new with the specified range of elements sorted + [] + val sortRangeWith<'T> : comparer : ('T -> 'T -> int) -> index : int -> count : int -> list : FlatList<'T> -> FlatList<'T> + + /// Sorts a range of elements in the using the default comparer + /// The starting index + /// The number of elements to sort + /// The input + /// A new with the specified range of elements sorted + [] + val sortRange<'T when 'T : comparison> : index : int -> count : int -> list : FlatList<'T> -> FlatList<'T> + + /// Sorts the elements in the using the specified comparer + /// The comparer to use + /// The input + /// A new with the elements sorted + [] + val sortWithComparer<'T> : comparer : IComparer<'T> -> list : FlatList<'T> -> FlatList<'T> + + /// Sorts the elements in the using the specified comparison function + /// The comparison function to use + /// The input + /// A new with the elements sorted + [] + val sortWith<'T> : comparer : ('T -> 'T -> int) -> list : FlatList<'T> -> FlatList<'T> + + /// Sorts the elements in the using the default comparer + /// The input + /// A new with the elements sorted + [] + val sort<'T> : list : FlatList<'T> -> FlatList<'T> + + /// Returns a new with the elements in reverse order. + /// The input . + /// The reversed . + [] + val rev<'T> : list : FlatList<'T> -> FlatList<'T> + + /// Returns a new that contains elements of the original sorted in descending order. + /// The input . + /// The sorted . + [] + val inline sortDescending<'T when 'T : comparison> : list : FlatList<'T> -> FlatList<'T> + + /// Returns a new that contains elements of the original sorted in descending order using the specified projection. + /// The function to transform the elements into a type that supports comparison. + /// The input . + /// The sorted . + [] + val inline sortByDescending<'T, 'Key when 'Key : comparison> : + projection : ('T -> 'Key) -> list : FlatList<'T> -> FlatList<'T> + + /// Sorts the using keys given by the given projection. Keys are compared using Operators.compare. + /// The function to transform the elements into a type supporting comparison. + /// The input . + /// The sorted . + [] + val sortBy<'T, 'Key when 'Key : comparison> : projection : ('T -> 'Key) -> list : FlatList<'T> -> FlatList<'T> + + ////////// Loop-based (now LINQ-based where applicable) ////////// + + /// Concatenates a of s into a single + /// The of s to concatenate + /// A new containing all elements from the input s + [] + val concat<'T> : arrs : FlatList> -> FlatList<'T> + + /// Builds a new from the elements of a by applying a mapping function to each element + /// A function to transform elements from the input + /// The input + /// A containing the transformed elements + /// + /// + /// let numbers = FlatList.ofArray [|1; 2; 3; 4; 5|] + /// let squares = FlatList.map (fun x -> x * x) numbers + /// // squares is [|1; 4; 9; 16; 25|] + /// + /// + [] + val inline map<'T, 'U> : mapping : ('T -> 'U) -> list : FlatList<'T> -> FlatList<'U> + + /// Builds a new whose elements are the results of applying the given function + /// to each of the elements of the . The integer index passed to the + /// function indicates the index of element being transformed. + /// A function to transform an element and its index into a result element. + /// The input . + /// The of transformed elements. + [] + val mapi<'T, 'U> : mapping : (int -> 'T -> 'U) -> list : FlatList<'T> -> FlatList<'U> + + /// Builds a new whose elements are the results of applying the given function + /// to the corresponding elements of the two collections pairwise, also passing the index of + /// the elements. The two input s must have the same lengths. + /// The function to transform pairs of input elements and their indices. + /// The first input . + /// The second input . + /// The of transformed elements. + [] + val mapi2<'T1, 'T2, 'U> : + mapping : (int -> 'T1 -> 'T2 -> 'U) -> list1 : FlatList<'T1> -> list2 : FlatList<'T2> -> FlatList<'U> + + /// Builds a new whose elements are the results of applying the given function + /// to the corresponding elements of the three collections pairwise, also passing the index of + /// the elements. The three input s must have the same lengths. + /// The function to transform triples of input elements and their indices. + /// The first input . + /// The second input . + /// The third input . + /// The of transformed elements. + [] + val mapi3<'T1, 'T2, 'T3, 'U> : + mapping : (int -> 'T1 -> 'T2 -> 'T3 -> 'U) -> + list1 : FlatList<'T1> -> + list2 : FlatList<'T2> -> + list3 : FlatList<'T3> -> + FlatList<'U> + + /// Builds a new collection whose elements are the results of applying the given function + /// to the corresponding elements of the two collections pairwise. The two input + /// s must have the same lengths. + /// The function to transform the pairs of the input elements. + /// The first input . + /// The second input . + /// The of transformed elements. + [] + val map2<'T1, 'T2, 'U> : mapping : ('T1 -> 'T2 -> 'U) -> list1 : FlatList<'T1> -> list2 : FlatList<'T2> -> FlatList<'U> + + /// Builds a new collection whose elements are the results of applying the given function + /// to the corresponding elements of the three collections pairwise. The three input + /// s must have the same lengths. + /// The function to transform the triples of the input elements. + /// The first input . + /// The second input . + /// The third input . + /// The of transformed elements. + [] + val map3<'T1, 'T2, 'T3, 'U> : + mapping : ('T1 -> 'T2 -> 'T3 -> 'U) -> + list1 : FlatList<'T1> -> + list2 : FlatList<'T2> -> + list3 : FlatList<'T3> -> + FlatList<'U> + + /// Builds a new whose elements are the results of applying the given function + /// to each of the elements of the while threading an accumulator argument + /// through the computation. + /// The function to transform elements from the input and + /// thread an accumulator state. + /// The initial state of the accumulator. + /// The input . + /// A of transformed elements, and the final accumulator value. + /// + /// + /// // Calculate a running sum while squaring each element + /// let numbers = FlatList.ofArray [|1; 2; 3; 4|] + /// let squares, sum = FlatList.mapFold (fun state x -> x * x, state + x) 0 numbers + /// // squares is [|1; 4; 9; 16|] + /// // sum is 10 + /// + /// + [] + val mapFold<'T, 'State, 'Result> : + mapping : ('State -> 'T -> 'Result * 'State) -> state : 'State -> list : FlatList<'T> -> FlatList<'Result> * 'State + + /// Builds a new whose elements are the results of applying the given function + /// to each of the elements of the while threading an accumulator argument + /// through the computation, starting from the end of the list. + /// The function to transform elements from the input and + /// thread an accumulator state, starting from the end. + /// The input . + /// The initial state of the accumulator. + /// A of transformed elements, and the final accumulator value. + /// + /// + /// // Create a reverse-order list of indices while computing sum + /// let chars = FlatList.ofArray [|'a'; 'b'; 'c'|] + /// let indices, sum = FlatList.mapFoldBack (fun x state -> state, state + 1) chars 0 + /// // indices is [|2; 1; 0|] + /// // sum is 3 + /// + /// + [] + val mapFoldBack<'T, 'State, 'Result> : + mapping : ('T -> 'State -> 'Result * 'State) -> list : FlatList<'T> -> state : 'State -> FlatList<'Result> * 'State + + /// Counts the number of elements in the that satisfy the given predicate + /// A function to project elements from the input + /// The input + /// A of key-value pairs where the key is the projected value and the value is the count + [] + val countBy<'T, 'Key> : projection : ('T -> 'Key) -> list : FlatList<'T> -> FlatList + + /// Creates a containing the elements of the original paired with their indices + /// The input + /// A containing pairs of indices and elements + [] + val indexed<'T> : list : FlatList<'T> -> FlatList + + /// Applies the given function to each element of the + /// A function to apply to each element + /// The input + [] + val inline iter<'T> : action : ('T -> unit) -> list : FlatList<'T> -> unit + + /// Applies the given function to each element of the and its index + /// A function to apply to each element and its index + /// The input + [] + val iteri<'T> : action : (int -> 'T -> unit) -> list : FlatList<'T> -> unit + + /// Applies the given function to pair of elements at the same position in the two s + /// A function to apply to pairs of elements + /// The first input + /// The second input + /// Thrown when the s have different lengths + [] + val iter2<'T1, 'T2> : action : ('T1 -> 'T2 -> unit) -> list1 : FlatList<'T1> -> list2 : FlatList<'T2> -> unit + + /// Applies the given function to the trio of elements at the same position in the three s. + /// A function to apply to trios of elements. + /// The first input + /// The second input + /// The third input + /// Thrown when the s have different lengths + [] + val iter3<'T1, 'T2, 'T3> : + action : ('T1 -> 'T2 -> 'T3 -> unit) -> list1 : FlatList<'T1> -> list2 : FlatList<'T2> -> list3 : FlatList<'T3> -> unit + + /// Applies the given function to the pair of elements at the same position in the two s along with their index + /// A function to apply to pairs of elements and their index + /// The first input + /// The second input + /// Thrown when the s have different lengths + [] + val iteri2<'T1, 'T2> : action : (int -> 'T1 -> 'T2 -> unit) -> list1 : FlatList<'T1> -> list2 : FlatList<'T2> -> unit + + /// Applies the given function to the trio of elements at the same position in the three s along with their index. + /// A function to apply to trios of elements and their index. + /// The first input + /// The second input + /// The third input + /// Thrown when the s have different lengths + [] + val iteri3<'T1, 'T2, 'T3> : + action : (int -> 'T1 -> 'T2 -> 'T3 -> unit) -> + list1 : FlatList<'T1> -> + list2 : FlatList<'T2> -> + list3 : FlatList<'T3> -> + unit + + /// Tests if any element of the satisfies the given predicate + /// A function to test each element + /// The input + /// True if any element satisfies the predicate, false otherwise + [] + val exists<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> bool + + /// Tests if any corresponding pair of elements from the two s satisfies the given predicate + /// A function to test pairs of elements + /// The first input + /// The second input + /// True if any pair of elements satisfies the predicate, false otherwise + /// Thrown when the s have different lengths + [] + val exists2<'T1, 'T2> : predicate : ('T1 -> 'T2 -> bool) -> list1 : FlatList<'T1> -> list2 : FlatList<'T2> -> bool + + /// Tests if any corresponding trio of elements from the three s satisfies the given predicate. + /// A function to test trios of elements. + /// The first input + /// The second input + /// The third input + /// True if any trio of elements satisfies the predicate, false otherwise. + /// Thrown when the s have different lengths + [] + val exists3<'T1, 'T2, 'T3> : + predicate : ('T1 -> 'T2 -> 'T3 -> bool) -> list1 : FlatList<'T1> -> list2 : FlatList<'T2> -> list3 : FlatList<'T3> -> bool + + /// Tests if all elements of the satisfy the given predicate + /// A function to test each element + /// The input + /// True if all elements satisfy the predicate, false otherwise + [] + val forall<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> bool + + /// Tests if all corresponding pairs of elements from the two s satisfy the given predicate + /// A function to test pairs of elements + /// The first input + /// The second input + /// True if all pairs of elements satisfy the predicate, false otherwise + /// Thrown when the s have different lengths + [] + val forall2<'T1, 'T2> : predicate : ('T1 -> 'T2 -> bool) -> list1 : FlatList<'T1> -> list2 : FlatList<'T2> -> bool + + /// Tests if all corresponding trios of elements from the three s satisfy the given predicate. + /// A function to test trios of elements. + /// The first input + /// The second input + /// The third input + /// True if all trios of elements satisfy the predicate, false otherwise. + /// Thrown when the s have different lengths + [] + val forall3<'T1, 'T2, 'T3> : + predicate : ('T1 -> 'T2 -> 'T3 -> bool) -> list1 : FlatList<'T1> -> list2 : FlatList<'T2> -> list3 : FlatList<'T3> -> bool + + /// Tests if the given element exists in the + /// The element to find + /// The input + /// True if the element exists in the , false otherwise + [] + val inline contains<'T> : item : 'T -> list : FlatList<'T> -> bool + + /// Splits the into two s, containing the elements for which the given predicate returns true and false respectively + /// A function to test each element + /// The input + /// A tuple of two s, containing the elements for which the predicate returns true and false respectively + [] + val partition<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> FlatList<'T> * FlatList<'T> + + /// Returns the first element for which the given predicate returns true + /// A function to test elements + /// The input + /// The first element for which the predicate returns true + /// Thrown if no element satisfies the predicate + [] + val find<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> 'T + + /// Returns the first element for which the given predicate returns true, or ValueNone if no such element exists + /// A function to test elements + /// The input + /// ValueSome value if an element satisfies the predicate, ValueNone otherwise + [] + val tryFind<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> 'T voption + + /// Returns the last element for which the given predicate returns true + /// A function to test elements + /// The input + /// The last element for which the predicate returns true + /// Thrown if no element satisfies the predicate + [] + val findBack<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> 'T + + /// Returns the last element for which the given predicate returns true, or ValueNone if no such element exists + /// A function to test elements + /// The input + /// ValueSome value if an element satisfies the predicate, ValueNone otherwise + [] + val tryFindBack<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> 'T voption + + /// Returns the last element for which the given function returns true. + /// The function to test elements. + /// The input list. + /// The last element for which the predicate returns true. + /// Thrown if no element satisfies the predicate. + [] + val findLast<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> 'T + + /// Returns the last element for which the given function returns true, or ValueNone if no such element exists. + /// The function to test elements. + /// The input list. + /// ValueSome value if an element satisfies the predicate, ValueNone otherwise + [] + val tryFindLast<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> 'T voption + + /// Returns the last index for which the given predicate returns true + /// A function to test elements + /// The input + /// The last index for which the predicate returns true + /// Thrown if no element satisfies the predicate + [] + val findIndexBack<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> int + + /// Returns the last index for which the given predicate returns true, or ValueNone if no such element exists + /// A function to test elements + /// The input + /// ValueSome index if an element satisfies the predicate, ValueNone otherwise + [] + val tryFindIndexBack<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> int voption + + /// Returns the index of the last element in the that satisfies the given predicate. + /// The function to test elements. + /// The input list. + /// The index of the last element that satisfies the predicate. + /// Thrown if no element satisfies the predicate. + [] + val findLastIndex<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> int + + /// Returns the index of the last element in the that satisfies the given predicate, or ValueNone if no such element exists. + /// The function to test elements. + /// The input list. + /// ValueSome index if an element satisfies the predicate, ValueNone otherwise + [] + val tryFindLastIndex<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> int voption + + /// Returns the first value for which the given function returns ValueSome value + /// A function to generate options from the elements + /// The input + /// The first value for which the chooser returns ValueSome value + /// Thrown if the chooser returns ValueNone for all elements + [] + val pick<'T, 'U> : chooser : ('T -> 'U voption) -> list : FlatList<'T> -> 'U + + /// Returns the first value for which the given function returns ValueSome value, or ValueNone + /// A function to generate options from the elements + /// The input + /// The first value for which the chooser returns ValueSome value, or ValueNone + [] + val tryPick<'T, 'U> : chooser : ('T -> 'U voption) -> list : FlatList<'T> -> 'U voption + + /// Returns the last value for which the given function returns ValueSome value. + /// A function to generate options from the elements. + /// The input list. + /// The last value for which the chooser returns ValueSome value. + /// Thrown if the chooser returns ValueNone for all elements. + [] + val pickBack<'T, 'U> : chooser : ('T -> 'U voption) -> list : FlatList<'T> -> 'U + + /// Returns the last value for which the given function returns ValueSome value. + /// A function to generate options from the elements. + /// The input list. + /// The last value for which the chooser returns ValueSome value, or ValueNone. + [] + val tryPickBack<'T, 'U> : chooser : ('T -> 'U voption) -> list : FlatList<'T> -> 'U voption + + /// Builds a new containing only the elements for which the given function returns ValueSome value + /// A function to generate options from the elements + /// The input + /// A containing the values wrapped in ValueSome by the chooser + [] + val choose<'T, 'U> : chooser : ('T -> 'U voption) -> list : FlatList<'T> -> FlatList<'U> + + /// Builds a new collection from the elements of the input collection for which the given function returns a ValueSome value. + /// The elements are processed in reverse order. + /// A function to generate options from the elements. + /// The input list. + /// A new list containing the values from the successful choices, in reverse order of processing. + [] + val chooseBack<'T, 'U> : chooser : ('T -> 'U voption) -> list : FlatList<'T> -> FlatList<'U> + + /// Creates a by applying a key-generating function to each element of the and grouping the elements by the resulting keys + /// A function to transform elements into keys + /// The input + /// A of tuples where each tuple contains a key and a of all elements that match the key + [] + val groupBy<'T, 'Key> : projection : ('T -> 'Key) -> list : FlatList<'T> -> FlatList)> + + /// Returns a new that contains the elements of the original but with duplicates eliminated by using the supplied projection function + /// A function to transform elements before comparing them + /// The input + /// A with distinct elements as determined by the projection function + [] + val distinctBy<'T, 'Key> : projection : ('T -> 'Key) -> list : FlatList<'T> -> FlatList<'T> + + /// Finds the first duplicate element in the . + /// The input . + /// The first duplicate element. + /// Thrown if no duplicate is found. + [] + val findDup<'T> : list : FlatList<'T> -> 'T + + /// Finds the first element in the that is a duplicate of a preceding element according to the given projection function. + /// The function to transform the elements into a type supporting comparison. + /// The input . + /// The first duplicate element. + /// Thrown if no duplicate is found. + [] + val findDupBy<'T, 'Key> : projection : ('T -> 'Key) -> list : FlatList<'T> -> 'T + + /// Creates a new by applying a mapping function to each element of the input and concatenating the results + /// A function to transform elements of the input into s + /// The input + /// A containing the concatenation of all the s generated by the mapping function + [] + val collect<'T, 'U> : mapping : ('T -> 'U seq) -> list : FlatList<'T> -> FlatList<'U> + + /// Gets an element in the at the specified index + /// The index of the element to retrieve + /// The input + /// ValueSome value containing the element, or ValueNone if the index is out of range + [] + val tryItem<'T> : index : int -> list : FlatList<'T> -> 'T voption + + /// Returns the first element of the + /// The input + /// The first element of the + /// Thrown when the is empty + [] + val head<'T> : list : FlatList<'T> -> 'T + + /// Returns the first element of the , or ValueNone if the is empty + /// The input + /// ValueSome value containing the first element, or ValueNone if the is empty + [] + val tryHead<'T> : list : FlatList<'T> -> 'T voption + + /// Returns the first element and the rest of the , or ValueNone if the is empty. + /// The input list. + /// An option containing the first element and the rest of the , or ValueNone. + [] + val tryHeadAndTail<'T> : list : FlatList<'T> -> ('T * FlatList<'T>) voption + + /// Returns the last element of the + /// The input + /// The last element of the + /// Thrown when the is empty + [] + val last<'T> : list : FlatList<'T> -> 'T + + /// Returns the last element of the , or ValueNone if the is empty + /// The input + /// ValueSome value containing the last element, or ValueNone if the is empty + [] + val tryLast<'T> : list : FlatList<'T> -> 'T voption + + /// Returns the last element and all but the last element of the , or ValueNone if the is empty. + /// The input list. + /// An option containing the last element and all but the last element of the , or ValueNone. + [] + val tryLastAndInit<'T> : list : FlatList<'T> -> (FlatList<'T> * 'T) voption + + /// Returns the without its first element + /// The input + /// A containing all elements of the input except the first one + [] + val tail<'T> : list : FlatList<'T> -> FlatList<'T> + + /// Returns the without its first element, or ValueNone if the is empty + /// The input + /// ValueSome value containing the without its first element, or ValueNone if the is empty + [] + val tryTail<'T> : list : FlatList<'T> -> FlatList<'T> voption + + /// Returns the first N elements of the + /// The number of elements to take + /// The input + /// A containing the first N elements + /// Thrown when is negative or greater than the length of the list. + [] + val take<'T> : count : int -> list : FlatList<'T> -> FlatList<'T> + + /// Returns the last elements of the . + /// The number of elements to take from the end of the . + /// The input list. + /// A new list containing the last elements. + /// Thrown when is negative or greater than the length of the list. + [] + val takeEnd<'T> : count : int -> list : FlatList<'T> -> FlatList<'T> + + /// Returns a containing the first elements of the input for which the given predicate returns true + /// A function to test each element + /// The input + /// A containing the first elements for which the predicate returns true + [] + val takeWhile<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> FlatList<'T> + + /// Returns the without its first N elements + /// The number of elements to skip + /// The input + /// A containing all except the first N elements + /// Thrown when is negative or greater than the length of the list. + [] + val skip<'T> : index : int -> list : FlatList<'T> -> FlatList<'T> + + /// Returns a new that does not contain the last elements of the original . + /// The number of elements to skip from the end of the . + /// The input list. + /// A new list without the last elements. + /// Thrown when is negative or greater than the length of the list. + [] + val skipEnd<'T> : count : int -> list : FlatList<'T> -> FlatList<'T> + + /// Returns a that skips the elements of the input while the given predicate returns true, then returns the rest + /// A function to test each element + /// The input + /// A that skips the elements while the predicate returns true, then contains the rest + [] + val skipWhile<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> FlatList<'T> + + /// Gets a sublist of the input + /// The index of the first element to include + /// The number of elements in the sublist + /// The input + /// A containing the elements from start index for the given count + /// Thrown when is negative, is negative, or the sum of and exceeds the length of the list. + [] + val sub<'T> : start : int -> count : int -> list : FlatList<'T> -> FlatList<'T> + + /// Returns a that contains no more than N elements of the input + /// The maximum number of elements to include + /// The input + /// A containing at most N elements + /// Thrown when is negative. + [] + val truncate<'T> : count : int -> list : FlatList<'T> -> FlatList<'T> + + /// Splits the into two s at the specified index + /// The index at which to split the + /// The input + /// A tuple of two s, the first containing the elements up to the index, the second containing the rest + [] + val splitAt<'T> : index : int -> list : FlatList<'T> -> FlatList<'T> * FlatList<'T> + + /// Splits the into chunks of size at most 'chunkSize' + /// The maximum size of each chunk + /// The input + /// The split into chunks + /// Thrown when chunkSize is not positive or when is default + [] + val chunkBySize<'T> : chunkSize : int -> list : FlatList<'T> -> FlatList> + + /// Applies a function to the builder and returns the resulting + /// The function to apply to the builder + /// The created from the builder after applying the function + [] + val inline build<'T> : f : (FlatList<'T>.Builder -> unit) -> FlatList<'T> + + /// Updates the by applying a function to a builder initialized with the 's elements + /// The function to apply to the builder + /// The input + /// The updated + [] + val inline update<'T> : f : (FlatList<'T>.Builder -> unit) -> list : FlatList<'T> -> FlatList<'T> + + /// Returns the index of the first element in the that satisfies the given predicate + /// The function to test the input elements + /// The input + /// The index of the first element that satisfies the predicate + /// Thrown if no element satisfies the predicate + [] + val findIndex<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> int + + /// Returns the index of the first element in the that satisfies the given predicate, or ValueNone if no such element exists + /// The function to test the input elements + /// The input + /// The index of the first element that satisfies the predicate, or ValueNone + [] + val tryFindIndex<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> int voption + + /// Returns a new containing elements corresponding to a sliding window of elements from the input + /// The size of the window + /// The input + /// The resulting of sliding windows + /// Thrown when windowSize is not positive or when is default + /// + /// + /// let numbers = FlatList.ofArray [|1; 2; 3; 4; 5|] + /// let windows = FlatList.windowed 3 numbers + /// // windows is [|[|1; 2; 3|]; [|2; 3; 4|]; [|3; 4; 5|]|] + /// + /// // Calculate moving averages + /// let movingAverages = + /// windows + /// |> FlatList.map (fun window -> + /// FlatList.average window) + /// // movingAverages is [|2.0; 3.0; 4.0|] + /// + /// + [] + val windowed<'T> : windowSize : int -> list : FlatList<'T> -> FlatList> + + /// Returns a new containing pairs of adjacent elements from the input + /// The input + /// The resulting of pairs + [] + val pairwise<'T> : list : FlatList<'T> -> FlatList + + /// Splits the input into at most count chunks. + /// The maximum number of chunks. + /// The input . + /// The split into chunks. + /// Thrown when count is not positive. + [] + val splitInto<'T> : count : int -> list : FlatList<'T> -> FlatList> + + /// Returns a new that contains the elements of the original but with duplicates removed + /// The input + /// The with distinct elements + [] + val distinct<'T> : list : FlatList<'T> -> FlatList<'T> + + /// Returns a new that contains all pairwise combinations of elements from the first and second s + /// The first input + /// The second input + /// The of all pairwise combinations + [] + val allPairs<'T, 'U> : xs : FlatList<'T> -> ys : FlatList<'U> -> FlatList<('T * 'U)> + + /// Returns a new with the elements permuted according to the specified permutation + /// The function that maps input indices to output indices + /// The input + /// The permuted + /// Thrown when the permutation function returns an out-of-range index + [] + val permute<'T> : indexMap : (int -> int) -> list : FlatList<'T> -> FlatList<'T> + + /// Combines the two s into a of pairs. The two s must have equal lengths + /// The first input + /// The second input + /// The of pairs + /// Thrown when the s have different lengths + [] + val zip<'T, 'U> : list1 : FlatList<'T> -> list2 : FlatList<'U> -> FlatList + + /// Combines the three s into a of triples. The three s must have equal lengths + /// The first input + /// The second input + /// The third input + /// The of triples + /// Thrown when the s have different lengths + [] + val zip3<'T, 'U, 'V> : list1 : FlatList<'T> -> list2 : FlatList<'U> -> list3 : FlatList<'V> -> FlatList + + /// Splits a of pairs into two s + /// The input + /// The two s unzipped from the input + [] + val unzip<'T, 'U> : list : FlatList -> struct (FlatList<'T> * FlatList<'U>) + + /// Splits a of triples into three s + /// The input + /// The three s unzipped from the input + [] + val unzip3<'T, 'U, 'V> : list : FlatList -> struct (FlatList<'T> * FlatList<'U> * FlatList<'V>) + + /// Returns the average of the elements in the + /// The input + /// The average of the elements + /// Thrown when the is empty + [] + val inline average<'T + when 'T : (static member (+) : 'T * 'T -> 'T) + and 'T : (static member DivideByInt : 'T * int -> 'T) + and 'T : (static member Zero : 'T)> : list : FlatList<'T> -> 'T + + /// Returns the average of the results of applying the function to each element of the + /// The function to transform the elements before averaging + /// The input + /// The average of the projected elements + /// Thrown when the is empty + [] + val inline averageBy<'T, 'U + when 'U : (static member (+) : 'U * 'U -> 'U) + and 'U : (static member DivideByInt : 'U * int -> 'U) + and 'U : (static member Zero : 'U)> : projection : ('T -> 'U) -> list : FlatList<'T> -> 'U + + /// Applies a function to each element of the , threading an accumulator argument through the computation + /// The function to update the state given the input elements + /// The initial state + /// The input + /// The final state + /// + /// + /// let numbers = FlatList.ofArray [|1; 2; 3; 4; 5|] + /// let sum = FlatList.fold (fun acc x -> acc + x) 0 numbers + /// // sum is 15 + /// + /// // Computing the average + /// let count = FlatList.length numbers + /// let total = FlatList.fold (fun sum x -> sum + x) 0 numbers + /// let avg = float total / float count + /// + /// + [] + val fold<'T, 'State> : folder : ('State -> 'T -> 'State) -> state : 'State -> list : FlatList<'T> -> 'State + + /// Applies a function to each element of the collection, threading an accumulator argument + /// through the computation. The integer index passed to the function indicates the index of the + /// element. The seed is used as the initial accumulator value. + /// The function to update the state given the index, the input elements, and the previous state. + /// The initial state. + /// The input list. + /// The final state. + [] + val foldi<'T, 'State> : folder : (int -> 'State -> 'T -> 'State) -> state : 'State -> list : FlatList<'T> -> 'State + + /// Applies a function to corresponding elements of two s, threading an accumulator argument through the computation + /// The function to update the state given the input elements from both s + /// The initial state + /// The first input + /// The second input + /// The final state + /// Thrown when the s have different lengths + [] + val fold2<'T1, 'T2, 'State> : + folder : ('State -> 'T1 -> 'T2 -> 'State) -> state : 'State -> list1 : FlatList<'T1> -> list2 : FlatList<'T2> -> 'State + + /// Applies a function to corresponding elements of two s, threading an accumulator argument + /// through the computation. The integer index passed to the function indicates the index of the + /// elements. The seed is used as the initial accumulator value. + /// The function to update the state given the index, the input elements from both s, and the previous state. + /// The initial state. + /// The first input list. + /// The second input list. + /// The final state. + /// Thrown when the lists have different lengths. + [] + val foldi2<'T1, 'T2, 'State> : + folder : (int -> 'State -> 'T1 -> 'T2 -> 'State) -> + state : 'State -> + list1 : FlatList<'T1> -> + list2 : FlatList<'T2> -> + 'State + + /// Applies a function to each element of the , threading an accumulator argument through the computation, starting from the end. + /// The function to update the state given the input elements, starting from the end. + /// The input . + /// The initial state. + /// The final state. + /// Thrown when the is default or empty + [] + val foldBack<'T, 'State> : folder : ('T -> 'State -> 'State) -> list : FlatList<'T> -> state : 'State -> 'State + + /// Applies a function to each element of the collection, starting from the end, threading an + /// accumulator argument through the computation. The integer index passed to the function indicates + /// the index of the element. The seed is used as the initial accumulator value. + /// The function to update the state given the index, the input elements, and the previous state. + /// The input list. + /// The initial state. + /// The final state. + [] + val foldBacki<'T, 'State> : folder : (int -> 'T -> 'State -> 'State) -> list : FlatList<'T> -> state : 'State -> 'State + + /// Applies a function to corresponding elements of two s, threading an accumulator argument through the computation, starting from the end. + /// The function to update the state given the input elements from both s, starting from the end. + /// The first input + /// The second input + /// The initial state. + /// The final state. + /// Thrown when the s have different lengths + [] + val foldBack2<'T1, 'T2, 'State> : + folder : ('T1 -> 'T2 -> 'State -> 'State) -> list1 : FlatList<'T1> -> list2 : FlatList<'T2> -> state : 'State -> 'State + + /// Applies a function to corresponding elements of two s, starting from the end, threading + /// an accumulator argument through the computation. The integer index passed to the function indicates + /// the index of the elements. The seed is used as the initial accumulator value. + /// The function to update the state given the index, the input elements from both s, and the previous state. + /// The first input list. + /// The second input list. + /// The initial state. + /// The final state. + /// Thrown when the lists have different lengths. + [] + val foldBacki2<'T1, 'T2, 'State> : + folder : (int -> 'T1 -> 'T2 -> 'State -> 'State) -> + list1 : FlatList<'T1> -> + list2 : FlatList<'T2> -> + state : 'State -> + 'State + + /// Applies a function to corresponding elements of three s, threading an accumulator argument through the computation, starting from the end. + /// The function to update the state given the input elements from all three s, starting from the end. + /// The first input + /// The second input + /// The third input + /// The initial state. + /// The final state. + /// Thrown when the s have different lengths + [] + val foldBack3<'T1, 'T2, 'T3, 'State> : + folder : ('T1 -> 'T2 -> 'T3 -> 'State -> 'State) -> + list1 : FlatList<'T1> -> + list2 : FlatList<'T2> -> + list3 : FlatList<'T3> -> + state : 'State -> + 'State + + /// Applies a function to each element of the , threading an accumulator argument through the computation. + /// This function takes the second argument, and applies the function to it and the first element of the . + /// Then, it passes this result into the function along with the second element, and so on. + /// Finally, it returns the final result. If the is empty, an exception is raised. + /// The function to reduce the with + /// The input + /// The final accumulated value + /// Thrown when the is empty + [] + val reduce<'T> : reduction : ('T -> 'T -> 'T) -> list : FlatList<'T> -> 'T + + /// Applies a function to each element of the , threading an accumulator argument through the computation, starting from the end. + /// This function takes the last element of the and the second-to-last element, and applies the function to them. + /// Then, it passes this result into the function along with the third-to-last element, and so on. + /// Finally, it returns the final result. If the is empty, an exception is raised. + /// The function to reduce the with, starting from the end + /// The input + /// The final accumulated value + /// Thrown when the is empty + [] + val reduceBack<'T> : reduction : ('T -> 'T -> 'T) -> list : FlatList<'T> -> 'T + + /// Like fold, but returns both the intermediate and final results + /// The function to update the state given the input elements + /// The initial state + /// The input + /// The of all intermediate and final states + [] + val scan<'T, 'State> : folder : ('State -> 'T -> 'State) -> state : 'State -> list : FlatList<'T> -> FlatList<'State> + + /// Like foldBack, but returns both the intermediate and final results + /// The function to update the state given the input elements, starting from the end + /// The input + /// The initial state + /// The of all intermediate and final states, in reverse order of computation + [] + val scanBack<'T, 'State> : folder : ('T -> 'State -> 'State) -> list : FlatList<'T> -> state : 'State -> FlatList<'State> + + /// Returns the only element of the . + /// The input . + /// The only element of the . + /// Thrown when the input does not have precisely one element. + [] + val exactlyOne<'T> : list : FlatList<'T> -> 'T + + /// Returns the only element of the or None if the is empty or contains more than one element. + /// The input . + /// The only element of the or None. + [] + val tryExactlyOne<'T> : list : FlatList<'T> -> 'T voption + + /// Returns a new list with the distinct elements of the input which do not appear in the itemsToExclude sequence + /// A sequence whose elements that also occur in the input will cause those elements to be removed + /// The input + /// A new that contains the distinct elements of list that do not appear in itemsToExclude + [] + val except<'T> : itemsToExclude : 'T seq -> list : FlatList<'T> -> FlatList<'T> + + /// Returns the sum of the elements in the . + /// The input . + /// The resulting sum. + /// Thrown when the input is default or empty. + [] + val inline sum<'T when 'T : (static member (+) : 'T * 'T -> 'T) and 'T : (static member Zero : 'T)> : + list : FlatList<'T> -> 'T + + /// Returns the sum of the results generated by applying the function to each element of the . + /// The function to transform the elements into the type to be summed. + /// The input . + /// The resulting sum. + /// Thrown when the input is default or empty. + [] + val inline sumBy<'T, 'U when 'U : (static member (+) : 'U * 'U -> 'U) and 'U : (static member Zero : 'U)> : + projection : ('T -> 'U) -> list : FlatList<'T> -> 'U + + /// Returns the transpose of the given sequence of s. + /// The input of s. + [] + val transpose<'T> : lists : FlatList> -> FlatList> + + /// Updates the element at the specified index in the array. + /// The index of the element to update. + /// The new value for the element. + /// The input array. + /// The updated array. + [] + val updateAt<'T> : index : int -> value : 'T -> list : FlatList<'T> -> FlatList<'T> + + /// Removes the element at the specified index from the array. + /// The index of the element to remove. + /// The input array. + /// The array with the element removed. + [] + val removeAt<'T> : index : int -> list : FlatList<'T> -> FlatList<'T> + + /// Inserts an element at the specified index in the array. + /// The index at which to insert the element. + /// The element to insert. + /// The input array. + /// The array with the element inserted. + [] + val insertAt<'T> : index : int -> value : 'T -> list : FlatList<'T> -> FlatList<'T> + + /// Inserts multiple elements at the specified index in the array. + /// The index at which to insert the elements. + /// The elements to insert. + /// The input array. + /// The array with the elements inserted. + [] + val insertManyAt<'T> : index : int -> values : 'T seq -> list : FlatList<'T> -> FlatList<'T> + + /// Generates a by repeatedly applying a function to a state. + /// The function to generate the next element and state. + /// The initial state. + /// The generated sequence. + [] + val unfold<'T, 'State> : generator : ('State -> struct ('T * 'State) voption) -> state : 'State -> FlatList<'T> + + /// Compares two arrays using a custom comparison function. + /// The function to compare elements. + /// The first input array. + /// The second input array. + /// The comparison result. + [] + val compareWith<'T> : comparer : ('T -> 'T -> int) -> list1 : FlatList<'T> -> list2 : FlatList<'T> -> int + + /// Returns the maximum element in the array. + /// The input array. + /// The maximum element. + /// Thrown when the input array is empty. + [] + val inline max<'T when 'T : comparison> : list : FlatList<'T> -> 'T + + /// Returns the maximum element in the array according to a projection function. + /// The function to transform the elements into a type supporting comparison. + /// The input array. + /// The maximum element. + /// Thrown when the input array is empty. + [] + val inline maxBy<'T, 'Key when 'Key : comparison> : projection : ('T -> 'Key) -> list : FlatList<'T> -> 'T + + /// Returns the minimum element in the . + /// The input . + /// The minimum element. + /// Thrown when the input is default or empty. + [] + val inline min<'T when 'T : comparison> : list : FlatList<'T> -> 'T + + /// Returns the minimum element in the according to a projection function. + /// The function to transform the elements into a type supporting comparison. + /// The input . + /// The minimum element. + /// Thrown when the input is default or empty. + [] + val inline minBy<'T, 'Key when 'Key : comparison> : projection : ('T -> 'Key) -> list : FlatList<'T> -> 'T diff --git a/src/FSharp.Collections.Immutable/Helper.fs b/src/FSharp.Collections.Immutable/Helper.fs new file mode 100644 index 0000000..43d3bdb --- /dev/null +++ b/src/FSharp.Collections.Immutable/Helper.fs @@ -0,0 +1,61 @@ +#if INTERACTIVE +namespace global +#else +namespace FSharp.Collections.Immutable +#endif + +[] +module internal ImmutableCollectionUtil = + + let inline checkNotNull name (arg : _ | null) = + match arg with + | null -> nullArg name + | _ -> () + +module internal ErrorStrings = + [] + let InputMustBeNonNegative = "The input must be non-negative." + + [] + let ListsHaveDifferentLengths = "The lists have different lengths." + +[] +module internal Struct = + + let fstv tuple = let struct (a, _) = tuple in a + let sndv tuple = let struct (_, b) = tuple in b + +[] +module internal ValueOption = + + module internal Seq = + + let vtryHead (source : 'T seq) = + use enumerator = source.GetEnumerator () + if not (enumerator.MoveNext ()) then + ValueNone + else if obj.ReferenceEquals (enumerator.Current, null) then + ValueNone + else + ValueSome enumerator.Current + + let vtryLast (source : 'T seq) = + use enumerator = source.GetEnumerator () + if not (enumerator.MoveNext ()) then + ValueNone + else + let mutable last = enumerator.Current + while enumerator.MoveNext () do + last <- enumerator.Current + if obj.ReferenceEquals (enumerator.Current, null) then + ValueNone + else + ValueSome last + + let vchoose mapping (source : 'T seq) = + source + |> Seq.map mapping + |> Seq.where ValueOption.isSome + |> Seq.map ValueOption.get + + let vtryFind predicate (source : 'T seq) = source |> Seq.where predicate |> vtryHead diff --git a/src/FSharp.Collections.Immutable/ImmutableCollectionUtil.fs b/src/FSharp.Collections.Immutable/ImmutableCollectionUtil.fs deleted file mode 100644 index 608fe4f..0000000 --- a/src/FSharp.Collections.Immutable/ImmutableCollectionUtil.fs +++ /dev/null @@ -1,19 +0,0 @@ -#if INTERACTIVE -namespace global -#else -namespace FSharp.Collections.Immutable -#endif - -[] -module internal ImmutableCollectionUtil = - let inline checkNotNull name (arg : _ | null) = - match arg with - | null -> nullArg name - | _ -> () - -module internal ErrorStrings = - [] - let InputMustBeNonNegative = "The input must be non-negative." - - [] - let ListsHaveDifferentLengths = "The lists have different lengths." diff --git a/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj b/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj index 407a068..afe226e 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj +++ b/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj @@ -1,7 +1,7 @@  - net8.0 + net9.0 $(AssemblyBaseName).Tests Exe @@ -9,7 +9,21 @@ - + + + + + + + + + + + + + + + diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList.fs deleted file mode 100644 index 351bac9..0000000 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList.fs +++ /dev/null @@ -1,10 +0,0 @@ -namespace FSharp.Collections.Immutable.Tests - -open System -open Microsoft.VisualStudio.TestTools.UnitTesting - -[] -type FlatListTests () = - - [] - member this.TestMethodPassing () = Assert.IsTrue (true) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/BasicOperationsTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/BasicOperationsTests.fs new file mode 100644 index 0000000..5d6b3de --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/BasicOperationsTests.fs @@ -0,0 +1,175 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable + +[] +type BasicOperationsTests () = + + [] + member _.``isEmpty returns true for empty FlatList`` () = + Assert.IsTrue (FlatList.isEmpty FlatList.empty) + Assert.IsFalse (FlatList.isEmpty (FlatList.singleton 1)) + + [] + member _.``isDefault returns true for default FlatList`` () = + let defaultList = Unchecked.defaultof> + Assert.IsTrue (FlatList.isDefault defaultList) + Assert.IsFalse (FlatList.isDefault FlatList.empty) + + [] + member _.``isDefaultOrEmpty returns true for default or empty FlatList`` () = + let defaultList = Unchecked.defaultof> + Assert.IsTrue (FlatList.isDefaultOrEmpty defaultList) + Assert.IsTrue (FlatList.isDefaultOrEmpty FlatList.empty) + Assert.IsFalse (FlatList.isDefaultOrEmpty (FlatList.singleton 1)) + + [] + member _.``length returns number of elements`` () = + Assert.AreEqual (0, FlatList.length FlatList.empty) + Assert.AreEqual (3, FlatList.length (FlatList.ofArray [| 1; 2; 3 |])) + + [] + member _.``item returns element at index`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + + Assert.AreEqual (10, FlatList.item 0 flatList) + Assert.AreEqual (20, FlatList.item 1 flatList) + Assert.AreEqual (30, FlatList.item 2 flatList) + + [] + [)>] + member _.``item throws for out of range index`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.item 3 flatList |> ignore + + [] + member _.``tryItem returns element or ValueNone`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + + Assert.AreEqual (ValueSome 10, FlatList.tryItem 0 flatList) + Assert.AreEqual (ValueSome 20, FlatList.tryItem 1 flatList) + Assert.AreEqual (ValueNone, FlatList.tryItem 3 flatList) + Assert.AreEqual (ValueNone, FlatList.tryItem -1 flatList) + Assert.AreEqual (ValueNone, FlatList.tryItem 0 (Unchecked.defaultof>)) + + [] + member _.``head returns first element`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + Assert.AreEqual (10, FlatList.head flatList) + + [] + [)>] + member _.``head throws for empty list`` () = FlatList.head FlatList.empty |> ignore + + [] + member _.``tryHead returns first element or ValueNone`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + Assert.AreEqual (ValueSome 10, FlatList.tryHead flatList) + Assert.AreEqual (ValueNone, FlatList.tryHead FlatList.empty) + + [] + member _.``tail returns all but first element`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + let result = FlatList.tail flatList + + Assert.AreEqual (2, result.Length) + Assert.AreEqual (20, result.[0]) + Assert.AreEqual (30, result.[1]) + + [] + [)>] + member _.``tail throws for empty list`` () = FlatList.tail FlatList.empty |> ignore + + [] + member _.``tryTail returns tail or ValueNone`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + + match FlatList.tryTail flatList with + | ValueSome tail -> + Assert.AreEqual (2, tail.Length) + Assert.AreEqual (20, tail.[0]) + Assert.AreEqual (30, tail.[1]) + | ValueNone -> Assert.Fail ("Should be ValueSome") + + Assert.AreEqual voption> (ValueNone, FlatList.tryTail FlatList.empty) + + [] + member _.``last returns last element`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + Assert.AreEqual (30, FlatList.last flatList) + + [] + [)>] + member _.``last throws for empty list`` () = FlatList.last FlatList.empty |> ignore + + [] + member _.``tryLast returns last element or ValueNone`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + Assert.AreEqual (ValueSome 30, FlatList.tryLast flatList) + Assert.AreEqual (ValueNone, FlatList.tryLast FlatList.empty) + + [] + member _.``exactlyOne returns the single element`` () = + let flatList = FlatList.singleton 42 + Assert.AreEqual (42, FlatList.exactlyOne flatList) + + [] + [)>] + member _.``exactlyOne throws for empty list`` () = FlatList.exactlyOne FlatList.empty |> ignore + + [] + [)>] + member _.``exactlyOne throws for list with multiple elements`` () = + FlatList.exactlyOne (FlatList.ofArray [| 1; 2 |]) |> ignore + + [] + member _.``tryExactlyOne returns the single element or ValueNone`` () = + let singletonList = FlatList.singleton 42 + let emptyList = FlatList.empty + let multipleList = FlatList.ofArray [| 1; 2 |] + + Assert.AreEqual (ValueSome 42, FlatList.tryExactlyOne singletonList) + Assert.AreEqual (ValueNone, FlatList.tryExactlyOne emptyList) + Assert.AreEqual (ValueNone, FlatList.tryExactlyOne multipleList) + + [] + member _.``tryHeadAndTail returns head and tail for non-empty list`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + + match FlatList.tryHeadAndTail flatList with + | ValueSome (head, tail) -> + Assert.AreEqual (1, head) + Assert.AreEqual (2, tail.Length) + Assert.AreEqual (2, tail.[0]) + Assert.AreEqual (3, tail.[1]) + | ValueNone -> Assert.Fail ("Should return ValueSome for non-empty list") + + [] + member _.``tryHeadAndTail returns ValueNone for empty list`` () = + let emptyList = FlatList.empty + let result = FlatList.tryHeadAndTail emptyList + + Assert.AreEqual<(int * FlatList) voption> (ValueNone, result) + + [] + member _.``tryLastAndInit returns init and last for non-empty list`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + + match FlatList.tryLastAndInit flatList with + | ValueSome (init, last) -> + Assert.AreEqual (3, last) + Assert.AreEqual (2, init.Length) + Assert.AreEqual (1, init.[0]) + Assert.AreEqual (2, init.[1]) + | ValueNone -> Assert.Fail ("Should return ValueSome for non-empty list") + + [] + member _.``tryLastAndInit returns ValueNone for empty list`` () = + let emptyList = FlatList.empty + let result = FlatList.tryLastAndInit emptyList + + Assert.AreEqual<(FlatList * int) voption> (ValueNone, result) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/BuilderTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/BuilderTests.fs new file mode 100644 index 0000000..ae75e79 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/BuilderTests.fs @@ -0,0 +1,97 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable + +[] +type BuilderTests () = + + [] + member _.``builder and ofBuilder create FlatList`` () = + let b = FlatList.builder () + b.Add (1) + b.Add (2) + b.Add (3) + + let flatList = FlatList.ofBuilder b + + Assert.AreEqual (3, flatList.Length) + Assert.AreEqual (1, flatList.[0]) + Assert.AreEqual (2, flatList.[1]) + Assert.AreEqual (3, flatList.[2]) + + [] + member _.``builderWith creates builder with capacity`` () = + let b = FlatList.builderWith 10 + for i = 1 to 10 do + b.Add (i) + + let flatList = FlatList.ofBuilder b + + Assert.AreEqual (10, flatList.Length) + for i = 0 to 9 do + Assert.AreEqual (i + 1, flatList.[i]) + + [] + member _.``moveFromBuilder builds FlatList and empties builder`` () = + let b = FlatList.builderWith 3 // Set exact capacity + b.Add (1) + b.Add (2) + b.Add (3) + + let flatList = FlatList.moveFromBuilder b + + Assert.AreEqual (3, flatList.Length) + Assert.AreEqual (0, b.Count) + + [] + member _.``toBuilder creates builder from FlatList`` () = + let original = FlatList.ofArray [| 1; 2; 3 |] + let builder = FlatList.toBuilder original + + Assert.AreEqual (original.Length, builder.Count) + for i = 0 to original.Length - 1 do + Assert.AreEqual (original.[i], builder.[i]) + + [] + member _.``Builder.add adds to builder`` () = + let b = FlatList.builder () + let returned = FlatList.Builder.add 42 b + + Assert.AreEqual (1, b.Count) + Assert.AreEqual (42, b.[0]) + Assert.AreSame (b, returned) // Check that same builder is returned + + [] + member _.``build applies function to builder and returns FlatList`` () = + let addItems (builder : FlatList.Builder) = + builder.Add (1) + builder.Add (2) + builder.Add (3) + + let flatList = FlatList.build addItems + + Assert.AreEqual (3, flatList.Length) + Assert.AreEqual (1, flatList.[0]) + Assert.AreEqual (2, flatList.[1]) + Assert.AreEqual (3, flatList.[2]) + + [] + member _.``update applies function to builder from FlatList`` () = + let original = FlatList.ofArray [| 1; 2; 3 |] + + let addItems (builder : FlatList.Builder) = + builder.Add (4) + builder.Add (5) + + let result = FlatList.update addItems original + + Assert.AreEqual (5, result.Length) + Assert.AreEqual (1, result.[0]) + Assert.AreEqual (2, result.[1]) + Assert.AreEqual (3, result.[2]) + Assert.AreEqual (4, result.[3]) + Assert.AreEqual (5, result.[4]) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/CreationTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/CreationTests.fs new file mode 100644 index 0000000..7227bb9 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/CreationTests.fs @@ -0,0 +1,209 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable + +[] +type CreationTests () = + + [] + member _.``empty returns empty FlatList`` () = + let empty = FlatList.empty + Assert.IsTrue (empty.IsEmpty) + Assert.AreEqual (0, empty.Length) + + [] + member _.``singleton creates FlatList with one element`` () = + let flatList = FlatList.singleton 42 + + Assert.AreEqual (1, flatList.Length) + Assert.AreEqual (42, flatList.[0]) + + [] + member _.``ofArray converts array to FlatList`` () = + let arr = [| 1; 2; 3 |] + let flatList = FlatList.ofArray arr + + Assert.AreEqual (arr.Length, flatList.Length) + for i = 0 to arr.Length - 1 do + Assert.AreEqual (arr.[i], flatList.[i]) + + [] + member _.``ofSeq converts sequence to FlatList`` () = + let seq = seq { + 1 + 2 + 3 + } + let flatList = FlatList.ofSeq seq + let expected = [| 1; 2; 3 |] + + Assert.AreEqual (expected.Length, flatList.Length) + for i = 0 to expected.Length - 1 do + Assert.AreEqual (expected.[i], flatList.[i]) + + [] + member _.``ofList converts list to FlatList`` () = + let list = [ 1; 2; 3 ] + let flatList = FlatList.ofList list + + Assert.AreEqual (list.Length, flatList.Length) + for i = 0 to list.Length - 1 do + Assert.AreEqual (list.[i], flatList.[i]) + + [] + member _.``init creates initialized FlatList`` () = + let flatList = FlatList.init 5 (fun i -> i * 2) + let expected = [| 0; 2; 4; 6; 8 |] + + Assert.AreEqual (expected.Length, flatList.Length) + for i = 0 to expected.Length - 1 do + Assert.AreEqual (expected.[i], flatList.[i]) + + [] + [)>] + member _.``init throws for negative count`` () = FlatList.init -1 id |> ignore + + [] + member _.``create makes FlatList with repeated values`` () = + let flatList = FlatList.create 3 "test" + + Assert.AreEqual (3, flatList.Length) + for i = 0 to flatList.Length - 1 do + Assert.AreEqual ("test", flatList.[i]) + + [] + member _.``replicate makes FlatList with repeated values`` () = + let flatList = FlatList.replicate 3 "test" + + Assert.AreEqual (3, flatList.Length) + for i = 0 to flatList.Length - 1 do + Assert.AreEqual ("test", flatList.[i]) + + [] + member _.``toSeq converts FlatList to sequence`` () = + let original = [| 1; 2; 3 |] + let flatList = FlatList.ofArray original + let seq = FlatList.toSeq flatList + + let result = Seq.toArray seq + CollectionAssert.AreEqual (original, result) + + [] + member _.``toArray converts FlatList to array`` () = + let original = [| 1; 2; 3 |] + let flatList = FlatList.ofArray original + let result = FlatList.toArray flatList + + CollectionAssert.AreEqual (original, result) + + [] + member _.``toList converts FlatList to list`` () = + let original = [| 1; 2; 3 |] + let flatList = FlatList.ofArray original + let result = FlatList.toList flatList + + Assert.AreEqual (original.Length, result.Length) + for i = 0 to original.Length - 1 do + Assert.AreEqual (original.[i], result.[i]) + + [] + member _.``copy makes a new FlatList with same elements`` () = + let original = FlatList.ofArray [| 1; 2; 3 |] + let copy = FlatList.copy original + + Assert.AreEqual (original.Length, copy.Length) + for i = 0 to original.Length - 1 do + Assert.AreEqual (original.[i], copy.[i]) + + // Verify both reference the same array under the hood + Assert.AreEqual (original.GetHashCode (), copy.GetHashCode ()) + + [] + member _.``zeroCreate creates FlatList with default values`` () = + let flatList = FlatList.zeroCreate 3 + + Assert.AreEqual (3, flatList.Length) + for i = 0 to flatList.Length - 1 do + Assert.AreEqual (0, flatList.[i]) + + [] + member _.``zeroCreate creates FlatList with reference type defaults`` () = + let flatList = FlatList.zeroCreate 2 + + Assert.AreEqual (2, flatList.Length) + for i = 0 to flatList.Length - 1 do + Assert.AreEqual (null, flatList.[i]) + + [] + member _.``ofOption creates singleton for Some`` () = + let result = FlatList.ofOption (Some 42) + + Assert.AreEqual (1, result.Length) + Assert.AreEqual (42, result.[0]) + + [] + member _.``ofOption creates empty for None`` () = + let result = FlatList.ofOption None + + Assert.AreEqual (0, result.Length) + Assert.IsTrue (FlatList.isEmpty result) + + [] + member _.``ofValueOption creates singleton for ValueSome`` () = + let result = FlatList.ofValueOption (ValueSome 42) + + Assert.AreEqual (1, result.Length) + Assert.AreEqual (42, result.[0]) + + [] + member _.``ofValueOption creates empty for ValueNone`` () = + let result = FlatList.ofValueOption ValueNone + + Assert.AreEqual (0, result.Length) + Assert.IsTrue (FlatList.isEmpty result) + + [] + member _.``toOption returns Some for singleton`` () = + let flatList = FlatList.singleton 42 + let result = FlatList.toOption flatList + + Assert.AreEqual (Some 42, result) + + [] + member _.``toOption returns None for empty`` () = + let flatList = FlatList.empty + let result = FlatList.toOption flatList + + Assert.AreEqual (None, result) + + [] + member _.``toOption returns None for multiple elements`` () = + let flatList = FlatList.ofArray [| 1; 2 |] + let result = FlatList.toOption flatList + + Assert.AreEqual (None, result) + + [] + member _.``toValueOption returns ValueSome for singleton`` () = + let flatList = FlatList.singleton 42 + let result = FlatList.toValueOption flatList + + Assert.AreEqual (ValueSome 42, result) + + [] + member _.``toValueOption returns ValueNone for empty`` () = + let flatList = FlatList.empty + let result = FlatList.toValueOption flatList + + Assert.AreEqual (ValueNone, result) + + [] + member _.``toValueOption returns ValueNone for multiple elements`` () = + let flatList = FlatList.ofArray [| 1; 2 |] + let result = FlatList.toValueOption flatList + + Assert.AreEqual (ValueNone, result) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FilterMapTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FilterMapTests.fs new file mode 100644 index 0000000..ae97b8d --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FilterMapTests.fs @@ -0,0 +1,215 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable + +[] +type FilterMapTests () = + + [] + member _.``map transforms elements`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + let result = FlatList.map (fun x -> x * 2) flatList + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (2, result.[0]) + Assert.AreEqual (4, result.[1]) + Assert.AreEqual (6, result.[2]) + + [] + member _.``mapi transforms elements with index`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + let result = FlatList.mapi (fun i x -> i + x) flatList + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (10, result.[0]) // 0 + 10 + Assert.AreEqual (21, result.[1]) // 1 + 20 + Assert.AreEqual (32, result.[2]) // 2 + 30 + + [] + member _.``map2 transforms pairs of elements`` () = + let list1 = FlatList.ofArray [| 1; 2; 3 |] + let list2 = FlatList.ofArray [| 10; 20; 30 |] + + let result = FlatList.map2 (fun x y -> x * y) list1 list2 + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (10, result.[0]) // 1 * 10 + Assert.AreEqual (40, result.[1]) // 2 * 20 + Assert.AreEqual (90, result.[2]) // 3 * 30 + + [] + [)>] + member _.``map2 throws when lists have different lengths`` () = + let list1 = FlatList.ofArray [| 1; 2 |] + let list2 = FlatList.ofArray [| 10; 20; 30 |] + + FlatList.map2 (fun x y -> x * y) list1 list2 |> ignore + + [] + member _.``mapi2 transforms pairs with index`` () = + let list1 = FlatList.ofArray [| 1; 2; 3 |] + let list2 = FlatList.ofArray [| 10; 20; 30 |] + + let result = FlatList.mapi2 (fun i x y -> i + x + y) list1 list2 + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (11, result.[0]) // 0 + 1 + 10 + Assert.AreEqual (23, result.[1]) // 1 + 2 + 20 + Assert.AreEqual (35, result.[2]) // 2 + 3 + 30 + + [] + [)>] + member _.``mapi2 throws when lists have different lengths`` () = + let list1 = FlatList.ofArray [| 1; 2 |] + let list2 = FlatList.ofArray [| 10; 20; 30 |] + + FlatList.mapi2 (fun i x y -> i + x + y) list1 list2 + |> ignore + + [] + member _.``filter keeps elements matching predicate`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5 |] + let result = FlatList.filter (fun x -> x % 2 = 0) flatList + + Assert.AreEqual (2, result.Length) + Assert.AreEqual (2, result.[0]) + Assert.AreEqual (4, result.[1]) + + [] + member _.``filter removes elements that do not match predicate`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5; 6 |] + let result = FlatList.filter (fun x -> x % 2 = 0) flatList + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (2, result.[0]) + Assert.AreEqual (4, result.[1]) + Assert.AreEqual (6, result.[2]) + + [] + member _.``where is alias for filter`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5 |] + let filtered = FlatList.filter (fun x -> x % 2 = 0) flatList + let wheered = FlatList.where (fun x -> x % 2 = 0) flatList + + Assert.AreEqual (filtered.Length, wheered.Length) + for i = 0 to filtered.Length - 1 do + Assert.AreEqual (filtered.[i], wheered.[i]) + + [] + member _.``where removes elements that do not match predicate`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5; 6 |] + let result = FlatList.where (fun x -> x % 2 = 0) flatList + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (2, result.[0]) + Assert.AreEqual (4, result.[1]) + Assert.AreEqual (6, result.[2]) + + [] + member _.``choose selects and maps elements`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5 |] + let result = FlatList.choose (fun x -> if x % 2 = 0 then ValueSome (x * 10) else ValueNone) flatList + + CollectionAssert.AreEqual ([| 20; 40 |], FlatList.toArray result) + + [] + member _.``choose transforms and filters elements`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5 |] + let result = FlatList.choose (fun x -> if x % 2 = 0 then ValueSome (x * 10) else ValueNone) flatList + + Assert.AreEqual (2, result.Length) + Assert.AreEqual (20, result.[0]) + Assert.AreEqual (40, result.[1]) + + [] + member _.``chooseBack transforms and filters elements in reverse`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5 |] + let result = FlatList.chooseBack (fun x -> if x % 2 = 0 then ValueSome (x * 10) else ValueNone) flatList + + Assert.AreEqual (2, result.Length) + Assert.AreEqual (40, result.[0]) + Assert.AreEqual (20, result.[1]) + + [] + member _.``collect maps and concatenates`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + let result = FlatList.collect (fun x -> [ x; x * 10 ]) flatList + + Assert.AreEqual (6, result.Length) + Assert.AreEqual (1, result.[0]) + Assert.AreEqual (10, result.[1]) + Assert.AreEqual (2, result.[2]) + Assert.AreEqual (20, result.[3]) + Assert.AreEqual (3, result.[4]) + Assert.AreEqual (30, result.[5]) + + [] + member _.``collect transforms and concatenates results`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + let result = FlatList.collect (fun x -> [| x; x * 10 |]) flatList + + Assert.AreEqual (6, result.Length) + Assert.AreEqual (1, result.[0]) + Assert.AreEqual (10, result.[1]) + Assert.AreEqual (2, result.[2]) + Assert.AreEqual (20, result.[3]) + Assert.AreEqual (3, result.[4]) + Assert.AreEqual (30, result.[5]) + + [] + member _.``partition splits elements based on predicate`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5 |] + let evens, odds = FlatList.partition (fun x -> x % 2 = 0) flatList + + CollectionAssert.AreEqual ([| 2; 4 |], FlatList.toArray evens) + CollectionAssert.AreEqual ([| 1; 3; 5 |], FlatList.toArray odds) + + [] + member _.``partition splits list by predicate`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5; 6 |] + let (evens, odds) = FlatList.partition (fun x -> x % 2 = 0) flatList + + Assert.AreEqual (3, evens.Length) + Assert.AreEqual (2, evens.[0]) + Assert.AreEqual (4, evens.[1]) + Assert.AreEqual (6, evens.[2]) + + Assert.AreEqual (3, odds.Length) + Assert.AreEqual (1, odds.[0]) + Assert.AreEqual (3, odds.[1]) + Assert.AreEqual (5, odds.[2]) + + [] + member _.``distinct removes duplicates`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 1; 2; 5 |] + let result = FlatList.distinct flatList + + Assert.AreEqual (4, result.Length) + Assert.IsTrue (FlatList.contains 1 result) + Assert.IsTrue (FlatList.contains 2 result) + Assert.IsTrue (FlatList.contains 3 result) + Assert.IsTrue (FlatList.contains 5 result) + + [] + member _.``distinctBy removes duplicates using projection`` () = + let flatList = FlatList.ofArray [| "apple"; "banana"; "apricot"; "berry" |] + let result = FlatList.distinctBy (fun (s : string) -> s.[0]) flatList + + Assert.AreEqual (2, result.Length) + // Only one item starting with 'a' and one with 'b' + Assert.IsTrue (result |> FlatList.exists (fun s -> s.[0] = 'a')) + Assert.IsTrue (result |> FlatList.exists (fun s -> s.[0] = 'b')) + + [] + member _.``indexed pairs elements with their indices`` () = + let flatList = FlatList.ofArray [| "a"; "b"; "c" |] + let result = FlatList.indexed flatList + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (struct (0, "a"), result.[0]) + Assert.AreEqual (struct (1, "b"), result.[1]) + Assert.AreEqual (struct (2, "c"), result.[2]) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTestBase.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTestBase.fs new file mode 100644 index 0000000..6fc3edc --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTestBase.fs @@ -0,0 +1,16 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable + +[] +module TestData = + let emptyIntList = FlatList.empty + let singletonIntList = FlatList.singleton 42 + let standardIntList = FlatList.ofArray [| 10; 20; 30; 40; 50 |] + let evenOddIntList = FlatList.ofArray [| 1; 2; 3; 4; 5; 6 |] + let repeatedIntList = FlatList.ofArray [| 1; 2; 3; 1; 2; 5 |] + let stringList = FlatList.ofArray [| "apple"; "banana"; "cherry"; "date" |] diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FoldTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FoldTests.fs new file mode 100644 index 0000000..c19178b --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FoldTests.fs @@ -0,0 +1,189 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable + +[] +type FoldTests () = + + [] + member _.``fold accumulates values`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + let result = FlatList.fold (fun acc x -> acc + x) 0 flatList + + Assert.AreEqual (10, result) // 0 + 1 + 2 + 3 + 4 = 10 + + [] + member _.``fold with string concatenation works`` () = + let flatList = FlatList.ofArray [| "a"; "b"; "c" |] + let result = FlatList.fold (fun acc x -> acc + x) "" flatList + + Assert.AreEqual ("abc", result) + + [] + member _.``fold2 accumulates from two lists`` () = + let list1 = FlatList.ofArray [| 1; 2; 3 |] + let list2 = FlatList.ofArray [| 10; 20; 30 |] + + let result = FlatList.fold2 (fun acc x y -> acc + x * y) 0 list1 list2 + + Assert.AreEqual (140, result) // 0 + (1*10) + (2*20) + (3*30) = 140 + + [] + [)>] + member _.``fold2 throws when lists have different lengths`` () = + let list1 = FlatList.ofArray [| 1; 2 |] + let list2 = FlatList.ofArray [| 10; 20; 30 |] + + FlatList.fold2 (fun acc x y -> acc + x * y) 0 list1 list2 + |> ignore + + [] + member _.``foldBack accumulates values starting from the end`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + let result = FlatList.foldBack (fun x acc -> acc - x) flatList 0 + + // With foldBack: (0 - 4) - 3 - 2 - 1 = -10 + Assert.AreEqual (-10, result) + + [] + member _.``foldBack with string concatenation works`` () = + let flatList = FlatList.ofArray [| "a"; "b"; "c" |] + let result = FlatList.foldBack (fun x acc -> x + acc) flatList "" + + // With foldBack: "a" + "b" + "c" + "" = "abc" + Assert.AreEqual ("abc", result) + + [] + member _.``foldBack2 accumulates from two lists starting from the end`` () = + let list1 = FlatList.ofArray [| 1; 2; 3 |] + let list2 = FlatList.ofArray [| 10; 20; 30 |] + + let result = FlatList.foldBack2 (fun x y acc -> acc + x * y) list1 list2 0 + + // With foldBack2: 0 + 3*30 + 2*20 + 1*10 = 140 + Assert.AreEqual (140, result) + + [] + [)>] + member _.``foldBack2 throws when lists have different lengths`` () = + let list1 = FlatList.ofArray [| 1; 2 |] + let list2 = FlatList.ofArray [| 10; 20; 30 |] + + FlatList.foldBack2 (fun x y acc -> acc + x * y) list1 list2 0 + |> ignore + + [] + member _.``reduce combines elements`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + let result = FlatList.reduce (fun acc x -> acc + x) flatList + + // 1 + 2 + 3 + 4 = 10 + Assert.AreEqual (10, result) + + [] + [)>] + member _.``reduce throws on empty list`` () = + FlatList.reduce (fun acc x -> acc + x) FlatList.empty + |> ignore + + [] + member _.``reduceBack combines elements starting from the end`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + let result = FlatList.reduceBack (fun x acc -> x - acc) flatList + + // 1 - (2 - (3 - 4)) = 1 - (2 - (-1)) = 1 - 3 = -2 + Assert.AreEqual (-2, result) + + [] + [)>] + member _.``reduceBack throws on empty list`` () = + FlatList.reduceBack (fun x acc -> x + acc) FlatList.empty + |> ignore + + [] + member _.``scan produces intermediate results`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + let result = FlatList.scan (fun acc x -> acc + x) 0 flatList + + Assert.AreEqual (5, result.Length) + Assert.AreEqual (0, result.[0]) // Initial state + Assert.AreEqual (1, result.[1]) // 0+1 + Assert.AreEqual (3, result.[2]) // 1+2 + Assert.AreEqual (6, result.[3]) // 3+3 + Assert.AreEqual (10, result.[4]) // 6+4 + + [] + member _.``scanBack produces intermediate results starting from the end`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + let result = FlatList.scanBack (fun x acc -> x + acc) flatList 0 + + Assert.AreEqual (5, result.Length) + Assert.AreEqual (10, result.[0]) // 1 + (2 + (3 + (4 + 0))) + Assert.AreEqual (9, result.[1]) // 2 + (3 + (4 + 0)) + Assert.AreEqual (7, result.[2]) // 3 + (4 + 0) + Assert.AreEqual (4, result.[3]) // 4 + 0 + Assert.AreEqual (0, result.[4]) // Initial state + + [] + member _.``sum calculates sum of elements`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + let result = FlatList.sum flatList + + Assert.AreEqual (10, result) // 1 + 2 + 3 + 4 = 10 + + [] + member _.``sumBy calculates sum using projection function`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + let result = FlatList.sumBy (fun x -> x * x) flatList + + Assert.AreEqual (30, result) // 1*1 + 2*2 + 3*3 + 4*4 = 30 + + [] + member _.``average calculates average of elements`` () = + let flatList = FlatList.ofArray [| 1.0; 2.0; 3.0; 4.0 |] + let result = FlatList.average flatList + + Assert.AreEqual (2.5, result) // (1 + 2 + 3 + 4) / 4 = 10 / 4 = 2.5 + + [] + [)>] + member _.``average throws on empty list`` () = FlatList.average FlatList.empty |> ignore + + [] + member _.``averageBy calculates average using projection function`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + let result = FlatList.averageBy float flatList + + Assert.AreEqual (2.5, result) // (1 + 2 + 3 + 4) / 4 = 10 / 4 = 2.5 + + [] + member _.``min finds minimum element`` () = + let flatList = FlatList.ofArray [| 5; 3; 9; 1; 8 |] + let result = FlatList.min flatList + + Assert.AreEqual (1, result) + + [] + member _.``minBy finds element with minimum projected value`` () = + let people = FlatList.ofArray [| ("Alice", 25); ("Bob", 18); ("Charlie", 32) |] + let result = FlatList.minBy snd people + + Assert.AreEqual (("Bob", 18), result) + + [] + member _.``max finds maximum element`` () = + let flatList = FlatList.ofArray [| 5; 3; 9; 1; 8 |] + let result = FlatList.max flatList + + Assert.AreEqual (9, result) + + [] + member _.``maxBy finds element with maximum projected value`` () = + let people = FlatList.ofArray [| ("Alice", 25); ("Bob", 18); ("Charlie", 32) |] + let result = FlatList.maxBy snd people + + Assert.AreEqual (("Charlie", 32), result) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/GroupingTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/GroupingTests.fs new file mode 100644 index 0000000..995b1d2 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/GroupingTests.fs @@ -0,0 +1,156 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable +open FSharp.Collections.Immutable.Tests + +[] +type GroupingTests () = + + [] + member _.``countBy groups and counts elements`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 1; 2; 5 |] + let result = FlatList.countBy id flatList + + Assert.AreEqual (4, result.Length) + + // Find the counts for each key + let countFor key = + result + |> FlatList.find (fun struct (k, _) -> k = key) + |> sndv + + Assert.AreEqual (2, countFor 1) + Assert.AreEqual (2, countFor 2) + Assert.AreEqual (1, countFor 3) + Assert.AreEqual (1, countFor 5) + + [] + member _.``groupBy groups elements`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 1; 2; 5 |] + let result = FlatList.groupBy (fun x -> x % 2) flatList + + Assert.AreEqual (2, result.Length) + + // Find group for a key + let groupFor key = + result + |> FlatList.find (fun struct (k, _) -> k = key) + |> sndv + + let evenGroup = groupFor 0 + let oddGroup = groupFor 1 + + Assert.AreEqual (2, evenGroup.Length) + Assert.AreEqual (4, oddGroup.Length) + + Assert.IsTrue (FlatList.forall (fun x -> x % 2 = 0) evenGroup) + Assert.IsTrue (FlatList.forall (fun x -> x % 2 = 1) oddGroup) + + [] + member _.``chunkBySize splits into chunks`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5; 6; 7 |] + let result = FlatList.chunkBySize 3 flatList + + Assert.AreEqual (3, result.Length) + + Assert.AreEqual (3, result.[0].Length) + Assert.AreEqual (3, result.[1].Length) + Assert.AreEqual (1, result.[2].Length) + + // Check first chunk + Assert.AreEqual (1, result.[0].[0]) + Assert.AreEqual (2, result.[0].[1]) + Assert.AreEqual (3, result.[0].[2]) + + // Check second chunk + Assert.AreEqual (4, result.[1].[0]) + Assert.AreEqual (5, result.[1].[1]) + Assert.AreEqual (6, result.[1].[2]) + + // Check third chunk + Assert.AreEqual (7, result.[2].[0]) + + [] + [)>] + member _.``chunkBySize throws for non-positive chunk size`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.chunkBySize 0 flatList |> ignore + + [] + member _.``splitInto divides list into specified number of chunks`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5; 6; 7; 8 |] + + // Split into 3 chunks: [|1; 2; 3|], [|4; 5; 6|], [|7; 8|] + let result = FlatList.splitInto 3 flatList + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (3, result.[0].Length) + Assert.AreEqual (3, result.[1].Length) + Assert.AreEqual (2, result.[2].Length) + + // Check first chunk + Assert.AreEqual (1, result.[0].[0]) + Assert.AreEqual (2, result.[0].[1]) + Assert.AreEqual (3, result.[0].[2]) + + // Check second chunk + Assert.AreEqual (4, result.[1].[0]) + Assert.AreEqual (5, result.[1].[1]) + Assert.AreEqual (6, result.[1].[2]) + + // Check third chunk + Assert.AreEqual (7, result.[2].[0]) + Assert.AreEqual (8, result.[2].[1]) + + [] + [)>] + member _.``splitInto throws for non-positive count`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.splitInto 0 flatList |> ignore + + [] + member _.``windowed creates sliding windows`` () = + let flatList = FlatList.ofArray [| 10; 20; 30; 40; 50 |] + let result = FlatList.windowed 3 flatList + + Assert.AreEqual (3, result.Length) + + Assert.AreEqual (3, result.[0].Length) + Assert.AreEqual (10, result.[0].[0]) + Assert.AreEqual (20, result.[0].[1]) + Assert.AreEqual (30, result.[0].[2]) + + Assert.AreEqual (3, result.[1].Length) + Assert.AreEqual (20, result.[1].[0]) + Assert.AreEqual (30, result.[1].[1]) + Assert.AreEqual (40, result.[1].[2]) + + Assert.AreEqual (3, result.[2].Length) + Assert.AreEqual (30, result.[2].[0]) + Assert.AreEqual (40, result.[2].[1]) + Assert.AreEqual (50, result.[2].[2]) + + [] + [)>] + member _.``windowed throws for non-positive window size`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.windowed 0 flatList |> ignore + + [] + member _.``pairwise creates adjacent pairs`` () = + let flatList = FlatList.ofArray [| 10; 20; 30; 40 |] + let result = FlatList.pairwise flatList + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (struct (10, 20), result.[0]) + Assert.AreEqual (struct (20, 30), result.[1]) + Assert.AreEqual (struct (30, 40), result.[2]) + + [] + member _.``pairwise returns empty for singleton or empty`` () = + Assert.AreEqual (0, (FlatList.pairwise (FlatList.singleton 1)).Length) + Assert.AreEqual (0, (FlatList.pairwise FlatList.empty).Length) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/IterationTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/IterationTests.fs new file mode 100644 index 0000000..1d18580 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/IterationTests.fs @@ -0,0 +1,109 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable + +[] +type IterationTests () = + + [] + member _.``iter applies function to each element`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + let mutable sum = 0 + + FlatList.iter (fun x -> sum <- sum + x) flatList + + Assert.AreEqual (6, sum) + + [] + member _.``iteri applies function with index`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + let mutable sum = 0 + + FlatList.iteri (fun i x -> sum <- sum + i + x) flatList + + Assert.AreEqual (63, sum) // (0+10) + (1+20) + (2+30) = 63 + + [] + member _.``iter2 applies function to pairs`` () = + let list1 = FlatList.ofArray [| 1; 2; 3 |] + let list2 = FlatList.ofArray [| 10; 20; 30 |] + let mutable sum = 0 + + FlatList.iter2 (fun x y -> sum <- sum + x + y) list1 list2 + + Assert.AreEqual (66, sum) // (1+10) + (2+20) + (3+30) = 66 + + [] + [)>] + member _.``iter2 throws for different length lists`` () = + let list1 = FlatList.ofArray [| 1; 2 |] + let list2 = FlatList.ofArray [| 10; 20; 30 |] + + FlatList.iter2 (fun x y -> ()) list1 list2 + + [] + member _.``iteri2 applies function with index`` () = + let list1 = FlatList.ofArray [| 1; 2; 3 |] + let list2 = FlatList.ofArray [| 10; 20; 30 |] + let mutable result = 0 + + FlatList.iteri2 (fun i x y -> result <- result + i + x + y) list1 list2 + + Assert.AreEqual (69, result) // (0+1+10) + (1+2+20) + (2+3+30) = 69 + + [] + member _.``contains checks if element exists`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + + Assert.IsTrue (FlatList.contains 2 flatList) + Assert.IsFalse (FlatList.contains 4 flatList) + + [] + member _.``exists checks if any element satisfies predicate`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + + Assert.IsTrue (FlatList.exists (fun x -> x = 2) flatList) + Assert.IsFalse (FlatList.exists (fun x -> x > 10) flatList) + + [] + member _.``exists2 checks elements from two lists`` () = + let list1 = FlatList.ofArray [| 1; 2; 3 |] + let list2 = FlatList.ofArray [| 3; 2; 1 |] + + Assert.IsTrue (FlatList.exists2 (fun x y -> x = y) list1 list2) + Assert.IsFalse (FlatList.exists2 (fun x y -> x > 10 && y > 10) list1 list2) + + [] + [)>] + member _.``exists2 throws when lists have different lengths`` () = + let list1 = FlatList.ofArray [| 1; 2 |] + let list2 = FlatList.ofArray [| 1; 2; 3 |] + + FlatList.exists2 (fun x y -> x = y) list1 list2 |> ignore + + [] + member _.``forall checks if all elements satisfy predicate`` () = + let flatList = FlatList.ofArray [| 2; 4; 6 |] + + Assert.IsTrue (FlatList.forall (fun x -> x % 2 = 0) flatList) + Assert.IsFalse (FlatList.forall (fun x -> x > 3) flatList) + + [] + member _.``forall2 checks all element pairs`` () = + let list1 = FlatList.ofArray [| 1; 2; 3 |] + let list2 = FlatList.ofArray [| 4; 5; 6 |] + + Assert.IsTrue (FlatList.forall2 (fun x y -> x < y) list1 list2) + Assert.IsFalse (FlatList.forall2 (fun x y -> x > y) list1 list2) + + [] + [)>] + member _.``forall2 throws when lists have different lengths`` () = + let list1 = FlatList.ofArray [| 1; 2 |] + let list2 = FlatList.ofArray [| 1; 2; 3 |] + + FlatList.forall2 (fun x y -> x = y) list1 list2 |> ignore diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/ManipulationTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/ManipulationTests.fs new file mode 100644 index 0000000..dee0bee --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/ManipulationTests.fs @@ -0,0 +1,361 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable + +[] +type ManipulationTests () = + + [] + member _.``append combines two FlatLists`` () = + let list1 = FlatList.ofArray [| 1; 2 |] + let list2 = FlatList.ofArray [| 3; 4 |] + + let result = FlatList.append list1 list2 + + Assert.AreEqual (4, result.Length) + Assert.AreEqual (1, result.[0]) + Assert.AreEqual (2, result.[1]) + Assert.AreEqual (3, result.[2]) + Assert.AreEqual (4, result.[3]) + + [] + member _.``concat combines multiple FlatLists`` () = + let lists = + FlatList.ofArray [| FlatList.ofArray [| 1; 2 |]; FlatList.ofArray [| 3; 4 |]; FlatList.ofArray [| 5; 6 |] |] + + let result = FlatList.concat lists + + Assert.AreEqual (6, result.Length) + for i = 0 to 5 do + Assert.AreEqual (i + 1, result.[i]) + + [] + member _.``take returns first N elements`` () = + let flatList = FlatList.ofArray [| 10; 20; 30; 40; 50 |] + + let result1 = FlatList.take 3 flatList + Assert.AreEqual (3, result1.Length) + Assert.AreEqual (10, result1.[0]) + Assert.AreEqual (20, result1.[1]) + Assert.AreEqual (30, result1.[2]) + + let result2 = FlatList.take 0 flatList + Assert.AreEqual (0, result2.Length) + + let result3 = FlatList.take 10 flatList + Assert.AreEqual (5, result3.Length) + + [] + [)>] + member _.``take throws for negative count`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.take -1 flatList |> ignore + + [] + member _.``takeWhile returns elements while predicate is true`` () = + let flatList = FlatList.ofArray [| 2; 4; 6; 7; 8; 10 |] + let result = FlatList.takeWhile (fun x -> x % 2 = 0) flatList + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (2, result.[0]) + Assert.AreEqual (4, result.[1]) + Assert.AreEqual (6, result.[2]) + + [] + member _.``skip returns all but first N elements`` () = + let flatList = FlatList.ofArray [| 10; 20; 30; 40; 50 |] + + let result1 = FlatList.skip 2 flatList + Assert.AreEqual (3, result1.Length) + Assert.AreEqual (30, result1.[0]) + Assert.AreEqual (40, result1.[1]) + Assert.AreEqual (50, result1.[2]) + + let result2 = FlatList.skip 0 flatList + Assert.AreEqual (5, result2.Length) + + let result3 = FlatList.skip 5 flatList + Assert.AreEqual (0, result3.Length) + + let result4 = FlatList.skip 10 flatList + Assert.AreEqual (0, result4.Length) + + [] + [)>] + member _.``skip throws for negative count`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.skip -1 flatList |> ignore + + [] + member _.``skipWhile skips elements while predicate is true`` () = + let flatList = FlatList.ofArray [| 2; 4; 6; 7; 8; 10 |] + let result = FlatList.skipWhile (fun x -> x % 2 = 0) flatList + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (7, result.[0]) + Assert.AreEqual (8, result.[1]) + Assert.AreEqual (10, result.[2]) + + [] + member _.``sub gets a sublist`` () = + let flatList = FlatList.ofArray [| 10; 20; 30; 40; 50 |] + let result = FlatList.sub 1 3 flatList + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (20, result.[0]) + Assert.AreEqual (30, result.[1]) + Assert.AreEqual (40, result.[2]) + + [] + member _.``truncate limits to at most N elements`` () = + let flatList = FlatList.ofArray [| 10; 20; 30; 40; 50 |] + + let result1 = FlatList.truncate 3 flatList + Assert.AreEqual (3, result1.Length) + Assert.AreEqual (10, result1.[0]) + Assert.AreEqual (20, result1.[1]) + Assert.AreEqual (30, result1.[2]) + + let result2 = FlatList.truncate 10 flatList + Assert.AreEqual (5, result2.Length) + + [] + member _.``splitAt splits list at index`` () = + let flatList = FlatList.ofArray [| 10; 20; 30; 40; 50 |] + let first, second = FlatList.splitAt 2 flatList + + Assert.AreEqual (2, first.Length) + Assert.AreEqual (10, first.[0]) + Assert.AreEqual (20, first.[1]) + + Assert.AreEqual (3, second.Length) + Assert.AreEqual (30, second.[0]) + Assert.AreEqual (40, second.[1]) + Assert.AreEqual (50, second.[2]) + + [] + member _.``updateAt updates element at given index`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + let result = FlatList.updateAt 1 99 flatList + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (10, result.[0]) + Assert.AreEqual (99, result.[1]) + Assert.AreEqual (30, result.[2]) + + [] + member _.``removeAt removes element at given index`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + let result = FlatList.removeAt 1 flatList + + Assert.AreEqual (2, result.Length) + Assert.AreEqual (10, result.[0]) + Assert.AreEqual (30, result.[1]) + + [] + member _.``insertAt inserts element at given index`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + + // Insert at beginning + let result1 = FlatList.insertAt 0 5 flatList + Assert.AreEqual (4, result1.Length) + Assert.AreEqual (5, result1.[0]) + Assert.AreEqual (10, result1.[1]) + + // Insert in middle + let result2 = FlatList.insertAt 2 25 flatList + Assert.AreEqual (4, result2.Length) + Assert.AreEqual (10, result2.[0]) + Assert.AreEqual (20, result2.[1]) + Assert.AreEqual (25, result2.[2]) + Assert.AreEqual (30, result2.[3]) + + // Insert at end + let result3 = FlatList.insertAt 3 40 flatList + Assert.AreEqual (4, result3.Length) + Assert.AreEqual (10, result3.[0]) + Assert.AreEqual (20, result3.[1]) + Assert.AreEqual (30, result3.[2]) + Assert.AreEqual (40, result3.[3]) + + [] + member _.``insertManyAt inserts multiple elements at given index`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + let valuesToInsert = [| 21; 22; 23 |] + + let result = FlatList.insertManyAt 2 valuesToInsert flatList + + Assert.AreEqual (6, result.Length) + Assert.AreEqual (10, result.[0]) + Assert.AreEqual (20, result.[1]) + Assert.AreEqual (21, result.[2]) + Assert.AreEqual (22, result.[3]) + Assert.AreEqual (23, result.[4]) + Assert.AreEqual (30, result.[5]) + + [] + member _.``removeRange removes elements in range`` () = + let flatList = FlatList.ofArray [| 10; 20; 30; 40; 50 |] + let result = FlatList.removeRange 1 3 flatList + + Assert.AreEqual (2, result.Length) + Assert.AreEqual (10, result.[0]) + Assert.AreEqual (50, result.[1]) + + [] + member _.``removeAll removes specified elements`` () = + let flatList = FlatList.ofArray [| 10; 20; 30; 20; 40 |] + let itemsToRemove = [| 20; 30 |] + + let result = FlatList.removeAll itemsToRemove flatList + + Assert.AreEqual (2, result.Length) + Assert.AreEqual (10, result.[0]) + Assert.AreEqual (40, result.[1]) + + [] + member _.``except removes elements from another collection`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5; 3; 1 |] + let toExclude = [| 1; 3; 9 |] + + let result = FlatList.except toExclude flatList + + Assert.AreEqual (3, result.Length) + Assert.IsTrue (FlatList.contains 2 result) + Assert.IsTrue (FlatList.contains 4 result) + Assert.IsTrue (FlatList.contains 5 result) + + [] + member _.``takeEnd returns last N elements`` () = + let flatList = FlatList.ofArray [| 10; 20; 30; 40; 50 |] + + let result1 = FlatList.takeEnd 3 flatList + Assert.AreEqual (3, result1.Length) + Assert.AreEqual (30, result1.[0]) + Assert.AreEqual (40, result1.[1]) + Assert.AreEqual (50, result1.[2]) + + let result2 = FlatList.takeEnd 0 flatList + Assert.AreEqual (0, result2.Length) + + [] + [)>] + member _.``takeEnd throws for negative count`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.takeEnd -1 flatList |> ignore + + [] + member _.``skipEnd skips last N elements`` () = + let flatList = FlatList.ofArray [| 10; 20; 30; 40; 50 |] + + let result1 = FlatList.skipEnd 2 flatList + Assert.AreEqual (3, result1.Length) + Assert.AreEqual (10, result1.[0]) + Assert.AreEqual (20, result1.[1]) + Assert.AreEqual (30, result1.[2]) + + let result2 = FlatList.skipEnd 0 flatList + Assert.AreEqual (5, result2.Length) + + [] + [)>] + member _.``skipEnd throws for negative count`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.skipEnd -1 flatList |> ignore + + [] + member _.``findDup finds first duplicate element`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 2; 4 |] + let result = FlatList.findDup flatList + + Assert.AreEqual (2, result) + + [] + [)>] + member _.``findDup throws when no duplicates`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.findDup flatList |> ignore + + [] + member _.``findDupBy finds first duplicate element by projection`` () = + let flatList = FlatList.ofArray [| "apple"; "banana"; "avocado"; "cherry" |] + let result = FlatList.findDupBy (fun (s : string) -> s.[0]) flatList + + Assert.AreEqual ("avocado", result) + + [] + [)>] + member _.``findDupBy throws when no duplicates`` () = + let flatList = FlatList.ofArray [| "apple"; "banana"; "cherry" |] + FlatList.findDupBy (fun (s : string) -> s.[0]) flatList + |> ignore + + [] + member _.``unfold generates FlatList from state`` () = + let result = + FlatList.unfold + (fun state -> + if state > 5 then + ValueNone + else + ValueSome (struct (state, state + 1)) + ) + 1 + + Assert.AreEqual (5, result.Length) + Assert.AreEqual (1, result.[0]) + Assert.AreEqual (2, result.[1]) + Assert.AreEqual (3, result.[2]) + Assert.AreEqual (4, result.[3]) + Assert.AreEqual (5, result.[4]) + + [] + member _.``unfold handles empty generation`` () = + let result = FlatList.unfold (fun _ -> ValueNone) 1 + + Assert.AreEqual (0, result.Length) + Assert.IsTrue (FlatList.isEmpty result) + + [] + member _.``compareWith compares two FlatLists`` () = + let list1 = FlatList.ofArray [| 1; 2; 3 |] + let list2 = FlatList.ofArray [| 1; 2; 3 |] + let list3 = FlatList.ofArray [| 1; 2; 4 |] + let list4 = FlatList.ofArray [| 1; 2 |] + + Assert.AreEqual (0, FlatList.compareWith compare list1 list2) + Assert.IsTrue (FlatList.compareWith compare list1 list3 < 0) + Assert.IsTrue (FlatList.compareWith compare list3 list1 > 0) + Assert.IsTrue (FlatList.compareWith compare list4 list1 < 0) + Assert.IsTrue (FlatList.compareWith compare list1 list4 > 0) + + [] + member _.``transpose transposes matrix of lists`` () = + let matrix = + FlatList.ofArray [| FlatList.ofArray [| 1; 2; 3 |]; FlatList.ofArray [| 4; 5; 6 |]; FlatList.ofArray [| 7; 8; 9 |] |] + + let result = FlatList.transpose matrix + + Assert.AreEqual (3, result.Length) + + let col1 = result.[0] + Assert.AreEqual (3, col1.Length) + Assert.AreEqual (1, col1.[0]) + Assert.AreEqual (4, col1.[1]) + Assert.AreEqual (7, col1.[2]) + + let col2 = result.[1] + Assert.AreEqual (3, col2.Length) + Assert.AreEqual (2, col2.[0]) + Assert.AreEqual (5, col2.[1]) + Assert.AreEqual (8, col2.[2]) + + let col3 = result.[2] + Assert.AreEqual (3, col3.Length) + Assert.AreEqual (3, col3.[0]) + Assert.AreEqual (6, col3.[1]) + Assert.AreEqual (9, col3.[2]) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/MapFoldTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/MapFoldTests.fs new file mode 100644 index 0000000..8e345c4 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/MapFoldTests.fs @@ -0,0 +1,83 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable + +[] +type MapFoldTests () = + + [] + member _.``mapFold transforms elements and accumulates state`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + let mapped, total = FlatList.mapFold (fun state x -> (x * 2, state + x)) 0 flatList + + // Mapped elements are doubled: [2; 4; 6; 8] + // State accumulates the sum of original elements: 0 + 1 + 2 + 3 + 4 = 10 + Assert.AreEqual (4, mapped.Length) + Assert.AreEqual (2, mapped.[0]) + Assert.AreEqual (4, mapped.[1]) + Assert.AreEqual (6, mapped.[2]) + Assert.AreEqual (8, mapped.[3]) + Assert.AreEqual (10, total) + + [] + member _.``mapFold works with empty list`` () = + let emptyList = FlatList.empty + let mapped, state = FlatList.mapFold (fun state x -> (x * 2, state + x)) 42 emptyList + + // Should return an empty list and the initial state unchanged + Assert.IsTrue (mapped.IsEmpty) + Assert.AreEqual (42, state) + + [] + member _.``mapFold with string concatenation`` () = + let flatList = FlatList.ofArray [| "a"; "b"; "c" |] + let mapped, concatenated = + FlatList.mapFold (fun state x -> (x.ToUpper (), state + x)) "" flatList + + // Mapped elements are uppercase: ["A"; "B"; "C"] + // State concatenates the original strings: "abc" + Assert.AreEqual (3, mapped.Length) + Assert.AreEqual ("A", mapped.[0]) + Assert.AreEqual ("B", mapped.[1]) + Assert.AreEqual ("C", mapped.[2]) + Assert.AreEqual ("abc", concatenated) + + [] + member _.``mapFoldBack transforms elements and accumulates state in reverse`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + let mapped, total = FlatList.mapFoldBack (fun x state -> (x * 2, state + x)) flatList 0 + + // Mapped elements are doubled: [2; 4; 6; 8] + // State accumulates in reverse: 0 + 4 + 3 + 2 + 1 = 10 + Assert.AreEqual (4, mapped.Length) + Assert.AreEqual (2, mapped.[0]) + Assert.AreEqual (4, mapped.[1]) + Assert.AreEqual (6, mapped.[2]) + Assert.AreEqual (8, mapped.[3]) + Assert.AreEqual (10, total) + + [] + member _.``mapFoldBack works with empty list`` () = + let emptyList = FlatList.empty + let mapped, state = FlatList.mapFoldBack (fun x state -> (x * 2, state + x)) emptyList 42 + + // Should return an empty list and the initial state unchanged + Assert.IsTrue (mapped.IsEmpty) + Assert.AreEqual (42, state) + + [] + member _.``mapFoldBack creates indices in reversed order`` () = + let chars = FlatList.ofArray [| 'a'; 'b'; 'c' |] + let indices, count = FlatList.mapFoldBack (fun _ state -> (state, state + 1)) chars 0 + + // Creates indices in reversed order: [2; 1; 0] + // Final state is the count of elements: 3 + Assert.AreEqual (3, indices.Length) + Assert.AreEqual (2, indices.[0]) + Assert.AreEqual (1, indices.[1]) + Assert.AreEqual (0, indices.[2]) + Assert.AreEqual (3, count) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/PairOperationTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/PairOperationTests.fs new file mode 100644 index 0000000..25c32c7 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/PairOperationTests.fs @@ -0,0 +1,160 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable + +[] +type PairOperationTests () = + + [] + member _.``allPairs creates all combinations`` () = + let list1 = FlatList.ofArray [| 1; 2 |] + let list2 = FlatList.ofArray [| 'a'; 'b'; 'c' |] + let result = FlatList.allPairs list1 list2 + + Assert.AreEqual (6, result.Length) + Assert.AreEqual ((1, 'a'), result.[0]) + Assert.AreEqual ((1, 'b'), result.[1]) + Assert.AreEqual ((1, 'c'), result.[2]) + Assert.AreEqual ((2, 'a'), result.[3]) + Assert.AreEqual ((2, 'b'), result.[4]) + Assert.AreEqual ((2, 'c'), result.[5]) + + [] + member _.``permute reorders elements`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + let result = FlatList.permute (fun i -> (i + 2) % 3) flatList + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (30, result.[0]) + Assert.AreEqual (10, result.[1]) + Assert.AreEqual (20, result.[2]) + + [] + [)>] + member _.``permute throws for invalid permutation function`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + FlatList.permute (fun _ -> 10) flatList |> ignore + + [] + member _.``zip combines two lists into pairs`` () = + let list1 = FlatList.ofArray [| 1; 2; 3 |] + let list2 = FlatList.ofArray [| "a"; "b"; "c" |] + + let result = FlatList.zip list1 list2 + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (struct (1, "a"), result.[0]) + Assert.AreEqual (struct (2, "b"), result.[1]) + Assert.AreEqual (struct (3, "c"), result.[2]) + + [] + [)>] + member _.``zip throws when lists have different lengths`` () = + let list1 = FlatList.ofArray [| 1; 2 |] + let list2 = FlatList.ofArray [| "a"; "b"; "c" |] + + FlatList.zip list1 list2 |> ignore + + [] + member _.``unzip splits pairs into two lists`` () = + let flatList = FlatList.ofArray [| struct (1, "a"); (2, "b"); (3, "c") |] + + let struct (list1, list2) = FlatList.unzip flatList + + Assert.AreEqual (3, list1.Length) + Assert.AreEqual (3, list2.Length) + + Assert.AreEqual (1, list1.[0]) + Assert.AreEqual (2, list1.[1]) + Assert.AreEqual (3, list1.[2]) + + Assert.AreEqual ("a", list2.[0]) + Assert.AreEqual ("b", list2.[1]) + Assert.AreEqual ("c", list2.[2]) + + [] + member _.``zip3 combines three lists into triples`` () = + let list1 = FlatList.ofArray [| 1; 2; 3 |] + let list2 = FlatList.ofArray [| "a"; "b"; "c" |] + let list3 = FlatList.ofArray [| true; false; true |] + + let result = FlatList.zip3 list1 list2 list3 + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (struct (1, "a", true), result.[0]) + Assert.AreEqual (struct (2, "b", false), result.[1]) + Assert.AreEqual (struct (3, "c", true), result.[2]) + + [] + [)>] + member _.``zip3 throws when lists have different lengths (first and second)`` () = + let list1 = FlatList.ofArray [| 1; 2 |] + let list2 = FlatList.ofArray [| "a"; "b"; "c" |] + let list3 = FlatList.ofArray [| true; false; true |] + + FlatList.zip3 list1 list2 list3 |> ignore + + [] + [)>] + member _.``zip3 throws when lists have different lengths (first and third)`` () = + let list1 = FlatList.ofArray [| 1; 2 |] + let list2 = FlatList.ofArray [| "a"; "b" |] + let list3 = FlatList.ofArray [| true; false; true |] + + FlatList.zip3 list1 list2 list3 |> ignore + + [] + member _.``unzip3 splits triples into three lists`` () = + let flatList = FlatList.ofArray [| struct (1, "a", true); (2, "b", false); (3, "c", true) |] + + let struct (list1, list2, list3) = FlatList.unzip3 flatList + + Assert.AreEqual (3, list1.Length) + Assert.AreEqual (3, list2.Length) + Assert.AreEqual (3, list3.Length) + + Assert.AreEqual (1, list1.[0]) + Assert.AreEqual (2, list1.[1]) + Assert.AreEqual (3, list1.[2]) + + Assert.AreEqual ("a", list2.[0]) + Assert.AreEqual ("b", list2.[1]) + Assert.AreEqual ("c", list2.[2]) + + Assert.AreEqual (true, list3.[0]) + Assert.AreEqual (false, list3.[1]) + Assert.AreEqual (true, list3.[2]) + + [] + member _.``transpose reorients list of lists`` () = + let matrix = FlatList.ofArray [| FlatList.ofArray [| 1; 2; 3 |]; FlatList.ofArray [| 4; 5; 6 |] |] + + let result = FlatList.transpose matrix + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (2, result.[0].Length) + Assert.AreEqual (2, result.[1].Length) + Assert.AreEqual (2, result.[2].Length) + + // First column + Assert.AreEqual (1, result.[0].[0]) + Assert.AreEqual (4, result.[0].[1]) + + // Second column + Assert.AreEqual (2, result.[1].[0]) + Assert.AreEqual (5, result.[1].[1]) + + // Third column + Assert.AreEqual (3, result.[2].[0]) + Assert.AreEqual (6, result.[2].[1]) + + [] + [)>] + member _.``transpose throws when inner arrays have different lengths`` () = + let matrix = FlatList.ofArray [| FlatList.ofArray [| 1; 2; 3 |]; FlatList.ofArray [| 4; 5 |] |] + + FlatList.transpose matrix |> ignore diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/SearchTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/SearchTests.fs new file mode 100644 index 0000000..04677ac --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/SearchTests.fs @@ -0,0 +1,199 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable + +[] +type SearchTests () = + + [] + member _.``find returns first element matching predicate`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + Assert.AreEqual (2, FlatList.find (fun x -> x % 2 = 0) flatList) + + [] + [)>] + member _.``find throws when no element satisfies predicate`` () = + let flatList = FlatList.ofArray [| 1; 3; 5 |] + FlatList.find (fun x -> x % 2 = 0) flatList |> ignore + + [] + member _.``tryFind returns element or ValueNone`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + + Assert.AreEqual (ValueSome 2, FlatList.tryFind (fun x -> x % 2 = 0) flatList) + Assert.AreEqual (ValueNone, FlatList.tryFind (fun x -> x > 10) flatList) + + [] + member _.``findBack returns last element matching predicate`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 2 |] + Assert.AreEqual (2, FlatList.findBack (fun x -> x % 2 = 0) flatList) + + [] + [)>] + member _.``findBack throws when no element satisfies predicate`` () = + let flatList = FlatList.ofArray [| 1; 3; 5 |] + FlatList.findBack (fun x -> x % 2 = 0) flatList |> ignore + + [] + member _.``tryFindBack returns element or ValueNone`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 2 |] + + Assert.AreEqual (ValueSome 2, FlatList.tryFindBack (fun x -> x % 2 = 0) flatList) + Assert.AreEqual (ValueNone, FlatList.tryFindBack (fun x -> x > 10) flatList) + + [] + member _.``findIndex returns index of first element matching predicate`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + Assert.AreEqual (1, FlatList.findIndex (fun x -> x % 2 = 0) flatList) + + [] + [)>] + member _.``findIndex throws when no element satisfies predicate`` () = + let flatList = FlatList.ofArray [| 1; 3; 5 |] + FlatList.findIndex (fun x -> x % 2 = 0) flatList |> ignore + + [] + member _.``tryFindIndex returns index or ValueNone`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + + Assert.AreEqual (ValueSome 1, FlatList.tryFindIndex (fun x -> x % 2 = 0) flatList) + Assert.AreEqual (ValueNone, FlatList.tryFindIndex (fun x -> x > 10) flatList) + + [] + member _.``findIndexBack returns index of last element matching predicate`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 2 |] + Assert.AreEqual (4, FlatList.findIndexBack (fun x -> x % 2 = 0) flatList) + + [] + [)>] + member _.``findIndexBack throws when no element satisfies predicate`` () = + let flatList = FlatList.ofArray [| 1; 3; 5 |] + FlatList.findIndexBack (fun x -> x % 2 = 0) flatList + |> ignore + + [] + member _.``tryFindIndexBack returns index or ValueNone`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 2 |] + + Assert.AreEqual (ValueSome 4, FlatList.tryFindIndexBack (fun x -> x % 2 = 0) flatList) + Assert.AreEqual (ValueNone, FlatList.tryFindIndexBack (fun x -> x > 10) flatList) + + [] + member _.``pick returns first value from chooser that returns ValueSome`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + let result = FlatList.pick (fun x -> if x > 2 then ValueSome (x * 10) else ValueNone) flatList + + Assert.AreEqual (30, result) + + [] + [)>] + member _.``pick throws when chooser returns ValueNone for all elements`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.pick (fun x -> if x > 10 then ValueSome x else ValueNone) flatList + |> ignore + + [] + member _.``tryPick returns first value from chooser that returns ValueSome, or ValueNone`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + + let result1 = FlatList.tryPick (fun x -> if x > 2 then ValueSome (x * 10) else ValueNone) flatList + Assert.AreEqual (ValueSome 30, result1) + + let result2 = FlatList.tryPick (fun x -> if x > 10 then ValueSome x else ValueNone) flatList + Assert.AreEqual (ValueNone, result2) + + [] + member _.``index returns position of first occurrence of item`` () = + let flatList = FlatList.ofArray [| 10; 20; 30; 20; 40 |] + + Assert.AreEqual (1, FlatList.index 20 flatList) + + [] + [)>] + member _.``index throws when item not found`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + FlatList.index 50 flatList |> ignore + + [] + member _.``lastIndex returns position of last occurrence of item`` () = + let flatList = FlatList.ofArray [| 10; 20; 30; 20; 40 |] + + Assert.AreEqual (3, FlatList.lastIndex 20 flatList) + + [] + [)>] + member _.``lastIndex throws when item not found`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + FlatList.lastIndex 50 flatList |> ignore + + [] + member _.``tryFindBack returns last element matching predicate`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 2 |] + + Assert.AreEqual (ValueSome 2, FlatList.tryFindBack (fun x -> x % 2 = 0) flatList) + Assert.AreEqual (ValueNone, FlatList.tryFindBack (fun x -> x > 10) flatList) + + [] + member _.``findLast returns last element matching predicate`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 2 |] + Assert.AreEqual (2, FlatList.findLast (fun x -> x % 2 = 0) flatList) + + [] + [)>] + member _.``findLast throws when no element satisfies predicate`` () = + let flatList = FlatList.ofArray [| 1; 3; 5 |] + FlatList.findLast (fun x -> x % 2 = 0) flatList |> ignore + + [] + member _.``tryFindLast returns last element matching predicate or ValueNone`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 2 |] + + Assert.AreEqual (ValueSome 2, FlatList.tryFindLast (fun x -> x % 2 = 0) flatList) + Assert.AreEqual (ValueNone, FlatList.tryFindLast (fun x -> x > 10) flatList) + + [] + member _.``findLastIndex returns index of last element matching predicate`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 2 |] + Assert.AreEqual (4, FlatList.findLastIndex (fun x -> x % 2 = 0) flatList) + + [] + [)>] + member _.``findLastIndex throws when no element satisfies predicate`` () = + let flatList = FlatList.ofArray [| 1; 3; 5 |] + FlatList.findLastIndex (fun x -> x % 2 = 0) flatList + |> ignore + + [] + member _.``tryFindLastIndex returns index of last element matching predicate or ValueNone`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 2 |] + + Assert.AreEqual (ValueSome 4, FlatList.tryFindLastIndex (fun x -> x % 2 = 0) flatList) + Assert.AreEqual (ValueNone, FlatList.tryFindLastIndex (fun x -> x > 10) flatList) + + [] + member _.``pickBack returns last value from chooser that returns ValueSome`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 3 |] + let result = FlatList.pickBack (fun x -> if x = 3 then ValueSome (x * 10) else ValueNone) flatList + + Assert.AreEqual (30, result) + + [] + [)>] + member _.``pickBack throws when chooser returns ValueNone for all elements`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.pickBack (fun x -> if x > 10 then ValueSome x else ValueNone) flatList + |> ignore + + [] + member _.``tryPickBack returns last value from chooser that returns ValueSome, or ValueNone`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 3 |] + + let result1 = FlatList.tryPickBack (fun x -> if x = 3 then ValueSome (x * 10) else ValueNone) flatList + Assert.AreEqual (ValueSome 30, result1) + + let result2 = FlatList.tryPickBack (fun x -> if x > 10 then ValueSome x else ValueNone) flatList + Assert.AreEqual (ValueNone, result2) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/SortTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/SortTests.fs new file mode 100644 index 0000000..d843649 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/SortTests.fs @@ -0,0 +1,126 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable + +[] +type SortTests () = + + [] + member _.``sort orders elements using default comparer`` () = + let flatList = FlatList.ofArray [| 3; 1; 4; 2; 5 |] + let result = FlatList.sort flatList + + CollectionAssert.AreEqual ([| 1; 2; 3; 4; 5 |], FlatList.toArray result) + + [] + member _.``sortDescending orders elements in reverse`` () = + let flatList = FlatList.ofArray [| 3; 1; 4; 2; 5 |] + let result = FlatList.sortDescending flatList + + CollectionAssert.AreEqual ([| 5; 4; 3; 2; 1 |], FlatList.toArray result) + + [] + member _.``sortBy orders using key selector`` () = + let flatList = FlatList.ofArray [| "apple"; "banana"; "cherry"; "date"; "fig" |] + let result = FlatList.sortBy String.length flatList + + // Should be sorted by length: "fig", "date", "apple", "banana", "cherry" + CollectionAssert.AreEqual ([| "fig"; "date"; "apple"; "banana"; "cherry" |], FlatList.toArray result) + + [] + member _.``sortByDescending orders using key selector in reverse`` () = + let flatList = FlatList.ofArray [| "apple"; "banana"; "cherry"; "date"; "fig" |] + let result = FlatList.sortByDescending String.length flatList + + // Should be sorted by length descending: "banana", "cherry", "apple", "date", "fig" + CollectionAssert.AreEqual ([| "banana"; "cherry"; "apple"; "date"; "fig" |], FlatList.toArray result) + + [] + member _.``sortWith uses custom comparison function`` () = + let flatList = FlatList.ofArray [| 3; 1; 4; 2; 5 |] + + // Sort using a custom comparison that compares the remainder when divided by 3 + let result = FlatList.sortWith (fun x y -> compare (x % 3) (y % 3)) flatList + + // Should be: 3, 6, 9 (rem 0), then 1, 4, 7 (rem 1), then 2, 5, 8 (rem 2) + // From our input: 3 (rem 0), then 1, 4 (rem 1), then 2, 5 (rem 2) + CollectionAssert.AreEqual ([| 3; 1; 4; 2; 5 |], FlatList.toArray result) + + [] + member _.``sortWithComparer uses IComparer`` () = + let flatList = FlatList.ofArray [| 3; 1; 4; 2; 5 |] + + let reverseComparer = + { new IComparer with + member _.Compare (x, y) = compare y x + } + + let result = FlatList.sortWithComparer reverseComparer flatList + + // Should sort in reverse + CollectionAssert.AreEqual ([| 5; 4; 3; 2; 1 |], FlatList.toArray result) + + [] + member _.``sortRange sorts portion of list`` () = + let flatList = FlatList.ofArray [| 3; 1; 4; 2; 5 |] + + // Sort only elements 1, 2, 3 (indices 1, 2, 3) + let result = FlatList.sortRange 1 3 flatList + + // Should be: 3, 1, 2, 4, 5 + CollectionAssert.AreEqual ([| 3; 1; 2; 4; 5 |], FlatList.toArray result) + + [] + member _.``sortRangeWith sorts portion with custom comparison`` () = + let flatList = FlatList.ofArray [| 3; 1; 4; 2; 5 |] + + // Sort only elements 1, 2, 3 (indices 1, 2, 3) in reverse + let result = FlatList.sortRangeWith (fun x y -> compare y x) 1 3 flatList + + // Should be: 3, 4, 2, 1, 5 + CollectionAssert.AreEqual ([| 3; 4; 2; 1; 5 |], FlatList.toArray result) + + [] + member _.``sortRangeWithComparer uses IComparer for portion`` () = + let flatList = FlatList.ofArray [| 3; 1; 4; 2; 5 |] + + let reverseComparer = + { new IComparer with + member _.Compare (x, y) = compare y x + } + + // Sort only elements 1, 2, 3 (indices 1, 2, 3) using reverse comparer + let result = FlatList.sortRangeWithComparer reverseComparer 1 3 flatList + + // Should be: 3, 4, 2, 1, 5 + CollectionAssert.AreEqual ([| 3; 4; 2; 1; 5 |], FlatList.toArray result) + + [] + member _.``compareWith compares elements`` () = + let list1 = FlatList.ofArray [| 1; 2; 3 |] + let list2 = FlatList.ofArray [| 1; 2; 4 |] + let list3 = FlatList.ofArray [| 1; 2; 3; 4 |] + + // Custom comparer that considers elements equal if both odd or both even + let comparer x y = + if x % 2 = y % 2 then 0 + elif x % 2 < y % 2 then -1 + else 1 + + // list1 and list2 differ at position 2, where 3 and 4 have same parity + Assert.AreEqual (1, FlatList.compareWith comparer list1 list2) + + // list1 is shorter than list3 (where all elements match) + Assert.AreEqual (-1, FlatList.compareWith comparer list1 list3) + + // list3 is longer than list2 (where all elements match) + Assert.AreEqual (1, FlatList.compareWith comparer list3 list2) + + // Standard comparison should still work normally + Assert.AreEqual (-1, FlatList.compareWith compare list1 list2) + Assert.AreEqual (-1, FlatList.compareWith compare list1 list3) + Assert.AreEqual (1, FlatList.compareWith compare list2 list1) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/UtilityTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/UtilityTests.fs new file mode 100644 index 0000000..5023b21 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/UtilityTests.fs @@ -0,0 +1,246 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable + +[] +type UtilityTests () = + + [] + member _.``blit copies range of elements to array`` () = + let source = FlatList.ofArray [| 10; 20; 30; 40; 50 |] + let destination = Array.zeroCreate 5 + + FlatList.blit source 1 destination 2 3 + + Assert.AreEqual (0, destination.[0]) + Assert.AreEqual (0, destination.[1]) + Assert.AreEqual (20, destination.[2]) + Assert.AreEqual (30, destination.[3]) + Assert.AreEqual (40, destination.[4]) + + [] + member _.``unfold builds list from generator function`` () = + // Create a list of powers of 2 up to 2^5 + let result = + FlatList.unfold + (fun state -> + if state <= 32 then + ValueSome (state, state * 2) + else + ValueNone + ) + 1 + + CollectionAssert.AreEqual ([| 1; 2; 4; 8; 16; 32 |], FlatList.toArray result) + + [] + member _.``build creates list with builder function`` () = + let result = + FlatList.build (fun builder -> + builder.Add (1) + builder.Add (2) + builder.Add (3) + ) + + CollectionAssert.AreEqual ([| 1; 2; 3 |], FlatList.toArray result) + + [] + member _.``exactlyOne returns the only element`` () = + let flatList = FlatList.singleton 42 + let result = FlatList.exactlyOne flatList + + Assert.AreEqual (42, result) + + [] + [)>] + member _.``exactlyOne throws for empty list`` () = FlatList.exactlyOne FlatList.empty |> ignore + + [] + [)>] + member _.``exactlyOne throws for list with multiple elements`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.exactlyOne flatList |> ignore + + [] + member _.``tryExactlyOne returns element for single-element list`` () = + let flatList = FlatList.singleton 42 + let result = FlatList.tryExactlyOne flatList + + Assert.AreEqual (ValueSome 42, result) + + [] + member _.``tryExactlyOne returns ValueNone for empty list`` () = + let result = FlatList.tryExactlyOne FlatList.empty + + Assert.AreEqual (ValueNone, result) + + [] + member _.``tryExactlyOne returns ValueNone for multi-element list`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + let result = FlatList.tryExactlyOne flatList + + Assert.AreEqual (ValueNone, result) + + [] + member _.``transpose transforms rows into columns`` () = + let rows = FlatList.ofArray [| FlatList.ofArray [| 1; 2; 3 |]; FlatList.ofArray [| 4; 5; 6 |] |] + + let result = FlatList.transpose rows + + Assert.AreEqual (3, result.Length) // Result has 3 columns + + CollectionAssert.AreEqual ([| 1; 4 |], FlatList.toArray result.[0]) + CollectionAssert.AreEqual ([| 2; 5 |], FlatList.toArray result.[1]) + CollectionAssert.AreEqual ([| 3; 6 |], FlatList.toArray result.[2]) + + [] + [)>] + member _.``transpose throws when inner arrays have different lengths`` () = + let rows = FlatList.ofArray [| FlatList.ofArray [| 1; 2; 3 |]; FlatList.ofArray [| 4; 5 |] |] + + FlatList.transpose rows |> ignore + + [] + member _.``except removes elements from another sequence`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5 |] + let itemsToExclude = [| 2; 4; 6 |] // Note: 6 is not in the original list + + let result = FlatList.except itemsToExclude flatList + + CollectionAssert.AreEqual ([| 1; 3; 5 |], FlatList.toArray result) + + [] + member _.``ofList converts F# list to FlatList`` () = + let list = [ 1; 2; 3 ] + let result = FlatList.ofList list + + CollectionAssert.AreEqual ([| 1; 2; 3 |], FlatList.toArray result) + + [] + member _.``toList converts FlatList to F# list`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + let result = FlatList.toList flatList + + Assert.AreEqual ([ 1; 2; 3 ], result) + + [] + member _.``splitInto divides list into specified number of chunks`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5; 6; 7; 8 |] + let result = FlatList.splitInto 3 flatList + + Assert.AreEqual (3, result.Length) + CollectionAssert.AreEqual ([| 1; 2; 3 |], FlatList.toArray result.[0]) + CollectionAssert.AreEqual ([| 4; 5; 6 |], FlatList.toArray result.[1]) + CollectionAssert.AreEqual ([| 7; 8 |], FlatList.toArray result.[2]) + + [] + [)>] + member _.``splitInto throws for non-positive count`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.splitInto 0 flatList |> ignore + + [] + member _.``updateAt changes element at index`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5 |] + let result = FlatList.updateAt 2 42 flatList + + CollectionAssert.AreEqual ([| 1; 2; 42; 4; 5 |], FlatList.toArray result) + + [] + [)>] + member _.``updateAt throws for negative index`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.updateAt -1 42 flatList |> ignore + + [] + [)>] + member _.``updateAt throws for index beyond end`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.updateAt 3 42 flatList |> ignore + + [] + member _.``removeAt removes element at index`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5 |] + let result = FlatList.removeAt 2 flatList + + CollectionAssert.AreEqual ([| 1; 2; 4; 5 |], FlatList.toArray result) + + [] + [)>] + member _.``removeAt throws for negative index`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.removeAt -1 flatList |> ignore + + [] + [)>] + member _.``removeAt throws for index beyond end`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.removeAt 3 flatList |> ignore + + [] + member _.``insertAt adds element at index`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 5 |] + let result = FlatList.insertAt 3 4 flatList + + CollectionAssert.AreEqual ([| 1; 2; 3; 4; 5 |], FlatList.toArray result) + + [] + member _.``insertAt can add at beginning`` () = + let flatList = FlatList.ofArray [| 2; 3; 4 |] + let result = FlatList.insertAt 0 1 flatList + + CollectionAssert.AreEqual ([| 1; 2; 3; 4 |], FlatList.toArray result) + + [] + member _.``insertAt can add at end`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + let result = FlatList.insertAt 3 4 flatList + + CollectionAssert.AreEqual ([| 1; 2; 3; 4 |], FlatList.toArray result) + + [] + [)>] + member _.``insertAt throws for negative index`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.insertAt -1 0 flatList |> ignore + + [] + [)>] + member _.``insertAt throws for index too high`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.insertAt 4 0 flatList |> ignore + + [] + member _.``insertManyAt adds multiple elements at index`` () = + let flatList = FlatList.ofArray [| 1; 5 |] + let result = FlatList.insertManyAt 1 [| 2; 3; 4 |] flatList + + CollectionAssert.AreEqual ([| 1; 2; 3; 4; 5 |], FlatList.toArray result) + + [] + [)>] + member _.``insertManyAt throws for negative index`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.insertManyAt -1 [| 4; 5 |] flatList |> ignore + + [] + [)>] + member _.``insertManyAt throws for index too high`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.insertManyAt 4 [| 4; 5 |] flatList |> ignore + + [] + member _.``copy creates a new identical list`` () = + let original = FlatList.ofArray [| 1; 2; 3 |] + let copied = FlatList.copy original + + // Should be equal but not the same reference + CollectionAssert.AreEqual (FlatList.toArray original, FlatList.toArray copied) + + // Since ImmutableArray is a value type with structural equality, + // these should actually be equal (not reference equality) + Assert.AreEqual> (original, copied) diff --git a/tests/FSharp.Collections.Immutable.Tests/Helpers.fs b/tests/FSharp.Collections.Immutable.Tests/Helpers.fs new file mode 100644 index 0000000..1cf318f --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Helpers.fs @@ -0,0 +1,5 @@ +[] +module FSharp.Collections.Immutable.Tests.Helpers + +let fstv tuple = let struct (a, _) = tuple in a +let sndv tuple = let struct (_, b) = tuple in b