From 1f1532e81fec6a415491f1667e0fe248462e6620 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 12:14:24 +0200 Subject: [PATCH 01/27] feat: `FlatList` module with the vocabulary of the FSharp.Core `Array` module Rewrites `FlatList` after ADR 0001 in `FlatList/FlatList.fsi` and `FlatList/FlatList.fs`: 144 functions in the canonical category order, each documented in the signature file. - Most functions unwrap the underlying array without a copy, call the `Array` function and wrap its fresh result, so that they keep the semantics and the exceptions of `Array`. The range functions repeat the checks of `Array.take`, `skip` and `splitAt` and slice, which copies nothing for an empty or a whole range. - `try*` functions return `voption`, tuples are struct tuples, and the `Option` and `Tuple` namespaces repeat those functions with options and reference tuples; per-element tuple producers share one algorithm that takes the tuple constructor. - `FlatListBuilder` works on the builder, and `flatList { }` collects its elements in a builder. - `Helper.fs` gets the FSharp.Core exception messages and helpers that the modules mirroring `List` will reuse. - The legacy `FlatList.fs` and the `Seq` module are removed. Co-Authored-By: Claude Opus 5.5 --- .../ComputationExpressions.fs | 19 + .../FSharp.Collections.Immutable.fsproj | 11 +- src/FSharp.Collections.Immutable/FlatList.fs | 593 -------- .../FlatList/FlatList.fs | 888 +++++++++++ .../FlatList/FlatList.fsi | 1313 +++++++++++++++++ .../FlatList/FlatListBuilder.fs | 195 +++ .../FlatList/FlatListCE.fs | 86 ++ .../FlatList/FlatListOption.fs | 60 + .../FlatList/FlatListOption.fsi | 126 ++ .../FlatList/FlatListTuple.fs | 114 ++ .../FlatList/FlatListTuple.fsi | 151 ++ src/FSharp.Collections.Immutable/Helper.fs | 298 +++- src/FSharp.Collections.Immutable/Seq.fs | 32 - 13 files changed, 3258 insertions(+), 628 deletions(-) create mode 100644 src/FSharp.Collections.Immutable/ComputationExpressions.fs delete mode 100644 src/FSharp.Collections.Immutable/FlatList.fs create mode 100644 src/FSharp.Collections.Immutable/FlatList/FlatList.fs create mode 100644 src/FSharp.Collections.Immutable/FlatList/FlatList.fsi create mode 100644 src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs create mode 100644 src/FSharp.Collections.Immutable/FlatList/FlatListCE.fs create mode 100644 src/FSharp.Collections.Immutable/FlatList/FlatListOption.fs create mode 100644 src/FSharp.Collections.Immutable/FlatList/FlatListOption.fsi create mode 100644 src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fs create mode 100644 src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fsi delete mode 100644 src/FSharp.Collections.Immutable/Seq.fs diff --git a/src/FSharp.Collections.Immutable/ComputationExpressions.fs b/src/FSharp.Collections.Immutable/ComputationExpressions.fs new file mode 100644 index 00000000..021c3d6c --- /dev/null +++ b/src/FSharp.Collections.Immutable/ComputationExpressions.fs @@ -0,0 +1,19 @@ +namespace FSharp.Collections.Immutable + +/// The computation expressions of the collections (ADR 0001, D7). +[] +module ComputationExpressions = + + /// Builds a flat list. + /// + /// + /// let squares = flatList { + /// yield 0 + /// for i in 1..3 do + /// yield i * i + /// yield! [ 100; 200 ] + /// } + /// + /// Evaluates to a flat list of 0, 1, 4, 9, 100 and 200. + /// + let flatList = FlatListCE () diff --git a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj index f460764a..645623a0 100644 --- a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj +++ b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj @@ -21,14 +21,21 @@ - + + + + + + + + - + diff --git a/src/FSharp.Collections.Immutable/FlatList.fs b/src/FSharp.Collections.Immutable/FlatList.fs deleted file mode 100644 index b6153265..00000000 --- a/src/FSharp.Collections.Immutable/FlatList.fs +++ /dev/null @@ -1,593 +0,0 @@ -#if INTERACTIVE -namespace global -#else -namespace FSharp.Collections.Immutable -#endif - -// 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 = - - type internal FlatListFactory = System.Collections.Immutable.ImmutableArray - - let inline internal checkNotDefault argName (list : FlatList<'T>) = - if list.IsDefault then - invalidArg argName "Uninstantiated ImmutableArray/FlatList" - - let inline internal check (list : FlatList<'T>) = checkNotDefault (nameof list) list - - ////////// Creating ////////// - - let inline empty<'T> : FlatList<_> = FlatListFactory.Create<'T>() - let inline singleton<'T> (item : 'T) : FlatList<'T> = FlatListFactory.Create<'T>(item) - - let inline ofSeq source = FlatListFactory.CreateRange source - let inline ofArray (source : _ array) = FlatListFactory.CreateRange source - - let inline toSeq (flatList : FlatList<_>) = flatList :> seq<_> - - let inline toArray (list : FlatList<_>) = - check list - Seq.toArray list - - ////////// Building ////////// - - let moveFromBuilder (builder : FlatList<_>.Builder) : FlatList<_> = - checkNotNull (nameof builder) builder - builder.MoveToImmutable () - - let ofBuilder (builder : FlatList<_>.Builder) : FlatList<_> = - checkNotNull (nameof builder) 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 () - - module Builder = - let inline private check (builder : FlatList<'T>.Builder) = checkNotNull (nameof builder) builder - - let add item builder = - check builder - builder.Add (item) - - let inline internal indexNotFound () = raise <| System.Collections.Generic.KeyNotFoundException () - - 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 item index list = - check list - list.[index] - - let append list1 list2 : FlatList<'T> = - checkNotDefault (nameof list1) list1 - checkNotDefault (nameof list2) list2 - list1.AddRange (list2 : FlatList<_>) - - /// 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 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 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 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) - - /// Removes the specified objects from the list. - let removeAll items list = removeAllWith HashIdentity.Structural items list - - /// 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 - - /// Removes all the elements that do not match the conditions defined by the specified predicate. - let where predicate list = filter predicate list - - /// Removes a range of elements from the list. - let removeRange index (count : int) list : FlatList<_> = - check list - list.RemoveRange (index, count) - - let blit source sourceIndex (destination : 'T[]) destinationIndex count = - checkNotDefault (nameof source) source - - try - source.CopyTo (sourceIndex, destination, destinationIndex, count) - with exn -> - raise exn // throw same exception with the correct stack trace. Update exception code - - let sortRangeWithComparer comparer index count list = - check list - 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 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 builder = builderWith count - - for i = 0 to count - 1 do - builder.Add <| initializer i - - moveFromBuilder builder - - let rec private concatAddLengths (arrs : FlatList>) i acc = - if i >= length arrs then - acc - else - concatAddLengths arrs (i + 1) (acc + arrs.[i].Length) - - let concat (arrs : FlatList>) = // consider generalizing - let result : FlatList<'T>.Builder = builderWith <| concatAddLengths arrs 0 0 - - for i = 0 to length arrs - 1 do - result.AddRange (arrs.[i] : FlatList<'T>) - - moveFromBuilder result - - let inline map mapping list = - check list - let builder = builderWithLengthOf list - - for i = 0 to length list - 1 do - builder.Add (mapping list.[i]) - - moveFromBuilder builder - - let countBy projection list = - check list - // need struct box optimization - let dict = new System.Collections.Generic.Dictionary<'Key, int> (HashIdentity.Structural) - - // Build the groupings - for v in list do - let key = projection v - let mutable prev = Unchecked.defaultof<_> - - if dict.TryGetValue (key, &prev) then - dict.[key] <- prev + 1 - else - dict.[key] <- 1 - - let res = builderWith dict.Count - let mutable i = 0 - - for group in dict do - res.Add (group.Key, group.Value) - i <- i + 1 - - moveFromBuilder res - - let indexed list = - check list - let builder = builderWithLengthOf list - - for i = 0 to length list - 1 do - builder.Add (i, list.[i]) - - moveFromBuilder builder - - let inline iter action list = - check list - - for i = 0 to length list - 1 do - action list.[i] - - 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 - - if len <> length list2 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 - - let map2 mapping list1 list2 = - checkNotDefault (nameof list1) list1 - checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt(mapping) - 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 - 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]) - - moveFromBuilder res - - let mapi2 mapping list1 list2 = - checkNotDefault (nameof list1) list1 - checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt(mapping) - 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) - let len1 = list1.Length - - if len1 <> list2.Length then - invalidArg (nameof list2) 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]) - - moveFromBuilder res - - let exists predicate list = - check list - let len = list.Length - let rec loop i = i < len && (predicate list.[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 len1 = list1.Length - - if len1 <> list2.Length then - invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - - let rec loop i = - i < len1 - && (f.Invoke (list1.[i], list2.[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 forall2 predicate list1 list2 = - checkNotDefault (nameof list1) list1 - checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt(predicate) - let len1 = list1.Length - - if len1 <> list2.Length then - invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - - let rec loop i = - i >= len1 - || (f.Invoke (list1.[i], list2.[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) - - // Return the list-of-lists. - let result = builderWith dict.Count - let mutable i = 0 - - for group in dict do - result.Add (group.Key, ofSeq group.Value) - i <- i + 1 - - moveFromBuilder result - - let pick chooser list = - check list - - let rec loop i = - if i >= list.Length then - indexNotFound () - else - match chooser list.[i] with - | None -> loop (i + 1) - | Some res -> res - - loop 0 - - let tryPick chooser list = - check list - - let rec loop i = - if i >= list.Length then - None - else - match chooser list.[i] with - | None -> loop (i + 1) - | res -> res - - loop 0 - - let choose chooser list = - check list - let res = builderWith list.Length - - for i = 0 to list.Length - 1 do - match chooser list.[i] with - | None -> () - | Some b -> res.Add (b) - - ofBuilder res - - let partition predicate list = - check list - let res1 = builderWith list.Length - let res2 = builderWith list.Length - - for i = 0 to list.Length - 1 do - let x = list.[i] - if predicate x then res1.Add (x) else res2.Add (x) - - ofBuilder res1, ofBuilder res2 - - let find predicate list = - check list - - let rec loop i = - if i >= list.Length then indexNotFound () - else if predicate list.[i] then list.[i] - else loop (i + 1) - - loop 0 - - let tryFind predicate list = - check list - - let rec loop i = - if i >= list.Length then None - else if predicate list.[i] then Some list.[i] - else loop (i + 1) - - loop 0 - - let findBack predicate list = - check list - - let rec loop i = - if i < 0 then indexNotFound () - else if predicate list.[i] then list.[i] - else loop (i - 1) - - loop <| length list - 1 - - let tryFindBack predicate list = - check list - - let rec loop i = - if i < 0 then None - else if predicate list.[i] then Some list.[i] - else loop (i + 1) - - loop <| length list - 1 - - let findIndexBack predicate list = - check list - - let rec loop i = - if i < 0 then indexNotFound () - else if predicate list.[i] then i - else loop (i - 1) - - loop <| length list - 1 - - let tryFindIndexBack predicate list = - check list - - let rec loop i = - if i < 0 then None - else if predicate list.[i] then Some i - else loop (i - 1) - - loop <| length list - 1 - // TODO: windowed - - ////////// Based on other operations ////////// - - let take count list = removeRange count (length list - count) list - - let inline private lengthWhile predicate list = - check list - let mutable count = 0 - - while count < list.Length && predicate list.[count] do - count <- count + 1 - - count - - let takeWhile predicate list = take (lengthWhile predicate list) list - - let skip index list = removeRange 0 index list - - let skipWhile predicate list = skip (lengthWhile predicate list) list - - let sub start stop list = skip start list |> take (stop - start - 1) - - let truncate count list = if count < length list then take count list else list - - let splitAt index list = take index list, skip index list - - let head list = item 0 list - - let tryItem index list = - if index >= length list || index < 0 then - None - else - Some (list.[index]) - - let tryHead list = tryItem 0 list - - let last (list : FlatList<_>) = list.[length list - 1] - - let tryLast list = tryItem (length list - 1) list - - let tail list = removeRange 1 (length list - 1) list - - let tryTail list = if isEmpty list then None else Some <| tail list - - let create count item = init count <| fun _ -> item // optimize - - let replicate count item = create item count - - let collect mapping list = concat <| map mapping list - - let inline build f = - let builder = builder () - f builder - moveFromBuilder builder - - let inline update f list = - let builder = toBuilder list - f builder - moveFromBuilder builder - -////////// - -module ImmutableArray = FlatList diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatList.fs b/src/FSharp.Collections.Immutable/FlatList/FlatList.fs new file mode 100644 index 00000000..27fd0081 --- /dev/null +++ b/src/FSharp.Collections.Immutable/FlatList/FlatList.fs @@ -0,0 +1,888 @@ +namespace FSharp.Collections.Immutable + +open System +open System.Collections.Generic +open System.Collections.Immutable +open System.Runtime.InteropServices + +type FlatList<'T> = ImmutableArray<'T> + +// Most functions unwrap the underlying array without a copy, call the FSharp.Core Array function, and wrap its fresh +// result without a copy (ADR 0001, D5), so that they have the semantics and the exceptions of the Array module (D6). +[] +[] +module FlatList = + + // ---- Creation ---- + + [] + let create count (value : 'T) = Array.create count value |> wrapArray + + [] + [] + let empty<'T> : FlatList<'T> = ImmutableArray<'T>.Empty + + [] + let init count (initializer : int -> 'T) = Array.init count initializer |> wrapArray + + [] + let ofArray (array : 'T array) = Array.copy array |> wrapArray + + [] + let ofList (list : 'T list) = Array.ofList list |> wrapArray + + [] + let ofReadOnlySpan (span : ReadOnlySpan<'T>) = ImmutableArray.Create<'T> span + + [] + let ofSeq (source : seq<'T>) = + match source with + // An immutable array needs no copy + | :? ImmutableArray<'T> as array when not array.IsDefault -> array + | _ -> Array.ofSeq source |> wrapArray + + [] + let ofSpan (span : Span<'T>) = ImmutableArray.Create<'T> span + + [] + let replicate count (initial : 'T) = Array.replicate count initial |> wrapArray + + [] + let singleton (value : 'T) = ImmutableArray.Create<'T> value + + [] + let unfold (generator : 'State -> struct ('T * 'State) voption) (state : 'State) = + let builder = ImmutableArray.CreateBuilder<'T>() + let mutable state = state + let mutable go = true + + while go do + match generator state with + | ValueSome (struct (item, next)) -> + builder.Add item + state <- next + | ValueNone -> go <- false + + builder.DrainToImmutable () + + // ---- Builder ---- + + [] + let build (action : FlatList<'T>.Builder -> unit) = + let builder = ImmutableArray.CreateBuilder<'T>() + action builder + builder.DrainToImmutable () + + [] + let builder () : FlatList<'T>.Builder = ImmutableArray.CreateBuilder<'T>() + + [] + let builderWith (capacity : int) : FlatList<'T>.Builder = ImmutableArray.CreateBuilder<'T> capacity + + [] + let moveFromBuilder (builder : FlatList<'T>.Builder) = + checkNotNull (nameof builder) builder + builder.MoveToImmutable () + + [] + let ofBuilder (builder : FlatList<'T>.Builder) = + checkNotNull (nameof builder) builder + builder.ToImmutable () + + [] + let toBuilder (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.ToBuilder () + + [] + let update (action : FlatList<'T>.Builder -> unit) (array : FlatList<'T>) = + checkNotDefault (nameof array) array + let builder = array.ToBuilder () + action builder + builder.DrainToImmutable () + + // ---- Properties ---- + + [] + let isEmpty (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.IsEmpty + + [] + let length (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.Length + + // ---- Access ---- + + [] + let exactlyOne (array : FlatList<'T>) = Array.exactlyOne (unwrapArray (nameof array) array) + + [] + let head (array : FlatList<'T>) = Array.head (unwrapArray (nameof array) array) + + [] + let item index (array : FlatList<'T>) = Array.item index (unwrapArray (nameof array) array) + + [] + let last (array : FlatList<'T>) = Array.last (unwrapArray (nameof array) array) + + [] + let tail (array : FlatList<'T>) = Array.tail (unwrapArray (nameof array) array) |> wrapArray + + [] + let tryExactlyOne (array : FlatList<'T>) = + checkNotDefault (nameof array) array + if array.Length = 1 then ValueSome array[0] else ValueNone + + [] + let tryHead (array : FlatList<'T>) = + checkNotDefault (nameof array) array + if array.IsEmpty then ValueNone else ValueSome array[0] + + [] + let tryItem index (array : FlatList<'T>) = + checkNotDefault (nameof array) array + + if index < 0 || index >= array.Length then + ValueNone + else + ValueSome array[index] + + [] + let tryLast (array : FlatList<'T>) = + checkNotDefault (nameof array) array + + if array.IsEmpty then + ValueNone + else + ValueSome array[array.Length - 1] + + [] + let tryTail (array : FlatList<'T>) = + let source = unwrapArray (nameof array) array + + if source.Length = 0 then + ValueNone + else + ValueSome (Array.tail source |> wrapArray) + + // ---- Search ---- + + [] + let binarySearch (value : 'T) (array : FlatList<'T>) = + checkNotDefault (nameof array) array + ImmutableArray.BinarySearch (array, value, LanguagePrimitives.FastGenericComparer<'T>) + + [] + let contains (value : 'T) (array : FlatList<'T>) = Array.contains value (unwrapArray (nameof array) array) + + [] + let exists predicate (array : FlatList<'T>) = Array.exists predicate (unwrapArray (nameof array) array) + + [] + let exists2 predicate (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = + Array.exists2 predicate (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) + + [] + let find predicate (array : FlatList<'T>) = Array.find predicate (unwrapArray (nameof array) array) + + [] + let findBack predicate (array : FlatList<'T>) = Array.findBack predicate (unwrapArray (nameof array) array) + + [] + let findIndex predicate (array : FlatList<'T>) = Array.findIndex predicate (unwrapArray (nameof array) array) + + [] + let findIndexBack predicate (array : FlatList<'T>) = Array.findIndexBack predicate (unwrapArray (nameof array) array) + + [] + let forall predicate (array : FlatList<'T>) = Array.forall predicate (unwrapArray (nameof array) array) + + [] + let forall2 predicate (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = + Array.forall2 predicate (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) + + [] + let indexOf (value : 'T) (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.IndexOf value + + [] + let indexOfWith (comparer : IEqualityComparer<'T>) (value : 'T) (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.IndexOf (value, 0, array.Length, comparer) + + [] + let lastIndexOf (value : 'T) (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.LastIndexOf value + + [] + let lastIndexOfWith (comparer : IEqualityComparer<'T>) (value : 'T) (array : FlatList<'T>) = + checkNotDefault (nameof array) array + + // The BCL rejects the start index -1 of an empty array, so an empty array answers without asking it + if array.IsEmpty then + -1 + else + array.LastIndexOf (value, array.Length - 1, array.Length, comparer) + + [] + let pick (chooser : 'T -> 'U voption) (array : FlatList<'T>) = + let source = unwrapArray (nameof array) array + let mutable result = ValueNone + let mutable index = 0 + + while result.IsNone && index < source.Length do + result <- chooser source[index] + index <- index + 1 + + match result with + | ValueSome value -> value + | ValueNone -> Raise.indexNotFound () + + [] + let tryFind predicate (array : FlatList<'T>) = + let source = unwrapArray (nameof array) array + let mutable result = ValueNone + let mutable index = 0 + + while result.IsNone && index < source.Length do + if predicate source[index] then + result <- ValueSome source[index] + + index <- index + 1 + + result + + [] + let tryFindBack predicate (array : FlatList<'T>) = + let source = unwrapArray (nameof array) array + let mutable result = ValueNone + let mutable index = source.Length - 1 + + while result.IsNone && index >= 0 do + if predicate source[index] then + result <- ValueSome source[index] + + index <- index - 1 + + result + + [] + let tryFindIndex predicate (array : FlatList<'T>) = + let source = unwrapArray (nameof array) array + let mutable result = ValueNone + let mutable index = 0 + + while result.IsNone && index < source.Length do + if predicate source[index] then + result <- ValueSome index + + index <- index + 1 + + result + + [] + let tryFindIndexBack predicate (array : FlatList<'T>) = + let source = unwrapArray (nameof array) array + let mutable result = ValueNone + let mutable index = source.Length - 1 + + while result.IsNone && index >= 0 do + if predicate source[index] then + result <- ValueSome index + + index <- index - 1 + + result + + [] + let tryPick (chooser : 'T -> 'U voption) (array : FlatList<'T>) = + let source = unwrapArray (nameof array) array + let mutable result = ValueNone + let mutable index = 0 + + while result.IsNone && index < source.Length do + result <- chooser source[index] + index <- index + 1 + + result + + // ---- Transformation ---- + + [] + let add (value : 'T) (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.Add value + + [] + let allPairs (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = + Arrays.allPairs structPair (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) + |> wrapArray + + [] + let append (array1 : FlatList<'T>) (array2 : FlatList<'T>) = + checkNotDefault (nameof array1) array1 + checkNotDefault (nameof array2) array2 + array1.AddRange array2 + + [] + let choose (chooser : 'T -> 'U voption) (array : FlatList<'T>) = + let source = unwrapArray (nameof array) array + let builder = ImmutableArray.CreateBuilder<'U>() + + for item in source do + match chooser item with + | ValueSome value -> builder.Add value + | ValueNone -> () + + builder.DrainToImmutable () + + [] + let chunkBySize chunkSize (array : FlatList<'T>) = + Array.chunkBySize chunkSize (unwrapArray (nameof array) array) + |> Array.map wrapArray + |> wrapArray + + [] + let collect (mapping : 'T -> FlatList<'U>) (array : FlatList<'T>) = + let source = unwrapArray (nameof array) array + let builder = ImmutableArray.CreateBuilder<'U>() + + for item in source do + builder.AddRange (mapping item) + + builder.DrainToImmutable () + + [] + let concat (arrays : seq>) = + checkNotNull (nameof arrays) arrays + + arrays + |> Seq.map (fun array -> unwrapArray (nameof arrays) array) + |> Array.concat + |> wrapArray + + [] + let filter predicate (array : FlatList<'T>) = + Array.filter predicate (unwrapArray (nameof array) array) + |> wrapArray + + [] + let indexed (array : FlatList<'T>) = + Arrays.indexed structPair (unwrapArray (nameof array) array) + |> wrapArray + + [] + let insertAt index (value : 'T) (source : FlatList<'T>) = + Array.insertAt index value (unwrapArray (nameof source) source) + |> wrapArray + + [] + let insertManyAt index (values : seq<'T>) (source : FlatList<'T>) = + Array.insertManyAt index values (unwrapArray (nameof source) source) + |> wrapArray + + [] + let map (mapping : 'T -> 'U) (array : FlatList<'T>) = + Array.map mapping (unwrapArray (nameof array) array) + |> wrapArray + + [] + let map2 (mapping : 'T1 -> 'T2 -> 'U) (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = + Array.map2 mapping (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) + |> wrapArray + + [] + let map3 (mapping : 'T1 -> 'T2 -> 'T3 -> 'U) (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) (array3 : FlatList<'T3>) = + Array.map3 + mapping + (unwrapArray (nameof array1) array1) + (unwrapArray (nameof array2) array2) + (unwrapArray (nameof array3) array3) + |> wrapArray + + [] + let mapi (mapping : int -> 'T -> 'U) (array : FlatList<'T>) = + Array.mapi mapping (unwrapArray (nameof array) array) + |> wrapArray + + [] + let mapi2 (mapping : int -> 'T1 -> 'T2 -> 'U) (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = + Array.mapi2 mapping (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) + |> wrapArray + + [] + let pairwise (array : FlatList<'T>) = + Arrays.pairwise structPair (unwrapArray (nameof array) array) + |> wrapArray + + [] + let permute indexMap (array : FlatList<'T>) = + Array.permute indexMap (unwrapArray (nameof array) array) + |> wrapArray + + [] + let removeAt index (source : FlatList<'T>) = + Array.removeAt index (unwrapArray (nameof source) source) + |> wrapArray + + [] + let removeManyAt index count (source : FlatList<'T>) = + Array.removeManyAt index count (unwrapArray (nameof source) source) + |> wrapArray + + [] + let rev (array : FlatList<'T>) = Array.rev (unwrapArray (nameof array) array) |> wrapArray + + [] + let transpose (arrays : seq>) = + checkNotNull (nameof arrays) arrays + + arrays + |> Seq.map (fun array -> unwrapArray (nameof arrays) array) + |> Array.transpose + |> Array.map wrapArray + |> wrapArray + + [] + let unzip (array : FlatList) = + let struct (first, second) = Arrays.unzip id (unwrapArray (nameof array) array) + struct (wrapArray first, wrapArray second) + + [] + let unzip3 (array : FlatList) = + let struct (first, second, third) = Arrays.unzip3 id (unwrapArray (nameof array) array) + struct (wrapArray first, wrapArray second, wrapArray third) + + [] + let updateAt index (value : 'T) (source : FlatList<'T>) = + Array.updateAt index value (unwrapArray (nameof source) source) + |> wrapArray + + [] + let where predicate (array : FlatList<'T>) = + Array.filter predicate (unwrapArray (nameof array) array) + |> wrapArray + + [] + let windowed windowSize (array : FlatList<'T>) = + Array.windowed windowSize (unwrapArray (nameof array) array) + |> Array.map wrapArray + |> wrapArray + + [] + let zip (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = + let source1 = unwrapArray (nameof array1) array1 + let source2 = unwrapArray (nameof array2) array2 + ArrayChecks.sameLengths (nameof array1) source1 (nameof array2) source2 + Arrays.zip structPair source1 source2 |> wrapArray + + [] + let zip3 (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) (array3 : FlatList<'T3>) = + let source1 = unwrapArray (nameof array1) array1 + let source2 = unwrapArray (nameof array2) array2 + let source3 = unwrapArray (nameof array3) array3 + ArrayChecks.sameLengths3 (nameof array1) source1 (nameof array2) source2 (nameof array3) source3 + + Arrays.zip3 structTriple source1 source2 source3 + |> wrapArray + + // ---- Folding ---- + + // The inline functions can use public members only, so they check and unwrap the array themselves + [] + let inline average (array : FlatList< ^T >) : ^T = + if array.IsDefault then + nullArg (nameof array) + + Array.average ( + ImmutableCollectionsMarshal.AsArray array + |> Unchecked.nonNull + ) + + [] + let inline averageBy ([] projection : 'T -> ^U) (array : FlatList<'T>) : ^U = + if array.IsDefault then + nullArg (nameof array) + + Array.averageBy + projection + (ImmutableCollectionsMarshal.AsArray array + |> Unchecked.nonNull) + + [] + let compareWith comparer (array1 : FlatList<'T>) (array2 : FlatList<'T>) = + Array.compareWith comparer (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) + + [] + let fold (folder : 'State -> 'T -> 'State) (state : 'State) (array : FlatList<'T>) = + Array.fold folder state (unwrapArray (nameof array) array) + + [] + let fold2 (folder : 'State -> 'T1 -> 'T2 -> 'State) (state : 'State) (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = + Array.fold2 folder state (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) + + [] + let foldBack (folder : 'T -> 'State -> 'State) (array : FlatList<'T>) (state : 'State) = + Array.foldBack folder (unwrapArray (nameof array) array) state + + [] + let foldBack2 (folder : 'T1 -> 'T2 -> 'State -> 'State) (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) (state : 'State) = + Array.foldBack2 folder (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) state + + [] + let iter action (array : FlatList<'T>) = Array.iter action (unwrapArray (nameof array) array) + + [] + let iter2 action (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = + Array.iter2 action (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) + + [] + let iteri action (array : FlatList<'T>) = Array.iteri action (unwrapArray (nameof array) array) + + [] + let iteri2 action (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = + Array.iteri2 action (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) + + [] + let mapFold (mapping : 'State -> 'T -> struct ('Result * 'State)) (state : 'State) (array : FlatList<'T>) = + let source = unwrapArray (nameof array) array + let results = Array.zeroCreate source.Length + let mutable state = state + + for index = 0 to source.Length - 1 do + let struct (result, next) = mapping state source[index] + results[index] <- result + state <- next + + struct (wrapArray results, state) + + [] + let mapFoldBack (mapping : 'T -> 'State -> struct ('Result * 'State)) (array : FlatList<'T>) (state : 'State) = + let source = unwrapArray (nameof array) array + let results = Array.zeroCreate source.Length + let mutable state = state + + for index = source.Length - 1 downto 0 do + let struct (result, next) = mapping source[index] state + results[index] <- result + state <- next + + struct (wrapArray results, state) + + [] + let max (array : FlatList<'T>) = Array.max (unwrapArray (nameof array) array) + + [] + let maxBy (projection : 'T -> 'U) (array : FlatList<'T>) = Array.maxBy projection (unwrapArray (nameof array) array) + + [] + let min (array : FlatList<'T>) = Array.min (unwrapArray (nameof array) array) + + [] + let minBy (projection : 'T -> 'U) (array : FlatList<'T>) = Array.minBy projection (unwrapArray (nameof array) array) + + [] + let reduce reduction (array : FlatList<'T>) = Array.reduce reduction (unwrapArray (nameof array) array) + + [] + let reduceBack reduction (array : FlatList<'T>) = Array.reduceBack reduction (unwrapArray (nameof array) array) + + [] + let scan (folder : 'State -> 'T -> 'State) (state : 'State) (array : FlatList<'T>) = + Array.scan folder state (unwrapArray (nameof array) array) + |> wrapArray + + [] + let scanBack (folder : 'T -> 'State -> 'State) (array : FlatList<'T>) (state : 'State) = + Array.scanBack folder (unwrapArray (nameof array) array) state + |> wrapArray + + [] + let inline sum (array : FlatList< ^T >) : ^T = + if array.IsDefault then + nullArg (nameof array) + + Array.sum ( + ImmutableCollectionsMarshal.AsArray array + |> Unchecked.nonNull + ) + + [] + let inline sumBy ([] projection : 'T -> ^U) (array : FlatList<'T>) : ^U = + if array.IsDefault then + nullArg (nameof array) + + Array.sumBy + projection + (ImmutableCollectionsMarshal.AsArray array + |> Unchecked.nonNull) + + // ---- Partitioning and grouping ---- + + [] + let countBy (projection : 'T -> 'Key) (array : FlatList<'T>) = + Arrays.countBy structPair projection (unwrapArray (nameof array) array) + |> wrapArray + + [] + let distinct (array : FlatList<'T>) = + Array.distinct (unwrapArray (nameof array) array) + |> wrapArray + + [] + let distinctBy (projection : 'T -> 'Key) (array : FlatList<'T>) = + Array.distinctBy projection (unwrapArray (nameof array) array) + |> wrapArray + + [] + let except (itemsToExclude : seq<'T>) (array : FlatList<'T>) = + // FSharp.Core checks the items to exclude first + checkNotNull (nameof itemsToExclude) itemsToExclude + Array.except itemsToExclude (unwrapArray (nameof array) array) + |> wrapArray + + [] + let groupBy (projection : 'T -> 'Key) (array : FlatList<'T>) = + Arrays.groupBy (fun key items -> struct (key, wrapArray items)) projection (unwrapArray (nameof array) array) + |> wrapArray + + [] + let partition predicate (array : FlatList<'T>) = + let accepted, rejected = Array.partition predicate (unwrapArray (nameof array) array) + struct (wrapArray accepted, wrapArray rejected) + + [] + let partitionWith (partitioner : 'T -> Choice<'T1, 'T2>) (array : FlatList<'T>) = + let first, second = Array.partitionWith partitioner (unwrapArray (nameof array) array) + struct (wrapArray first, wrapArray second) + + // The range functions repeat the checks of Array.skip, Array.take and Array.splitAt and then slice: a slice copies the + // selected elements, unless the range is empty or covers the whole array (ADR 0001, D5) + + [] + let skip count (array : FlatList<'T>) = + checkNotDefault (nameof array) array + + if count > array.Length then + Raise.invalidArgOutOfRange (nameof count) count "array.Length" array.Length + + if count = array.Length then + empty + else + let count = Operators.max count 0 + array.Slice (count, array.Length - count) + + [] + let skipLast count (array : FlatList<'T>) = + checkNotDefault (nameof array) array + + if count > array.Length then + Raise.invalidArgOutOfRange (nameof count) count "array.Length" array.Length + + if count = array.Length then + empty + else + array.Slice (0, array.Length - Operators.max count 0) + + [] + let skipWhile predicate (array : FlatList<'T>) = + let source = unwrapArray (nameof array) array + let mutable index = 0 + + while index < source.Length && predicate source[index] do + index <- index + 1 + + array.Slice (index, source.Length - index) + + [] + let splitAt index (array : FlatList<'T>) = + checkNotDefault (nameof array) array + + if index < 0 then + Raise.invalidArgInputMustBeNonNegative (nameof index) index + + if array.Length < index then + Raise.notEnoughElements () + + struct (array.Slice (0, index), array.Slice (index, array.Length - index)) + + [] + let splitInto count (array : FlatList<'T>) = + Array.splitInto count (unwrapArray (nameof array) array) + |> Array.map wrapArray + |> wrapArray + + [] + let take count (array : FlatList<'T>) = + checkNotDefault (nameof array) array + + if count < 0 then + Raise.invalidArgInputMustBeNonNegative (nameof count) count + + if count = 0 then + empty + else + if count > array.Length then + Raise.notEnoughElements () + + array.Slice (0, count) + + [] + let takeLast count (array : FlatList<'T>) = + checkNotDefault (nameof array) array + + if count < 0 then + Raise.invalidArgInputMustBeNonNegative (nameof count) count + + if count = 0 then + empty + else + if count > array.Length then + Raise.notEnoughElements () + + array.Slice (array.Length - count, count) + + [] + let takeWhile predicate (array : FlatList<'T>) = + let source = unwrapArray (nameof array) array + let mutable count = 0 + + while count < source.Length && predicate source[count] do + count <- count + 1 + + array.Slice (0, count) + + [] + let truncate count (array : FlatList<'T>) = + checkNotDefault (nameof array) array + + if count <= 0 then + empty + else + array.Slice (0, Operators.min count array.Length) + + // ---- Sorting ---- + + [] + let sort (array : FlatList<'T>) = Array.sort (unwrapArray (nameof array) array) |> wrapArray + + [] + let sortBy (projection : 'T -> 'Key) (array : FlatList<'T>) = + Array.sortBy projection (unwrapArray (nameof array) array) + |> wrapArray + + [] + let sortByDescending (projection : 'T -> 'Key) (array : FlatList<'T>) = + Array.sortByDescending projection (unwrapArray (nameof array) array) + |> wrapArray + + [] + let sortDescending (array : FlatList<'T>) = + Array.sortDescending (unwrapArray (nameof array) array) + |> wrapArray + + [] + let sortWith comparer (array : FlatList<'T>) = + Array.sortWith comparer (unwrapArray (nameof array) array) + |> wrapArray + + [] + let sortWithComparer (comparer : IComparer<'T>) (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.Sort comparer + + // ---- Random ---- + + [] + let randomChoice (source : FlatList<'T>) = Array.randomChoice (unwrapArray (nameof source) source) + + [] + let randomChoiceBy randomizer (source : FlatList<'T>) = Array.randomChoiceBy randomizer (unwrapArray (nameof source) source) + + [] + let randomChoiceWith (random : Random) (source : FlatList<'T>) = + Array.randomChoiceWith random (unwrapArray (nameof source) source) + + [] + let randomChoices count (source : FlatList<'T>) = + Array.randomChoices count (unwrapArray (nameof source) source) + |> wrapArray + + [] + let randomChoicesBy randomizer count (source : FlatList<'T>) = + Array.randomChoicesBy randomizer count (unwrapArray (nameof source) source) + |> wrapArray + + [] + let randomChoicesWith (random : Random) count (source : FlatList<'T>) = + Array.randomChoicesWith random count (unwrapArray (nameof source) source) + |> wrapArray + + [] + let randomSample count (source : FlatList<'T>) = + Array.randomSample count (unwrapArray (nameof source) source) + |> wrapArray + + [] + let randomSampleBy randomizer count (source : FlatList<'T>) = + Array.randomSampleBy randomizer count (unwrapArray (nameof source) source) + |> wrapArray + + [] + let randomSampleWith (random : Random) count (source : FlatList<'T>) = + Array.randomSampleWith random count (unwrapArray (nameof source) source) + |> wrapArray + + [] + let randomShuffle (source : FlatList<'T>) = + Array.randomShuffle (unwrapArray (nameof source) source) + |> wrapArray + + [] + let randomShuffleBy randomizer (source : FlatList<'T>) = + Array.randomShuffleBy randomizer (unwrapArray (nameof source) source) + |> wrapArray + + [] + let randomShuffleWith (random : Random) (source : FlatList<'T>) = + Array.randomShuffleWith random (unwrapArray (nameof source) source) + |> wrapArray + + // ---- Conversion ---- + + [] + let asMemory (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.AsMemory () + + [] + let asSpan (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.AsSpan () + + [] + let blit (source : FlatList<'T>) sourceIndex (target : 'T array) targetIndex count = + Array.blit (unwrapArray (nameof source) source) sourceIndex target targetIndex count + + [] + let toArray (array : FlatList<'T>) = Array.copy (unwrapArray (nameof array) array) + + [] + let toList (array : FlatList<'T>) = Array.toList (unwrapArray (nameof array) array) + + [] + let toSeq (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array :> seq<'T> + + // ---- Type-specific ---- + + [] + let isDefault (array : FlatList<'T>) = array.IsDefault + + [] + let isDefaultOrEmpty (array : FlatList<'T>) = array.IsDefaultOrEmpty diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi b/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi new file mode 100644 index 00000000..4ecff2b8 --- /dev/null +++ b/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi @@ -0,0 +1,1313 @@ +namespace FSharp.Collections.Immutable + +open System +open System.Collections.Generic +open System.Collections.Immutable + +/// An immutable array, . +/// The name comes from a similar construct in the sources of the F# compiler. +type FlatList<'T> = ImmutableArray<'T> + +/// Functions for , the immutable array. +/// +/// +/// The module mirrors : each function has the semantics, the +/// argument checks and the exceptions of the function with the same name in that module (ADR 0001). Functions that +/// FSharp.Core lacks wrap a member of and throw what that +/// member throws. +/// +/// +/// A default immutable array has no underlying array. Every function except +/// +/// and +/// throws for it, as +/// does for a array. +/// +/// +/// Functions that return an optional value return a , and functions +/// that return tuples return struct tuples. The namespaces and +/// contain a module with the same name whose functions use options +/// and reference tuples instead. +/// +/// +[] +[] +module FlatList = + + // ---- Creation ---- + + /// Creates a flat list whose elements are all the given value. + /// The length of the flat list. + /// The value of every element. + /// The created flat list. + /// Thrown when is negative. + /// O(n). + [] + val create : count : int -> value : 'T -> FlatList<'T> + + /// The empty flat list. + /// O(1). The value is . + [] + [] + val empty<'T> : FlatList<'T> + + /// Creates a flat list from a function of the index. + /// The length of the flat list. + /// The function that computes the element at each index, called in index order. + /// The created flat list. + /// Thrown when is negative. + /// O(n). + [] + val init : count : int -> initializer : (int -> 'T) -> FlatList<'T> + + /// Creates a flat list from a copy of an array. + /// The input array. + /// The flat list of the elements of the array. + /// Thrown when is . + /// O(n). Later changes to the array do not change the flat list. + [] + val ofArray : array : 'T array -> FlatList<'T> + + /// Creates a flat list from a list. + /// The input list. + /// The flat list of the elements of the list. + /// O(n). + [] + val ofList : list : 'T list -> FlatList<'T> + + /// Creates a flat list from a copy of a read-only span. + /// The input span. + /// The flat list of the elements of the span. + /// O(n). The function wraps . + [] + val ofReadOnlySpan : span : ReadOnlySpan<'T> -> FlatList<'T> + + /// Creates a flat list from a sequence. + /// The input sequence. + /// The flat list of the elements of the sequence. + /// Thrown when is . + /// O(n). + [] + val ofSeq : source : seq<'T> -> FlatList<'T> + + /// Creates a flat list from a copy of a span. + /// The input span. + /// The flat list of the elements of the span. + /// O(n). The function wraps . + [] + val ofSpan : span : Span<'T> -> FlatList<'T> + + /// Creates a flat list whose elements are all the given value. + /// The length of the flat list. + /// The value of every element. + /// The created flat list. + /// Thrown when is negative. + /// O(n). + [] + val replicate : count : int -> initial : 'T -> FlatList<'T> + + /// Creates a flat list of one element. + /// The element. + /// The flat list of one element. + /// O(1). + [] + val singleton : value : 'T -> FlatList<'T> + + /// Creates a flat list from a generator, which returns the next element and the next state. + /// The function that returns the next element and state, or ValueNone to stop. + /// The initial state. + /// The generated flat list. + /// O(n). + [] + val unfold : generator : ('State -> struct ('T * 'State) voption) -> state : 'State -> FlatList<'T> + + // ---- Builder ---- + + /// Creates a flat list with a builder that an action fills. + /// The action that adds the elements to the builder. + /// The flat list of the elements that the action added. + /// O(n). + [] + val build : action : (FlatList<'T>.Builder -> unit) -> FlatList<'T> + + /// Creates an empty builder. + /// A builder of capacity 8. + /// O(1). The function wraps . + [] + val builder : unit -> FlatList<'T>.Builder + + /// Creates an empty builder of a given capacity. + /// The initial capacity. + /// The builder. + /// Thrown when is negative. + /// O(capacity). The function wraps . + [] + val builderWith : capacity : int -> FlatList<'T>.Builder + + /// Returns the contents of a full builder as a flat list without a copy and empties the builder. + /// The builder, whose count must equal its capacity. + /// The flat list of the elements of the builder. + /// Thrown when is . + /// Thrown when the count of the builder differs from its capacity. + /// O(1). The function wraps . + [] + val moveFromBuilder : builder : FlatList<'T>.Builder -> FlatList<'T> + + /// Returns a copy of the contents of a builder as a flat list. + /// The builder, which stays unchanged. + /// The flat list of the elements of the builder. + /// Thrown when is . + /// O(n). The function wraps . + [] + val ofBuilder : builder : FlatList<'T>.Builder -> FlatList<'T> + + /// Creates a builder that contains a copy of the elements of a flat list. + /// The input flat list. + /// The builder. + /// O(n). The function wraps . + [] + val toBuilder : array : FlatList<'T> -> FlatList<'T>.Builder + + /// Creates a flat list from a builder over the elements of a flat list, after an action changes the builder. + /// The action that changes the builder. + /// The input flat list, which stays unchanged. + /// The flat list of the elements of the changed builder. + /// O(n). + [] + val update : action : (FlatList<'T>.Builder -> unit) -> array : FlatList<'T> -> FlatList<'T> + + // ---- Properties ---- + + /// Returns whether the flat list has no elements. + /// The input flat list. + /// when the flat list is empty. + /// O(1). + [] + val isEmpty : array : FlatList<'T> -> bool + + /// Returns the number of elements of the flat list. + /// The input flat list. + /// The length of the flat list. + /// O(1). + [] + val length : array : FlatList<'T> -> int + + // ---- Access ---- + + /// Returns the only element of the flat list. + /// The input flat list. + /// The only element. + /// Thrown when the flat list does not have exactly one element. + /// O(1). + [] + val exactlyOne : array : FlatList<'T> -> 'T + + /// Returns the first element of the flat list. + /// The input flat list. + /// The first element. + /// Thrown when the flat list is empty. + /// O(1). + [] + val head : array : FlatList<'T> -> 'T + + /// Returns the element at an index. + /// The index of the element. + /// The input flat list. + /// The element at the index. + /// Thrown when the index is negative or not less than the length. + /// O(1). + [] + val item : index : int -> array : FlatList<'T> -> 'T + + /// Returns the last element of the flat list. + /// The input flat list. + /// The last element. + /// Thrown when the flat list is empty. + /// O(1). + [] + val last : array : FlatList<'T> -> 'T + + /// Returns the flat list without its first element. + /// The input flat list. + /// A copy of the elements after the first one. + /// Thrown when the flat list is empty. + /// O(n). + [] + val tail : array : FlatList<'T> -> FlatList<'T> + + /// Returns the only element of the flat list, or ValueNone when it does not have exactly one element. + /// The input flat list. + /// The only element, or ValueNone. + /// O(1). + [] + val tryExactlyOne : array : FlatList<'T> -> 'T voption + + /// Returns the first element of the flat list, or ValueNone when it is empty. + /// The input flat list. + /// The first element, or ValueNone. + /// O(1). + [] + val tryHead : array : FlatList<'T> -> 'T voption + + /// Returns the element at an index, or ValueNone when the index is out of range. + /// The index of the element. + /// The input flat list. + /// The element at the index, or ValueNone. + /// O(1). + [] + val tryItem : index : int -> array : FlatList<'T> -> 'T voption + + /// Returns the last element of the flat list, or ValueNone when it is empty. + /// The input flat list. + /// The last element, or ValueNone. + /// O(1). + [] + val tryLast : array : FlatList<'T> -> 'T voption + + /// Returns the flat list without its first element, or ValueNone when it is empty. + /// The input flat list. + /// A copy of the elements after the first one, or ValueNone. + /// O(n). + [] + val tryTail : array : FlatList<'T> -> FlatList<'T> voption + + // ---- Search ---- + + /// Searches a sorted flat list for a value with the binary search algorithm. + /// The value to search for. + /// The input flat list, sorted in the order of F# generic comparison. + /// + /// The index of the value, or the bitwise complement of the index of the first larger element, or the bitwise + /// complement of the length when no element is larger. + /// + /// + /// O(log n). The function compares with , + /// the order that produces. + /// + [] + val binarySearch : value : 'T -> array : FlatList<'T> -> int when 'T : comparison + + /// Returns whether the flat list contains an element equal to a value, in F# structural equality. + /// The value to find. + /// The input flat list. + /// when an element equals the value. + /// O(n). + [] + val contains : value : 'T -> array : FlatList<'T> -> bool when 'T : equality + + /// Returns whether any element satisfies the predicate. + /// The function to test the elements. + /// The input flat list. + /// when an element satisfies the predicate. + /// O(n). + [] + val exists : predicate : ('T -> bool) -> array : FlatList<'T> -> bool + + /// Returns whether any pair of elements at the same index satisfies the predicate. + /// The function to test the pairs. + /// The first flat list. + /// The second flat list. + /// when a pair satisfies the predicate. + /// Thrown when the flat lists have different lengths. + /// O(n). + [] + val exists2 : predicate : ('T1 -> 'T2 -> bool) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> bool + + /// Returns the first element that satisfies the predicate. + /// The function to test the elements. + /// The input flat list. + /// The first element that satisfies the predicate. + /// Thrown when no element satisfies the predicate. + /// O(n). + [] + val find : predicate : ('T -> bool) -> array : FlatList<'T> -> 'T + + /// Returns the last element that satisfies the predicate. + /// The function to test the elements. + /// The input flat list. + /// The last element that satisfies the predicate. + /// Thrown when no element satisfies the predicate. + /// O(n). + [] + val findBack : predicate : ('T -> bool) -> array : FlatList<'T> -> 'T + + /// Returns the index of the first element that satisfies the predicate. + /// The function to test the elements. + /// The input flat list. + /// The index of the first element that satisfies the predicate. + /// Thrown when no element satisfies the predicate. + /// O(n). + [] + val findIndex : predicate : ('T -> bool) -> array : FlatList<'T> -> int + + /// Returns the index of the last element that satisfies the predicate. + /// The function to test the elements. + /// The input flat list. + /// The index of the last element that satisfies the predicate. + /// Thrown when no element satisfies the predicate. + /// O(n). + [] + val findIndexBack : predicate : ('T -> bool) -> array : FlatList<'T> -> int + + /// Returns whether all elements satisfy the predicate. + /// The function to test the elements. + /// The input flat list. + /// when every element satisfies the predicate. + /// O(n). + [] + val forall : predicate : ('T -> bool) -> array : FlatList<'T> -> bool + + /// Returns whether all pairs of elements at the same index satisfy the predicate. + /// The function to test the pairs. + /// The first flat list. + /// The second flat list. + /// when every pair satisfies the predicate. + /// Thrown when the flat lists have different lengths. + /// O(n). + [] + val forall2 : predicate : ('T1 -> 'T2 -> bool) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> bool + + /// Returns the index of the first element equal to a value, in . + /// The value to find. + /// The input flat list. + /// The index of the first equal element, or -1. + /// O(n). The function wraps . + [] + val indexOf : value : 'T -> array : FlatList<'T> -> int + + /// Returns the index of the first element equal to a value in a given equality comparer. + /// The equality comparer. + /// The value to find. + /// The input flat list. + /// The index of the first equal element, or -1. + /// + /// O(n). The function wraps + /// . + /// + [] + val indexOfWith : comparer : IEqualityComparer<'T> -> value : 'T -> array : FlatList<'T> -> int + + /// Returns the index of the last element equal to a value, in . + /// The value to find. + /// The input flat list. + /// The index of the last equal element, or -1. + /// O(n). The function wraps . + [] + val lastIndexOf : value : 'T -> array : FlatList<'T> -> int + + /// Returns the index of the last element equal to a value in a given equality comparer. + /// The equality comparer. + /// The value to find. + /// The input flat list. + /// The index of the last equal element, or -1. + /// + /// O(n). The function wraps + /// . + /// + [] + val lastIndexOfWith : comparer : IEqualityComparer<'T> -> value : 'T -> array : FlatList<'T> -> int + + /// Returns the first value that the chooser returns for the elements. + /// The function that returns a value, or ValueNone to go on. + /// The input flat list. + /// The first chosen value. + /// Thrown when the chooser returns ValueNone for every element. + /// O(n). + [] + val pick : chooser : ('T -> 'U voption) -> array : FlatList<'T> -> 'U + + /// Returns the first element that satisfies the predicate, or ValueNone. + /// The function to test the elements. + /// The input flat list. + /// The first element that satisfies the predicate, or ValueNone. + /// O(n). + [] + val tryFind : predicate : ('T -> bool) -> array : FlatList<'T> -> 'T voption + + /// Returns the last element that satisfies the predicate, or ValueNone. + /// The function to test the elements. + /// The input flat list. + /// The last element that satisfies the predicate, or ValueNone. + /// O(n). + [] + val tryFindBack : predicate : ('T -> bool) -> array : FlatList<'T> -> 'T voption + + /// Returns the index of the first element that satisfies the predicate, or ValueNone. + /// The function to test the elements. + /// The input flat list. + /// The index of the first element that satisfies the predicate, or ValueNone. + /// O(n). + [] + val tryFindIndex : predicate : ('T -> bool) -> array : FlatList<'T> -> int voption + + /// Returns the index of the last element that satisfies the predicate, or ValueNone. + /// The function to test the elements. + /// The input flat list. + /// The index of the last element that satisfies the predicate, or ValueNone. + /// O(n). + [] + val tryFindIndexBack : predicate : ('T -> bool) -> array : FlatList<'T> -> int voption + + /// Returns the first value that the chooser returns for the elements, or ValueNone. + /// The function that returns a value, or ValueNone to go on. + /// The input flat list. + /// The first chosen value, or ValueNone. + /// O(n). + [] + val tryPick : chooser : ('T -> 'U voption) -> array : FlatList<'T> -> 'U voption + + // ---- Transformation ---- + + /// Returns a flat list with a value added after the last element. + /// The value to add. + /// The input flat list. + /// A flat list of the elements and the value. + /// O(n). The function wraps . + [] + val add : value : 'T -> array : FlatList<'T> -> FlatList<'T> + + /// Returns every element of the first flat list paired with every element of the second one. + /// The first flat list. + /// The second flat list. + /// The pairs, in the order of the first flat list and then of the second one. + /// O(n × m). + [] + val allPairs : array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> FlatList + + /// Returns a flat list of the elements of the first flat list followed by the elements of the second one. + /// The first flat list. + /// The second flat list. + /// The concatenation. + /// O(n + m). When one of the flat lists is empty, the function returns the other one. + [] + val append : array1 : FlatList<'T> -> array2 : FlatList<'T> -> FlatList<'T> + + /// Returns the values that the chooser returns for the elements. + /// The function that returns a value, or ValueNone to skip the element. + /// The input flat list. + /// The chosen values. + /// O(n). + [] + val choose : chooser : ('T -> 'U voption) -> array : FlatList<'T> -> FlatList<'U> + + /// Divides the flat list into chunks of a given size. + /// The maximum size of a chunk. + /// The input flat list. + /// The chunks; the last one can be smaller. + /// Thrown when is not positive. + /// O(n). The chunks are copies. + [] + val chunkBySize : chunkSize : int -> array : FlatList<'T> -> FlatList> + + /// Concatenates the flat lists that a function returns for the elements. + /// The function that returns a flat list for each element. + /// The input flat list. + /// The concatenation of the returned flat lists. + /// O(n + the total length of the returned flat lists). + [] + val collect : mapping : ('T -> FlatList<'U>) -> array : FlatList<'T> -> FlatList<'U> + + /// Concatenates a sequence of flat lists. + /// The flat lists. + /// The concatenation. + /// Thrown when is . + /// O(the total length). + [] + val concat : arrays : seq> -> FlatList<'T> + + /// Returns the elements that satisfy the predicate. + /// The function to test the elements. + /// The input flat list. + /// The elements that satisfy the predicate, in their order. + /// O(n). + [] + val filter : predicate : ('T -> bool) -> array : FlatList<'T> -> FlatList<'T> + + /// Returns every element with its index. + /// The input flat list. + /// The pairs of index and element. + /// O(n). + [] + val indexed : array : FlatList<'T> -> FlatList + + /// Returns a flat list with a value inserted at an index. + /// The index of the inserted value, from 0 to the length. + /// The value to insert. + /// The input flat list. + /// The flat list with the value. + /// Thrown when the index is negative or greater than the length. + /// O(n). + [] + val insertAt : index : int -> value : 'T -> source : FlatList<'T> -> FlatList<'T> + + /// Returns a flat list with values inserted at an index. + /// The index of the first inserted value, from 0 to the length. + /// The values to insert. + /// The input flat list. + /// The flat list with the values. + /// Thrown when the index is negative or greater than the length. + /// O(n + m). + [] + val insertManyAt : index : int -> values : seq<'T> -> source : FlatList<'T> -> FlatList<'T> + + /// Returns the results of a function applied to each element. + /// The function to transform the elements. + /// The input flat list. + /// The results, in the order of the elements. + /// O(n). + [] + val map : mapping : ('T -> 'U) -> array : FlatList<'T> -> FlatList<'U> + + /// Returns the results of a function applied to the pairs of elements at the same index. + /// The function to transform the pairs. + /// The first flat list. + /// The second flat list. + /// The results. + /// Thrown when the flat lists have different lengths. + /// O(n). + [] + val map2 : mapping : ('T1 -> 'T2 -> 'U) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> FlatList<'U> + + /// Returns the results of a function applied to the triples of elements at the same index. + /// The function to transform the triples. + /// The first flat list. + /// The second flat list. + /// The third flat list. + /// The results. + /// Thrown when the flat lists have different lengths. + /// O(n). + [] + val map3 : + mapping : ('T1 -> 'T2 -> 'T3 -> 'U) -> + array1 : FlatList<'T1> -> + array2 : FlatList<'T2> -> + array3 : FlatList<'T3> -> + FlatList<'U> + + /// Returns the results of a function applied to each element and its index. + /// The function to transform the elements. + /// The input flat list. + /// The results. + /// O(n). + [] + val mapi : mapping : (int -> 'T -> 'U) -> array : FlatList<'T> -> FlatList<'U> + + /// Returns the results of a function applied to the index and the pair of elements at that index. + /// The function to transform the pairs. + /// The first flat list. + /// The second flat list. + /// The results. + /// Thrown when the flat lists have different lengths. + /// O(n). + [] + val mapi2 : mapping : (int -> 'T1 -> 'T2 -> 'U) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> FlatList<'U> + + /// Returns every element with its successor. + /// The input flat list. + /// The pairs of neighbours; empty for fewer than two elements. + /// O(n). + [] + val pairwise : array : FlatList<'T> -> FlatList + + /// Returns the elements moved to the indices that a function computes. + /// The function that maps the index of each element to its new index. + /// The input flat list. + /// The permuted flat list. + /// Thrown when the function does not compute a permutation. + /// O(n). + [] + val permute : indexMap : (int -> int) -> array : FlatList<'T> -> FlatList<'T> + + /// Returns a flat list without the element at an index. + /// The index of the removed element. + /// The input flat list. + /// The flat list without the element. + /// Thrown when the index is out of range. + /// O(n). + [] + val removeAt : index : int -> source : FlatList<'T> -> FlatList<'T> + + /// Returns a flat list without a range of elements. + /// The index of the first removed element. + /// The number of removed elements. + /// The input flat list. + /// The flat list without the range. + /// Thrown when the range is out of range. + /// O(n). + [] + val removeManyAt : index : int -> count : int -> source : FlatList<'T> -> FlatList<'T> + + /// Returns the elements in reverse order. + /// The input flat list. + /// The reversed flat list. + /// O(n). + [] + val rev : array : FlatList<'T> -> FlatList<'T> + + /// Returns the transpose of a sequence of flat lists of the same length. + /// The flat lists. + /// A flat list whose element i holds the element i of every input flat list. + /// Thrown when the flat lists have different lengths. + /// O(the total length). + [] + val transpose : arrays : seq> -> FlatList> + + /// Splits a flat list of pairs into two flat lists. + /// The input flat list. + /// The first components and the second components. + /// O(n). + [] + val unzip : array : FlatList -> struct (FlatList<'T1> * FlatList<'T2>) + + /// Splits a flat list of triples into three flat lists. + /// The input flat list. + /// The first, the second and the third components. + /// O(n). + [] + val unzip3 : array : FlatList -> struct (FlatList<'T1> * FlatList<'T2> * FlatList<'T3>) + + /// Returns a flat list with the element at an index replaced by a value. + /// The index of the replaced element. + /// The new value. + /// The input flat list. + /// The flat list with the new value. + /// Thrown when the index is out of range. + /// O(n). + [] + val updateAt : index : int -> value : 'T -> source : FlatList<'T> -> FlatList<'T> + + /// Returns the elements that satisfy the predicate. + /// The function to test the elements. + /// The input flat list. + /// The elements that satisfy the predicate, in their order. + /// O(n). A synonym of . + [] + val where : predicate : ('T -> bool) -> array : FlatList<'T> -> FlatList<'T> + + /// Returns the windows of a given size that slide over the flat list. + /// The size of a window. + /// The input flat list. + /// The windows; empty when the flat list is shorter than a window. + /// Thrown when is not positive. + /// O(n × windowSize). The windows are copies. + [] + val windowed : windowSize : int -> array : FlatList<'T> -> FlatList> + + /// Combines two flat lists into a flat list of pairs. + /// The first flat list. + /// The second flat list. + /// The pairs of elements at the same index. + /// Thrown when the flat lists have different lengths. + /// O(n). + [] + val zip : array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> FlatList + + /// Combines three flat lists into a flat list of triples. + /// The first flat list. + /// The second flat list. + /// The third flat list. + /// The triples of elements at the same index. + /// Thrown when the flat lists have different lengths. + /// O(n). + [] + val zip3 : array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> array3 : FlatList<'T3> -> FlatList + + // ---- Folding ---- + + /// Returns the average of the elements. + /// The input flat list. + /// The average. + /// Thrown when the flat list is empty. + /// O(n). + [] + val inline average : + array : FlatList< ^T > -> ^T + when ^T : (static member (+) : ^T * ^T -> ^T) + and ^T : (static member DivideByInt : ^T * int -> ^T) + and ^T : (static member Zero : ^T) + + /// Returns the average of the results of a function applied to each element. + /// The function to transform the elements. + /// The input flat list. + /// The average of the results. + /// Thrown when the flat list is empty. + /// O(n). + [] + val inline averageBy : + projection : ('T -> ^U) -> array : FlatList<'T> -> ^U + when ^U : (static member (+) : ^U * ^U -> ^U) + and ^U : (static member DivideByInt : ^U * int -> ^U) + and ^U : (static member Zero : ^U) + + /// Compares two flat lists element by element with a comparer. + /// The function that compares two elements. + /// The first flat list. + /// The second flat list. + /// + /// The first non-zero result of the comparer, or the comparison of the lengths when every common element compares equal. + /// + /// O(n). + [] + val compareWith : comparer : ('T -> 'T -> int) -> array1 : FlatList<'T> -> array2 : FlatList<'T> -> int + + /// Applies a function to a state and each element, from the first element to the last one. + /// The function that computes the next state. + /// The initial state. + /// The input flat list. + /// The final state. + /// O(n). + [] + val fold : folder : ('State -> 'T -> 'State) -> state : 'State -> array : FlatList<'T> -> 'State + + /// Applies a function to a state and each pair of elements at the same index, from the first index to the last one. + /// The function that computes the next state. + /// The initial state. + /// The first flat list. + /// The second flat list. + /// The final state. + /// Thrown when the flat lists have different lengths. + /// O(n). + [] + val fold2 : + folder : ('State -> 'T1 -> 'T2 -> 'State) -> state : 'State -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> 'State + + /// Applies a function to each element and a state, from the last element to the first one. + /// The function that computes the next state. + /// The input flat list. + /// The initial state. + /// The final state. + /// O(n). + [] + val foldBack : folder : ('T -> 'State -> 'State) -> array : FlatList<'T> -> state : 'State -> 'State + + /// Applies a function to each pair of elements at the same index and a state, from the last index to the first one. + /// The function that computes the next state. + /// The first flat list. + /// The second flat list. + /// The initial state. + /// The final state. + /// Thrown when the flat lists have different lengths. + /// O(n). + [] + val foldBack2 : + folder : ('T1 -> 'T2 -> 'State -> 'State) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> state : 'State -> 'State + + /// Applies an action to each element. + /// The action. + /// The input flat list. + /// O(n). + [] + val iter : action : ('T -> unit) -> array : FlatList<'T> -> unit + + /// Applies an action to each pair of elements at the same index. + /// The action. + /// The first flat list. + /// The second flat list. + /// Thrown when the flat lists have different lengths. + /// O(n). + [] + val iter2 : action : ('T1 -> 'T2 -> unit) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> unit + + /// Applies an action to each element and its index. + /// The action. + /// The input flat list. + /// O(n). + [] + val iteri : action : (int -> 'T -> unit) -> array : FlatList<'T> -> unit + + /// Applies an action to the index and the pair of elements at that index. + /// The action. + /// The first flat list. + /// The second flat list. + /// Thrown when the flat lists have different lengths. + /// O(n). + [] + val iteri2 : action : (int -> 'T1 -> 'T2 -> unit) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> unit + + /// Maps each element and threads a state through the mapping, from the first element to the last one. + /// The function that returns the result for an element and the next state. + /// The initial state. + /// The input flat list. + /// The results and the final state. + /// O(n). + [] + val mapFold : + mapping : ('State -> 'T -> struct ('Result * 'State)) -> + state : 'State -> + array : FlatList<'T> -> + struct (FlatList<'Result> * 'State) + + /// Maps each element and threads a state through the mapping, from the last element to the first one. + /// The function that returns the result for an element and the next state. + /// The input flat list. + /// The initial state. + /// The results, in the order of the elements, and the final state. + /// O(n). + [] + val mapFoldBack : + mapping : ('T -> 'State -> struct ('Result * 'State)) -> + array : FlatList<'T> -> + state : 'State -> + struct (FlatList<'Result> * 'State) + + /// Returns the greatest element, in F# generic comparison. + /// The input flat list. + /// The greatest element. + /// Thrown when the flat list is empty. + /// O(n). + [] + val max : array : FlatList<'T> -> 'T when 'T : comparison + + /// Returns the element with the greatest result of a projection. + /// The function to transform the elements for the comparison. + /// The input flat list. + /// The first element with the greatest result. + /// Thrown when the flat list is empty. + /// O(n). + [] + val maxBy : projection : ('T -> 'U) -> array : FlatList<'T> -> 'T when 'U : comparison + + /// Returns the smallest element, in F# generic comparison. + /// The input flat list. + /// The smallest element. + /// Thrown when the flat list is empty. + /// O(n). + [] + val min : array : FlatList<'T> -> 'T when 'T : comparison + + /// Returns the element with the smallest result of a projection. + /// The function to transform the elements for the comparison. + /// The input flat list. + /// The first element with the smallest result. + /// Thrown when the flat list is empty. + /// O(n). + [] + val minBy : projection : ('T -> 'U) -> array : FlatList<'T> -> 'T when 'U : comparison + + /// Combines the elements with a function, from the first element to the last one. + /// The function that combines the result so far with the next element. + /// The input flat list. + /// The combined value. + /// Thrown when the flat list is empty. + /// O(n). + [] + val reduce : reduction : ('T -> 'T -> 'T) -> array : FlatList<'T> -> 'T + + /// Combines the elements with a function, from the last element to the first one. + /// The function that combines an element with the result so far. + /// The input flat list. + /// The combined value. + /// Thrown when the flat list is empty. + /// O(n). + [] + val reduceBack : reduction : ('T -> 'T -> 'T) -> array : FlatList<'T> -> 'T + + /// Returns every state of a fold from the first element to the last one, the initial state first. + /// The function that computes the next state. + /// The initial state. + /// The input flat list. + /// The states; one more than the elements. + /// O(n). + [] + val scan : folder : ('State -> 'T -> 'State) -> state : 'State -> array : FlatList<'T> -> FlatList<'State> + + /// Returns every state of a fold from the last element to the first one, the initial state last. + /// The function that computes the next state. + /// The input flat list. + /// The initial state. + /// The states; one more than the elements. + /// O(n). + [] + val scanBack : folder : ('T -> 'State -> 'State) -> array : FlatList<'T> -> state : 'State -> FlatList<'State> + + /// Returns the sum of the elements. + /// The input flat list. + /// The sum; zero for an empty flat list. + /// O(n). + [] + val inline sum : array : FlatList< ^T > -> ^T when ^T : (static member (+) : ^T * ^T -> ^T) and ^T : (static member Zero : ^T) + + /// Returns the sum of the results of a function applied to each element. + /// The function to transform the elements. + /// The input flat list. + /// The sum of the results; zero for an empty flat list. + /// O(n). + [] + val inline sumBy : + projection : ('T -> ^U) -> array : FlatList<'T> -> ^U + when ^U : (static member (+) : ^U * ^U -> ^U) and ^U : (static member Zero : ^U) + + // ---- Partitioning and grouping ---- + + /// Counts the elements of each key of a projection. + /// The function that returns the key of an element. + /// The input flat list. + /// The keys, in the order of their first occurrence, with the number of their elements. + /// O(n). The keys compare in F# structural equality. + [] + val countBy : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList when 'Key : equality + + /// Returns the elements without duplicates, in F# structural equality. + /// The input flat list. + /// The first occurrence of each element, in their order. + /// O(n). + [] + val distinct : array : FlatList<'T> -> FlatList<'T> when 'T : equality + + /// Returns the elements without duplicates of the result of a projection. + /// The function that returns the key of an element. + /// The input flat list. + /// The first element of each key, in their order. + /// O(n). + [] + val distinctBy : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList<'T> when 'Key : equality + + /// Returns the distinct elements that do not equal any of the excluded items. + /// The items to remove. + /// The input flat list. + /// The distinct remaining elements, in their order. + /// Thrown when is . + /// O(n + m). + [] + val except : itemsToExclude : seq<'T> -> array : FlatList<'T> -> FlatList<'T> when 'T : equality + + /// Groups the elements by the key of a projection. + /// The function that returns the key of an element. + /// The input flat list. + /// The keys, in the order of their first occurrence, with their elements in their order. + /// O(n). The keys compare in F# structural equality. + [] + val groupBy : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList)> when 'Key : equality + + /// Splits the elements by a predicate. + /// The function to test the elements. + /// The input flat list. + /// The elements that satisfy the predicate and the other ones, both in their order. + /// O(n). + [] + val partition : predicate : ('T -> bool) -> array : FlatList<'T> -> struct (FlatList<'T> * FlatList<'T>) + + /// Splits the elements by the choice that a function returns for them. + /// The function that returns Choice1Of2 or Choice2Of2 for an element. + /// The input flat list. + /// The values of the first choices and of the second choices, both in the order of the elements. + /// O(n). + [] + val partitionWith : partitioner : ('T -> Choice<'T1, 'T2>) -> array : FlatList<'T> -> struct (FlatList<'T1> * FlatList<'T2>) + + /// Returns the flat list without its first elements. + /// The number of elements to skip; a negative count skips nothing. + /// The input flat list. + /// The remaining elements. + /// Thrown when the count exceeds the length. + /// O(n). The result is a copy, unless it is empty or the whole flat list. + [] + val skip : count : int -> array : FlatList<'T> -> FlatList<'T> + + /// Returns the flat list without its last elements. + /// The number of elements to skip; a negative count skips nothing. + /// The input flat list. + /// The remaining elements. + /// Thrown when the count exceeds the length. + /// O(n). The checks follow . The result is a copy, unless it is empty or the whole flat list. + [] + val skipLast : count : int -> array : FlatList<'T> -> FlatList<'T> + + /// Returns the flat list without its first elements that satisfy the predicate. + /// The function to test the elements. + /// The input flat list. + /// The elements from the first one that does not satisfy the predicate. + /// O(n). The result is a copy, unless it is empty or the whole flat list. + [] + val skipWhile : predicate : ('T -> bool) -> array : FlatList<'T> -> FlatList<'T> + + /// Splits the flat list at an index. + /// The index of the first element of the second part. + /// The input flat list. + /// The elements before the index and the elements from the index. + /// Thrown when the index is negative. + /// Thrown when the index exceeds the length. + /// O(n). The parts are copies, unless they are empty or the whole flat list. + [] + val splitAt : index : int -> array : FlatList<'T> -> struct (FlatList<'T> * FlatList<'T>) + + /// Splits the flat list into at most a given number of parts of nearly equal length. + /// The maximum number of parts. + /// The input flat list. + /// The parts; the first ones are one element longer when the length does not divide evenly. + /// Thrown when is not positive. + /// O(n). The parts are copies. + [] + val splitInto : count : int -> array : FlatList<'T> -> FlatList> + + /// Returns the first elements of the flat list. + /// The number of elements to take. + /// The input flat list. + /// The first elements. + /// Thrown when the count is negative. + /// Thrown when the count exceeds the length. + /// O(n). The result is a copy, unless it is empty or the whole flat list. + [] + val take : count : int -> array : FlatList<'T> -> FlatList<'T> + + /// Returns the last elements of the flat list. + /// The number of elements to take. + /// The input flat list. + /// The last elements. + /// Thrown when the count is negative. + /// Thrown when the count exceeds the length. + /// O(n). The checks follow . The result is a copy, unless it is empty or the whole flat list. + [] + val takeLast : count : int -> array : FlatList<'T> -> FlatList<'T> + + /// Returns the first elements of the flat list that satisfy the predicate. + /// The function to test the elements. + /// The input flat list. + /// The elements before the first one that does not satisfy the predicate. + /// O(n). The result is a copy, unless it is empty or the whole flat list. + [] + val takeWhile : predicate : ('T -> bool) -> array : FlatList<'T> -> FlatList<'T> + + /// Returns at most a given number of first elements. + /// The maximum number of elements; a count that is not positive returns an empty flat list. + /// The input flat list. + /// The first elements. + /// O(n). The result is a copy, unless it is empty or the whole flat list. + [] + val truncate : count : int -> array : FlatList<'T> -> FlatList<'T> + + // ---- Sorting ---- + + /// Sorts the elements in the order of F# generic comparison. + /// The input flat list. + /// The sorted flat list. + /// O(n log n). This is not a stable sort, as . + [] + val sort : array : FlatList<'T> -> FlatList<'T> when 'T : comparison + + /// Sorts the elements by the result of a projection, in the order of F# generic comparison. + /// The function that returns the key of an element. + /// The input flat list. + /// The sorted flat list. + /// O(n log n). This is not a stable sort. + [] + val sortBy : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList<'T> when 'Key : comparison + + /// Sorts the elements by the result of a projection, in descending order. + /// The function that returns the key of an element. + /// The input flat list. + /// The sorted flat list. + /// O(n log n). This is not a stable sort. + [] + val sortByDescending : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList<'T> when 'Key : comparison + + /// Sorts the elements in descending order of F# generic comparison. + /// The input flat list. + /// The sorted flat list. + /// O(n log n). This is not a stable sort. + [] + val sortDescending : array : FlatList<'T> -> FlatList<'T> when 'T : comparison + + /// Sorts the elements with a comparison function. + /// The function that compares two elements. + /// The input flat list. + /// The sorted flat list. + /// O(n log n). This is not a stable sort. + [] + val sortWith : comparer : ('T -> 'T -> int) -> array : FlatList<'T> -> FlatList<'T> + + /// Sorts the elements with a comparer. + /// The comparer. + /// The input flat list. + /// The sorted flat list; the input itself when it is already sorted. + /// + /// O(n log n). This is not a stable sort. The function wraps + /// . + /// + [] + val sortWithComparer : comparer : IComparer<'T> -> array : FlatList<'T> -> FlatList<'T> + + // ---- Random ---- + + /// Returns a random element, chosen with . + /// The input flat list. + /// A random element. + /// Thrown when the flat list is empty. + /// O(1). + [] + val randomChoice : source : FlatList<'T> -> 'T + + /// Returns a random element, chosen with a function that returns numbers in [0, 1). + /// The function that returns a random number in [0, 1). + /// The input flat list. + /// A random element. + /// Thrown when the flat list is empty. + /// Thrown when the randomizer returns a number outside [0, 1). + /// O(1). + [] + val randomChoiceBy : randomizer : (unit -> float) -> source : FlatList<'T> -> 'T + + /// Returns a random element, chosen with a given random number generator. + /// The random number generator. + /// The input flat list. + /// A random element. + /// Thrown when is . + /// Thrown when the flat list is empty. + /// O(1). + [] + val randomChoiceWith : random : Random -> source : FlatList<'T> -> 'T + + /// Returns random elements, chosen with ; an element can repeat. + /// The number of elements to choose. + /// The input flat list. + /// The chosen elements. + /// Thrown when the count is negative or the flat list is empty. + /// O(count). + [] + val randomChoices : count : int -> source : FlatList<'T> -> FlatList<'T> + + /// Returns random elements, chosen with a function that returns numbers in [0, 1); an element can repeat. + /// The function that returns a random number in [0, 1). + /// The number of elements to choose. + /// The input flat list. + /// The chosen elements. + /// Thrown when the count is negative or the flat list is empty. + /// Thrown when the randomizer returns a number outside [0, 1). + /// O(count). + [] + val randomChoicesBy : randomizer : (unit -> float) -> count : int -> source : FlatList<'T> -> FlatList<'T> + + /// Returns random elements, chosen with a given random number generator; an element can repeat. + /// The random number generator. + /// The number of elements to choose. + /// The input flat list. + /// The chosen elements. + /// Thrown when is . + /// Thrown when the count is negative or the flat list is empty. + /// O(count). + [] + val randomChoicesWith : random : Random -> count : int -> source : FlatList<'T> -> FlatList<'T> + + /// Returns random elements at distinct indices, chosen with . + /// The number of elements to choose. + /// The input flat list. + /// The chosen elements. + /// Thrown when the count is negative or exceeds the length. + /// O(count). + [] + val randomSample : count : int -> source : FlatList<'T> -> FlatList<'T> + + /// Returns random elements at distinct indices, chosen with a function that returns numbers in [0, 1). + /// The function that returns a random number in [0, 1). + /// The number of elements to choose. + /// The input flat list. + /// The chosen elements. + /// Thrown when the count is negative or exceeds the length. + /// Thrown when the randomizer returns a number outside [0, 1). + /// O(count). + [] + val randomSampleBy : randomizer : (unit -> float) -> count : int -> source : FlatList<'T> -> FlatList<'T> + + /// Returns random elements at distinct indices, chosen with a given random number generator. + /// The random number generator. + /// The number of elements to choose. + /// The input flat list. + /// The chosen elements. + /// Thrown when is . + /// Thrown when the count is negative or exceeds the length. + /// O(count). + [] + val randomSampleWith : random : Random -> count : int -> source : FlatList<'T> -> FlatList<'T> + + /// Returns the elements in a random order, chosen with . + /// The input flat list. + /// The shuffled flat list. + /// O(n). + [] + val randomShuffle : source : FlatList<'T> -> FlatList<'T> + + /// Returns the elements in a random order, chosen with a function that returns numbers in [0, 1). + /// The function that returns a random number in [0, 1). + /// The input flat list. + /// The shuffled flat list. + /// Thrown when the randomizer returns a number outside [0, 1). + /// O(n). + [] + val randomShuffleBy : randomizer : (unit -> float) -> source : FlatList<'T> -> FlatList<'T> + + /// Returns the elements in a random order, chosen with a given random number generator. + /// The random number generator. + /// The input flat list. + /// The shuffled flat list. + /// Thrown when is . + /// O(n). + [] + val randomShuffleWith : random : Random -> source : FlatList<'T> -> FlatList<'T> + + // ---- Conversion ---- + + /// Returns a read-only memory region over the elements, without a copy. + /// The input flat list. + /// The memory region. + /// O(1). The function wraps . + [] + val asMemory : array : FlatList<'T> -> ReadOnlyMemory<'T> + + /// Returns a read-only span over the elements, without a copy. + /// The input flat list. + /// The span. + /// + /// O(1). The function wraps . A range of the span is a + /// view of a range of the flat list that copies nothing. + /// + [] + val asSpan : array : FlatList<'T> -> ReadOnlySpan<'T> + + /// Copies a range of the flat list into an array. + /// The input flat list. + /// The index of the first element to copy. + /// The array to copy to. + /// The index in the target array of the first copied element. + /// The number of elements to copy. + /// Thrown when is . + /// Thrown when a range is outside its flat list or array. + /// O(count). + [] + val blit : source : FlatList<'T> -> sourceIndex : int -> target : 'T array -> targetIndex : int -> count : int -> unit + + /// Returns a new array with the elements. + /// The input flat list. + /// A copy of the elements. + /// O(n). + [] + val toArray : array : FlatList<'T> -> 'T array + + /// Returns a list of the elements. + /// The input flat list. + /// The list of the elements. + /// O(n). + [] + val toList : array : FlatList<'T> -> 'T list + + /// Returns the flat list as a sequence. + /// The input flat list. + /// The sequence of the elements. + /// O(1). + [] + val toSeq : array : FlatList<'T> -> seq<'T> + + // ---- Type-specific ---- + + /// Returns whether the immutable array is a default value without an underlying array. + /// The input flat list, which can be a default value. + /// for a default value. + /// O(1). The function wraps . + [] + val isDefault : array : FlatList<'T> -> bool + + /// Returns whether the immutable array is a default value or empty. + /// The input flat list, which can be a default value. + /// for a default value or an empty flat list. + /// O(1). The function wraps . + [] + val isDefaultOrEmpty : array : FlatList<'T> -> bool diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs b/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs new file mode 100644 index 00000000..b8df10e2 --- /dev/null +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs @@ -0,0 +1,195 @@ +namespace FSharp.Collections.Immutable + +open System +open System.Collections.Generic +open System.Collections.Immutable + +/// +/// Functions for the builder of a flat list, . +/// +/// +/// The builder is mutable. The functions that change it return the same builder, so that the calls chain with the pipe +/// operator. creates a builder, and +/// +/// returns its contents as a flat list. +/// +[] +[] +module FlatListBuilder = + + // ---- Properties ---- + + /// Returns the number of elements that the builder can hold without growing. + /// The builder. + /// The capacity. + [] + let capacity (builder : FlatList<'T>.Builder) = builder.Capacity + + /// Returns the number of elements in the builder. + /// The builder. + /// The number of elements. + [] + let length (builder : FlatList<'T>.Builder) = builder.Count + + // ---- Access ---- + + /// Returns the element at an index. + /// The index of the element. + /// The builder. + /// The element. + /// Thrown when the index is out of range. + [] + let item index (builder : FlatList<'T>.Builder) = builder[index] + + // ---- Search ---- + + /// Returns whether the builder contains a value, in . + /// The value to find. + /// The builder. + /// when an element equals the value. + [] + let contains (value : 'T) (builder : FlatList<'T>.Builder) = builder.Contains value + + /// Returns the index of the first element equal to a value, or -1. + /// The value to find. + /// The builder. + /// The index, or -1. + [] + let indexOf (value : 'T) (builder : FlatList<'T>.Builder) = builder.IndexOf value + + /// Returns the index of the last element equal to a value, or -1. + /// The value to find. + /// The builder. + /// The index, or -1. + [] + let lastIndexOf (value : 'T) (builder : FlatList<'T>.Builder) = builder.LastIndexOf value + + // ---- Transformation ---- + + /// Adds a value after the last element. + /// The value to add. + /// The builder. + /// The same builder. + [] + let add (value : 'T) (builder : FlatList<'T>.Builder) = + builder.Add value + builder + + /// Adds values after the last element. + /// The values to add. + /// The builder. + /// The same builder. + [] + let addRange (values : seq<'T>) (builder : FlatList<'T>.Builder) = + builder.AddRange values + builder + + /// Removes every element. + /// The builder. + /// The same builder. + [] + let clear (builder : FlatList<'T>.Builder) = + builder.Clear () + builder + + /// Inserts a value at an index. + /// The index of the inserted value, from 0 to the count. + /// The value to insert. + /// The builder. + /// The same builder. + /// Thrown when the index is out of range. + [] + let insertAt index (value : 'T) (builder : FlatList<'T>.Builder) = + builder.Insert (index, value) + builder + + /// Removes the first element equal to a value, in . + /// The value to remove. + /// The builder. + /// when an element was removed. + [] + let remove (value : 'T) (builder : FlatList<'T>.Builder) = builder.Remove value + + /// Removes the element at an index. + /// The index of the removed element. + /// The builder. + /// The same builder. + /// Thrown when the index is out of range. + [] + let removeAt index (builder : FlatList<'T>.Builder) = + builder.RemoveAt index + builder + + /// Reverses the order of the elements. + /// The builder. + /// The same builder. + [] + let rev (builder : FlatList<'T>.Builder) = + builder.Reverse () + builder + + /// Sets the number of elements that the builder can hold without growing. + /// The new capacity, not less than the count. + /// The builder. + /// The same builder. + /// Thrown when the capacity is less than the count. + [] + let setCapacity capacity (builder : FlatList<'T>.Builder) = + builder.Capacity <- capacity + builder + + /// Replaces the element at an index. + /// The index of the replaced element. + /// The new value. + /// The builder. + /// The same builder. + /// Thrown when the index is out of range. + [] + let updateAt index (value : 'T) (builder : FlatList<'T>.Builder) = + builder[index] <- value + builder + + // ---- Sorting ---- + + /// Sorts the elements in the order of F# generic comparison. + /// The builder. + /// The same builder. + /// This is not a stable sort. + [] + let sort (builder : FlatList<'T>.Builder) = + builder.Sort LanguagePrimitives.FastGenericComparer<'T> + builder + + /// Sorts the elements with a comparison function. + /// The function that compares two elements. + /// The builder. + /// The same builder. + /// This is not a stable sort. + [] + let sortWith (comparer : 'T -> 'T -> int) (builder : FlatList<'T>.Builder) = + builder.Sort (Comparison<'T> comparer) + builder + + /// Sorts the elements with a comparer. + /// The comparer. + /// The builder. + /// The same builder. + /// This is not a stable sort. + [] + let sortWithComparer (comparer : IComparer<'T>) (builder : FlatList<'T>.Builder) = + builder.Sort comparer + builder + + // ---- Conversion ---- + + /// Returns the contents of the builder as a flat list and empties the builder. + /// The builder. + /// The flat list of the elements; without a copy when the count equals the capacity. + [] + let drainToFlatList (builder : FlatList<'T>.Builder) : FlatList<'T> = builder.DrainToImmutable () + + /// Returns a new array with the elements of the builder. + /// The builder. + /// A copy of the elements. + [] + let toArray (builder : FlatList<'T>.Builder) = builder.ToArray () diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fs b/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fs new file mode 100644 index 00000000..f95c4a13 --- /dev/null +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fs @@ -0,0 +1,86 @@ +namespace FSharp.Collections.Immutable + +open System +open System.Collections.Immutable + +/// The body of a flatList { } expression: a function that adds the elements to a builder. +type FlatListBuffer<'T> = FlatList<'T>.Builder -> unit + +/// The builder of the flatList { } computation expression. +/// +/// The expression collects its elements in an and +/// returns them without a copy when the builder is full (ADR 0001, D7). +/// +[] +type FlatListCE () = + + /// An expression without elements. + member inline _.Zero () : FlatListBuffer<'T> = ignore + + /// Adds an element. + member inline _.Yield (item : 'T) : FlatListBuffer<'T> = fun builder -> builder.Add item + + /// Adds the elements of a sequence. + member inline _.YieldFrom (items : seq<'T>) : FlatListBuffer<'T> = fun builder -> builder.AddRange items + + /// Adds the elements of an array. + member inline _.YieldFrom (items : 'T array) : FlatListBuffer<'T> = fun builder -> builder.AddRange items + + /// Adds the elements of a flat list. + member inline _.YieldFrom (items : FlatList<'T>) : FlatListBuffer<'T> = fun builder -> builder.AddRange items + + /// Adds the elements of another expression body. + member inline _.YieldFrom (buffer : FlatListBuffer<'T>) : FlatListBuffer<'T> = buffer + + /// Adds the elements of two parts of the expression, in order. + member inline _.Combine (first : FlatListBuffer<'T>, second : FlatListBuffer<'T>) : FlatListBuffer<'T> = + fun builder -> + first builder + second builder + + /// Runs the body of the expression each time the elements are collected. + member inline _.Delay ([] delayed : unit -> FlatListBuffer<'T>) : FlatListBuffer<'T> = + fun builder -> delayed () builder + + /// Adds the elements of the body for each item of a sequence. + member inline _.For (source : seq<'U>, [] body : 'U -> FlatListBuffer<'T>) : FlatListBuffer<'T> = + fun builder -> + for item in source do + body item builder + + /// Adds the elements of the body while the guard holds. + member inline _.While ([] guard : unit -> bool, body : FlatListBuffer<'T>) : FlatListBuffer<'T> = + fun builder -> + while guard () do + body builder + + /// Handles the exceptions of the body. + member inline _.TryWith + (body : FlatListBuffer<'T>, [] handler : exn -> FlatListBuffer<'T>) + : FlatListBuffer<'T> + = + fun builder -> + try + body builder + with ex -> + handler ex builder + + /// Runs the compensation after the body. + member inline _.TryFinally (body : FlatListBuffer<'T>, [] compensation : unit -> unit) : FlatListBuffer<'T> = + fun builder -> + try + body builder + finally + compensation () + + /// Disposes a resource after the body. + member inline _.Using (resource : 'Resource, [] body : 'Resource -> FlatListBuffer<'T>) : FlatListBuffer<'T> = + fun builder -> + use resource = resource + body resource builder + + /// Collects the elements of the expression. + member _.Run (buffer : FlatListBuffer<'T>) : FlatList<'T> = + let builder = ImmutableArray.CreateBuilder<'T>() + buffer builder + builder.DrainToImmutable () diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListOption.fs b/src/FSharp.Collections.Immutable/FlatList/FlatListOption.fs new file mode 100644 index 00000000..6265c305 --- /dev/null +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListOption.fs @@ -0,0 +1,60 @@ +namespace FSharp.Collections.Immutable.Option + +open FSharp.Collections.Immutable + +// The functions call the FSharp.Core Array functions, which take and return options, over the underlying array, or +// convert the result of the default module where it implements the function itself. +[] +[] +module FlatList = + + // ---- Creation ---- + + [] + let unfold (generator : 'State -> ('T * 'State) option) (state : 'State) = Array.unfold generator state |> wrapArray + + // ---- Access ---- + + [] + let tryExactlyOne (array : FlatList<'T>) = Array.tryExactlyOne (unwrapArray (nameof array) array) + + [] + let tryHead (array : FlatList<'T>) = Array.tryHead (unwrapArray (nameof array) array) + + [] + let tryItem index (array : FlatList<'T>) = Array.tryItem index (unwrapArray (nameof array) array) + + [] + let tryLast (array : FlatList<'T>) = Array.tryLast (unwrapArray (nameof array) array) + + [] + let tryTail (array : FlatList<'T>) = + FSharp.Collections.Immutable.FlatList.tryTail array + |> ValueOption.toOption + + // ---- Search ---- + + [] + let pick (chooser : 'T -> 'U option) (array : FlatList<'T>) = Array.pick chooser (unwrapArray (nameof array) array) + + [] + let tryFind predicate (array : FlatList<'T>) = Array.tryFind predicate (unwrapArray (nameof array) array) + + [] + let tryFindBack predicate (array : FlatList<'T>) = Array.tryFindBack predicate (unwrapArray (nameof array) array) + + [] + let tryFindIndex predicate (array : FlatList<'T>) = Array.tryFindIndex predicate (unwrapArray (nameof array) array) + + [] + let tryFindIndexBack predicate (array : FlatList<'T>) = Array.tryFindIndexBack predicate (unwrapArray (nameof array) array) + + [] + let tryPick (chooser : 'T -> 'U option) (array : FlatList<'T>) = Array.tryPick chooser (unwrapArray (nameof array) array) + + // ---- Transformation ---- + + [] + let choose (chooser : 'T -> 'U option) (array : FlatList<'T>) = + Array.choose chooser (unwrapArray (nameof array) array) + |> wrapArray diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListOption.fsi b/src/FSharp.Collections.Immutable/FlatList/FlatListOption.fsi new file mode 100644 index 00000000..21c99667 --- /dev/null +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListOption.fsi @@ -0,0 +1,126 @@ +namespace FSharp.Collections.Immutable.Option + +open FSharp.Collections.Immutable + +/// +/// The functions of that take or return an optional +/// value, with instead of +/// . +/// +/// +/// After open FSharp.Collections.Immutable.Option, these functions shadow the default ones, and the other functions +/// of the default module stay available (ADR 0001, D3). Each function has the signature of the function with the same +/// name in where that module has one. +/// +[] +[] +module FlatList = + + // ---- Creation ---- + + /// Creates a flat list from a generator, which returns the next element and the next state. + /// The function that returns the next element and state, or None to stop. + /// The initial state. + /// The generated flat list. + /// O(n). + [] + val unfold : generator : ('State -> ('T * 'State) option) -> state : 'State -> FlatList<'T> + + // ---- Access ---- + + /// Returns the only element of the flat list, or None when it does not have exactly one element. + /// The input flat list. + /// The only element, or None. + /// O(1). + [] + val tryExactlyOne : array : FlatList<'T> -> 'T option + + /// Returns the first element of the flat list, or None when it is empty. + /// The input flat list. + /// The first element, or None. + /// O(1). + [] + val tryHead : array : FlatList<'T> -> 'T option + + /// Returns the element at an index, or None when the index is out of range. + /// The index of the element. + /// The input flat list. + /// The element at the index, or None. + /// O(1). + [] + val tryItem : index : int -> array : FlatList<'T> -> 'T option + + /// Returns the last element of the flat list, or None when it is empty. + /// The input flat list. + /// The last element, or None. + /// O(1). + [] + val tryLast : array : FlatList<'T> -> 'T option + + /// Returns the flat list without its first element, or None when it is empty. + /// The input flat list. + /// A copy of the elements after the first one, or None. + /// O(n). + [] + val tryTail : array : FlatList<'T> -> FlatList<'T> option + + // ---- Search ---- + + /// Returns the first value that the chooser returns for the elements. + /// The function that returns a value, or None to go on. + /// The input flat list. + /// The first chosen value. + /// Thrown when the chooser returns None for every element. + /// O(n). + [] + val pick : chooser : ('T -> 'U option) -> array : FlatList<'T> -> 'U + + /// Returns the first element that satisfies the predicate, or None. + /// The function to test the elements. + /// The input flat list. + /// The first element that satisfies the predicate, or None. + /// O(n). + [] + val tryFind : predicate : ('T -> bool) -> array : FlatList<'T> -> 'T option + + /// Returns the last element that satisfies the predicate, or None. + /// The function to test the elements. + /// The input flat list. + /// The last element that satisfies the predicate, or None. + /// O(n). + [] + val tryFindBack : predicate : ('T -> bool) -> array : FlatList<'T> -> 'T option + + /// Returns the index of the first element that satisfies the predicate, or None. + /// The function to test the elements. + /// The input flat list. + /// The index of the first element that satisfies the predicate, or None. + /// O(n). + [] + val tryFindIndex : predicate : ('T -> bool) -> array : FlatList<'T> -> int option + + /// Returns the index of the last element that satisfies the predicate, or None. + /// The function to test the elements. + /// The input flat list. + /// The index of the last element that satisfies the predicate, or None. + /// O(n). + [] + val tryFindIndexBack : predicate : ('T -> bool) -> array : FlatList<'T> -> int option + + /// Returns the first value that the chooser returns for the elements, or None. + /// The function that returns a value, or None to go on. + /// The input flat list. + /// The first chosen value, or None. + /// O(n). + [] + val tryPick : chooser : ('T -> 'U option) -> array : FlatList<'T> -> 'U option + + // ---- Transformation ---- + + /// Returns the values that the chooser returns for the elements. + /// The function that returns a value, or None to skip the element. + /// The input flat list. + /// The chosen values. + /// O(n). + [] + val choose : chooser : ('T -> 'U option) -> array : FlatList<'T> -> FlatList<'U> diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fs b/src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fs new file mode 100644 index 00000000..ff878c38 --- /dev/null +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fs @@ -0,0 +1,114 @@ +namespace FSharp.Collections.Immutable.Tuple + +open FSharp.Collections.Immutable + +// The functions that build a tuple per element share their algorithm with the default module and only pass another +// tuple constructor; the others delegate to the default module and repack the tuples, which costs no second pass. +[] +[] +module FlatList = + + // ---- Creation ---- + + [] + let unfold (generator : 'State -> ('T * 'State) voption) (state : 'State) = + let generateStruct state = + match generator state with + | ValueSome (item, next) -> ValueSome (struct (item, next)) + | ValueNone -> ValueNone + + FSharp.Collections.Immutable.FlatList.unfold generateStruct state + + // ---- Transformation ---- + + [] + let allPairs (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = + Arrays.allPairs referencePair (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) + |> wrapArray + + [] + let indexed (array : FlatList<'T>) = + Arrays.indexed referencePair (unwrapArray (nameof array) array) + |> wrapArray + + [] + let pairwise (array : FlatList<'T>) = + Arrays.pairwise referencePair (unwrapArray (nameof array) array) + |> wrapArray + + [] + let unzip (array : FlatList<'T1 * 'T2>) = + let struct (first, second) = + Arrays.unzip (fun (item1, item2) -> struct (item1, item2)) (unwrapArray (nameof array) array) + + wrapArray first, wrapArray second + + [] + let unzip3 (array : FlatList<'T1 * 'T2 * 'T3>) = + let struct (first, second, third) = + Arrays.unzip3 (fun (item1, item2, item3) -> struct (item1, item2, item3)) (unwrapArray (nameof array) array) + + wrapArray first, wrapArray second, wrapArray third + + [] + let zip (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = + let source1 = unwrapArray (nameof array1) array1 + let source2 = unwrapArray (nameof array2) array2 + ArrayChecks.sameLengths (nameof array1) source1 (nameof array2) source2 + Arrays.zip referencePair source1 source2 |> wrapArray + + [] + let zip3 (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) (array3 : FlatList<'T3>) = + let source1 = unwrapArray (nameof array1) array1 + let source2 = unwrapArray (nameof array2) array2 + let source3 = unwrapArray (nameof array3) array3 + ArrayChecks.sameLengths3 (nameof array1) source1 (nameof array2) source2 (nameof array3) source3 + Arrays.zip3 referenceTriple source1 source2 source3 + |> wrapArray + + // ---- Folding ---- + + [] + let mapFold (mapping : 'State -> 'T -> 'Result * 'State) (state : 'State) (array : FlatList<'T>) = + let mapStruct state item = + let result, next = mapping state item + struct (result, next) + + let struct (results, final) = FSharp.Collections.Immutable.FlatList.mapFold mapStruct state array + results, final + + [] + let mapFoldBack (mapping : 'T -> 'State -> 'Result * 'State) (array : FlatList<'T>) (state : 'State) = + let mapStruct item state = + let result, next = mapping item state + struct (result, next) + + let struct (results, final) = FSharp.Collections.Immutable.FlatList.mapFoldBack mapStruct array state + results, final + + // ---- Partitioning and grouping ---- + + [] + let countBy (projection : 'T -> 'Key) (array : FlatList<'T>) = + Arrays.countBy referencePair projection (unwrapArray (nameof array) array) + |> wrapArray + + [] + let groupBy (projection : 'T -> 'Key) (array : FlatList<'T>) = + Arrays.groupBy (fun key items -> key, wrapArray items) projection (unwrapArray (nameof array) array) + |> wrapArray + + [] + let partition predicate (array : FlatList<'T>) = + let struct (accepted, rejected) = FSharp.Collections.Immutable.FlatList.partition predicate array + accepted, rejected + + [] + let partitionWith (partitioner : 'T -> Choice<'T1, 'T2>) (array : FlatList<'T>) = + let struct (first, second) = FSharp.Collections.Immutable.FlatList.partitionWith partitioner array + first, second + + [] + let splitAt index (array : FlatList<'T>) = + let struct (before, after) = FSharp.Collections.Immutable.FlatList.splitAt index array + before, after diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fsi b/src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fsi new file mode 100644 index 00000000..390b447e --- /dev/null +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fsi @@ -0,0 +1,151 @@ +namespace FSharp.Collections.Immutable.Tuple + +open FSharp.Collections.Immutable + +/// +/// The functions of that take or return tuples, with +/// reference tuples instead of struct tuples. +/// +/// +/// After open FSharp.Collections.Immutable.Tuple, these functions shadow the default ones, and the other functions +/// of the default module stay available (ADR 0001, D3). Optional values stay +/// ; open the namespace +/// after this one for the shapes of +/// . +/// +[] +[] +module FlatList = + + // ---- Creation ---- + + /// Creates a flat list from a generator, which returns the next element and the next state. + /// The function that returns the next element and state, or ValueNone to stop. + /// The initial state. + /// The generated flat list. + /// O(n). + [] + val unfold : generator : ('State -> ('T * 'State) voption) -> state : 'State -> FlatList<'T> + + // ---- Transformation ---- + + /// Returns every element of the first flat list paired with every element of the second one. + /// The first flat list. + /// The second flat list. + /// The pairs, in the order of the first flat list and then of the second one. + /// O(n × m). + [] + val allPairs : array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> FlatList<'T1 * 'T2> + + /// Returns every element with its index. + /// The input flat list. + /// The pairs of index and element. + /// O(n). + [] + val indexed : array : FlatList<'T> -> FlatList + + /// Returns every element with its successor. + /// The input flat list. + /// The pairs of neighbours; empty for fewer than two elements. + /// O(n). + [] + val pairwise : array : FlatList<'T> -> FlatList<'T * 'T> + + /// Splits a flat list of pairs into two flat lists. + /// The input flat list. + /// The first components and the second components. + /// O(n). + [] + val unzip : array : FlatList<'T1 * 'T2> -> FlatList<'T1> * FlatList<'T2> + + /// Splits a flat list of triples into three flat lists. + /// The input flat list. + /// The first, the second and the third components. + /// O(n). + [] + val unzip3 : array : FlatList<'T1 * 'T2 * 'T3> -> FlatList<'T1> * FlatList<'T2> * FlatList<'T3> + + /// Combines two flat lists into a flat list of pairs. + /// The first flat list. + /// The second flat list. + /// The pairs of elements at the same index. + /// Thrown when the flat lists have different lengths. + /// O(n). + [] + val zip : array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> FlatList<'T1 * 'T2> + + /// Combines three flat lists into a flat list of triples. + /// The first flat list. + /// The second flat list. + /// The third flat list. + /// The triples of elements at the same index. + /// Thrown when the flat lists have different lengths. + /// O(n). + [] + val zip3 : array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> array3 : FlatList<'T3> -> FlatList<'T1 * 'T2 * 'T3> + + // ---- Folding ---- + + /// Maps each element and threads a state through the mapping, from the first element to the last one. + /// The function that returns the result for an element and the next state. + /// The initial state. + /// The input flat list. + /// The results and the final state. + /// O(n). + [] + val mapFold : + mapping : ('State -> 'T -> 'Result * 'State) -> state : 'State -> array : FlatList<'T> -> FlatList<'Result> * 'State + + /// Maps each element and threads a state through the mapping, from the last element to the first one. + /// The function that returns the result for an element and the next state. + /// The input flat list. + /// The initial state. + /// The results, in the order of the elements, and the final state. + /// O(n). + [] + val mapFoldBack : + mapping : ('T -> 'State -> 'Result * 'State) -> array : FlatList<'T> -> state : 'State -> FlatList<'Result> * 'State + + // ---- Partitioning and grouping ---- + + /// Counts the elements of each key of a projection. + /// The function that returns the key of an element. + /// The input flat list. + /// The keys, in the order of their first occurrence, with the number of their elements. + /// O(n). The keys compare in F# structural equality. + [] + val countBy : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList<'Key * int> when 'Key : equality + + /// Groups the elements by the key of a projection. + /// The function that returns the key of an element. + /// The input flat list. + /// The keys, in the order of their first occurrence, with their elements in their order. + /// O(n). The keys compare in F# structural equality. + [] + val groupBy : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList<'Key * FlatList<'T>> when 'Key : equality + + /// Splits the elements by a predicate. + /// The function to test the elements. + /// The input flat list. + /// The elements that satisfy the predicate and the other ones, both in their order. + /// O(n). + [] + val partition : predicate : ('T -> bool) -> array : FlatList<'T> -> FlatList<'T> * FlatList<'T> + + /// Splits the elements by the choice that a function returns for them. + /// The function that returns Choice1Of2 or Choice2Of2 for an element. + /// The input flat list. + /// The values of the first choices and of the second choices, both in the order of the elements. + /// O(n). + [] + val partitionWith : partitioner : ('T -> Choice<'T1, 'T2>) -> array : FlatList<'T> -> FlatList<'T1> * FlatList<'T2> + + /// Splits the flat list at an index. + /// The index of the first element of the second part. + /// The input flat list. + /// The elements before the index and the elements from the index. + /// Thrown when the index is negative. + /// Thrown when the index exceeds the length. + /// O(n). The parts are copies, unless they are empty or the whole flat list. + [] + val splitAt : index : int -> array : FlatList<'T> -> FlatList<'T> * FlatList<'T> diff --git a/src/FSharp.Collections.Immutable/Helper.fs b/src/FSharp.Collections.Immutable/Helper.fs index b4e365b7..ad29679f 100644 --- a/src/FSharp.Collections.Immutable/Helper.fs +++ b/src/FSharp.Collections.Immutable/Helper.fs @@ -4,7 +4,11 @@ namespace global namespace FSharp.Collections.Immutable #endif +open System +open System.Collections.Generic open System.Collections.Immutable +open System.Linq +open System.Runtime.InteropServices /// Argument checks shared by the collection modules. /// The module keeps its old name, so that the modules written before ADR 0001 compile unchanged until they are replaced. @@ -28,10 +32,302 @@ module internal ImmutableCollectionUtil = if array.IsDefault then nullArg name -/// Exception messages of the modules written before ADR 0001. + /// The array under an immutable array, without a copy (ADR 0001, D5). + /// + /// The caller reads the array and never writes to it: the immutable array and every value that shares it see the same + /// memory. A default immutable array throws for . + /// + let inline unwrapArray name (array : ImmutableArray<'T>) : 'T array = + checkNotDefault name array + // The check above guarantees an underlying array + ImmutableCollectionsMarshal.AsArray array + |> Unchecked.nonNull + + /// An immutable array over an array, without a copy (ADR 0001, D5). + /// Only an array that nobody can write to any more may be wrapped: a fresh result of a computation. + let inline wrapArray (array : 'T array) : ImmutableArray<'T> = ImmutableCollectionsMarshal.AsImmutableArray array + + /// The constructor of a struct pair, for the algorithms shared by the default and the `Tuple` modules. + let inline structPair (first : 'T1) (second : 'T2) = struct (first, second) + + /// The constructor of a reference pair, for the algorithms shared by the default and the `Tuple` modules. + let inline referencePair (first : 'T1) (second : 'T2) = (first, second) + + /// The constructor of a struct triple, for the algorithms shared by the default and the `Tuple` modules. + let inline structTriple (first : 'T1) (second : 'T2) (third : 'T3) = struct (first, second, third) + + /// The constructor of a reference triple, for the algorithms shared by the default and the `Tuple` modules. + let inline referenceTriple (first : 'T1) (second : 'T2) (third : 'T3) = (first, second, third) + +/// The exception messages of FSharp.Core (FSCore.resx), in English. +/// +/// The modules throw what the FSharp.Core function that they mirror throws (ADR 0001, D6). FSharp.Core keeps its resources +/// internal, so the texts are copied. +/// module internal ErrorStrings = + + [] + let ArraysHadDifferentLengths = "The arrays have different lengths." + + [] + let IndexOutOfBounds = "The index was outside the range of elements in the list." + + [] + let InputArrayEmpty = "The input array was empty." + + [] + let InputListWasEmpty = "The input list was empty." + [] let InputMustBeNonNegative = "The input must be non-negative." + [] + let InputMustBePositive = "The input must be positive." + + [] + let InputSequenceEmpty = "The input sequence was empty." + + [] + let InputSequenceTooLong = "The input sequence contains more than one element." + + [] + let KeyNotFound = "The item, key, or index was not found in the collection." + + [] + let KeyNotFoundAlt = "An index satisfying the predicate was not found in the collection." + + [] + let ListsHadDifferentLengths = "The lists had different lengths." + + /// The message of the modules written before ADR 0001. [] let ListsHaveDifferentLengths = "The lists have different lengths." + + [] + let NotAPermutation = "The function did not compute a permutation." + + [] + let NotEnoughElements = "The input sequence has an insufficient number of elements." + + [] + let OutOfRange = "The index is outside the legal range." + +/// +/// The exceptions of the DetailedExceptions module of FSharp.Core, with the same types, messages and parameter names. +/// +/// The callers pass the names of their own parameters (ADR 0001, D6). +module internal Raise = + + let inline private invalidArgFormat (arg : string) (format : string) (paramArray : obj array) = + raise (ArgumentException (String.Format (format, paramArray), arg)) + + /// The arrays of a function of two arrays have different lengths. + let invalidArgDifferentArrayLength (arg1 : string) (len1 : int) (arg2 : string) (len2 : int) : 'T = + invalidArgFormat arg1 "{0}\n{1}.Length = {2}, {3}.Length = {4}" [| + ErrorStrings.ArraysHadDifferentLengths + arg1 + len1 + arg2 + len2 + |] + + /// The lists of a function of two lists have different lengths; `diff` is how much shorter `arg1` is. + let invalidArgDifferentListLength (arg1 : string) (arg2 : string) (diff : int) : 'T = + invalidArgFormat arg1 "{0}\n{1} is {2} {3} shorter than {4}" [| + ErrorStrings.ListsHadDifferentLengths + arg1 + diff + (if diff = 1 then "element" else "elements") + arg2 + |] + + /// The arrays of a function of three arrays have different lengths. + let invalidArg3ArraysDifferent (arg1 : string) (arg2 : string) (arg3 : string) (len1 : int) (len2 : int) (len3 : int) : 'T = + invalidArgFormat + (String.Concat [| arg1; ", "; arg2; ", "; arg3 |]) + "{0}\n {1}.Length = {2}, {3}.Length = {4}, {5}.Length = {6}" + [| ErrorStrings.ArraysHadDifferentLengths; arg1; len1; arg2; len2; arg3; len3 |] + + /// The lists of a function of three lists have different lengths. + let invalidArg3ListsDifferent (arg1 : string) (arg2 : string) (arg3 : string) (len1 : int) (len2 : int) (len3 : int) : 'T = + invalidArgFormat + (String.Concat [| arg1; ", "; arg2; ", "; arg3 |]) + "{0}\n {1}.Length = {2}, {3}.Length = {4}, {5}.Length = {6}" + [| ErrorStrings.ListsHadDifferentLengths; arg1; len1; arg2; len2; arg3; len3 |] + + /// A count or an index is negative. + let invalidArgInputMustBeNonNegative (arg : string) (count : int) : 'T = + invalidArgFormat arg "{0}\n{1} = {2}" [| ErrorStrings.InputMustBeNonNegative; arg; count |] + + /// A size is not positive. + let invalidArgInputMustBePositive (arg : string) (count : int) : 'T = + invalidArgFormat arg "{0}\n{1} = {2}" [| ErrorStrings.InputMustBePositive; arg; count |] + + /// An index or a count is beyond a bound. + let invalidArgOutOfRange (arg : string) (index : int) (text : string) (bound : int) : 'T = + let message = + String.Format ("{0}\n{1} = {2}, {3} = {4}", [| box ErrorStrings.OutOfRange; arg; index; text; bound |]) + + raise (ArgumentOutOfRangeException (arg, message)) + + /// A list is shorter than an index. + let invalidOpListNotEnoughElements (index : int) : 'T = + let message = + String.Format ( + "{0}\nThe list was {1} {2} shorter than the index", + [| box ErrorStrings.NotEnoughElements; index; (if index = 1 then "element" else "elements") |] + ) + + raise (InvalidOperationException message) + + /// No element satisfies the predicate of a `find` or `pick` function. + let indexNotFound () : 'T = raise (KeyNotFoundException ErrorStrings.KeyNotFoundAlt) + + /// A collection has fewer elements than a function needs. + let notEnoughElements () : 'T = raise (InvalidOperationException ErrorStrings.NotEnoughElements) + +/// The checks of the FSharp.Core Array module that the functions of several arrays share. +module internal ArrayChecks = + + /// The arrays of a function of two arrays must have the same length. + let inline sameLengths (name1 : string) (array1 : 'T1 array) (name2 : string) (array2 : 'T2 array) = + if array1.Length <> array2.Length then + Raise.invalidArgDifferentArrayLength name1 array1.Length name2 array2.Length + + /// The arrays of a function of three arrays must have the same length. + let inline sameLengths3 + (name1 : string) + (array1 : 'T1 array) + (name2 : string) + (array2 : 'T2 array) + (name3 : string) + (array3 : 'T3 array) + = + if + array1.Length <> array2.Length + || array1.Length <> array3.Length + then + Raise.invalidArg3ArraysDifferent name1 name2 name3 array1.Length array2.Length array3.Length + +/// +/// Algorithms over arrays that the default modules and the Tuple modules share (ADR 0001, D3). +/// +/// +/// Each algorithm takes the constructor of the tuples that it produces, so that neither variant pays for a second pass. +/// The callers check their arguments first, with the exceptions of the FSharp.Core module that they mirror. +/// +module internal Arrays = + + /// The pairs of every element of `array1` with every element of `array2`. + let inline allPairs ([] tuple : 'T1 -> 'T2 -> 'R) (array1 : 'T1 array) (array2 : 'T2 array) : 'R array = + let length2 = array2.Length + let result = Array.zeroCreate (array1.Length * length2) + + for i = 0 to array1.Length - 1 do + for j = 0 to length2 - 1 do + result[i * length2 + j] <- tuple array1[i] array2[j] + + result + + /// The keys of the projection in the order of their first occurrence, each with the number of its elements. + let inline countBy + ([] tuple : 'Key -> int -> 'R) + (projection : 'T -> 'Key) + (array : 'T array) + : 'R array when 'Key : equality = + // ToLookup keeps the order of the first occurrence of each key and accepts null keys, as FSharp.Core does + let lookup = array.ToLookup ((fun item -> projection item), HashIdentity.Structural<'Key>) + let result = Array.zeroCreate lookup.Count + let mutable index = 0 + + for grouping in lookup do + result[index] <- tuple grouping.Key (grouping.Count ()) + index <- index + 1 + + result + + /// The keys of the projection in the order of their first occurrence, each with its elements in their order. + let inline groupBy + ([] tuple : 'Key -> 'T array -> 'R) + (projection : 'T -> 'Key) + (array : 'T array) + : 'R array when 'Key : equality = + let lookup = array.ToLookup ((fun item -> projection item), HashIdentity.Structural<'Key>) + let result = Array.zeroCreate lookup.Count + let mutable index = 0 + + for grouping in lookup do + result[index] <- tuple grouping.Key (grouping.ToArray ()) + index <- index + 1 + + result + + /// Every element with its index. + let inline indexed ([] tuple : int -> 'T -> 'R) (array : 'T array) : 'R array = + let result = Array.zeroCreate array.Length + + for i = 0 to result.Length - 1 do + result[i] <- tuple i array[i] + + result + + /// Every element with its successor. + let inline pairwise ([] tuple : 'T -> 'T -> 'R) (array : 'T array) : 'R array = + if array.Length < 2 then + Array.Empty () + else + let result = Array.zeroCreate (array.Length - 1) + + for i = 0 to result.Length - 1 do + result[i] <- tuple array[i] array[i + 1] + + result + + /// Splits the pairs of an array with `split` into two arrays. + let inline unzip ([] split : 'P -> struct ('T1 * 'T2)) (array : 'P array) = + let result1 = Array.zeroCreate array.Length + let result2 = Array.zeroCreate array.Length + + for i = 0 to array.Length - 1 do + let struct (first, second) = split array[i] + result1[i] <- first + result2[i] <- second + + struct (result1, result2) + + /// Splits the triples of an array with `split` into three arrays. + let inline unzip3 ([] split : 'P -> struct ('T1 * 'T2 * 'T3)) (array : 'P array) = + let result1 = Array.zeroCreate array.Length + let result2 = Array.zeroCreate array.Length + let result3 = Array.zeroCreate array.Length + + for i = 0 to array.Length - 1 do + let struct (first, second, third) = split array[i] + result1[i] <- first + result2[i] <- second + result3[i] <- third + + struct (result1, result2, result3) + + /// Pairs the elements of two arrays of the same length. + let inline zip ([] tuple : 'T1 -> 'T2 -> 'R) (array1 : 'T1 array) (array2 : 'T2 array) : 'R array = + let result = Array.zeroCreate array1.Length + + for i = 0 to result.Length - 1 do + result[i] <- tuple array1[i] array2[i] + + result + + /// Combines the elements of three arrays of the same length. + let inline zip3 + ([] tuple : 'T1 -> 'T2 -> 'T3 -> 'R) + (array1 : 'T1 array) + (array2 : 'T2 array) + (array3 : 'T3 array) + : 'R array = + let result = Array.zeroCreate array1.Length + + for i = 0 to result.Length - 1 do + result[i] <- tuple array1[i] array2[i] array3[i] + + result diff --git a/src/FSharp.Collections.Immutable/Seq.fs b/src/FSharp.Collections.Immutable/Seq.fs deleted file mode 100644 index d9826b10..00000000 --- a/src/FSharp.Collections.Immutable/Seq.fs +++ /dev/null @@ -1,32 +0,0 @@ -#if INTERACTIVE -namespace global -#else -namespace FSharp.Collections.Immutable -#endif - -[] -module Seq = - - let inline ofFlatList flatList = FlatList.toSeq flatList - let inline toFlatList seq = FlatList.ofSeq seq - - let inline ofStack stack = Stack.toSeq stack - let inline toStack seq = Stack.ofSeq seq - - let inline ofImmutableList immutableList = ImmutableList.toSeq immutableList - let inline toImmutableList seq = ImmutableList.ofSeq seq - - let inline ofQueue queue = Queue.toSeq queue - let inline toQueue queue = Queue.ofSeq queue - - let inline ofHashMap hashMap = HashMap.toSeq hashMap - let inline toHashMap hashMap = HashMap.ofSeq hashMap - - let inline ofSortedMap sortedHashMap = SortedMap.toSeq sortedHashMap - let inline toSortedMap sortedHashMap = SortedMap.ofSeq sortedHashMap - - let inline ofHashSet hashSet = HashSet.toSeq hashSet - let inline toHashSet hashSet = HashSet.ofSeq hashSet - - let inline ofSortedSet sortedSet = SortedSet.toSeq sortedSet - let inline toSortedSet sortedSet = SortedSet.ofSeq sortedSet From b798db53791c2d73f87684d3cb174b9979d71317 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 12:14:24 +0200 Subject: [PATCH 02/27] test: shared contracts of the sequence-like modules, run for `FlatList` - `SeqOps`, `SeqCreationOps` and `IndexedOps` record the functions of a module; the `SeqLike` facet is a chain of abstract classes, one file per category, and `SeqCreation` and `Indexed` complete it. - Expected values come from the FSharp.Core `List` module over the enumerated elements, expected exceptions from the mirrored module. - `FlatList` derives one test class per facet and adds its own tests: default values, shared storage, the builder, the computation expression and the `Option` and `Tuple` namespaces. Co-Authored-By: Claude Opus 5.5 --- .../Contracts/ContractBase.fs | 24 ++ .../Contracts/IndexedContract.fs | 238 +++++++++++ .../Contracts/SeqCreationContract.fs | 141 +++++++ .../SeqLike/SeqLikeAccessContract.fs | 171 ++++++++ .../Contracts/SeqLike/SeqLikeContract.fs | 43 ++ .../SeqLike/SeqLikeFoldingContract.fs | 382 +++++++++++++++++ .../SeqLike/SeqLikePartitioningContract.fs | 303 ++++++++++++++ .../SeqLike/SeqLikeRandomContract.fs | 291 +++++++++++++ .../SeqLike/SeqLikeSearchContract.fs | 279 +++++++++++++ .../SeqLike/SeqLikeSortingContract.fs | 90 ++++ .../SeqLike/SeqLikeTransformationContract.fs | 386 ++++++++++++++++++ .../Contracts/SeqOps.fs | 200 +++++++++ .../FSharp.Collections.Immutable.Tests.fsproj | 19 + .../FlatList/FlatListBuilderTests.fs | 192 +++++++++ .../FlatListComputationExpressionTests.fs | 129 ++++++ .../FlatList/FlatListContractTests.fs | 19 + .../FlatList/FlatListOps.fs | 194 +++++++++ .../FlatList/FlatListOptionTests.fs | 153 +++++++ .../FlatList/FlatListTests.fs | 348 ++++++++++++++++ .../FlatList/FlatListTupleTests.fs | 196 +++++++++ 20 files changed, 3798 insertions(+) create mode 100644 tests/FSharp.Collections.Immutable.Tests/Contracts/ContractBase.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Contracts/IndexedContract.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Contracts/SeqCreationContract.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeAccessContract.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeContract.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeFoldingContract.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikePartitioningContract.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeRandomContract.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSearchContract.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSortingContract.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeTransformationContract.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Contracts/SeqOps.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListBuilderTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListContractTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOps.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOptionTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTupleTests.fs diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/ContractBase.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/ContractBase.fs new file mode 100644 index 00000000..9a121ef0 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/ContractBase.fs @@ -0,0 +1,24 @@ +namespace FSharp.Collections.Immutable.Tests + +/// The helpers that every contract base shares. +/// The name of the module under test, for the messages of the assertions. +/// The FSharp.Core module whose exceptions the module mirrors. +/// Maps the name of a parameter of the FSharp.Core oracle to the name of the parameter of the module. +[] +type ContractBase (name : string, mirror : Mirror, paramName : string -> string) = + + /// The result of the oracle of the FSharp.Core module that the module mirrors. + member _.Mirrored (arrayOracle : unit -> 'R, listOracle : unit -> 'R) = + match mirror with + | ArrayMirror -> arrayOracle () + | ListMirror -> listOracle () + + /// Asserts that a function of the module throws what the same function of the mirrored FSharp.Core module throws. + member _.ThrowsLikeFSharpCore (arrayOracle : unit -> 'A, listOracle : unit -> 'L, actual : unit -> 'R, message : string) = + ExceptionAssert.mirrored + mirror + paramName + (arrayOracle >> ignore) + (listOracle >> ignore) + (actual >> ignore) + $"%s{name}: %s{message}" diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/IndexedContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/IndexedContract.fs new file mode 100644 index 00000000..42aea500 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/IndexedContract.fs @@ -0,0 +1,238 @@ +namespace FSharp.Collections.Immutable.Tests + +open System.Collections.Generic +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// +/// The contract of the index-based functions of FlatList, IndexedList and IIndexedList +/// (ADR 0001, Appendix A). +/// +[] +type IndexedContract<'C> (ops : IndexedOps<'C>) = + inherit ContractBase (ops.Name, ops.Mirror, ops.ParamName) + + /// An equality comparer that ignores the sign, for the functions that take a comparer. + static let absoluteEquality = { + new IEqualityComparer with + member _.Equals (left, right) = abs left = abs right + member _.GetHashCode item = abs item + } + + /// The collection of the input with several elements and the elements that it enumerates. + let several () = + let collection = ops.OfSeq Inputs.several + struct (collection, ops.ToList collection) + + [] + member _.``item returns the element at the index`` () = + let struct (collection, elements) = several () + + for index in 0 .. elements.Length - 1 do + Assert.AreEqual(List.item index elements, ops.Item index collection, $"%s{ops.Name}.item %d{index}") + + [] + member this.``item throws for an index out of range as FSharp.Core does`` () = + let struct (collection, elements) = several () + + for index in [ -1; elements.Length ] do + this.ThrowsLikeFSharpCore ( + (fun () -> Array.item index (List.toArray elements)), + (fun () -> List.item index elements), + (fun () -> ops.Item index collection), + $"item %d{index} must throw" + ) + + [] + member _.``tryItem returns the element at the index or nothing`` () = + let struct (collection, elements) = several () + + for index in -1 .. elements.Length do + Assert.AreEqual( + List.tryItem index elements |> ValueOption.ofOption, + ops.TryItem index collection, + $"%s{ops.Name}.tryItem %d{index}" + ) + + [] + member _.``indexOf returns the index of the first equal element or -1`` () = + let struct (collection, elements) = several () + + for value in [ 5; -3; 7; 1000 ] do + Assert.AreEqual( + List.tryFindIndex ((=) value) elements + |> Option.defaultValue -1, + ops.IndexOf value collection, + $"%s{ops.Name}.indexOf %d{value}" + ) + + [] + member _.``indexOfWith compares with the equality comparer`` () = + let struct (collection, elements) = several () + + for value in [ -5; 3; -12; 1000 ] do + Assert.AreEqual( + List.tryFindIndex (fun item -> abs item = abs value) elements + |> Option.defaultValue -1, + ops.IndexOfWith absoluteEquality value collection, + $"%s{ops.Name}.indexOfWith %d{value}" + ) + + [] + member _.``lastIndexOf returns the index of the last equal element or -1`` () = + let struct (collection, elements) = several () + + for value in [ 5; -3; 7; 1000 ] do + Assert.AreEqual( + List.tryFindIndexBack ((=) value) elements + |> Option.defaultValue -1, + ops.LastIndexOf value collection, + $"%s{ops.Name}.lastIndexOf %d{value}" + ) + + Assert.AreEqual(-1, ops.LastIndexOf 5 (ops.OfSeq Inputs.empty), $"%s{ops.Name}.lastIndexOf in an empty collection") + + [] + member _.``lastIndexOfWith compares with the equality comparer`` () = + let struct (collection, elements) = several () + + for value in [ -5; 3; -12; 1000 ] do + Assert.AreEqual( + List.tryFindIndexBack (fun item -> abs item = abs value) elements + |> Option.defaultValue -1, + ops.LastIndexOfWith absoluteEquality value collection, + $"%s{ops.Name}.lastIndexOfWith %d{value}" + ) + + Assert.AreEqual( + -1, + ops.LastIndexOfWith absoluteEquality 5 (ops.OfSeq Inputs.empty), + $"%s{ops.Name}.lastIndexOfWith in an empty collection" + ) + + [] + member _.``add appends the value`` () = + for input in Inputs.all do + let collection = ops.OfSeq input + + Assert.AreEqual( + ops.ToList collection @ [ 99 ], + ops.ToList (ops.Add 99 collection), + $"%s{ops.Name}.add to %A{input}" + ) + + [] + member _.``insertAt inserts the value at the index`` () = + let struct (collection, elements) = several () + + for index in [ 0; 3; elements.Length ] do + Assert.AreEqual( + List.insertAt index 99 elements, + ops.ToList (ops.InsertAt index 99 collection), + $"%s{ops.Name}.insertAt %d{index}" + ) + + [] + member this.``insertAt throws for an index out of range as FSharp.Core does`` () = + let struct (collection, elements) = several () + + for index in [ -1; elements.Length + 1 ] do + this.ThrowsLikeFSharpCore ( + (fun () -> Array.insertAt index 99 (List.toArray elements)), + (fun () -> List.insertAt index 99 elements), + (fun () -> ops.InsertAt index 99 collection), + $"insertAt %d{index} must throw" + ) + + [] + member _.``insertManyAt inserts the values at the index`` () = + let struct (collection, elements) = several () + + for index in [ 0; 3; elements.Length ] do + for values in [ []; [ 98; 99 ] ] do + Assert.AreEqual( + List.insertManyAt index values elements, + ops.ToList (ops.InsertManyAt index values collection), + $"%s{ops.Name}.insertManyAt %d{index} %A{values}" + ) + + [] + member this.``insertManyAt throws for an index out of range as FSharp.Core does`` () = + let struct (collection, elements) = several () + + for index in [ -1; elements.Length + 1 ] do + this.ThrowsLikeFSharpCore ( + (fun () -> Array.insertManyAt index [ 99 ] (List.toArray elements)), + (fun () -> List.insertManyAt index [ 99 ] elements), + (fun () -> ops.InsertManyAt index [ 99 ] collection), + $"insertManyAt %d{index} must throw" + ) + + [] + member _.``removeAt removes the element at the index`` () = + let struct (collection, elements) = several () + + for index in [ 0; 3; elements.Length - 1 ] do + Assert.AreEqual( + List.removeAt index elements, + ops.ToList (ops.RemoveAt index collection), + $"%s{ops.Name}.removeAt %d{index}" + ) + + [] + member this.``removeAt throws for an index out of range as FSharp.Core does`` () = + let struct (collection, elements) = several () + + for index in [ -1; elements.Length ] do + this.ThrowsLikeFSharpCore ( + (fun () -> Array.removeAt index (List.toArray elements)), + (fun () -> List.removeAt index elements), + (fun () -> ops.RemoveAt index collection), + $"removeAt %d{index} must throw" + ) + + [] + member _.``removeManyAt removes the elements of the range`` () = + let struct (collection, elements) = several () + + for struct (index, count) in [ struct (0, 2); struct (3, 3); struct (elements.Length - 1, 1); struct (2, 0) ] do + Assert.AreEqual( + List.removeManyAt index count elements, + ops.ToList (ops.RemoveManyAt index count collection), + $"%s{ops.Name}.removeManyAt %d{index} %d{count}" + ) + + [] + member this.``removeManyAt throws for a range out of range as FSharp.Core does`` () = + let struct (collection, elements) = several () + + for struct (index, count) in [ struct (-1, 1); struct (elements.Length - 1, 2); struct (0, -1) ] do + this.ThrowsLikeFSharpCore ( + (fun () -> Array.removeManyAt index count (List.toArray elements)), + (fun () -> List.removeManyAt index count elements), + (fun () -> ops.RemoveManyAt index count collection), + $"removeManyAt %d{index} %d{count} must throw" + ) + + [] + member _.``updateAt replaces the element at the index`` () = + let struct (collection, elements) = several () + + for index in [ 0; elements.Length - 1 ] do + Assert.AreEqual( + List.updateAt index 99 elements, + ops.ToList (ops.UpdateAt index 99 collection), + $"%s{ops.Name}.updateAt %d{index}" + ) + + [] + member this.``updateAt throws for an index out of range as FSharp.Core does`` () = + let struct (collection, elements) = several () + + for index in [ -1; elements.Length ] do + this.ThrowsLikeFSharpCore ( + (fun () -> Array.updateAt index 99 (List.toArray elements)), + (fun () -> List.updateAt index 99 elements), + (fun () -> ops.UpdateAt index 99 collection), + $"updateAt %d{index} must throw" + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqCreationContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqCreationContract.fs new file mode 100644 index 00000000..a634a381 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqCreationContract.fs @@ -0,0 +1,141 @@ +namespace FSharp.Collections.Immutable.Tests + +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// +/// The contract of the creation functions of the concrete sequence-like modules: FlatList, IndexedList, +/// Queue and Stack (ADR 0001, Appendix A). +/// +[] +type SeqCreationContract<'C> (ops : SeqCreationOps<'C>) = + inherit ContractBase (ops.Name, ops.Mirror, ops.ParamName) + + [] + member _.``empty has no elements`` () = + Assert.IsEmpty (ops.ToList (ops.Empty ()), $"%s{ops.Name}.empty must have no elements") + + [] + member _.``init creates the elements in index order`` () = + for count in [ 0; 1; 5 ] do + Assert.AreEqual( + List.init count (fun index -> index * index), + ops.ToList (ops.Init count (fun index -> index * index)), + $"%s{ops.Name}.init %d{count}" + ) + + [] + member this.``init throws for a negative count as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.init -1 id), + (fun () -> List.init -1 id), + (fun () -> ops.Init -1 id), + "init -1 must throw" + ) + + [] + member _.``ofArray copies the elements in order`` () = + for input in Inputs.all do + let source = List.toArray input + let collection = ops.OfArray source + + if source.Length > 0 then + source[0] <- 1000 + + Assert.AreEqual(input, ops.ToList collection, $"%s{ops.Name}.ofArray must copy %A{input}") + + [] + member this.``ofArray throws for a null array as FSharp.Core does`` () = + let source = Unchecked.defaultof + + this.ThrowsLikeFSharpCore ( + (fun () -> Array.copy source), + (fun () -> List.ofArray source), + (fun () -> ops.OfArray source), + "ofArray of a null array must throw" + ) + + [] + member _.``ofList keeps the elements in order`` () = + for input in Inputs.all do + Assert.AreEqual(input, ops.ToList (ops.OfList input), $"%s{ops.Name}.ofList of %A{input}") + + [] + member _.``ofReadOnlySpan copies the elements in order`` () = + for input in Inputs.all do + Assert.AreEqual( + input, + ops.ToList (ops.OfReadOnlySpan (List.toArray input)), + $"%s{ops.Name}.ofReadOnlySpan of %A{input}" + ) + + [] + member _.``ofSeq keeps the elements in order`` () = + for input in Inputs.all do + Assert.AreEqual(input, ops.ToList (ops.OfSeq input), $"%s{ops.Name}.ofSeq of the list %A{input}") + + Assert.AreEqual( + input, + ops.ToList (ops.OfSeq (List.toArray input)), + $"%s{ops.Name}.ofSeq of the array %A{input}" + ) + + Assert.AreEqual( + input, + ops.ToList (ops.OfSeq (Seq.map id input)), + $"%s{ops.Name}.ofSeq of a lazy sequence of %A{input}" + ) + + [] + member this.``ofSeq throws for a null sequence as FSharp.Core does`` () = + let source = Unchecked.defaultof> + + this.ThrowsLikeFSharpCore ( + (fun () -> Array.ofSeq source), + (fun () -> List.ofSeq source), + (fun () -> ops.OfSeq source), + "ofSeq of a null sequence must throw" + ) + + [] + member _.``ofSpan copies the elements in order`` () = + for input in Inputs.all do + Assert.AreEqual(input, ops.ToList (ops.OfSpan (List.toArray input)), $"%s{ops.Name}.ofSpan of %A{input}") + + [] + member _.``replicate repeats the value`` () = + for count in [ 0; 1; 4 ] do + Assert.AreEqual( + List.replicate count 7, + ops.ToList (ops.Replicate count 7), + $"%s{ops.Name}.replicate %d{count}" + ) + + [] + member this.``replicate throws for a negative count as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.replicate -1 7), + (fun () -> List.replicate -1 7), + (fun () -> ops.Replicate -1 7), + "replicate -1 must throw" + ) + + [] + member _.``singleton has one element`` () = + Assert.AreEqual([ 42 ], ops.ToList (ops.Singleton 42), $"%s{ops.Name}.singleton must have one element") + + [] + member _.``unfold generates the elements until the generator stops`` () = + let generator (state : int) = + if state > 0 then + ValueSome (struct (state * 10, state - 1)) + else + ValueNone + let generatorOption (state : int) = if state > 0 then Some (state * 10, state - 1) else None + + for start in [ 0; 1; 5 ] do + Assert.AreEqual( + List.unfold generatorOption start, + ops.ToList (ops.Unfold generator start), + $"%s{ops.Name}.unfold from %d{start}" + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeAccessContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeAccessContract.fs new file mode 100644 index 00000000..f7d25c42 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeAccessContract.fs @@ -0,0 +1,171 @@ +namespace FSharp.Collections.Immutable.Tests + +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// The helpers that the contract bases of the sequence-like modules share. +/// +/// The contract tests compute their expectations with the FSharp.Core List module over the elements in the order +/// in which the collection enumerates them, so one test body serves FIFO, LIFO and indexed collections (ADR 0001, D8). +/// +[] +type SeqContractBase<'C> (ops : SeqOps<'C>) = + inherit ContractBase (ops.Name, ops.Mirror, ops.ParamName) + + /// Runs a check on every given input: the collection of its elements and the elements that the collection enumerates. + member _.ForEach (inputs : int list list, check : 'C -> int list -> unit) = + for input in inputs do + let collection = ops.OfSeq input + check collection (ops.ToList collection) + +/// The contract of the properties and the access functions of a sequence-like module. +[] +type SeqLikeAccessContract<'C> (ops : SeqOps<'C>) = + inherit SeqContractBase<'C> (ops) + + // ---- Properties ---- + + [] + member this.``isEmpty is true only for a collection without elements`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual(List.isEmpty elements, ops.IsEmpty collection, $"%s{ops.Name}.isEmpty of %A{elements}") + ) + + [] + member this.``length counts the elements`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual(List.length elements, ops.Length collection, $"%s{ops.Name}.length of %A{elements}") + ) + + // ---- Access ---- + + [] + member this.``exactlyOne returns the only element`` () = + let collection = ops.OfSeq Inputs.singleton + Assert.AreEqual( + List.exactlyOne Inputs.singleton, + ops.ExactlyOne collection, + $"%s{ops.Name}.exactlyOne of one element" + ) + + [] + member this.``exactlyOne throws for no element or several elements as FSharp.Core does`` () = + for input in [ Inputs.empty; Inputs.several ] do + this.ThrowsLikeFSharpCore ( + (fun () -> Array.exactlyOne (List.toArray input)), + (fun () -> List.exactlyOne input), + (fun () -> ops.ExactlyOne (ops.OfSeq input)), + $"exactlyOne of %A{input} must throw" + ) + + [] + member this.``head returns the first element`` () = + this.ForEach ( + Inputs.nonEmpty, + fun collection elements -> + Assert.AreEqual(List.head elements, ops.Head collection, $"%s{ops.Name}.head of %A{elements}") + ) + + [] + member this.``head throws for an empty collection as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.head Array.empty), + (fun () -> List.head List.empty), + (fun () -> ops.Head (ops.OfSeq Inputs.empty)), + "head of an empty collection must throw" + ) + + [] + member this.``last returns the last element`` () = + this.ForEach ( + Inputs.nonEmpty, + fun collection elements -> + Assert.AreEqual(List.last elements, ops.Last collection, $"%s{ops.Name}.last of %A{elements}") + ) + + [] + member this.``last throws for an empty collection as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.last Array.empty), + (fun () -> List.last List.empty), + (fun () -> ops.Last (ops.OfSeq Inputs.empty)), + "last of an empty collection must throw" + ) + + [] + member this.``tail returns the elements after the first one`` () = + this.ForEach ( + Inputs.nonEmpty, + fun collection elements -> + Assert.AreEqual( + List.tail elements, + ops.ToList (ops.Tail collection), + $"%s{ops.Name}.tail of %A{elements}" + ) + ) + + [] + member this.``tail throws for an empty collection as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.tail Array.empty), + (fun () -> List.tail List.empty), + (fun () -> ops.Tail (ops.OfSeq Inputs.empty)), + "tail of an empty collection must throw" + ) + + [] + member this.``tryExactlyOne returns the only element or nothing`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.tryExactlyOne elements |> ValueOption.ofOption, + ops.TryExactlyOne collection, + $"%s{ops.Name}.tryExactlyOne of %A{elements}" + ) + ) + + [] + member this.``tryHead returns the first element or nothing`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.tryHead elements |> ValueOption.ofOption, + ops.TryHead collection, + $"%s{ops.Name}.tryHead of %A{elements}" + ) + ) + + [] + member this.``tryLast returns the last element or nothing`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.tryLast elements |> ValueOption.ofOption, + ops.TryLast collection, + $"%s{ops.Name}.tryLast of %A{elements}" + ) + ) + + [] + member this.``tryTail returns the elements after the first one or nothing`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + let expected = + match elements with + | [] -> ValueNone + | _ :: rest -> ValueSome rest + + Assert.AreEqual( + expected, + ops.TryTail collection |> ValueOption.map ops.ToList, + $"%s{ops.Name}.tryTail of %A{elements}" + ) + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeContract.fs new file mode 100644 index 00000000..45fc4aa3 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeContract.fs @@ -0,0 +1,43 @@ +namespace FSharp.Collections.Immutable.Tests + +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// +/// The contract of the functions that every sequence-like module provides: FlatList, IndexedList, +/// IIndexedList, Queue, IQueue, Stack and IStack (ADR 0001, Appendix A). +/// +/// +/// The facet is a chain of abstract classes, one file per category; a collection derives one test class from this last one. +/// +[] +type SeqLikeContract<'C> (ops : SeqOps<'C>) = + inherit SeqLikeRandomContract<'C> (ops) + + [] + member this.``toArray returns the elements in order`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + CollectionAssert.AreEqual (List.toArray elements, ops.ToArray collection, $"%s{ops.Name}.toArray of %A{elements}") + ) + + [] + member _.``toArray returns a new array`` () = + let collection = ops.OfSeq Inputs.several + let array = ops.ToArray collection + array[0] <- 1000 + + Assert.AreEqual( + Inputs.several, + ops.ToList collection, + $"%s{ops.Name}.toArray must return a copy that does not change the collection" + ) + + [] + member this.``toSeq returns the elements in order`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual(elements, List.ofSeq (ops.ToSeq collection), $"%s{ops.Name}.toSeq of %A{elements}") + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeFoldingContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeFoldingContract.fs new file mode 100644 index 00000000..193e8365 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeFoldingContract.fs @@ -0,0 +1,382 @@ +namespace FSharp.Collections.Immutable.Tests + +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// The contract of the folding functions of a sequence-like module. +[] +type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = + inherit SeqLikeTransformationContract<'C> (ops) + + /// A folder whose state records the order in which it saw the elements. + static let record (state : int list) (item : int) = item :: state + + /// A folder from the end whose state records the order in which it saw the elements. + static let recordBack (item : int) (state : int list) = item :: state + + [] + member _.``average returns the average of the elements`` () = + for input in [ [ 1.5 ]; [ 1.0; 2.0; 4.5; -3.0 ] ] do + Assert.AreEqual(List.average input, ops.Average input, $"%s{ops.Name}.average of %A{input}") + + [] + member this.``average throws for an empty collection as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.average Array.empty), + (fun () -> List.average List.empty), + (fun () -> ops.Average []), + "average of an empty collection must throw" + ) + + [] + member this.``averageBy returns the average of the projections`` () = + this.ForEach ( + Inputs.nonEmpty, + fun collection elements -> + Assert.AreEqual( + List.averageBy float elements, + ops.AverageBy float collection, + $"%s{ops.Name}.averageBy of %A{elements}" + ) + ) + + [] + member this.``averageBy throws for an empty collection as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.averageBy float Array.empty), + (fun () -> List.averageBy float List.empty), + (fun () -> ops.AverageBy float (ops.OfSeq Inputs.empty)), + "averageBy of an empty collection must throw" + ) + + [] + member _.``compareWith compares element by element and then by length`` () = + for first in Inputs.all do + for second in Inputs.all @ [ [ 5; -3 ]; [ 5; 0 ] ] do + let collection = ops.OfSeq first + let other = ops.OfSeq second + + Assert.AreEqual( + List.compareWith compare (ops.ToList collection) (ops.ToList other) + |> sign, + ops.CompareWith compare collection other |> sign, + $"%s{ops.Name}.compareWith of %A{first} and %A{second}" + ) + + [] + member this.``fold visits the elements from the first one`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.fold record [] elements, + ops.Fold record [] collection, + $"%s{ops.Name}.fold over %A{elements}" + ) + ) + + [] + member _.``fold2 visits the pairs from the first index`` () = + let collection = ops.OfSeq Inputs.several + let other = ops.OfSeq Inputs.other + let folder (state : int list) a b = a * 100 + b :: state + + Assert.AreEqual( + List.fold2 folder [] (ops.ToList collection) (ops.ToList other), + ops.Fold2 folder [] collection other, + $"%s{ops.Name}.fold2 over two collections" + ) + + [] + member this.``fold2 throws for different lengths as FSharp.Core does`` () = + let folder (state : int list) (_ : int) (_ : int) = state + + this.ThrowsLikeFSharpCore ( + (fun () -> Array.fold2 folder [] (List.toArray Inputs.singleton) (List.toArray Inputs.several)), + (fun () -> List.fold2 folder [] Inputs.singleton Inputs.several), + (fun () -> ops.Fold2 folder [] (ops.OfSeq Inputs.singleton) (ops.OfSeq Inputs.several)), + "fold2 of collections of different lengths must throw" + ) + + [] + member this.``foldBack visits the elements from the last one`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.foldBack recordBack elements [], + ops.FoldBack recordBack collection [], + $"%s{ops.Name}.foldBack over %A{elements}" + ) + ) + + [] + member _.``foldBack2 visits the pairs from the last index`` () = + let collection = ops.OfSeq Inputs.several + let other = ops.OfSeq Inputs.other + let folder a b (state : int list) = a * 100 + b :: state + + Assert.AreEqual( + List.foldBack2 folder (ops.ToList collection) (ops.ToList other) [], + ops.FoldBack2 folder collection other [], + $"%s{ops.Name}.foldBack2 over two collections" + ) + + [] + member this.``foldBack2 throws for different lengths as FSharp.Core does`` () = + let folder (_ : int) (_ : int) (state : int list) = state + + this.ThrowsLikeFSharpCore ( + (fun () -> Array.foldBack2 folder (List.toArray Inputs.singleton) (List.toArray Inputs.several) []), + (fun () -> List.foldBack2 folder Inputs.singleton Inputs.several []), + (fun () -> ops.FoldBack2 folder (ops.OfSeq Inputs.singleton) (ops.OfSeq Inputs.several) []), + "foldBack2 of collections of different lengths must throw" + ) + + [] + member this.``iter visits every element in order`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + let visited = ResizeArray() + ops.Iter visited.Add collection + Assert.AreEqual(elements, List.ofSeq visited, $"%s{ops.Name}.iter over %A{elements}") + ) + + [] + member _.``iter2 visits every pair in order`` () = + let collection = ops.OfSeq Inputs.several + let other = ops.OfSeq Inputs.other + let visited = ResizeArray() + ops.Iter2 (fun a b -> visited.Add (struct (a, b))) collection other + + Assert.AreEqual( + List.zip (ops.ToList collection) (ops.ToList other) + |> List.map (fun (a, b) -> struct (a, b)), + List.ofSeq visited, + $"%s{ops.Name}.iter2 over two collections" + ) + + [] + member this.``iter2 throws for different lengths as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.iter2 (fun _ _ -> ()) (List.toArray Inputs.singleton) (List.toArray Inputs.several)), + (fun () -> List.iter2 (fun _ _ -> ()) Inputs.singleton Inputs.several), + (fun () -> ops.Iter2 (fun _ _ -> ()) (ops.OfSeq Inputs.singleton) (ops.OfSeq Inputs.several)), + "iter2 of collections of different lengths must throw" + ) + + [] + member this.``iteri visits every element with its index`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + let visited = ResizeArray() + ops.Iteri (fun index item -> visited.Add (struct (index, item))) collection + + Assert.AreEqual( + List.indexed elements + |> List.map (fun (index, item) -> struct (index, item)), + List.ofSeq visited, + $"%s{ops.Name}.iteri over %A{elements}" + ) + ) + + [] + member _.``iteri2 visits every pair with its index`` () = + let collection = ops.OfSeq Inputs.several + let other = ops.OfSeq Inputs.other + let visited = ResizeArray() + ops.Iteri2 (fun index a b -> visited.Add (struct (index, a, b))) collection other + + Assert.AreEqual( + List.zip (ops.ToList collection) (ops.ToList other) + |> List.mapi (fun index (a, b) -> struct (index, a, b)), + List.ofSeq visited, + $"%s{ops.Name}.iteri2 over two collections" + ) + + [] + member this.``iteri2 throws for different lengths as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.iteri2 (fun _ _ _ -> ()) (List.toArray Inputs.singleton) (List.toArray Inputs.several)), + (fun () -> List.iteri2 (fun _ _ _ -> ()) Inputs.singleton Inputs.several), + (fun () -> ops.Iteri2 (fun _ _ _ -> ()) (ops.OfSeq Inputs.singleton) (ops.OfSeq Inputs.several)), + "iteri2 of collections of different lengths must throw" + ) + + [] + member this.``mapFold maps the elements and threads the state from the first one`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + let expectedResults, expectedState = List.mapFold (fun state item -> item * state, state + 1) 1 elements + let struct (results, state) = + ops.MapFold (fun state item -> struct (item * state, state + 1)) 1 collection + Assert.AreEqual(expectedResults, results, $"%s{ops.Name}.mapFold results over %A{elements}") + Assert.AreEqual(expectedState, state, $"%s{ops.Name}.mapFold state over %A{elements}") + ) + + [] + member this.``mapFoldBack maps the elements and threads the state from the last one`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + let expectedResults, expectedState = + List.mapFoldBack (fun item state -> item * state, state + 1) elements 1 + + let struct (results, state) = + ops.MapFoldBack (fun item state -> struct (item * state, state + 1)) collection 1 + Assert.AreEqual(expectedResults, results, $"%s{ops.Name}.mapFoldBack results over %A{elements}") + Assert.AreEqual(expectedState, state, $"%s{ops.Name}.mapFoldBack state over %A{elements}") + ) + + [] + member this.``max returns the greatest element`` () = + this.ForEach ( + Inputs.nonEmpty, + fun collection elements -> + Assert.AreEqual(List.max elements, ops.Max collection, $"%s{ops.Name}.max of %A{elements}") + ) + + [] + member this.``max throws for an empty collection as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.max Array.empty), + (fun () -> List.max List.empty), + (fun () -> ops.Max (ops.OfSeq Inputs.empty)), + "max of an empty collection must throw" + ) + + [] + member this.``maxBy returns the first element with the greatest projection`` () = + this.ForEach ( + Inputs.nonEmpty, + fun collection elements -> + Assert.AreEqual(List.maxBy abs elements, ops.MaxBy abs collection, $"%s{ops.Name}.maxBy of %A{elements}") + ) + + [] + member this.``maxBy throws for an empty collection as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.maxBy abs Array.empty), + (fun () -> List.maxBy abs List.empty), + (fun () -> ops.MaxBy abs (ops.OfSeq Inputs.empty)), + "maxBy of an empty collection must throw" + ) + + [] + member this.``min returns the smallest element`` () = + this.ForEach ( + Inputs.nonEmpty, + fun collection elements -> + Assert.AreEqual(List.min elements, ops.Min collection, $"%s{ops.Name}.min of %A{elements}") + ) + + [] + member this.``min throws for an empty collection as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.min Array.empty), + (fun () -> List.min List.empty), + (fun () -> ops.Min (ops.OfSeq Inputs.empty)), + "min of an empty collection must throw" + ) + + [] + member this.``minBy returns the first element with the smallest projection`` () = + this.ForEach ( + Inputs.nonEmpty, + fun collection elements -> + Assert.AreEqual(List.minBy abs elements, ops.MinBy abs collection, $"%s{ops.Name}.minBy of %A{elements}") + ) + + [] + member this.``minBy throws for an empty collection as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.minBy abs Array.empty), + (fun () -> List.minBy abs List.empty), + (fun () -> ops.MinBy abs (ops.OfSeq Inputs.empty)), + "minBy of an empty collection must throw" + ) + + [] + member this.``reduce combines the elements from the first one`` () = + this.ForEach ( + Inputs.nonEmpty, + fun collection elements -> + Assert.AreEqual( + List.reduce (fun a b -> a * 2 - b) elements, + ops.Reduce (fun a b -> a * 2 - b) collection, + $"%s{ops.Name}.reduce of %A{elements}" + ) + ) + + [] + member this.``reduce throws for an empty collection as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.reduce (+) Array.empty), + (fun () -> List.reduce (+) List.empty), + (fun () -> ops.Reduce (+) (ops.OfSeq Inputs.empty)), + "reduce of an empty collection must throw" + ) + + [] + member this.``reduceBack combines the elements from the last one`` () = + this.ForEach ( + Inputs.nonEmpty, + fun collection elements -> + Assert.AreEqual( + List.reduceBack (fun a b -> a * 2 - b) elements, + ops.ReduceBack (fun a b -> a * 2 - b) collection, + $"%s{ops.Name}.reduceBack of %A{elements}" + ) + ) + + [] + member this.``reduceBack throws for an empty collection as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.reduceBack (+) Array.empty), + (fun () -> List.reduceBack (+) List.empty), + (fun () -> ops.ReduceBack (+) (ops.OfSeq Inputs.empty)), + "reduceBack of an empty collection must throw" + ) + + [] + member this.``scan returns every state from the first element`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.scan record [] elements, + ops.Scan record [] collection, + $"%s{ops.Name}.scan over %A{elements}" + ) + ) + + [] + member this.``scanBack returns every state from the last element`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.scanBack recordBack elements [], + ops.ScanBack recordBack collection [], + $"%s{ops.Name}.scanBack over %A{elements}" + ) + ) + + [] + member this.``sum adds the elements`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual(List.sum elements, ops.Sum collection, $"%s{ops.Name}.sum of %A{elements}") + ) + + [] + member this.``sumBy adds the projections`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual(List.sumBy abs elements, ops.SumBy abs collection, $"%s{ops.Name}.sumBy of %A{elements}") + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikePartitioningContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikePartitioningContract.fs new file mode 100644 index 00000000..c0675609 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikePartitioningContract.fs @@ -0,0 +1,303 @@ +namespace FSharp.Collections.Immutable.Tests + +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// The contract of the partitioning and grouping functions of a sequence-like module. +[] +type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = + inherit SeqLikeFoldingContract<'C> (ops) + + /// The counts to try on a collection of the given length: negative, zero, inside and the whole length. + static let countsFor (length : int) = List.distinct [ -1; 0; min 3 length; length ] + + [] + member this.``countBy counts the elements of each key in the order of the first occurrence`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.countBy (fun item -> item % 3) elements + |> List.map (fun (key, count) -> struct (key, count)), + ops.CountBy (fun item -> item % 3) collection, + $"%s{ops.Name}.countBy of %A{elements}" + ) + ) + + [] + member this.``distinct keeps the first occurrence of each element`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.distinct elements, + ops.ToList (ops.Distinct collection), + $"%s{ops.Name}.distinct of %A{elements}" + ) + ) + + [] + member this.``distinctBy keeps the first element of each key`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.distinctBy abs elements, + ops.ToList (ops.DistinctBy abs collection), + $"%s{ops.Name}.distinctBy of %A{elements}" + ) + ) + + [] + member this.``except removes the excluded items and the duplicates`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.except [ 5; 12 ] elements, + ops.ToList (ops.Except [ 5; 12 ] collection), + $"%s{ops.Name}.except of %A{elements}" + ) + ) + + [] + member this.``except throws for null items to exclude as FSharp.Core does`` () = + let excluded = Unchecked.defaultof> + + this.ThrowsLikeFSharpCore ( + (fun () -> Array.except excluded (List.toArray Inputs.several)), + (fun () -> List.except excluded Inputs.several), + (fun () -> ops.Except excluded (ops.OfSeq Inputs.several)), + "except of null items must throw" + ) + + [] + member this.``groupBy groups the elements by key in the order of the first occurrence`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.groupBy (fun item -> item % 3) elements + |> List.map (fun (key, items) -> struct (key, items)), + ops.GroupBy (fun item -> item % 3) collection, + $"%s{ops.Name}.groupBy of %A{elements}" + ) + ) + + [] + member this.``partition splits the elements by the predicate`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + let expectedAccepted, expectedRejected = List.partition (fun item -> item > 0) elements + let struct (accepted, rejected) = ops.Partition (fun item -> item > 0) collection + Assert.AreEqual( + expectedAccepted, + ops.ToList accepted, + $"%s{ops.Name}.partition accepted of %A{elements}" + ) + Assert.AreEqual( + expectedRejected, + ops.ToList rejected, + $"%s{ops.Name}.partition rejected of %A{elements}" + ) + ) + + [] + member this.``partitionWith splits the elements by the choice`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + let partitioner (item : int) = + if item > 0 then + Choice1Of2 item + else + Choice2Of2 (string item) + let expectedFirst, expectedSecond = List.partitionWith partitioner elements + let struct (first, second) = ops.PartitionWith partitioner collection + Assert.AreEqual(expectedFirst, first, $"%s{ops.Name}.partitionWith first choices of %A{elements}") + Assert.AreEqual(expectedSecond, second, $"%s{ops.Name}.partitionWith second choices of %A{elements}") + ) + + [] + member this.``skip drops the first elements`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + for count in countsFor elements.Length do + Assert.AreEqual( + List.skip (max count 0) elements, + ops.ToList (ops.Skip count collection), + $"%s{ops.Name}.skip %d{count} of %A{elements}" + ) + ) + + [] + member this.``skip throws for a count beyond the length as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.skip 9 (List.toArray Inputs.several)), + (fun () -> List.skip 9 Inputs.several), + (fun () -> ops.Skip 9 (ops.OfSeq Inputs.several)), + "skip beyond the length must throw" + ) + + [] + member this.``skipLast drops the last elements`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + for count in countsFor elements.Length do + Assert.AreEqual( + List.take (elements.Length - max count 0) elements, + ops.ToList (ops.SkipLast count collection), + $"%s{ops.Name}.skipLast %d{count} of %A{elements}" + ) + ) + + [] + member this.``skipLast throws for a count beyond the length as skip does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.skip 9 (List.toArray Inputs.several)), + (fun () -> List.skip 9 Inputs.several), + (fun () -> ops.SkipLast 9 (ops.OfSeq Inputs.several)), + "skipLast beyond the length must throw" + ) + + [] + member this.``skipWhile drops the first elements that satisfy the predicate`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + for limit in [ -10; 6; 100 ] do + Assert.AreEqual( + List.skipWhile (fun item -> item < limit) elements, + ops.ToList (ops.SkipWhile (fun item -> item < limit) collection), + $"%s{ops.Name}.skipWhile below %d{limit} of %A{elements}" + ) + ) + + [] + member this.``splitAt splits the elements at the index`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + for index in List.distinct [ 0; min 3 elements.Length; elements.Length ] do + let expectedBefore, expectedAfter = List.splitAt index elements + let struct (before, after) = ops.SplitAt index collection + + Assert.AreEqual( + expectedBefore, + ops.ToList before, + $"%s{ops.Name}.splitAt %d{index} first part of %A{elements}" + ) + + Assert.AreEqual( + expectedAfter, + ops.ToList after, + $"%s{ops.Name}.splitAt %d{index} second part of %A{elements}" + ) + ) + + [] + member this.``splitAt throws for a negative index or one beyond the length as FSharp.Core does`` () = + for index in [ -1; 9 ] do + this.ThrowsLikeFSharpCore ( + (fun () -> Array.splitAt index (List.toArray Inputs.several)), + (fun () -> List.splitAt index Inputs.several), + (fun () -> ops.SplitAt index (ops.OfSeq Inputs.several)), + $"splitAt %d{index} must throw" + ) + + [] + member this.``splitInto splits the elements into parts of nearly equal length`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + for count in [ 1; 3; 8; 9 ] do + Assert.AreEqual( + List.splitInto count elements, + ops.SplitInto count collection, + $"%s{ops.Name}.splitInto %d{count} of %A{elements}" + ) + ) + + [] + member this.``splitInto throws for a count that is not positive as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.splitInto 0 (List.toArray Inputs.several)), + (fun () -> List.splitInto 0 Inputs.several), + (fun () -> ops.SplitInto 0 (ops.OfSeq Inputs.several)), + "splitInto 0 must throw" + ) + + [] + member this.``take returns the first elements`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + for count in List.distinct [ 0; min 3 elements.Length; elements.Length ] do + Assert.AreEqual( + List.take count elements, + ops.ToList (ops.Take count collection), + $"%s{ops.Name}.take %d{count} of %A{elements}" + ) + ) + + [] + member this.``take throws for a negative count or one beyond the length as FSharp.Core does`` () = + for count in [ -1; 9 ] do + this.ThrowsLikeFSharpCore ( + (fun () -> Array.take count (List.toArray Inputs.several)), + (fun () -> List.take count Inputs.several), + (fun () -> ops.Take count (ops.OfSeq Inputs.several)), + $"take %d{count} must throw" + ) + + [] + member this.``takeLast returns the last elements`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + for count in List.distinct [ 0; min 3 elements.Length; elements.Length ] do + Assert.AreEqual( + List.skip (elements.Length - count) elements, + ops.ToList (ops.TakeLast count collection), + $"%s{ops.Name}.takeLast %d{count} of %A{elements}" + ) + ) + + [] + member this.``takeLast throws for a negative count or one beyond the length as take does`` () = + for count in [ -1; 9 ] do + this.ThrowsLikeFSharpCore ( + (fun () -> Array.take count (List.toArray Inputs.several)), + (fun () -> List.take count Inputs.several), + (fun () -> ops.TakeLast count (ops.OfSeq Inputs.several)), + $"takeLast %d{count} must throw" + ) + + [] + member this.``takeWhile returns the first elements that satisfy the predicate`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + for limit in [ -10; 6; 100 ] do + Assert.AreEqual( + List.takeWhile (fun item -> item < limit) elements, + ops.ToList (ops.TakeWhile (fun item -> item < limit) collection), + $"%s{ops.Name}.takeWhile below %d{limit} of %A{elements}" + ) + ) + + [] + member this.``truncate returns at most the given number of first elements`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + for count in countsFor elements.Length @ [ elements.Length + 5 ] do + Assert.AreEqual( + List.truncate count elements, + ops.ToList (ops.Truncate count collection), + $"%s{ops.Name}.truncate %d{count} of %A{elements}" + ) + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeRandomContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeRandomContract.fs new file mode 100644 index 00000000..f2a30a24 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeRandomContract.fs @@ -0,0 +1,291 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// The contract of the random functions of a sequence-like module. +/// +/// With a seeded generator or a fixed randomizer, a function returns what the function of the mirrored FSharp.Core module +/// returns for the same elements; with , the tests check the properties of the result. +/// +[] +type SeqLikeRandomContract<'C> (ops : SeqOps<'C>) = + inherit SeqLikeSortingContract<'C> (ops) + + static let seed = 42 + + /// A randomizer that returns the same sequence of numbers for every instance. + static let randomizer () = + let random = Random seed + fun () -> random.NextDouble () + + /// The elements of a result as a sorted list, for the comparison of multisets. + static let sorted (items : int list) = List.sort items + + [] + member this.``randomChoice returns an element`` () = + this.ForEach ( + Inputs.nonEmpty, + fun collection elements -> + let chosen = ops.RandomChoice collection + Assert.Contains (chosen, elements, $"%s{ops.Name}.randomChoice must return an element of %A{elements}") + ) + + [] + member this.``randomChoice throws for an empty collection as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.randomChoice Array.empty), + (fun () -> List.randomChoice List.empty), + (fun () -> ops.RandomChoice (ops.OfSeq Inputs.empty)), + "randomChoice of an empty collection must throw" + ) + + [] + member this.``randomChoiceBy chooses as FSharp.Core does with the same randomizer`` () = + this.ForEach ( + Inputs.nonEmpty, + fun collection elements -> + let expected = + this.Mirrored ( + (fun () -> Array.randomChoiceBy (randomizer ()) (List.toArray elements)), + fun () -> List.randomChoiceBy (randomizer ()) elements + ) + + Assert.AreEqual( + expected, + ops.RandomChoiceBy (randomizer ()) collection, + $"%s{ops.Name}.randomChoiceBy of %A{elements}" + ) + ) + + [] + member this.``randomChoiceBy throws for a randomizer outside the range as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.randomChoiceBy (fun () -> 1.0) (List.toArray Inputs.several)), + (fun () -> List.randomChoiceBy (fun () -> 1.0) Inputs.several), + (fun () -> ops.RandomChoiceBy (fun () -> 1.0) (ops.OfSeq Inputs.several)), + "randomChoiceBy with a randomizer that returns 1.0 must throw" + ) + + [] + member this.``randomChoiceWith chooses as FSharp.Core does with the same generator`` () = + this.ForEach ( + Inputs.nonEmpty, + fun collection elements -> + let expected = + this.Mirrored ( + (fun () -> Array.randomChoiceWith (Random seed) (List.toArray elements)), + fun () -> List.randomChoiceWith (Random seed) elements + ) + + Assert.AreEqual( + expected, + ops.RandomChoiceWith (Random seed) collection, + $"%s{ops.Name}.randomChoiceWith of %A{elements}" + ) + ) + + [] + member this.``randomChoiceWith throws for a null generator as FSharp.Core does`` () = + let random = Unchecked.defaultof + + this.ThrowsLikeFSharpCore ( + (fun () -> Array.randomChoiceWith random (List.toArray Inputs.several)), + (fun () -> List.randomChoiceWith random Inputs.several), + (fun () -> ops.RandomChoiceWith random (ops.OfSeq Inputs.several)), + "randomChoiceWith of a null generator must throw" + ) + + [] + member this.``randomChoices returns the given number of elements`` () = + this.ForEach ( + Inputs.nonEmpty, + fun collection elements -> + let chosen = ops.ToList (ops.RandomChoices 20 collection) + Assert.HasCount (20, chosen, $"%s{ops.Name}.randomChoices 20 must return 20 elements") + + for item in chosen do + Assert.Contains (item, elements, $"%s{ops.Name}.randomChoices must return elements of %A{elements}") + ) + + [] + member this.``randomChoices throws for a negative count or an empty collection as FSharp.Core does`` () = + for struct (count, input) in [ struct (-1, Inputs.several); struct (3, Inputs.empty) ] do + this.ThrowsLikeFSharpCore ( + (fun () -> Array.randomChoices count (List.toArray input)), + (fun () -> List.randomChoices count input), + (fun () -> ops.RandomChoices count (ops.OfSeq input)), + $"randomChoices %d{count} of %A{input} must throw" + ) + + [] + member this.``randomChoicesBy chooses as FSharp.Core does with the same randomizer`` () = + this.ForEach ( + Inputs.nonEmpty, + fun collection elements -> + let expected = + this.Mirrored ( + (fun () -> + Array.randomChoicesBy (randomizer ()) 5 (List.toArray elements) + |> List.ofArray + ), + fun () -> List.randomChoicesBy (randomizer ()) 5 elements + ) + + Assert.AreEqual( + expected, + ops.ToList (ops.RandomChoicesBy (randomizer ()) 5 collection), + $"%s{ops.Name}.randomChoicesBy of %A{elements}" + ) + ) + + [] + member this.``randomChoicesWith chooses as FSharp.Core does with the same generator`` () = + this.ForEach ( + Inputs.nonEmpty, + fun collection elements -> + let expected = + this.Mirrored ( + (fun () -> + Array.randomChoicesWith (Random seed) 5 (List.toArray elements) + |> List.ofArray + ), + fun () -> List.randomChoicesWith (Random seed) 5 elements + ) + + Assert.AreEqual( + expected, + ops.ToList (ops.RandomChoicesWith (Random seed) 5 collection), + $"%s{ops.Name}.randomChoicesWith of %A{elements}" + ) + ) + + [] + member this.``randomSample returns elements at distinct indices`` () = + this.ForEach ( + Inputs.nonEmpty, + fun collection elements -> + let count = min 3 elements.Length + let sample = ops.ToList (ops.RandomSample count collection) + Assert.HasCount (count, sample, $"%s{ops.Name}.randomSample %d{count} must return %d{count} elements") + + // Every sampled element must be available in the input as often as it is sampled + let remaining = ResizeArray elements + + for item in sample do + Assert.IsTrue ( + remaining.Remove item, + $"%s{ops.Name}.randomSample must take each element of %A{elements} once at most" + ) + ) + + [] + member this.``randomSample throws for a negative count or one beyond the length as FSharp.Core does`` () = + for count in [ -1; 9 ] do + this.ThrowsLikeFSharpCore ( + (fun () -> Array.randomSample count (List.toArray Inputs.several)), + (fun () -> List.randomSample count Inputs.several), + (fun () -> ops.RandomSample count (ops.OfSeq Inputs.several)), + $"randomSample %d{count} must throw" + ) + + [] + member this.``randomSampleBy samples as FSharp.Core does with the same randomizer`` () = + this.ForEach ( + Inputs.nonEmpty, + fun collection elements -> + let count = min 3 elements.Length + + let expected = + this.Mirrored ( + (fun () -> + Array.randomSampleBy (randomizer ()) count (List.toArray elements) + |> List.ofArray + ), + fun () -> List.randomSampleBy (randomizer ()) count elements + ) + + Assert.AreEqual( + expected, + ops.ToList (ops.RandomSampleBy (randomizer ()) count collection), + $"%s{ops.Name}.randomSampleBy of %A{elements}" + ) + ) + + [] + member this.``randomSampleWith samples as FSharp.Core does with the same generator`` () = + this.ForEach ( + Inputs.nonEmpty, + fun collection elements -> + let count = min 3 elements.Length + + let expected = + this.Mirrored ( + (fun () -> + Array.randomSampleWith (Random seed) count (List.toArray elements) + |> List.ofArray + ), + fun () -> List.randomSampleWith (Random seed) count elements + ) + + Assert.AreEqual( + expected, + ops.ToList (ops.RandomSampleWith (Random seed) count collection), + $"%s{ops.Name}.randomSampleWith of %A{elements}" + ) + ) + + [] + member this.``randomShuffle returns a permutation of the elements`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + sorted elements, + sorted (ops.ToList (ops.RandomShuffle collection)), + $"%s{ops.Name}.randomShuffle must keep the elements of %A{elements}" + ) + ) + + [] + member this.``randomShuffleBy shuffles as FSharp.Core does with the same randomizer`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + let expected = + this.Mirrored ( + (fun () -> + Array.randomShuffleBy (randomizer ()) (List.toArray elements) + |> List.ofArray + ), + fun () -> List.randomShuffleBy (randomizer ()) elements + ) + + Assert.AreEqual( + expected, + ops.ToList (ops.RandomShuffleBy (randomizer ()) collection), + $"%s{ops.Name}.randomShuffleBy of %A{elements}" + ) + ) + + [] + member this.``randomShuffleWith shuffles as FSharp.Core does with the same generator`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + let expected = + this.Mirrored ( + (fun () -> + Array.randomShuffleWith (Random seed) (List.toArray elements) + |> List.ofArray + ), + fun () -> List.randomShuffleWith (Random seed) elements + ) + + Assert.AreEqual( + expected, + ops.ToList (ops.RandomShuffleWith (Random seed) collection), + $"%s{ops.Name}.randomShuffleWith of %A{elements}" + ) + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSearchContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSearchContract.fs new file mode 100644 index 00000000..0b00376f --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSearchContract.fs @@ -0,0 +1,279 @@ +namespace FSharp.Collections.Immutable.Tests + +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// The contract of the search functions of a sequence-like module. +[] +type SeqLikeSearchContract<'C> (ops : SeqOps<'C>) = + inherit SeqLikeAccessContract<'C> (ops) + + static let isEven (item : int) = item % 2 = 0 + static let isNegative (item : int) = item < 0 + static let never (_ : int) = false + + /// A chooser that returns a value for the negative elements only. + static let negatedIfNegative (item : int) = if item < 0 then ValueSome (-item) else ValueNone + + /// The same chooser as `negatedIfNegative`, for the FSharp.Core oracle. + static let negatedIfNegativeOption (item : int) = if item < 0 then Some (-item) else None + + [] + member this.``contains finds an element equal to the value`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + for value in [ 42; 5; -3; 1000 ] do + Assert.AreEqual( + List.contains value elements, + ops.Contains value collection, + $"%s{ops.Name}.contains %d{value} in %A{elements}" + ) + ) + + [] + member this.``exists finds an element that satisfies the predicate`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + for predicate in [ isEven; isNegative; never ] do + Assert.AreEqual( + List.exists predicate elements, + ops.Exists predicate collection, + $"%s{ops.Name}.exists over %A{elements}" + ) + ) + + [] + member this.``exists2 tests the pairs at the same index`` () = + let collection = ops.OfSeq Inputs.several + let other = ops.OfSeq Inputs.other + let elements = ops.ToList collection + let otherElements = ops.ToList other + + for predicate in [ (fun a b -> a + b = 6); (fun a b -> a > b); (fun _ _ -> false) ] do + Assert.AreEqual( + List.exists2 predicate elements otherElements, + ops.Exists2 predicate collection other, + $"%s{ops.Name}.exists2 over %A{elements} and %A{otherElements}" + ) + + [] + member this.``exists2 throws for different lengths as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.exists2 (fun _ _ -> false) (List.toArray Inputs.singleton) (List.toArray Inputs.several)), + (fun () -> List.exists2 (fun _ _ -> false) Inputs.singleton Inputs.several), + (fun () -> ops.Exists2 (fun _ _ -> false) (ops.OfSeq Inputs.singleton) (ops.OfSeq Inputs.several)), + "exists2 of collections of different lengths must throw" + ) + + [] + member this.``find returns the first element that satisfies the predicate`` () = + this.ForEach ( + [ Inputs.several ], + fun collection elements -> + for predicate in [ isEven; isNegative ] do + Assert.AreEqual( + List.find predicate elements, + ops.Find predicate collection, + $"%s{ops.Name}.find in %A{elements}" + ) + ) + + [] + member this.``find throws when no element satisfies the predicate as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.find never (List.toArray Inputs.several)), + (fun () -> List.find never Inputs.several), + (fun () -> ops.Find never (ops.OfSeq Inputs.several)), + "find without a match must throw" + ) + + [] + member this.``findBack returns the last element that satisfies the predicate`` () = + this.ForEach ( + [ Inputs.several ], + fun collection elements -> + for predicate in [ isEven; isNegative ] do + Assert.AreEqual( + List.findBack predicate elements, + ops.FindBack predicate collection, + $"%s{ops.Name}.findBack in %A{elements}" + ) + ) + + [] + member this.``findBack throws when no element satisfies the predicate as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.findBack never (List.toArray Inputs.several)), + (fun () -> List.findBack never Inputs.several), + (fun () -> ops.FindBack never (ops.OfSeq Inputs.several)), + "findBack without a match must throw" + ) + + [] + member this.``findIndex returns the index of the first element that satisfies the predicate`` () = + this.ForEach ( + [ Inputs.several ], + fun collection elements -> + for predicate in [ isEven; isNegative ] do + Assert.AreEqual( + List.findIndex predicate elements, + ops.FindIndex predicate collection, + $"%s{ops.Name}.findIndex in %A{elements}" + ) + ) + + [] + member this.``findIndex throws when no element satisfies the predicate as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.findIndex never (List.toArray Inputs.several)), + (fun () -> List.findIndex never Inputs.several), + (fun () -> ops.FindIndex never (ops.OfSeq Inputs.several)), + "findIndex without a match must throw" + ) + + [] + member this.``findIndexBack returns the index of the last element that satisfies the predicate`` () = + this.ForEach ( + [ Inputs.several ], + fun collection elements -> + for predicate in [ isEven; isNegative ] do + Assert.AreEqual( + List.findIndexBack predicate elements, + ops.FindIndexBack predicate collection, + $"%s{ops.Name}.findIndexBack in %A{elements}" + ) + ) + + [] + member this.``findIndexBack throws when no element satisfies the predicate as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.findIndexBack never (List.toArray Inputs.several)), + (fun () -> List.findIndexBack never Inputs.several), + (fun () -> ops.FindIndexBack never (ops.OfSeq Inputs.several)), + "findIndexBack without a match must throw" + ) + + [] + member this.``forall checks that every element satisfies the predicate`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + for predicate in [ isEven; (fun item -> item < 100); never ] do + Assert.AreEqual( + List.forall predicate elements, + ops.Forall predicate collection, + $"%s{ops.Name}.forall over %A{elements}" + ) + ) + + [] + member this.``forall2 tests every pair at the same index`` () = + let collection = ops.OfSeq Inputs.several + let other = ops.OfSeq Inputs.other + let elements = ops.ToList collection + let otherElements = ops.ToList other + + for predicate in [ (fun a b -> a < b); (fun a b -> a <> b); (fun _ _ -> false) ] do + Assert.AreEqual( + List.forall2 predicate elements otherElements, + ops.Forall2 predicate collection other, + $"%s{ops.Name}.forall2 over %A{elements} and %A{otherElements}" + ) + + [] + member this.``forall2 throws for different lengths as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.forall2 (fun _ _ -> true) (List.toArray Inputs.singleton) (List.toArray Inputs.several)), + (fun () -> List.forall2 (fun _ _ -> true) Inputs.singleton Inputs.several), + (fun () -> ops.Forall2 (fun _ _ -> true) (ops.OfSeq Inputs.singleton) (ops.OfSeq Inputs.several)), + "forall2 of collections of different lengths must throw" + ) + + [] + member this.``pick returns the first chosen value`` () = + this.ForEach ( + [ Inputs.several ], + fun collection elements -> + Assert.AreEqual( + List.pick negatedIfNegativeOption elements, + ops.Pick negatedIfNegative collection, + $"%s{ops.Name}.pick in %A{elements}" + ) + ) + + [] + member this.``pick throws when the chooser returns nothing as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.pick (fun _ -> None) (List.toArray Inputs.several) : int), + (fun () -> List.pick (fun _ -> None) Inputs.several : int), + (fun () -> ops.Pick (fun _ -> ValueNone) (ops.OfSeq Inputs.several)), + "pick without a choice must throw" + ) + + [] + member this.``tryFind returns the first element that satisfies the predicate or nothing`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + for predicate in [ isEven; isNegative; never ] do + Assert.AreEqual( + List.tryFind predicate elements |> ValueOption.ofOption, + ops.TryFind predicate collection, + $"%s{ops.Name}.tryFind in %A{elements}" + ) + ) + + [] + member this.``tryFindBack returns the last element that satisfies the predicate or nothing`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + for predicate in [ isEven; isNegative; never ] do + Assert.AreEqual( + List.tryFindBack predicate elements |> ValueOption.ofOption, + ops.TryFindBack predicate collection, + $"%s{ops.Name}.tryFindBack in %A{elements}" + ) + ) + + [] + member this.``tryFindIndex returns the index of the first element that satisfies the predicate or nothing`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + for predicate in [ isEven; isNegative; never ] do + Assert.AreEqual( + List.tryFindIndex predicate elements |> ValueOption.ofOption, + ops.TryFindIndex predicate collection, + $"%s{ops.Name}.tryFindIndex in %A{elements}" + ) + ) + + [] + member this.``tryFindIndexBack returns the index of the last element that satisfies the predicate or nothing`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + for predicate in [ isEven; isNegative; never ] do + Assert.AreEqual( + List.tryFindIndexBack predicate elements + |> ValueOption.ofOption, + ops.TryFindIndexBack predicate collection, + $"%s{ops.Name}.tryFindIndexBack in %A{elements}" + ) + ) + + [] + member this.``tryPick returns the first chosen value or nothing`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.tryPick negatedIfNegativeOption elements + |> ValueOption.ofOption, + ops.TryPick negatedIfNegative collection, + $"%s{ops.Name}.tryPick in %A{elements}" + ) + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSortingContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSortingContract.fs new file mode 100644 index 00000000..cd8e646c --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSortingContract.fs @@ -0,0 +1,90 @@ +namespace FSharp.Collections.Immutable.Tests + +open System.Collections.Generic +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// The contract of the sorting functions of a sequence-like module. +/// +/// Equal keys belong to equal elements in these inputs, so the results do not depend on the stability of the sort; +/// StableSortContract checks the stability of the modules that promise it. +/// +[] +type SeqLikeSortingContract<'C> (ops : SeqOps<'C>) = + inherit SeqLikePartitioningContract<'C> (ops) + + [] + member this.``sort orders the elements ascending`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.sort elements, + ops.ToList (ops.Sort collection), + $"%s{ops.Name}.sort of %A{elements}" + ) + ) + + [] + member this.``sortBy orders the elements by the projection`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.sortBy abs elements, + ops.ToList (ops.SortBy abs collection), + $"%s{ops.Name}.sortBy of %A{elements}" + ) + ) + + [] + member this.``sortByDescending orders the elements by the projection descending`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.sortByDescending abs elements, + ops.ToList (ops.SortByDescending abs collection), + $"%s{ops.Name}.sortByDescending of %A{elements}" + ) + ) + + [] + member this.``sortDescending orders the elements descending`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.sortDescending elements, + ops.ToList (ops.SortDescending collection), + $"%s{ops.Name}.sortDescending of %A{elements}" + ) + ) + + [] + member this.``sortWith orders the elements with the comparison function`` () = + let descending (left : int) (right : int) = compare right left + + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.sortWith descending elements, + ops.ToList (ops.SortWith descending collection), + $"%s{ops.Name}.sortWith of %A{elements}" + ) + ) + + [] + member this.``sortWithComparer orders the elements with the comparer`` () = + let descending = Comparer.Create (fun left right -> compare right left) + + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.sortDescending elements, + ops.ToList (ops.SortWithComparer descending collection), + $"%s{ops.Name}.sortWithComparer of %A{elements}" + ) + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeTransformationContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeTransformationContract.fs new file mode 100644 index 00000000..ca5fd415 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeTransformationContract.fs @@ -0,0 +1,386 @@ +namespace FSharp.Collections.Immutable.Tests + +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// The contract of the transformation functions of a sequence-like module. +[] +type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = + inherit SeqLikeSearchContract<'C> (ops) + + static let toStructPairs (pairs : ('T1 * 'T2) list) = + pairs + |> List.map (fun (first, second) -> struct (first, second)) + + static let toStructTriples (triples : ('T1 * 'T2 * 'T3) list) = + triples + |> List.map (fun (first, second, third) -> struct (first, second, third)) + + [] + member this.``allPairs pairs every element with every element of the other collection`` () = + let other = ops.OfSeq [ 1; 2; 3 ] + let otherElements = ops.ToList other + + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.allPairs elements otherElements |> toStructPairs, + ops.AllPairs collection other, + $"%s{ops.Name}.allPairs of %A{elements} and %A{otherElements}" + ) + ) + + [] + member this.``append concatenates two collections`` () = + for first in Inputs.all do + for second in Inputs.all do + let collection = ops.OfSeq first + let other = ops.OfSeq second + + Assert.AreEqual( + ops.ToList collection @ ops.ToList other, + ops.ToList (ops.Append collection other), + $"%s{ops.Name}.append of %A{first} and %A{second}" + ) + + [] + member this.``choose keeps the chosen values in order`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + let chooser (item : int) = if item > 0 then ValueSome (item * 10) else ValueNone + let chooserOption (item : int) = if item > 0 then Some (item * 10) else None + + Assert.AreEqual( + List.choose chooserOption elements, + ops.ToList (ops.Choose chooser collection), + $"%s{ops.Name}.choose of %A{elements}" + ) + ) + + [] + member this.``chunkBySize divides the elements into chunks`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + for size in [ 1; 3; 8; 9 ] do + Assert.AreEqual( + List.chunkBySize size elements, + ops.ChunkBySize size collection, + $"%s{ops.Name}.chunkBySize %d{size} of %A{elements}" + ) + ) + + [] + member this.``chunkBySize throws for a size that is not positive as FSharp.Core does`` () = + for size in [ 0; -1 ] do + this.ThrowsLikeFSharpCore ( + (fun () -> Array.chunkBySize size (List.toArray Inputs.several)), + (fun () -> List.chunkBySize size Inputs.several), + (fun () -> ops.ChunkBySize size (ops.OfSeq Inputs.several)), + $"chunkBySize %d{size} must throw" + ) + + [] + member this.``collect concatenates the collections of the elements`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.collect (fun item -> [ item; item * 10 ]) elements, + ops.ToList (ops.Collect (fun item -> ops.OfSeq [ item; item * 10 ]) collection), + $"%s{ops.Name}.collect of %A{elements}" + ) + ) + + [] + member this.``concat concatenates a sequence of collections`` () = + let collections = Inputs.all |> List.map (fun input -> ops.OfSeq input) + let elements = collections |> List.map ops.ToList + + Assert.AreEqual( + List.concat elements, + ops.ToList (ops.Concat collections), + $"%s{ops.Name}.concat of %A{elements}" + ) + Assert.AreEqual([], ops.ToList (ops.Concat []), $"%s{ops.Name}.concat of no collections") + + [] + member this.``filter keeps the elements that satisfy the predicate`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.filter (fun item -> item > 0) elements, + ops.ToList (ops.Filter (fun item -> item > 0) collection), + $"%s{ops.Name}.filter of %A{elements}" + ) + ) + + [] + member this.``indexed pairs every element with its index`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.indexed elements |> toStructPairs, + ops.Indexed collection, + $"%s{ops.Name}.indexed of %A{elements}" + ) + ) + + [] + member this.``map transforms every element`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.map (fun item -> item * 3) elements, + ops.ToList (ops.Map (fun item -> item * 3) collection), + $"%s{ops.Name}.map of %A{elements}" + ) + ) + + [] + member this.``map2 transforms the pairs at the same index`` () = + let collection = ops.OfSeq Inputs.several + let other = ops.OfSeq Inputs.other + + Assert.AreEqual( + List.map2 (fun a b -> a * 100 + b) (ops.ToList collection) (ops.ToList other), + ops.ToList (ops.Map2 (fun a b -> a * 100 + b) collection other), + $"%s{ops.Name}.map2 of two collections" + ) + + [] + member this.``map2 throws for different lengths as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.map2 (+) (List.toArray Inputs.singleton) (List.toArray Inputs.several)), + (fun () -> List.map2 (+) Inputs.singleton Inputs.several), + (fun () -> ops.Map2 (+) (ops.OfSeq Inputs.singleton) (ops.OfSeq Inputs.several)), + "map2 of collections of different lengths must throw" + ) + + [] + member this.``map3 transforms the triples at the same index`` () = + let collection = ops.OfSeq Inputs.several + let other = ops.OfSeq Inputs.other + let third = ops.OfSeq Inputs.third + + Assert.AreEqual( + List.map3 (fun a b c -> a * 10000 + b * 100 + c) (ops.ToList collection) (ops.ToList other) (ops.ToList third), + ops.ToList (ops.Map3 (fun a b c -> a * 10000 + b * 100 + c) collection other third), + $"%s{ops.Name}.map3 of three collections" + ) + + [] + member this.``map3 throws for different lengths as FSharp.Core does`` () = + let shortArray = List.toArray Inputs.singleton + let longArray = List.toArray Inputs.several + + this.ThrowsLikeFSharpCore ( + (fun () -> Array.map3 (fun a b c -> a + b + c) longArray shortArray longArray), + (fun () -> List.map3 (fun a b c -> a + b + c) Inputs.several Inputs.singleton Inputs.several), + (fun () -> + ops.Map3 + (fun a b c -> a + b + c) + (ops.OfSeq Inputs.several) + (ops.OfSeq Inputs.singleton) + (ops.OfSeq Inputs.several) + ), + "map3 of collections of different lengths must throw" + ) + + [] + member this.``mapi transforms every element with its index`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.mapi (fun index item -> index * 1000 + item) elements, + ops.ToList (ops.Mapi (fun index item -> index * 1000 + item) collection), + $"%s{ops.Name}.mapi of %A{elements}" + ) + ) + + [] + member this.``mapi2 transforms the pairs at the same index with the index`` () = + let collection = ops.OfSeq Inputs.several + let other = ops.OfSeq Inputs.other + + Assert.AreEqual( + List.mapi2 (fun index a b -> index * 10000 + a * 100 + b) (ops.ToList collection) (ops.ToList other), + ops.ToList (ops.Mapi2 (fun index a b -> index * 10000 + a * 100 + b) collection other), + $"%s{ops.Name}.mapi2 of two collections" + ) + + [] + member this.``mapi2 throws for different lengths as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.mapi2 (fun _ a b -> a + b) (List.toArray Inputs.singleton) (List.toArray Inputs.several)), + (fun () -> List.mapi2 (fun _ a b -> a + b) Inputs.singleton Inputs.several), + (fun () -> ops.Mapi2 (fun _ a b -> a + b) (ops.OfSeq Inputs.singleton) (ops.OfSeq Inputs.several)), + "mapi2 of collections of different lengths must throw" + ) + + [] + member this.``pairwise pairs every element with its successor`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.pairwise elements |> toStructPairs, + ops.Pairwise collection, + $"%s{ops.Name}.pairwise of %A{elements}" + ) + ) + + [] + member this.``permute moves every element to its new index`` () = + this.ForEach ( + Inputs.nonEmpty, + fun collection elements -> + let length = List.length elements + let indexMap index = (index + 3) % length + + Assert.AreEqual( + List.permute indexMap elements, + ops.ToList (ops.Permute indexMap collection), + $"%s{ops.Name}.permute of %A{elements}" + ) + ) + + [] + member this.``permute throws for a function that is not a permutation as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.permute (fun _ -> 0) (List.toArray Inputs.several)), + (fun () -> List.permute (fun _ -> 0) Inputs.several), + (fun () -> ops.Permute (fun _ -> 0) (ops.OfSeq Inputs.several)), + "permute with a function that is not a permutation must throw" + ) + + [] + member this.``rev reverses the elements`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual(List.rev elements, ops.ToList (ops.Rev collection), $"%s{ops.Name}.rev of %A{elements}") + ) + + [] + member this.``transpose turns the collections into their columns`` () = + let collections = + [ Inputs.several; Inputs.other; Inputs.third ] + |> List.map (fun input -> ops.OfSeq input) + let elements = collections |> List.map ops.ToList + + Assert.AreEqual( + List.transpose elements, + ops.Transpose collections, + $"%s{ops.Name}.transpose of %A{elements}" + ) + + [] + member this.``transpose throws for collections of different lengths as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.transpose [ List.toArray Inputs.several; List.toArray Inputs.singleton ]), + (fun () -> List.transpose [ Inputs.several; Inputs.singleton ]), + (fun () -> ops.Transpose [ ops.OfSeq Inputs.several; ops.OfSeq Inputs.singleton ]), + "transpose of collections of different lengths must throw" + ) + + [] + member _.``unzip splits the pairs`` () = + let pairs = List.zip Inputs.several Inputs.other + let struct (first, second) = ops.Unzip (toStructPairs pairs) + let expectedFirst, expectedSecond = List.unzip pairs + Assert.AreEqual(expectedFirst, first, $"%s{ops.Name}.unzip must return the first components") + Assert.AreEqual(expectedSecond, second, $"%s{ops.Name}.unzip must return the second components") + + [] + member _.``unzip3 splits the triples`` () = + let triples = List.zip3 Inputs.several Inputs.other Inputs.third + let struct (first, second, third) = ops.Unzip3 (toStructTriples triples) + let expectedFirst, expectedSecond, expectedThird = List.unzip3 triples + Assert.AreEqual(expectedFirst, first, $"%s{ops.Name}.unzip3 must return the first components") + Assert.AreEqual(expectedSecond, second, $"%s{ops.Name}.unzip3 must return the second components") + Assert.AreEqual(expectedThird, third, $"%s{ops.Name}.unzip3 must return the third components") + + [] + member this.``where keeps the elements that satisfy the predicate`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + Assert.AreEqual( + List.where (fun item -> item % 2 = 0) elements, + ops.ToList (ops.Where (fun item -> item % 2 = 0) collection), + $"%s{ops.Name}.where of %A{elements}" + ) + ) + + [] + member this.``windowed returns the windows that slide over the elements`` () = + this.ForEach ( + Inputs.all, + fun collection elements -> + for size in [ 1; 3; 8; 9 ] do + Assert.AreEqual( + List.windowed size elements, + ops.Windowed size collection, + $"%s{ops.Name}.windowed %d{size} of %A{elements}" + ) + ) + + [] + member this.``windowed throws for a size that is not positive as FSharp.Core does`` () = + for size in [ 0; -1 ] do + this.ThrowsLikeFSharpCore ( + (fun () -> Array.windowed size (List.toArray Inputs.several)), + (fun () -> List.windowed size Inputs.several), + (fun () -> ops.Windowed size (ops.OfSeq Inputs.several)), + $"windowed %d{size} must throw" + ) + + [] + member _.``zip pairs the elements at the same index`` () = + let collection = ops.OfSeq Inputs.several + let other = ops.OfSeq Inputs.other + + Assert.AreEqual( + List.zip (ops.ToList collection) (ops.ToList other) + |> toStructPairs, + ops.Zip collection other, + $"%s{ops.Name}.zip of two collections" + ) + + [] + member this.``zip throws for different lengths as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.zip (List.toArray Inputs.singleton) (List.toArray Inputs.several)), + (fun () -> List.zip Inputs.singleton Inputs.several), + (fun () -> ops.Zip (ops.OfSeq Inputs.singleton) (ops.OfSeq Inputs.several)), + "zip of collections of different lengths must throw" + ) + + [] + member _.``zip3 combines the elements at the same index`` () = + let collection = ops.OfSeq Inputs.several + let other = ops.OfSeq Inputs.other + let third = ops.OfSeq Inputs.third + + Assert.AreEqual( + List.zip3 (ops.ToList collection) (ops.ToList other) (ops.ToList third) + |> toStructTriples, + ops.Zip3 collection other third, + $"%s{ops.Name}.zip3 of three collections" + ) + + [] + member this.``zip3 throws for different lengths as FSharp.Core does`` () = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.zip3 (List.toArray Inputs.several) (List.toArray Inputs.singleton) (List.toArray Inputs.several)), + (fun () -> List.zip3 Inputs.several Inputs.singleton Inputs.several), + (fun () -> ops.Zip3 (ops.OfSeq Inputs.several) (ops.OfSeq Inputs.singleton) (ops.OfSeq Inputs.several)), + "zip3 of collections of different lengths must throw" + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqOps.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqOps.fs new file mode 100644 index 00000000..af0e7d36 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqOps.fs @@ -0,0 +1,200 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open System.Collections.Generic + +/// The functions of a sequence-like module for the contract tests, over elements. +/// +/// +/// A record of functions checks at compile time that a module provides every function of a facet (ADR 0001, D8). The +/// fields follow the order of the signature files. +/// +/// +/// Results of another element type are lists: the instance of a collection converts its results with its own +/// toList, so that one record type serves every collection. +/// +/// +type SeqOps<'C> = { + /// The name of the module, for the messages of the assertions. + Name : string + /// The FSharp.Core module whose exceptions the module mirrors. + Mirror : Mirror + /// Maps the name of a parameter of the FSharp.Core oracle to the name of the parameter of the module. + ParamName : string -> string + /// Creates a collection that enumerates the given elements in their order. + OfSeq : seq -> 'C + /// The elements in the order in which the collection enumerates them. + ToList : 'C -> int list + + IsEmpty : 'C -> bool + Length : 'C -> int + + ExactlyOne : 'C -> int + Head : 'C -> int + Last : 'C -> int + Tail : 'C -> 'C + TryExactlyOne : 'C -> int voption + TryHead : 'C -> int voption + TryLast : 'C -> int voption + TryTail : 'C -> 'C voption + + Contains : int -> 'C -> bool + Exists : (int -> bool) -> 'C -> bool + Exists2 : (int -> int -> bool) -> 'C -> 'C -> bool + Find : (int -> bool) -> 'C -> int + FindBack : (int -> bool) -> 'C -> int + FindIndex : (int -> bool) -> 'C -> int + FindIndexBack : (int -> bool) -> 'C -> int + Forall : (int -> bool) -> 'C -> bool + Forall2 : (int -> int -> bool) -> 'C -> 'C -> bool + Pick : (int -> int voption) -> 'C -> int + TryFind : (int -> bool) -> 'C -> int voption + TryFindBack : (int -> bool) -> 'C -> int voption + TryFindIndex : (int -> bool) -> 'C -> int voption + TryFindIndexBack : (int -> bool) -> 'C -> int voption + TryPick : (int -> int voption) -> 'C -> int voption + + AllPairs : 'C -> 'C -> struct (int * int) list + Append : 'C -> 'C -> 'C + Choose : (int -> int voption) -> 'C -> 'C + ChunkBySize : int -> 'C -> int list list + Collect : (int -> 'C) -> 'C -> 'C + Concat : 'C list -> 'C + Filter : (int -> bool) -> 'C -> 'C + Indexed : 'C -> struct (int * int) list + Map : (int -> int) -> 'C -> 'C + Map2 : (int -> int -> int) -> 'C -> 'C -> 'C + Map3 : (int -> int -> int -> int) -> 'C -> 'C -> 'C -> 'C + Mapi : (int -> int -> int) -> 'C -> 'C + Mapi2 : (int -> int -> int -> int) -> 'C -> 'C -> 'C + Pairwise : 'C -> struct (int * int) list + Permute : (int -> int) -> 'C -> 'C + Rev : 'C -> 'C + Transpose : 'C list -> int list list + /// Unzips a collection of the given pairs. + Unzip : struct (int * int) list -> struct (int list * int list) + /// Unzips a collection of the given triples. + Unzip3 : struct (int * int * int) list -> struct (int list * int list * int list) + Where : (int -> bool) -> 'C -> 'C + Windowed : int -> 'C -> int list list + Zip : 'C -> 'C -> struct (int * int) list + Zip3 : 'C -> 'C -> 'C -> struct (int * int * int) list + + /// Averages a collection of the given floats. + Average : float list -> float + AverageBy : (int -> float) -> 'C -> float + CompareWith : (int -> int -> int) -> 'C -> 'C -> int + Fold : (int list -> int -> int list) -> int list -> 'C -> int list + Fold2 : (int list -> int -> int -> int list) -> int list -> 'C -> 'C -> int list + FoldBack : (int -> int list -> int list) -> 'C -> int list -> int list + FoldBack2 : (int -> int -> int list -> int list) -> 'C -> 'C -> int list -> int list + Iter : (int -> unit) -> 'C -> unit + Iter2 : (int -> int -> unit) -> 'C -> 'C -> unit + Iteri : (int -> int -> unit) -> 'C -> unit + Iteri2 : (int -> int -> int -> unit) -> 'C -> 'C -> unit + MapFold : (int -> int -> struct (int * int)) -> int -> 'C -> struct (int list * int) + MapFoldBack : (int -> int -> struct (int * int)) -> 'C -> int -> struct (int list * int) + Max : 'C -> int + MaxBy : (int -> int) -> 'C -> int + Min : 'C -> int + MinBy : (int -> int) -> 'C -> int + Reduce : (int -> int -> int) -> 'C -> int + ReduceBack : (int -> int -> int) -> 'C -> int + Scan : (int list -> int -> int list) -> int list -> 'C -> int list list + ScanBack : (int -> int list -> int list) -> 'C -> int list -> int list list + Sum : 'C -> int + SumBy : (int -> int) -> 'C -> int + + CountBy : (int -> int) -> 'C -> struct (int * int) list + Distinct : 'C -> 'C + DistinctBy : (int -> int) -> 'C -> 'C + Except : seq -> 'C -> 'C + GroupBy : (int -> int) -> 'C -> struct (int * int list) list + Partition : (int -> bool) -> 'C -> struct ('C * 'C) + PartitionWith : (int -> Choice) -> 'C -> struct (int list * string list) + Skip : int -> 'C -> 'C + SkipLast : int -> 'C -> 'C + SkipWhile : (int -> bool) -> 'C -> 'C + SplitAt : int -> 'C -> struct ('C * 'C) + SplitInto : int -> 'C -> int list list + Take : int -> 'C -> 'C + TakeLast : int -> 'C -> 'C + TakeWhile : (int -> bool) -> 'C -> 'C + Truncate : int -> 'C -> 'C + + Sort : 'C -> 'C + SortBy : (int -> int) -> 'C -> 'C + SortByDescending : (int -> int) -> 'C -> 'C + SortDescending : 'C -> 'C + SortWith : (int -> int -> int) -> 'C -> 'C + SortWithComparer : IComparer -> 'C -> 'C + + RandomChoice : 'C -> int + RandomChoiceBy : (unit -> float) -> 'C -> int + RandomChoiceWith : Random -> 'C -> int + RandomChoices : int -> 'C -> 'C + RandomChoicesBy : (unit -> float) -> int -> 'C -> 'C + RandomChoicesWith : Random -> int -> 'C -> 'C + RandomSample : int -> 'C -> 'C + RandomSampleBy : (unit -> float) -> int -> 'C -> 'C + RandomSampleWith : Random -> int -> 'C -> 'C + RandomShuffle : 'C -> 'C + RandomShuffleBy : (unit -> float) -> 'C -> 'C + RandomShuffleWith : Random -> 'C -> 'C + + ToArray : 'C -> int array + ToSeq : 'C -> seq +} + +/// The creation functions of a sequence-like module for the contract tests. +/// A function value cannot take a span, so the span functions take an array that the instance turns into a span. +type SeqCreationOps<'C> = { + /// The name of the module, for the messages of the assertions. + Name : string + /// The FSharp.Core module whose exceptions the module mirrors. + Mirror : Mirror + /// Maps the name of a parameter of the FSharp.Core oracle to the name of the parameter of the module. + ParamName : string -> string + /// The elements in the order in which the collection enumerates them. + ToList : 'C -> int list + + Empty : unit -> 'C + Init : int -> (int -> int) -> 'C + OfArray : int array -> 'C + OfList : int list -> 'C + OfReadOnlySpan : int array -> 'C + OfSeq : seq -> 'C + OfSpan : int array -> 'C + Replicate : int -> int -> 'C + Singleton : int -> 'C + Unfold : (int -> struct (int * int) voption) -> int -> 'C +} + +/// The index-based functions of a sequence-like module for the contract tests. +type IndexedOps<'C> = { + /// The name of the module, for the messages of the assertions. + Name : string + /// The FSharp.Core module whose exceptions the module mirrors. + Mirror : Mirror + /// Maps the name of a parameter of the FSharp.Core oracle to the name of the parameter of the module. + ParamName : string -> string + /// Creates a collection that enumerates the given elements in their order. + OfSeq : seq -> 'C + /// The elements in the order in which the collection enumerates them. + ToList : 'C -> int list + + Item : int -> 'C -> int + TryItem : int -> 'C -> int voption + + IndexOf : int -> 'C -> int + IndexOfWith : IEqualityComparer -> int -> 'C -> int + LastIndexOf : int -> 'C -> int + LastIndexOfWith : IEqualityComparer -> int -> 'C -> int + + Add : int -> 'C -> 'C + InsertAt : int -> int -> 'C -> 'C + InsertManyAt : int -> seq -> 'C -> 'C + RemoveAt : int -> 'C -> 'C + RemoveManyAt : int -> int -> 'C -> 'C + UpdateAt : int -> int -> 'C -> 'C +} 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 547aa7af..fb5c688f 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj +++ b/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj @@ -22,7 +22,26 @@ + + + + + + + + + + + + + + + + + + + diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListBuilderTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListBuilderTests.fs new file mode 100644 index 00000000..4eb8a12f --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListBuilderTests.fs @@ -0,0 +1,192 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open System.Collections.Generic +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// The builder functions of `FlatList` and the `FlatListBuilder` module. +[] +type FlatListBuilderTests () = + + /// A builder that holds the elements of the input with several elements. + static let filled () = + let builder = FlatList.builder() + builder.AddRange Inputs.several + builder + + [] + member _.``build returns the elements that the action adds`` () = + let array = FlatList.build (fun builder -> builder.AddRange Inputs.several) + Assert.AreEqual(Inputs.several, FlatList.toList array, "FlatList.build must return the added elements") + + [] + member _.``builder starts empty`` () = + let builder = FlatList.builder() + Assert.AreEqual(0, builder.Count, "FlatList.builder must start without elements") + + [] + member _.``builderWith reserves the capacity`` () = + let builder = FlatList.builderWith 10 + Assert.AreEqual(10, builder.Capacity, "FlatList.builderWith must reserve the capacity") + Assert.AreEqual(0, builder.Count, "FlatList.builderWith must start without elements") + + [] + member _.``builderWith throws for a negative capacity`` () = + Assert.ThrowsExactly( + Action (fun () -> FlatList.builderWith -1 |> ignore), + "FlatList.builderWith must reject a negative capacity as the BCL does" + ) + |> ignore + + [] + member _.``moveFromBuilder takes the contents of a full builder`` () = + let builder = FlatList.builderWith 3 + builder.AddRange [ 1; 2; 3 ] + let array = FlatList.moveFromBuilder builder + Assert.AreEqual([ 1; 2; 3 ], FlatList.toList array, "FlatList.moveFromBuilder must return the elements") + Assert.AreEqual(0, builder.Count, "FlatList.moveFromBuilder must empty the builder") + + [] + member _.``moveFromBuilder throws for a builder that is not full`` () = + let builder = FlatList.builderWith 3 + builder.Add 1 + + Assert.ThrowsExactly( + Action (fun () -> FlatList.moveFromBuilder builder |> ignore), + "FlatList.moveFromBuilder must reject a builder whose count differs from its capacity" + ) + |> ignore + + [] + member _.``ofBuilder copies the contents and keeps the builder`` () = + let builder = filled () + let array = FlatList.ofBuilder builder + builder.Add 99 + Assert.AreEqual(Inputs.several, FlatList.toList array, "FlatList.ofBuilder must copy the elements") + Assert.AreEqual(Inputs.several.Length + 1, builder.Count, "FlatList.ofBuilder must keep the builder usable") + + [] + member _.``ofBuilder throws for a null builder`` () = + let ex = + Assert.ThrowsExactly( + Action (fun () -> + FlatList.ofBuilder (Unchecked.defaultof.Builder>) + |> ignore + ), + "FlatList.ofBuilder must reject a null builder" + ) + + Assert.AreEqual("builder", ex.ParamName, "FlatList.ofBuilder must name the builder parameter") + + [] + member _.``toBuilder copies the elements into a new builder`` () = + let array = FlatList.ofList Inputs.several + let builder = FlatList.toBuilder array + builder.Add 99 + Assert.AreEqual(Inputs.several @ [ 99 ], List.ofSeq builder, "FlatList.toBuilder must hold the elements") + Assert.AreEqual(Inputs.several, FlatList.toList array, "FlatList.toBuilder must not change the flat list") + + [] + member _.``update applies the action to a copy`` () = + let array = FlatList.ofList Inputs.several + let updated = FlatList.update (fun builder -> builder.RemoveAt 0) array + Assert.AreEqual(List.tail Inputs.several, FlatList.toList updated, "FlatList.update must apply the action") + Assert.AreEqual(Inputs.several, FlatList.toList array, "FlatList.update must not change the input") + + [] + member _.``add, addRange and insertAt return the same builder`` () = + let builder = FlatList.builder() + let result = + builder + |> FlatListBuilder.add 1 + |> FlatListBuilder.addRange [ 2; 3 ] + |> FlatListBuilder.insertAt 0 0 + Assert.AreSame (builder, result, "The functions that change a builder must return it") + Assert.AreEqual([ 0; 1; 2; 3 ], List.ofSeq builder, "add, addRange and insertAt must change the builder") + + [] + member _.``capacity, setCapacity and length report the builder`` () = + let builder = + FlatList.builderWith 4 + |> FlatListBuilder.add 1 + |> FlatListBuilder.setCapacity 10 + Assert.AreEqual(10, FlatListBuilder.capacity builder, "FlatListBuilder.capacity must return the capacity") + Assert.AreEqual(1, FlatListBuilder.length builder, "FlatListBuilder.length must count the elements") + + [] + member _.``clear removes every element`` () = + let builder = filled () |> FlatListBuilder.clear + Assert.AreEqual(0, FlatListBuilder.length builder, "FlatListBuilder.clear must remove every element") + + [] + member _.``contains, indexOf and lastIndexOf search the builder`` () = + let builder = filled () + Assert.IsTrue (FlatListBuilder.contains 8 builder, "FlatListBuilder.contains must find 8") + Assert.AreEqual(1, FlatListBuilder.indexOf -3 builder, "FlatListBuilder.indexOf must return the first index") + Assert.AreEqual(5, FlatListBuilder.lastIndexOf -3 builder, "FlatListBuilder.lastIndexOf must return the last index") + Assert.AreEqual(-1, FlatListBuilder.indexOf 1000 builder, "FlatListBuilder.indexOf must return -1 when absent") + + [] + member _.``drainToFlatList returns the elements and empties the builder`` () = + let builder = filled () + let array = FlatListBuilder.drainToFlatList builder + Assert.AreEqual( + Inputs.several, + FlatList.toList array, + "FlatListBuilder.drainToFlatList must return the elements" + ) + Assert.AreEqual(0, FlatListBuilder.length builder, "FlatListBuilder.drainToFlatList must empty the builder") + + [] + member _.``item and updateAt read and replace an element`` () = + let builder = filled () |> FlatListBuilder.updateAt 2 99 + Assert.AreEqual(99, FlatListBuilder.item 2 builder, "FlatListBuilder.updateAt must replace the element") + + [] + member _.``remove and removeAt remove elements`` () = + let builder = filled () + Assert.IsTrue (FlatListBuilder.remove -3 builder, "FlatListBuilder.remove must report a removed element") + Assert.IsFalse (FlatListBuilder.remove 1000 builder, "FlatListBuilder.remove must report a missing element") + FlatListBuilder.removeAt 0 builder |> ignore + Assert.AreEqual([ 8; 5; 0; -3; 12; 7 ], List.ofSeq builder, "remove and removeAt must remove the elements") + + [] + member _.``rev reverses the builder`` () = + let builder = filled () |> FlatListBuilder.rev + Assert.AreEqual(List.rev Inputs.several, List.ofSeq builder, "FlatListBuilder.rev must reverse the elements") + + [] + member _.``sort, sortWith and sortWithComparer order the builder`` () = + Assert.AreEqual( + List.sort Inputs.several, + List.ofSeq (filled () |> FlatListBuilder.sort), + "FlatListBuilder.sort must sort ascending" + ) + + Assert.AreEqual( + List.sortDescending Inputs.several, + List.ofSeq ( + filled () + |> FlatListBuilder.sortWith (fun left right -> compare right left) + ), + "FlatListBuilder.sortWith must use the comparison function" + ) + + Assert.AreEqual( + List.sortDescending Inputs.several, + List.ofSeq ( + filled () + |> FlatListBuilder.sortWithComparer (Comparer.Create (fun left right -> compare right left)) + ), + "FlatListBuilder.sortWithComparer must use the comparer" + ) + + [] + member _.``toArray copies the elements`` () = + let builder = filled () + CollectionAssert.AreEqual ( + List.toArray Inputs.several, + FlatListBuilder.toArray builder, + "FlatListBuilder.toArray must copy the elements" + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs new file mode 100644 index 00000000..fdd9f09f --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs @@ -0,0 +1,129 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// The `flatList { }` computation expression. +[] +type FlatListComputationExpressionTests () = + + [] + member _.``an expression without elements is empty`` () = + let array : FlatList = flatList { () } + Assert.IsTrue (FlatList.isEmpty array, "flatList { () } must be empty") + + [] + member _.``yield adds the elements in order`` () = + let array = flatList { + yield 1 + yield 2 + yield 3 + } + + Assert.AreEqual([ 1; 2; 3 ], FlatList.toList array, "flatList must keep the order of yield") + + [] + member _.``implicit yields add the elements in order`` () = + let array = flatList { + 1 + 2 + 3 + } + + Assert.AreEqual([ 1; 2; 3 ], FlatList.toList array, "flatList must keep the order of implicit yields") + + // nameof cannot name the overloaded YieldFrom, so the category is the expression itself + [] + member _.``yield! adds a sequence, an array, a flat list and another expression`` () = + let inner = flatList { yield 7 } + + let array = flatList { + yield! [ 1; 2 ] + yield! [| 3; 4 |] + yield! FlatList.ofList [ 5; 6 ] + yield! inner + } + + Assert.AreEqual([ 1; 2; 3; 4; 5; 6; 7 ], FlatList.toList array, "flatList must add the elements of yield!") + + [] + member _.``for adds the elements of the body for each item`` () = + let array = flatList { + for item in 1..3 do + yield item * 10 + } + + Assert.AreEqual([ 10; 20; 30 ], FlatList.toList array, "flatList must run the body of for for each item") + + [] + member _.``while adds the elements while the guard holds`` () = + let mutable count = 0 + + let array = flatList { + while count < 3 do + count <- count + 1 + yield count + } + + Assert.AreEqual([ 1; 2; 3 ], FlatList.toList array, "flatList must run the body of while while the guard holds") + + [] + member _.``if without else and following elements combine`` () = + let array = flatList { + if false then + yield 0 + + yield 1 + + if true then + yield 2 + } + + Assert.AreEqual([ 1; 2 ], FlatList.toList array, "flatList must combine conditional and plain elements") + + [] + member _.``try with adds the elements of the handler after an exception`` () = + let array = flatList { + try + yield 1 + failwith "fails" + with _ -> + yield 2 + } + + Assert.AreEqual( + [ 1; 2 ], + FlatList.toList array, + "flatList must keep the elements before the exception and add the handler's" + ) + + [] + member _.``try finally runs the compensation`` () = + let mutable compensated = false + + let array = flatList { + try + yield 1 + finally + compensated <- true + } + + Assert.IsTrue (compensated, "flatList must run the compensation of try finally") + Assert.AreEqual([ 1 ], FlatList.toList array, "flatList must keep the elements of the body of try finally") + + [] + member _.``use disposes the resource after the body`` () = + let mutable disposed = false + + let array = flatList { + use _resource = { + new IDisposable with + member _.Dispose () = disposed <- true + } + + yield 1 + } + + Assert.IsTrue (disposed, "flatList must dispose the resource of use") + Assert.AreEqual([ 1 ], FlatList.toList array, "flatList must keep the elements of the body of use") diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListContractTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListContractTests.fs new file mode 100644 index 00000000..5e013380 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListContractTests.fs @@ -0,0 +1,19 @@ +namespace FSharp.Collections.Immutable.Tests + +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// The sequence-like contract for `FlatList`. +[] +type FlatListSeqLikeTests () = + inherit SeqLikeContract> (FlatListOps.seqLike) + +/// The creation contract for `FlatList`. +[] +type FlatListCreationTests () = + inherit SeqCreationContract> (FlatListOps.creation) + +/// The index-based contract for `FlatList`. +[] +type FlatListIndexedTests () = + inherit IndexedContract> (FlatListOps.indexed) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOps.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOps.fs new file mode 100644 index 00000000..d3597e32 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOps.fs @@ -0,0 +1,194 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open FSharp.Collections.Immutable + +/// The functions of the `FlatList` module for the contract tests. +[] +module FlatListOps = + + let private toLists (lists : FlatList>) = lists |> FlatList.toList |> List.map FlatList.toList + + let private toListPair (struct (first : FlatList<'T1>, second : FlatList<'T2>)) = + struct (FlatList.toList first, FlatList.toList second) + + /// The functions of the sequence-like facet. `FlatList` names its parameters as the `Array` module does. + let seqLike : SeqOps> = { + Name = nameof FlatList + Mirror = ArrayMirror + ParamName = id + OfSeq = FlatList.ofSeq + ToList = FlatList.toList + + IsEmpty = FlatList.isEmpty + Length = FlatList.length + + ExactlyOne = FlatList.exactlyOne + Head = FlatList.head + Last = FlatList.last + Tail = FlatList.tail + TryExactlyOne = FlatList.tryExactlyOne + TryHead = FlatList.tryHead + TryLast = FlatList.tryLast + TryTail = FlatList.tryTail + + Contains = FlatList.contains + Exists = FlatList.exists + Exists2 = FlatList.exists2 + Find = FlatList.find + FindBack = FlatList.findBack + FindIndex = FlatList.findIndex + FindIndexBack = FlatList.findIndexBack + Forall = FlatList.forall + Forall2 = FlatList.forall2 + Pick = FlatList.pick + TryFind = FlatList.tryFind + TryFindBack = FlatList.tryFindBack + TryFindIndex = FlatList.tryFindIndex + TryFindIndexBack = FlatList.tryFindIndexBack + TryPick = FlatList.tryPick + + AllPairs = fun array1 array2 -> FlatList.allPairs array1 array2 |> FlatList.toList + Append = FlatList.append + Choose = FlatList.choose + ChunkBySize = fun chunkSize array -> FlatList.chunkBySize chunkSize array |> toLists + Collect = FlatList.collect + Concat = FlatList.concat + Filter = FlatList.filter + Indexed = FlatList.indexed >> FlatList.toList + Map = FlatList.map + Map2 = FlatList.map2 + Map3 = FlatList.map3 + Mapi = FlatList.mapi + Mapi2 = FlatList.mapi2 + Pairwise = FlatList.pairwise >> FlatList.toList + Permute = FlatList.permute + Rev = FlatList.rev + Transpose = fun arrays -> FlatList.transpose arrays |> toLists + Unzip = fun pairs -> FlatList.unzip (FlatList.ofList pairs) |> toListPair + Unzip3 = + fun triples -> + let struct (first, second, third) = FlatList.unzip3 (FlatList.ofList triples) + struct (FlatList.toList first, FlatList.toList second, FlatList.toList third) + Where = FlatList.where + Windowed = fun windowSize array -> FlatList.windowed windowSize array |> toLists + Zip = fun array1 array2 -> FlatList.zip array1 array2 |> FlatList.toList + Zip3 = fun array1 array2 array3 -> FlatList.zip3 array1 array2 array3 |> FlatList.toList + + Average = fun items -> FlatList.average (FlatList.ofList items) + AverageBy = FlatList.averageBy + CompareWith = FlatList.compareWith + Fold = FlatList.fold + Fold2 = FlatList.fold2 + FoldBack = FlatList.foldBack + FoldBack2 = FlatList.foldBack2 + Iter = FlatList.iter + Iter2 = FlatList.iter2 + Iteri = FlatList.iteri + Iteri2 = FlatList.iteri2 + MapFold = + fun mapping state array -> + let struct (results, final) = FlatList.mapFold mapping state array + struct (FlatList.toList results, final) + MapFoldBack = + fun mapping array state -> + let struct (results, final) = FlatList.mapFoldBack mapping array state + struct (FlatList.toList results, final) + Max = FlatList.max + MaxBy = FlatList.maxBy + Min = FlatList.min + MinBy = FlatList.minBy + Reduce = FlatList.reduce + ReduceBack = FlatList.reduceBack + Scan = fun folder state array -> FlatList.scan folder state array |> FlatList.toList + ScanBack = fun folder array state -> FlatList.scanBack folder array state |> FlatList.toList + Sum = FlatList.sum + SumBy = FlatList.sumBy + + CountBy = fun projection array -> FlatList.countBy projection array |> FlatList.toList + Distinct = FlatList.distinct + DistinctBy = FlatList.distinctBy + Except = FlatList.except + GroupBy = + fun projection array -> + FlatList.groupBy projection array + |> FlatList.toList + |> List.map (fun struct (key, items) -> struct (key, FlatList.toList items)) + Partition = FlatList.partition + PartitionWith = fun partitioner array -> FlatList.partitionWith partitioner array |> toListPair + Skip = FlatList.skip + SkipLast = FlatList.skipLast + SkipWhile = FlatList.skipWhile + SplitAt = FlatList.splitAt + SplitInto = fun count array -> FlatList.splitInto count array |> toLists + Take = FlatList.take + TakeLast = FlatList.takeLast + TakeWhile = FlatList.takeWhile + Truncate = FlatList.truncate + + Sort = FlatList.sort + SortBy = FlatList.sortBy + SortByDescending = FlatList.sortByDescending + SortDescending = FlatList.sortDescending + SortWith = FlatList.sortWith + SortWithComparer = FlatList.sortWithComparer + + RandomChoice = FlatList.randomChoice + RandomChoiceBy = FlatList.randomChoiceBy + RandomChoiceWith = FlatList.randomChoiceWith + RandomChoices = FlatList.randomChoices + RandomChoicesBy = FlatList.randomChoicesBy + RandomChoicesWith = FlatList.randomChoicesWith + RandomSample = FlatList.randomSample + RandomSampleBy = FlatList.randomSampleBy + RandomSampleWith = FlatList.randomSampleWith + RandomShuffle = FlatList.randomShuffle + RandomShuffleBy = FlatList.randomShuffleBy + RandomShuffleWith = FlatList.randomShuffleWith + + ToArray = FlatList.toArray + ToSeq = FlatList.toSeq + } + + /// The functions of the creation facet. + let creation : SeqCreationOps> = { + Name = nameof FlatList + Mirror = ArrayMirror + ParamName = id + ToList = FlatList.toList + + Empty = fun () -> FlatList.empty + Init = FlatList.init + OfArray = FlatList.ofArray + OfList = FlatList.ofList + OfReadOnlySpan = fun array -> FlatList.ofReadOnlySpan (ReadOnlySpan array) + OfSeq = FlatList.ofSeq + OfSpan = fun array -> FlatList.ofSpan (Span array) + Replicate = FlatList.replicate + Singleton = FlatList.singleton + Unfold = FlatList.unfold + } + + /// The functions of the index-based facet. + let indexed : IndexedOps> = { + Name = nameof FlatList + Mirror = ArrayMirror + ParamName = id + OfSeq = FlatList.ofSeq + ToList = FlatList.toList + + Item = FlatList.item + TryItem = FlatList.tryItem + + IndexOf = FlatList.indexOf + IndexOfWith = FlatList.indexOfWith + LastIndexOf = FlatList.lastIndexOf + LastIndexOfWith = FlatList.lastIndexOfWith + + Add = FlatList.add + InsertAt = FlatList.insertAt + InsertManyAt = FlatList.insertManyAt + RemoveAt = FlatList.removeAt + RemoveManyAt = FlatList.removeManyAt + UpdateAt = FlatList.updateAt + } diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOptionTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOptionTests.fs new file mode 100644 index 00000000..239924ff --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOptionTests.fs @@ -0,0 +1,153 @@ +namespace FSharp.Collections.Immutable.Tests + +open FSharp.Collections.Immutable +open FSharp.Collections.Immutable.Tuple +open FSharp.Collections.Immutable.Option +open Microsoft.VisualStudio.TestTools.UnitTesting + +// The namespaces are opened in the order that gives the shapes of FSharp.Core: Tuple first, Option last. +// The type annotations of the results are the proof that the functions of the Option namespace shadow the default ones. + +/// The `FlatList` module of the `Option` namespace. +[] +type FlatListOptionTests () = + + static let array = FlatList.ofList Inputs.several + + static let negatedIfNegative (item : int) = if item < 0 then Some (-item) else None + + [] + member _.``unfold takes a generator of options of reference tuples`` () = + let generator (state : int) = if state > 0 then Some (state, state - 1) else None + let generated : FlatList = FlatList.unfold generator 3 + Assert.AreEqual( + List.unfold generator 3, + FlatList.toList generated, + "Option.FlatList.unfold must generate as List.unfold does" + ) + + [] + member _.``tryExactlyOne returns an option`` () = + let found : int option = FlatList.tryExactlyOne (FlatList.singleton 5) + + OverrideAssert.optionMatches + (FSharp.Collections.Immutable.FlatList.tryExactlyOne (FlatList.singleton 5)) + found + "Option.FlatList.tryExactlyOne must match the default after conversion" + + [] + member _.``tryHead returns an option`` () = + let found : int option = FlatList.tryHead array + OverrideAssert.optionMatches + (FSharp.Collections.Immutable.FlatList.tryHead array) + found + "Option.FlatList.tryHead must match the default" + + [] + member _.``tryItem returns an option`` () = + for index in [ -1; 2; 8 ] do + let found : int option = FlatList.tryItem index array + + OverrideAssert.optionMatches + (FSharp.Collections.Immutable.FlatList.tryItem index array) + found + $"Option.FlatList.tryItem %d{index} must match the default" + + [] + member _.``tryLast returns an option`` () = + let found : int option = FlatList.tryLast array + OverrideAssert.optionMatches + (FSharp.Collections.Immutable.FlatList.tryLast array) + found + "Option.FlatList.tryLast must match the default" + + [] + member _.``tryTail returns an option`` () = + let found : FlatList option = FlatList.tryTail array + + Assert.AreEqual( + FSharp.Collections.Immutable.FlatList.tryTail array + |> ValueOption.map FlatList.toList + |> ValueOption.toOption, + found |> Option.map FlatList.toList, + "Option.FlatList.tryTail must match the default" + ) + + [] + member _.``pick takes a chooser of options`` () = + let picked : int = FlatList.pick negatedIfNegative array + Assert.AreEqual( + List.pick negatedIfNegative Inputs.several, + picked, + "Option.FlatList.pick must pick as List.pick does" + ) + + [] + member _.``tryFind returns an option`` () = + let found : int option = FlatList.tryFind (fun item -> item > 6) array + + OverrideAssert.optionMatches + (FSharp.Collections.Immutable.FlatList.tryFind (fun item -> item > 6) array) + found + "Option.FlatList.tryFind must match the default" + + [] + member _.``tryFindBack returns an option`` () = + let found : int option = FlatList.tryFindBack (fun item -> item > 6) array + + OverrideAssert.optionMatches + (FSharp.Collections.Immutable.FlatList.tryFindBack (fun item -> item > 6) array) + found + "Option.FlatList.tryFindBack must match the default" + + [] + member _.``tryFindIndex returns an option`` () = + let found : int option = FlatList.tryFindIndex (fun item -> item > 6) array + + OverrideAssert.optionMatches + (FSharp.Collections.Immutable.FlatList.tryFindIndex (fun item -> item > 6) array) + found + "Option.FlatList.tryFindIndex must match the default" + + [] + member _.``tryFindIndexBack returns an option`` () = + let found : int option = FlatList.tryFindIndexBack (fun item -> item > 6) array + + OverrideAssert.optionMatches + (FSharp.Collections.Immutable.FlatList.tryFindIndexBack (fun item -> item > 6) array) + found + "Option.FlatList.tryFindIndexBack must match the default" + + [] + member _.``tryPick takes a chooser of options and returns an option`` () = + let found : int option = FlatList.tryPick negatedIfNegative array + Assert.AreEqual( + List.tryPick negatedIfNegative Inputs.several, + found, + "Option.FlatList.tryPick must pick as List.tryPick does" + ) + + [] + member _.``choose takes a chooser of options`` () = + let chosen : FlatList = FlatList.choose negatedIfNegative array + + Assert.AreEqual( + List.choose negatedIfNegative Inputs.several, + FlatList.toList chosen, + "Option.FlatList.choose must choose as List.choose does" + ) + + [] + member _.``the functions without an option keep resolving to the default module`` () = + let mapped : FlatList = FlatList.map (fun item -> item * 2) array + let zipped : FlatList = FlatList.zip array array + Assert.AreEqual( + List.map (fun item -> item * 2) Inputs.several, + FlatList.toList mapped, + "FlatList.map must resolve to the default module" + ) + Assert.AreEqual<(int * int) list>( + List.zip Inputs.several Inputs.several, + FlatList.toList zipped, + "FlatList.zip must resolve to the Tuple module" + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs new file mode 100644 index 00000000..7d2dce14 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs @@ -0,0 +1,348 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open System.Collections.Generic +open System.Runtime.InteropServices +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// The behaviour of `FlatList` that the shared contracts do not cover: default values, shared storage and the extras. +[] +type FlatListTests () = + + static let defaultArray = Unchecked.defaultof> + + static let nullArray = Unchecked.defaultof + + /// The array under a flat list, to tell whether two flat lists share their storage. + static let underlying (array : FlatList) = ImmutableCollectionsMarshal.AsArray array + + [] + member _.``create repeats the value`` () = + Assert.AreEqual([ 7; 7; 7 ], FlatList.toList (FlatList.create 3 7), "FlatList.create must repeat the value") + Assert.IsEmpty (FlatList.create 0 7, "FlatList.create 0 must be empty") + + [] + member _.``create throws for a negative count as Array does`` () = + ExceptionAssert.same + id + (fun () -> Array.create -1 7 |> ignore) + (fun () -> FlatList.create -1 7 |> ignore) + "FlatList.create -1 must throw as Array.create does" + + [] + member _.``isDefault and isDefaultOrEmpty tell a default value from an empty one`` () = + Assert.IsTrue (FlatList.isDefault defaultArray, "isDefault must be true for a default value") + Assert.IsFalse (FlatList.isDefault FlatList.empty, "isDefault must be false for an empty flat list") + Assert.IsTrue (FlatList.isDefaultOrEmpty defaultArray, "isDefaultOrEmpty must be true for a default value") + Assert.IsTrue (FlatList.isDefaultOrEmpty FlatList.empty, "isDefaultOrEmpty must be true for an empty flat list") + Assert.IsFalse ( + FlatList.isDefaultOrEmpty (FlatList.singleton 1), + "isDefaultOrEmpty must be false for a non-empty flat list" + ) + + [] + member _.``functions reject a default flat list as Array rejects a null array`` () = + let cases : struct (string * (unit -> unit) * (unit -> unit)) list = [ + struct (nameof FlatList.allPairs, + (fun () -> Array.allPairs nullArray [| 1 |] |> ignore), + (fun () -> + FlatList.allPairs defaultArray (FlatList.singleton 1) + |> ignore + )) + struct (nameof FlatList.append, + (fun () -> Array.append [| 1 |] nullArray |> ignore), + (fun () -> + FlatList.append (FlatList.singleton 1) defaultArray + |> ignore + )) + struct (nameof FlatList.average, + (fun () -> Array.average (Unchecked.defaultof) |> ignore), + (fun () -> + FlatList.average (Unchecked.defaultof>) + |> ignore + )) + struct (nameof FlatList.choose, + (fun () -> Array.choose Some nullArray |> ignore), + (fun () -> FlatList.choose ValueSome defaultArray |> ignore)) + struct (nameof FlatList.chunkBySize, + (fun () -> Array.chunkBySize 2 nullArray |> ignore), + (fun () -> FlatList.chunkBySize 2 defaultArray |> ignore)) + struct (nameof FlatList.contains, + (fun () -> Array.contains 1 nullArray |> ignore), + (fun () -> FlatList.contains 1 defaultArray |> ignore)) + struct (nameof FlatList.countBy, + (fun () -> Array.countBy id nullArray |> ignore), + (fun () -> FlatList.countBy id defaultArray |> ignore)) + struct (nameof FlatList.distinct, + (fun () -> Array.distinct nullArray |> ignore), + (fun () -> FlatList.distinct defaultArray |> ignore)) + struct (nameof FlatList.exactlyOne, + (fun () -> Array.exactlyOne nullArray |> ignore), + (fun () -> FlatList.exactlyOne defaultArray |> ignore)) + struct (nameof FlatList.except, + (fun () -> Array.except [ 1 ] nullArray |> ignore), + (fun () -> FlatList.except [ 1 ] defaultArray |> ignore)) + struct (nameof FlatList.exists, + (fun () -> Array.exists ((=) 1) nullArray |> ignore), + (fun () -> FlatList.exists ((=) 1) defaultArray |> ignore)) + struct (nameof FlatList.filter, + (fun () -> Array.filter ((=) 1) nullArray |> ignore), + (fun () -> FlatList.filter ((=) 1) defaultArray |> ignore)) + struct (nameof FlatList.fold, + (fun () -> Array.fold (+) 0 nullArray |> ignore), + (fun () -> FlatList.fold (+) 0 defaultArray |> ignore)) + struct (nameof FlatList.groupBy, + (fun () -> Array.groupBy id nullArray |> ignore), + (fun () -> FlatList.groupBy id defaultArray |> ignore)) + struct (nameof FlatList.head, + (fun () -> Array.head nullArray |> ignore), + (fun () -> FlatList.head defaultArray |> ignore)) + struct (nameof FlatList.indexed, + (fun () -> Array.indexed nullArray |> ignore), + (fun () -> FlatList.indexed defaultArray |> ignore)) + struct (nameof FlatList.insertAt, + (fun () -> Array.insertAt 0 1 nullArray |> ignore), + (fun () -> FlatList.insertAt 0 1 defaultArray |> ignore)) + struct (nameof FlatList.isEmpty, + (fun () -> Array.isEmpty nullArray |> ignore), + (fun () -> FlatList.isEmpty defaultArray |> ignore)) + struct (nameof FlatList.iter, (fun () -> Array.iter ignore nullArray), (fun () -> FlatList.iter ignore defaultArray)) + struct (nameof FlatList.map, + (fun () -> Array.map id nullArray |> ignore), + (fun () -> FlatList.map id defaultArray |> ignore)) + struct (nameof FlatList.mapFold, + (fun () -> Array.mapFold (fun s x -> x, s) 0 nullArray |> ignore), + (fun () -> + FlatList.mapFold (fun s x -> struct (x, s)) 0 defaultArray + |> ignore + )) + struct (nameof FlatList.max, + (fun () -> Array.max nullArray |> ignore), + (fun () -> FlatList.max defaultArray |> ignore)) + struct (nameof FlatList.pairwise, + (fun () -> Array.pairwise nullArray |> ignore), + (fun () -> FlatList.pairwise defaultArray |> ignore)) + struct (nameof FlatList.partition, + (fun () -> Array.partition ((=) 1) nullArray |> ignore), + (fun () -> FlatList.partition ((=) 1) defaultArray |> ignore)) + struct (nameof FlatList.pick, + (fun () -> Array.pick Some nullArray |> ignore), + (fun () -> FlatList.pick ValueSome defaultArray |> ignore)) + struct (nameof FlatList.randomChoice, + (fun () -> Array.randomChoice nullArray |> ignore), + (fun () -> FlatList.randomChoice defaultArray |> ignore)) + struct (nameof FlatList.rev, + (fun () -> Array.rev nullArray |> ignore), + (fun () -> FlatList.rev defaultArray |> ignore)) + struct (nameof FlatList.skip, + (fun () -> Array.skip 0 nullArray |> ignore), + (fun () -> FlatList.skip 0 defaultArray |> ignore)) + struct (nameof FlatList.sort, + (fun () -> Array.sort nullArray |> ignore), + (fun () -> FlatList.sort defaultArray |> ignore)) + struct (nameof FlatList.splitAt, + (fun () -> Array.splitAt 0 nullArray |> ignore), + (fun () -> FlatList.splitAt 0 defaultArray |> ignore)) + struct (nameof FlatList.sum, + (fun () -> Array.sum nullArray |> ignore), + (fun () -> FlatList.sum defaultArray |> ignore)) + struct (nameof FlatList.tail, + (fun () -> Array.tail nullArray |> ignore), + (fun () -> FlatList.tail defaultArray |> ignore)) + struct (nameof FlatList.take, + (fun () -> Array.take 0 nullArray |> ignore), + (fun () -> FlatList.take 0 defaultArray |> ignore)) + struct (nameof FlatList.toList, + (fun () -> Array.toList nullArray |> ignore), + (fun () -> FlatList.toList defaultArray |> ignore)) + struct (nameof FlatList.tryFind, + (fun () -> Array.tryFind ((=) 1) nullArray |> ignore), + (fun () -> FlatList.tryFind ((=) 1) defaultArray |> ignore)) + struct (nameof FlatList.tryHead, + (fun () -> Array.tryHead nullArray |> ignore), + (fun () -> FlatList.tryHead defaultArray |> ignore)) + struct (nameof FlatList.unzip, + (fun () -> + Array.unzip (Unchecked.defaultof<(int * int) array>) + |> ignore + ), + (fun () -> + FlatList.unzip (Unchecked.defaultof>) + |> ignore + )) + struct (nameof FlatList.windowed, + (fun () -> Array.windowed 2 nullArray |> ignore), + (fun () -> FlatList.windowed 2 defaultArray |> ignore)) + struct (nameof FlatList.zip, + (fun () -> Array.zip [| 1 |] nullArray |> ignore), + (fun () -> FlatList.zip (FlatList.singleton 1) defaultArray |> ignore)) + ] + + for struct (name, oracle, actual) in cases do + ExceptionAssert.same id oracle actual $"FlatList.%s{name} must reject a default flat list" + + [] + member _.``functions without an Array counterpart reject a default flat list`` () = + let cases : struct (string * string * (unit -> unit)) list = [ + struct (nameof FlatList.add, "array", (fun () -> FlatList.add 1 defaultArray |> ignore)) + struct (nameof FlatList.asMemory, "array", (fun () -> FlatList.asMemory defaultArray |> ignore)) + struct (nameof FlatList.binarySearch, "array", (fun () -> FlatList.binarySearch 1 defaultArray |> ignore)) + struct (nameof FlatList.blit, "source", (fun () -> FlatList.blit defaultArray 0 [| 0 |] 0 0)) + // Array.concat dereferences a null element array without a check; a default element is a default flat list (D6) + struct (nameof FlatList.concat, "arrays", (fun () -> FlatList.concat [ defaultArray ] |> ignore)) + struct (nameof FlatList.transpose, "arrays", (fun () -> FlatList.transpose [ defaultArray ] |> ignore)) + struct (nameof FlatList.indexOf, "array", (fun () -> FlatList.indexOf 1 defaultArray |> ignore)) + struct (nameof FlatList.indexOfWith, + "array", + (fun () -> + FlatList.indexOfWith EqualityComparer.Default 1 defaultArray + |> ignore + )) + struct (nameof FlatList.lastIndexOf, "array", (fun () -> FlatList.lastIndexOf 1 defaultArray |> ignore)) + struct (nameof FlatList.lastIndexOfWith, + "array", + (fun () -> + FlatList.lastIndexOfWith EqualityComparer.Default 1 defaultArray + |> ignore + )) + struct (nameof FlatList.length, "array", (fun () -> FlatList.length defaultArray |> ignore)) + struct (nameof FlatList.skipLast, "array", (fun () -> FlatList.skipLast 0 defaultArray |> ignore)) + struct (nameof FlatList.sortWithComparer, + "array", + (fun () -> + FlatList.sortWithComparer Comparer.Default defaultArray + |> ignore + )) + struct (nameof FlatList.takeLast, "array", (fun () -> FlatList.takeLast 0 defaultArray |> ignore)) + struct (nameof FlatList.toBuilder, "array", (fun () -> FlatList.toBuilder defaultArray |> ignore)) + struct (nameof FlatList.toSeq, "array", (fun () -> FlatList.toSeq defaultArray |> ignore)) + struct (nameof FlatList.tryTail, "array", (fun () -> FlatList.tryTail defaultArray |> ignore)) + struct (nameof FlatList.update, "array", (fun () -> FlatList.update ignore defaultArray |> ignore)) + ] + + for struct (name, paramName, actual) in cases do + let ex = + Assert.ThrowsExactly(Action actual, $"FlatList.%s{name} must reject a default flat list") + Assert.AreEqual( + paramName, + ex.ParamName, + $"FlatList.%s{name} must name the parameter of the default flat list" + ) + + [] + member _.``asSpan views the elements`` () = + let array = FlatList.ofList Inputs.several + let span = FlatList.asSpan array + CollectionAssert.AreEqual (List.toArray Inputs.several, span.ToArray (), "FlatList.asSpan must view the elements") + + [] + member _.``asMemory views the underlying array without a copy`` () = + let array = FlatList.ofList Inputs.several + let memory = FlatList.asMemory array + let isArray, segment = MemoryMarshal.TryGetArray memory + Assert.IsTrue (isArray, "FlatList.asMemory must view an array") + Assert.AreSame (underlying array, segment.Array, "FlatList.asMemory must view the underlying array") + Assert.HasCount (Inputs.several.Length, segment, "FlatList.asMemory must view every element") + + [] + member _.``binarySearch finds a value or the complement of its insertion point`` () = + let array = FlatList.ofList [ 1; 3; 5; 7 ] + Assert.AreEqual(2, FlatList.binarySearch 5 array, "FlatList.binarySearch must find 5 at index 2") + Assert.AreEqual( + ~~~2, + FlatList.binarySearch 4 array, + "FlatList.binarySearch must return the complement of the insertion point of 4" + ) + Assert.AreEqual( + ~~~4, + FlatList.binarySearch 9 array, + "FlatList.binarySearch must return the complement of the length for 9" + ) + + [] + member _.``binarySearch uses the order of sort, which is ordinal for strings`` () = + let array = FlatList.ofList [ "b"; "A"; "a"; "B" ] |> FlatList.sort + Assert.AreEqual([ "A"; "B"; "a"; "b" ], FlatList.toList array, "FlatList.sort must order strings ordinally") + + for index in 0 .. array.Length - 1 do + Assert.AreEqual( + index, + FlatList.binarySearch array[index] array, + $"FlatList.binarySearch must find %s{array[index]}" + ) + + [] + member _.``blit copies a range into an array`` () = + let target = Array.zeroCreate 5 + FlatList.blit (FlatList.ofList Inputs.several) 2 target 1 3 + CollectionAssert.AreEqual ([| 0; 8; 5; 0; 0 |], target, "FlatList.blit must copy three elements from index 2 to index 1") + + [] + member _.``blit throws for a range out of range as Array does`` () = + let source = List.toArray Inputs.several + + ExceptionAssert.same + id + (fun () -> Array.blit source 6 (Array.zeroCreate 5) 0 3) + (fun () -> FlatList.blit (FlatList.ofArray source) 6 (Array.zeroCreate 5) 0 3) + "FlatList.blit beyond the source must throw as Array.blit does" + + [] + member _.``ofSeq of a flat list shares its storage`` () = + let array = FlatList.ofList Inputs.several + Assert.AreSame (underlying array, underlying (FlatList.ofSeq array), "FlatList.ofSeq of a flat list must not copy it") + + [] + member _.``range functions return the input itself for the whole range`` () = + let array = FlatList.ofList Inputs.several + + let results = [ + nameof FlatList.take, FlatList.take 8 array + nameof FlatList.takeLast, FlatList.takeLast 8 array + nameof FlatList.takeWhile, FlatList.takeWhile (fun _ -> true) array + nameof FlatList.truncate, FlatList.truncate 100 array + nameof FlatList.skip, FlatList.skip -1 array + nameof FlatList.skipLast, FlatList.skipLast 0 array + nameof FlatList.skipWhile, FlatList.skipWhile (fun _ -> false) array + ] + + for name, result in results do + Assert.AreSame (underlying array, underlying result, $"FlatList.%s{name} of the whole range must return the input") + + [] + member _.``range functions copy a partial range`` () = + let array = FlatList.ofList Inputs.several + let struct (before, after) = FlatList.splitAt 3 array + + for name, result in + [ + nameof FlatList.take, FlatList.take 3 array + nameof FlatList.skip, FlatList.skip 3 array + nameof FlatList.splitAt, before + nameof FlatList.splitAt, after + ] do + Assert.AreNotSame (underlying array, underlying result, $"FlatList.%s{name} of a partial range must copy it") + + [] + member _.``append returns the other flat list when one is empty`` () = + let array = FlatList.ofList Inputs.several + Assert.AreSame ( + underlying array, + underlying (FlatList.append array FlatList.empty), + "FlatList.append of an empty second flat list must return the first one" + ) + Assert.AreSame ( + underlying array, + underlying (FlatList.append FlatList.empty array), + "FlatList.append of an empty first flat list must return the second one" + ) + + [] + member _.``sortWithComparer returns a sorted input itself`` () = + let array = FlatList.ofList [ 1; 2; 3 ] + Assert.AreSame ( + underlying array, + underlying (FlatList.sortWithComparer Comparer.Default array), + "FlatList.sortWithComparer must not copy a sorted flat list" + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTupleTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTupleTests.fs new file mode 100644 index 00000000..bc2b2cf5 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTupleTests.fs @@ -0,0 +1,196 @@ +namespace FSharp.Collections.Immutable.Tests + +open FSharp.Collections.Immutable +open FSharp.Collections.Immutable.Tuple +open Microsoft.VisualStudio.TestTools.UnitTesting + +// The type annotations of the results are the proof that the functions of the Tuple namespace shadow the default ones. + +/// The `FlatList` module of the `Tuple` namespace. +[] +type FlatListTupleTests () = + + static let array = FlatList.ofList Inputs.several + + static let other = FlatList.ofList Inputs.other + + static let third = FlatList.ofList Inputs.third + + [] + member _.``unfold takes a generator of value options of reference tuples`` () = + let generator (state : int) = + if state > 0 then + ValueSome (state, state - 1) + else + ValueNone + let generated : FlatList = FlatList.unfold generator 3 + Assert.AreEqual([ 3; 2; 1 ], FlatList.toList generated, "Tuple.FlatList.unfold must generate the elements") + + [] + member _.``allPairs returns reference tuples`` () = + let pairs : FlatList = FlatList.allPairs array other + + Assert.AreEqual<(int * int) list>( + List.allPairs Inputs.several Inputs.other, + FlatList.toList pairs, + "Tuple.FlatList.allPairs must pair as List.allPairs does" + ) + + [] + member _.``indexed returns reference tuples`` () = + let pairs : FlatList = FlatList.indexed array + Assert.AreEqual<(int * int) list>( + List.indexed Inputs.several, + FlatList.toList pairs, + "Tuple.FlatList.indexed must index as List.indexed does" + ) + + [] + member _.``pairwise returns reference tuples`` () = + let pairs : FlatList = FlatList.pairwise array + + Assert.AreEqual<(int * int) list>( + List.pairwise Inputs.several, + FlatList.toList pairs, + "Tuple.FlatList.pairwise must pair as List.pairwise does" + ) + + [] + member _.``unzip takes and returns reference tuples`` () = + let (first : FlatList), (second : FlatList) = FlatList.unzip (FlatList.zip array other) + Assert.AreEqual(Inputs.several, FlatList.toList first, "Tuple.FlatList.unzip must return the first components") + Assert.AreEqual(Inputs.other, FlatList.toList second, "Tuple.FlatList.unzip must return the second components") + + [] + member _.``unzip3 takes and returns reference tuples`` () = + let (first : FlatList), (second : FlatList), (last : FlatList) = + FlatList.unzip3 (FlatList.zip3 array other third) + Assert.AreEqual(Inputs.several, FlatList.toList first, "Tuple.FlatList.unzip3 must return the first components") + Assert.AreEqual(Inputs.other, FlatList.toList second, "Tuple.FlatList.unzip3 must return the second components") + Assert.AreEqual(Inputs.third, FlatList.toList last, "Tuple.FlatList.unzip3 must return the third components") + + [] + member _.``zip returns reference tuples`` () = + let pairs : FlatList = FlatList.zip array other + Assert.AreEqual<(int * int) list>( + List.zip Inputs.several Inputs.other, + FlatList.toList pairs, + "Tuple.FlatList.zip must zip as List.zip does" + ) + + [] + member _.``zip throws for different lengths as the default does`` () = + ExceptionAssert.same + id + (fun () -> + FSharp.Collections.Immutable.FlatList.zip array (FlatList.singleton 1) + |> ignore + ) + (fun () -> FlatList.zip array (FlatList.singleton 1) |> ignore) + "Tuple.FlatList.zip must throw as the default zip does" + + [] + member _.``zip3 returns reference tuples`` () = + let triples : FlatList = FlatList.zip3 array other third + + Assert.AreEqual<(int * int * int) list>( + List.zip3 Inputs.several Inputs.other Inputs.third, + FlatList.toList triples, + "Tuple.FlatList.zip3 must zip as List.zip3 does" + ) + + [] + member _.``mapFold takes and returns reference tuples`` () = + let (results : FlatList), (state : int) = + FlatList.mapFold (fun state item -> item * state, state + 1) 1 array + let expectedResults, expectedState = + List.mapFold (fun state item -> item * state, state + 1) 1 Inputs.several + Assert.AreEqual( + expectedResults, + FlatList.toList results, + "Tuple.FlatList.mapFold must map as List.mapFold does" + ) + Assert.AreEqual(expectedState, state, "Tuple.FlatList.mapFold must thread the state as List.mapFold does") + + [] + member _.``mapFoldBack takes and returns reference tuples`` () = + let (results : FlatList), (state : int) = + FlatList.mapFoldBack (fun item state -> item * state, state + 1) array 1 + let expectedResults, expectedState = + List.mapFoldBack (fun item state -> item * state, state + 1) Inputs.several 1 + Assert.AreEqual( + expectedResults, + FlatList.toList results, + "Tuple.FlatList.mapFoldBack must map as List.mapFoldBack does" + ) + Assert.AreEqual(expectedState, state, "Tuple.FlatList.mapFoldBack must thread the state as List.mapFoldBack does") + + [] + member _.``countBy returns reference tuples`` () = + let counts : FlatList = FlatList.countBy (fun item -> item % 3) array + + Assert.AreEqual<(int * int) list>( + List.countBy (fun item -> item % 3) Inputs.several, + FlatList.toList counts, + "Tuple.FlatList.countBy must count as List.countBy does" + ) + + [] + member _.``groupBy returns reference tuples`` () = + let groups : FlatList> = FlatList.groupBy (fun item -> item % 3) array + + Assert.AreEqual<(int * int list) list>( + List.groupBy (fun item -> item % 3) Inputs.several, + groups + |> FlatList.toList + |> List.map (fun (key, items) -> key, FlatList.toList items), + "Tuple.FlatList.groupBy must group as List.groupBy does" + ) + + [] + member _.``partition returns a reference tuple`` () = + let (accepted : FlatList), (rejected : FlatList) = + FlatList.partition (fun item -> item > 0) array + let expectedAccepted, expectedRejected = List.partition (fun item -> item > 0) Inputs.several + Assert.AreEqual( + expectedAccepted, + FlatList.toList accepted, + "Tuple.FlatList.partition must return the accepted elements" + ) + Assert.AreEqual( + expectedRejected, + FlatList.toList rejected, + "Tuple.FlatList.partition must return the rejected elements" + ) + + [] + member _.``partitionWith returns a reference tuple`` () = + let partitioner (item : int) = + if item > 0 then + Choice1Of2 item + else + Choice2Of2 (string item) + let (first : FlatList), (second : FlatList) = FlatList.partitionWith partitioner array + let expectedFirst, expectedSecond = List.partitionWith partitioner Inputs.several + Assert.AreEqual( + expectedFirst, + FlatList.toList first, + "Tuple.FlatList.partitionWith must return the first choices" + ) + Assert.AreEqual( + expectedSecond, + FlatList.toList second, + "Tuple.FlatList.partitionWith must return the second choices" + ) + + [] + member _.``splitAt returns a reference tuple`` () = + let (before : FlatList), (after : FlatList) = FlatList.splitAt 3 array + let expectedBefore, expectedAfter = List.splitAt 3 Inputs.several + Assert.AreEqual(expectedBefore, FlatList.toList before, "Tuple.FlatList.splitAt must return the first part") + Assert.AreEqual(expectedAfter, FlatList.toList after, "Tuple.FlatList.splitAt must return the second part") + + [] + member _.``the functions without a tuple keep resolving to the default module`` () = + let found : int voption = FlatList.tryFind (fun item -> item > 6) array + Assert.AreEqual(ValueSome 8, found, "FlatList.tryFind must resolve to the default module") From 43e4fd54df0c84247ff6a74a06bc3afd0ab32454 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 12:14:24 +0200 Subject: [PATCH 03/27] docs: `FlatList` in the changelog and the collection table Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 21 +++++++++++++++++++++ README.md | 2 +- docsSrc/Explanations/Background.md | 2 +- docsSrc/index.md | 2 +- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae631ade..09b4494d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +* `FlatList` has every function of the FSharp.Core `Array` module that applies to an immutable array, with its semantics + and exceptions, plus `add`, `asMemory`, `asSpan`, `binarySearch`, `indexOf`, `indexOfWith`, `lastIndexOf`, + `lastIndexOfWith`, `ofReadOnlySpan`, `ofSpan`, `skipLast`, `sortWithComparer`, `takeLast` and `tryTail`. +* The `FSharp.Collections.Immutable.Option.FlatList` and `FSharp.Collections.Immutable.Tuple.FlatList` modules repeat the + functions that use value options or struct tuples with options and reference tuples. +* The `FlatListBuilder` module works on the builder of a flat list, and `flatList { }` builds a flat list. + ### Changed * The package targets `net8.0` and `net10.0`. * The package requires FSharp.Core 10.1.301 or later, the first release with `partitionWith`. +* `FlatList`: the `try*` functions return `voption`; `choose`, `pick`, `tryPick` and `unfold` take functions that return + `voption`; the functions that return tuples return struct tuples. +* `FlatList`: every function throws what the FSharp.Core `Array` function of the same name throws. `take` and `splitAt` + throw for a count beyond the length, and a default `ImmutableArray` throws `ArgumentNullException`. + +### Removed + +* `FlatList.index`, `indexWith`, `indexFrom`, `indexFromWith`, `indexRange`, `indexRangeWith` and their `lastIndex` + counterparts: use `indexOf`, `indexOfWith`, `lastIndexOf` and `lastIndexOfWith`. +* `FlatList.removeAll`, `removeAllWith`, `removeRange`, `sortRange`, `sortRangeWith`, `sortRangeWithComparer` and `sub`, + and the `ImmutableArray` module alias of `FlatList`. +* The `Seq` module of conversions between the collections. ## [1.0.0] - 2020-02-23 diff --git a/README.md b/README.md index 58e776c9..a10138fc 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Please also join the [F# Open Source Group](http://fsharp.github.com) | Module | .NET type | Computation expression | |---|---|---| -| `FlatList` | `ImmutableArray<'T>` | – | +| `FlatList` | `ImmutableArray<'T>` | `flatList { }` | | `ImmutableList` | `ImmutableList<'T>` | – | | `Stack` | `ImmutableStack<'T>` | – | | `Queue` | `ImmutableQueue<'T>` | – | diff --git a/docsSrc/Explanations/Background.md b/docsSrc/Explanations/Background.md index c568bf59..822820fa 100644 --- a/docsSrc/Explanations/Background.md +++ b/docsSrc/Explanations/Background.md @@ -26,7 +26,7 @@ While F# has its own built-in immutable collections, System.Collections.Immutabl | Module | .NET type | Computation expression | |---|---|---| -| `FlatList` | `ImmutableArray<'T>` | – | +| `FlatList` | `ImmutableArray<'T>` | `flatList { }` | | `ImmutableList` | `ImmutableList<'T>` | – | | `Stack` | `ImmutableStack<'T>` | – | | `Queue` | `ImmutableQueue<'T>` | – | diff --git a/docsSrc/index.md b/docsSrc/index.md index 39c16874..276ad8fe 100644 --- a/docsSrc/index.md +++ b/docsSrc/index.md @@ -8,7 +8,7 @@ F# bindings for [System.Collections.Immutable](https://learn.microsoft.com/en-us | Module | .NET type | Computation expression | |---|---|---| -| `FlatList` | `ImmutableArray<'T>` | – | +| `FlatList` | `ImmutableArray<'T>` | `flatList { }` | | `ImmutableList` | `ImmutableList<'T>` | – | | `Stack` | `ImmutableStack<'T>` | – | | `Queue` | `ImmutableQueue<'T>` | – | From 8ef6a974ee8c2e70ce30ceb542d5ca35cd9fd9da Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 12:48:47 +0200 Subject: [PATCH 04/27] fixup! feat: `FlatList` module with the vocabulary of the FSharp.Core `Array` module F# ignores `[]` on a module: the modules compile as `FlatList`, `Option.FlatList`, `Tuple.FlatList` and `FlatListBuilder`, as the legacy modules of the released package did. The attributes are removed, and the documentation references use the real IDs. Co-Authored-By: Claude Opus 5.5 --- src/FSharp.Collections.Immutable/FlatList/FlatList.fs | 1 - src/FSharp.Collections.Immutable/FlatList/FlatList.fsi | 9 ++++----- .../FlatList/FlatListBuilder.fs | 5 ++--- .../FlatList/FlatListOption.fs | 1 - .../FlatList/FlatListOption.fsi | 3 +-- .../FlatList/FlatListTuple.fs | 1 - .../FlatList/FlatListTuple.fsi | 3 +-- 7 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatList.fs b/src/FSharp.Collections.Immutable/FlatList/FlatList.fs index 27fd0081..3d08117a 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatList.fs +++ b/src/FSharp.Collections.Immutable/FlatList/FlatList.fs @@ -10,7 +10,6 @@ type FlatList<'T> = ImmutableArray<'T> // Most functions unwrap the underlying array without a copy, call the FSharp.Core Array function, and wrap its fresh // result without a copy (ADR 0001, D5), so that they have the semantics and the exceptions of the Array module (D6). [] -[] module FlatList = // ---- Creation ---- diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi b/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi index 4ecff2b8..9626cf26 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi +++ b/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi @@ -18,8 +18,8 @@ type FlatList<'T> = ImmutableArray<'T> /// /// /// A default immutable array has no underlying array. Every function except -/// -/// and +/// +/// and /// throws for it, as /// does for a array. /// @@ -31,7 +31,6 @@ type FlatList<'T> = ImmutableArray<'T> /// /// [] -[] module FlatList = // ---- Creation ---- @@ -282,7 +281,7 @@ module FlatList = /// /// /// O(log n). The function compares with , - /// the order that produces. + /// the order that produces. /// [] val binarySearch : value : 'T -> array : FlatList<'T> -> int when 'T : comparison @@ -680,7 +679,7 @@ module FlatList = /// The function to test the elements. /// The input flat list. /// The elements that satisfy the predicate, in their order. - /// O(n). A synonym of . + /// O(n). A synonym of . [] val where : predicate : ('T -> bool) -> array : FlatList<'T> -> FlatList<'T> diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs b/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs index b8df10e2..9f1fd471 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs @@ -9,12 +9,11 @@ open System.Collections.Immutable /// /// /// The builder is mutable. The functions that change it return the same builder, so that the calls chain with the pipe -/// operator. creates a builder, and -/// +/// operator. creates a builder, and +/// /// returns its contents as a flat list. /// [] -[] module FlatListBuilder = // ---- Properties ---- diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListOption.fs b/src/FSharp.Collections.Immutable/FlatList/FlatListOption.fs index 6265c305..87856235 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListOption.fs +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListOption.fs @@ -5,7 +5,6 @@ open FSharp.Collections.Immutable // The functions call the FSharp.Core Array functions, which take and return options, over the underlying array, or // convert the result of the default module where it implements the function itself. [] -[] module FlatList = // ---- Creation ---- diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListOption.fsi b/src/FSharp.Collections.Immutable/FlatList/FlatListOption.fsi index 21c99667..12544d3e 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListOption.fsi +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListOption.fsi @@ -3,7 +3,7 @@ namespace FSharp.Collections.Immutable.Option open FSharp.Collections.Immutable /// -/// The functions of that take or return an optional +/// The functions of that take or return an optional /// value, with instead of /// . /// @@ -13,7 +13,6 @@ open FSharp.Collections.Immutable /// name in where that module has one. /// [] -[] module FlatList = // ---- Creation ---- diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fs b/src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fs index ff878c38..e285c4ef 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fs +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fs @@ -5,7 +5,6 @@ open FSharp.Collections.Immutable // The functions that build a tuple per element share their algorithm with the default module and only pass another // tuple constructor; the others delegate to the default module and repack the tuples, which costs no second pass. [] -[] module FlatList = // ---- Creation ---- diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fsi b/src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fsi index 390b447e..307127c7 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fsi +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fsi @@ -3,7 +3,7 @@ namespace FSharp.Collections.Immutable.Tuple open FSharp.Collections.Immutable /// -/// The functions of that take or return tuples, with +/// The functions of that take or return tuples, with /// reference tuples instead of struct tuples. /// /// @@ -14,7 +14,6 @@ open FSharp.Collections.Immutable /// . /// [] -[] module FlatList = // ---- Creation ---- From 35ec01e277ce0e5e9d473f9c6457ad2b3bf358d6 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 12:55:34 +0200 Subject: [PATCH 05/27] fixup! feat: `FlatList` module with the vocabulary of the FSharp.Core `Array` module Review of #26: - `ofSeq` rejects a default flat list passed as a sequence with `ArgumentNullException`, as every function rejects a default flat list (D6); a valid flat list is still returned without a copy. - `collect` rejects a default flat list that the mapping returns with `ArgumentNullException`, as `concat` does. - `countBy` keeps one counter per key instead of every element: a dictionary over keys wrapped in a struct, which admits `null` keys, with F# structural equality and the order of first occurrence. Co-Authored-By: Claude Opus 5.5 --- .../FlatList/FlatList.fs | 11 +++-- .../FlatList/FlatList.fsi | 5 ++- src/FSharp.Collections.Immutable/Helper.fs | 42 +++++++++++++++---- .../FlatList/FlatListTests.fs | 21 ++++++++++ 4 files changed, 66 insertions(+), 13 deletions(-) diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatList.fs b/src/FSharp.Collections.Immutable/FlatList/FlatList.fs index 3d08117a..04472314 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatList.fs +++ b/src/FSharp.Collections.Immutable/FlatList/FlatList.fs @@ -36,8 +36,10 @@ module FlatList = [] let ofSeq (source : seq<'T>) = match source with - // An immutable array needs no copy - | :? ImmutableArray<'T> as array when not array.IsDefault -> array + // An immutable array needs no copy; a default one is a default FlatList, which no function accepts (ADR 0001, D6) + | :? ImmutableArray<'T> as array -> + checkNotDefault (nameof source) array + array | _ -> Array.ofSeq source |> wrapArray [] @@ -351,7 +353,10 @@ module FlatList = let builder = ImmutableArray.CreateBuilder<'U>() for item in source do - builder.AddRange (mapping item) + let items = mapping item + // Array.collect dereferences a null result; a default result is a default FlatList, rejected as in concat + checkNotDefault (nameof mapping) items + builder.AddRange items builder.DrainToImmutable () diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi b/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi index 9626cf26..ff9ab9b3 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi +++ b/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi @@ -84,8 +84,8 @@ module FlatList = /// Creates a flat list from a sequence. /// The input sequence. /// The flat list of the elements of the sequence. - /// Thrown when is . - /// O(n). + /// Thrown when is or a default flat list. + /// O(n). A flat list is returned as it is, without a copy. [] val ofSeq : source : seq<'T> -> FlatList<'T> @@ -502,6 +502,7 @@ module FlatList = /// The function that returns a flat list for each element. /// The input flat list. /// The concatenation of the returned flat lists. + /// Thrown when the mapping returns a default flat list. /// O(n + the total length of the returned flat lists). [] val collect : mapping : ('T -> FlatList<'U>) -> array : FlatList<'T> -> FlatList<'U> diff --git a/src/FSharp.Collections.Immutable/Helper.fs b/src/FSharp.Collections.Immutable/Helper.fs index ad29679f..37be54aa 100644 --- a/src/FSharp.Collections.Immutable/Helper.fs +++ b/src/FSharp.Collections.Immutable/Helper.fs @@ -229,20 +229,46 @@ module internal Arrays = result - /// The keys of the projection in the order of their first occurrence, each with the number of its elements. + /// A key that may be null, for a dictionary, which rejects null keys. + [] + type NullableKey<'Key> = { Key : 'Key } + + /// Compares wrapped keys in F# structural equality, which handles null keys, as FSharp.Core does for its keys. + let nullableKeyComparer<'Key when 'Key : equality> () = + let structural = HashIdentity.Structural<'Key> + + { + new IEqualityComparer> with + member _.Equals (left, right) = structural.Equals (left.Key, right.Key) + member _.GetHashCode key = structural.GetHashCode key.Key + } + + /// The keys of the projection in the order of their first occurrence, each with the number of its elements. + /// The counting keeps one counter per key, not the elements, so it needs memory for the keys only. let inline countBy ([] tuple : 'Key -> int -> 'R) (projection : 'T -> 'Key) (array : 'T array) : 'R array when 'Key : equality = - // ToLookup keeps the order of the first occurrence of each key and accepts null keys, as FSharp.Core does - let lookup = array.ToLookup ((fun item -> projection item), HashIdentity.Structural<'Key>) - let result = Array.zeroCreate lookup.Count - let mutable index = 0 + let indices = Dictionary, int>(nullableKeyComparer<'Key>()) + let keys = ResizeArray<'Key>() + let counts = ResizeArray() - for grouping in lookup do - result[index] <- tuple grouping.Key (grouping.Count ()) - index <- index + 1 + for item in array do + let key = projection item + let mutable index = 0 + + if indices.TryGetValue ({ Key = key }, &index) then + counts[index] <- counts[index] + 1 + else + indices.Add ({ Key = key }, keys.Count) + keys.Add key + counts.Add 1 + + let result = Array.zeroCreate keys.Count + + for index = 0 to result.Length - 1 do + result[index] <- tuple keys[index] counts[index] result diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs index 7d2dce14..f8a6bbb1 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs @@ -192,6 +192,15 @@ type FlatListTests () = // Array.concat dereferences a null element array without a check; a default element is a default flat list (D6) struct (nameof FlatList.concat, "arrays", (fun () -> FlatList.concat [ defaultArray ] |> ignore)) struct (nameof FlatList.transpose, "arrays", (fun () -> FlatList.transpose [ defaultArray ] |> ignore)) + // Array.collect dereferences a null result of the mapping; a default result is rejected as in concat + struct (nameof FlatList.collect, + "mapping", + (fun () -> + FlatList.collect (fun _ -> defaultArray) (FlatList.singleton 1) + |> ignore + )) + // A default flat list passed as a sequence is still a default flat list + struct (nameof FlatList.ofSeq, "source", (fun () -> FlatList.ofSeq (defaultArray :> seq) |> ignore)) struct (nameof FlatList.indexOf, "array", (fun () -> FlatList.indexOf 1 defaultArray |> ignore)) struct (nameof FlatList.indexOfWith, "array", @@ -230,6 +239,18 @@ type FlatListTests () = $"FlatList.%s{name} must name the parameter of the default flat list" ) + [] + member _.``countBy counts null keys like any other key`` () = + let array = FlatList.ofList Inputs.several + let keyOf (item : int) = if item > 0 then Some (item % 2) else None + + Assert.AreEqual( + List.countBy keyOf Inputs.several + |> List.map (fun (key, count) -> struct (key, count)), + FlatList.countBy keyOf array |> FlatList.toList, + "FlatList.countBy must count the key None, which is null, as List.countBy does" + ) + [] member _.``asSpan views the elements`` () = let array = FlatList.ofList Inputs.several From a8270050246ce8d948a04847a2074bc718013d3c Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 15:27:00 +0200 Subject: [PATCH 06/27] fixup! feat: `FlatList` module with the vocabulary of the FSharp.Core `Array` module Follows the reworked ADR 0001 (D2, D5): - `FlatList`, `Option.FlatList`, `Tuple.FlatList`, `FlatListBuilder` and `ComputationExpressions` are the root modules of their files. - The `FlatList<'T>` abbreviation moves to `FlatList/FlatListTypes.fs`, which needs no signature file. - `Helper.fs` caches the comparer of F# generic comparison per type (`DefaultComparer<'T>`); `binarySearch` and the builder sort use it, so a call no longer creates a comparer for a type that is not primitive. - The cref of `FastGenericComparer` in the remarks of `binarySearch` names the method. Co-Authored-By: Claude Opus 5.5 --- .../ComputationExpressions.fs | 30 +- .../FSharp.Collections.Immutable.fsproj | 1 + .../FlatList/FlatList.fs | 1537 +++++----- .../FlatList/FlatList.fsi | 2574 ++++++++--------- .../FlatList/FlatListBuilder.fs | 366 ++- .../FlatList/FlatListOption.fs | 73 +- .../FlatList/FlatListOption.fsi | 226 +- .../FlatList/FlatListTuple.fs | 215 +- .../FlatList/FlatListTuple.fsi | 273 +- .../FlatList/FlatListTypes.fs | 10 + src/FSharp.Collections.Immutable/Helper.fs | 28 + 11 files changed, 2676 insertions(+), 2657 deletions(-) create mode 100644 src/FSharp.Collections.Immutable/FlatList/FlatListTypes.fs diff --git a/src/FSharp.Collections.Immutable/ComputationExpressions.fs b/src/FSharp.Collections.Immutable/ComputationExpressions.fs index 021c3d6c..327945a0 100644 --- a/src/FSharp.Collections.Immutable/ComputationExpressions.fs +++ b/src/FSharp.Collections.Immutable/ComputationExpressions.fs @@ -1,19 +1,17 @@ -namespace FSharp.Collections.Immutable - /// The computation expressions of the collections (ADR 0001, D7). [] -module ComputationExpressions = +module FSharp.Collections.Immutable.ComputationExpressions - /// Builds a flat list. - /// - /// - /// let squares = flatList { - /// yield 0 - /// for i in 1..3 do - /// yield i * i - /// yield! [ 100; 200 ] - /// } - /// - /// Evaluates to a flat list of 0, 1, 4, 9, 100 and 200. - /// - let flatList = FlatListCE () +/// Builds a flat list. +/// +/// +/// let squares = flatList { +/// yield 0 +/// for i in 1..3 do +/// yield i * i +/// yield! [ 100; 200 ] +/// } +/// +/// Evaluates to a flat list of 0, 1, 4, 9, 100 and 200. +/// +let flatList = FlatListCE () diff --git a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj index 645623a0..d0aae447 100644 --- a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj +++ b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj @@ -21,6 +21,7 @@ + diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatList.fs b/src/FSharp.Collections.Immutable/FlatList/FlatList.fs index 04472314..2f969b34 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatList.fs +++ b/src/FSharp.Collections.Immutable/FlatList/FlatList.fs @@ -1,892 +1,889 @@ -namespace FSharp.Collections.Immutable +[] +module FSharp.Collections.Immutable.FlatList open System open System.Collections.Generic open System.Collections.Immutable open System.Runtime.InteropServices -type FlatList<'T> = ImmutableArray<'T> - // Most functions unwrap the underlying array without a copy, call the FSharp.Core Array function, and wrap its fresh // result without a copy (ADR 0001, D5), so that they have the semantics and the exceptions of the Array module (D6). -[] -module FlatList = - - // ---- Creation ---- - - [] - let create count (value : 'T) = Array.create count value |> wrapArray - - [] - [] - let empty<'T> : FlatList<'T> = ImmutableArray<'T>.Empty - - [] - let init count (initializer : int -> 'T) = Array.init count initializer |> wrapArray - - [] - let ofArray (array : 'T array) = Array.copy array |> wrapArray - - [] - let ofList (list : 'T list) = Array.ofList list |> wrapArray - - [] - let ofReadOnlySpan (span : ReadOnlySpan<'T>) = ImmutableArray.Create<'T> span - - [] - let ofSeq (source : seq<'T>) = - match source with - // An immutable array needs no copy; a default one is a default FlatList, which no function accepts (ADR 0001, D6) - | :? ImmutableArray<'T> as array -> - checkNotDefault (nameof source) array - array - | _ -> Array.ofSeq source |> wrapArray - - [] - let ofSpan (span : Span<'T>) = ImmutableArray.Create<'T> span - - [] - let replicate count (initial : 'T) = Array.replicate count initial |> wrapArray - - [] - let singleton (value : 'T) = ImmutableArray.Create<'T> value - - [] - let unfold (generator : 'State -> struct ('T * 'State) voption) (state : 'State) = - let builder = ImmutableArray.CreateBuilder<'T>() - let mutable state = state - let mutable go = true - - while go do - match generator state with - | ValueSome (struct (item, next)) -> - builder.Add item - state <- next - | ValueNone -> go <- false - - builder.DrainToImmutable () - // ---- Builder ---- +// ---- Creation ---- - [] - let build (action : FlatList<'T>.Builder -> unit) = - let builder = ImmutableArray.CreateBuilder<'T>() - action builder - builder.DrainToImmutable () +[] +let create count (value : 'T) = Array.create count value |> wrapArray - [] - let builder () : FlatList<'T>.Builder = ImmutableArray.CreateBuilder<'T>() +[] +[] +let empty<'T> : FlatList<'T> = ImmutableArray<'T>.Empty - [] - let builderWith (capacity : int) : FlatList<'T>.Builder = ImmutableArray.CreateBuilder<'T> capacity +[] +let init count (initializer : int -> 'T) = Array.init count initializer |> wrapArray - [] - let moveFromBuilder (builder : FlatList<'T>.Builder) = - checkNotNull (nameof builder) builder - builder.MoveToImmutable () +[] +let ofArray (array : 'T array) = Array.copy array |> wrapArray - [] - let ofBuilder (builder : FlatList<'T>.Builder) = - checkNotNull (nameof builder) builder - builder.ToImmutable () +[] +let ofList (list : 'T list) = Array.ofList list |> wrapArray - [] - let toBuilder (array : FlatList<'T>) = - checkNotDefault (nameof array) array - array.ToBuilder () +[] +let ofReadOnlySpan (span : ReadOnlySpan<'T>) = ImmutableArray.Create<'T> span - [] - let update (action : FlatList<'T>.Builder -> unit) (array : FlatList<'T>) = - checkNotDefault (nameof array) array - let builder = array.ToBuilder () - action builder - builder.DrainToImmutable () +[] +let ofSeq (source : seq<'T>) = + match source with + // An immutable array needs no copy; a default one is a default FlatList, which no function accepts (ADR 0001, D6) + | :? ImmutableArray<'T> as array -> + checkNotDefault (nameof source) array + array + | _ -> Array.ofSeq source |> wrapArray - // ---- Properties ---- +[] +let ofSpan (span : Span<'T>) = ImmutableArray.Create<'T> span - [] - let isEmpty (array : FlatList<'T>) = - checkNotDefault (nameof array) array - array.IsEmpty +[] +let replicate count (initial : 'T) = Array.replicate count initial |> wrapArray - [] - let length (array : FlatList<'T>) = - checkNotDefault (nameof array) array - array.Length +[] +let singleton (value : 'T) = ImmutableArray.Create<'T> value - // ---- Access ---- +[] +let unfold (generator : 'State -> struct ('T * 'State) voption) (state : 'State) = + let builder = ImmutableArray.CreateBuilder<'T>() + let mutable state = state + let mutable go = true - [] - let exactlyOne (array : FlatList<'T>) = Array.exactlyOne (unwrapArray (nameof array) array) + while go do + match generator state with + | ValueSome (struct (item, next)) -> + builder.Add item + state <- next + | ValueNone -> go <- false - [] - let head (array : FlatList<'T>) = Array.head (unwrapArray (nameof array) array) + builder.DrainToImmutable () - [] - let item index (array : FlatList<'T>) = Array.item index (unwrapArray (nameof array) array) +// ---- Builder ---- - [] - let last (array : FlatList<'T>) = Array.last (unwrapArray (nameof array) array) - - [] - let tail (array : FlatList<'T>) = Array.tail (unwrapArray (nameof array) array) |> wrapArray - - [] - let tryExactlyOne (array : FlatList<'T>) = - checkNotDefault (nameof array) array - if array.Length = 1 then ValueSome array[0] else ValueNone - - [] - let tryHead (array : FlatList<'T>) = - checkNotDefault (nameof array) array - if array.IsEmpty then ValueNone else ValueSome array[0] +[] +let build (action : FlatList<'T>.Builder -> unit) = + let builder = ImmutableArray.CreateBuilder<'T>() + action builder + builder.DrainToImmutable () - [] - let tryItem index (array : FlatList<'T>) = - checkNotDefault (nameof array) array +[] +let builder () : FlatList<'T>.Builder = ImmutableArray.CreateBuilder<'T>() - if index < 0 || index >= array.Length then - ValueNone - else - ValueSome array[index] - - [] - let tryLast (array : FlatList<'T>) = - checkNotDefault (nameof array) array - - if array.IsEmpty then - ValueNone - else - ValueSome array[array.Length - 1] +[] +let builderWith (capacity : int) : FlatList<'T>.Builder = ImmutableArray.CreateBuilder<'T> capacity - [] - let tryTail (array : FlatList<'T>) = - let source = unwrapArray (nameof array) array +[] +let moveFromBuilder (builder : FlatList<'T>.Builder) = + checkNotNull (nameof builder) builder + builder.MoveToImmutable () - if source.Length = 0 then - ValueNone - else - ValueSome (Array.tail source |> wrapArray) +[] +let ofBuilder (builder : FlatList<'T>.Builder) = + checkNotNull (nameof builder) builder + builder.ToImmutable () - // ---- Search ---- +[] +let toBuilder (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.ToBuilder () - [] - let binarySearch (value : 'T) (array : FlatList<'T>) = - checkNotDefault (nameof array) array - ImmutableArray.BinarySearch (array, value, LanguagePrimitives.FastGenericComparer<'T>) +[] +let update (action : FlatList<'T>.Builder -> unit) (array : FlatList<'T>) = + checkNotDefault (nameof array) array + let builder = array.ToBuilder () + action builder + builder.DrainToImmutable () - [] - let contains (value : 'T) (array : FlatList<'T>) = Array.contains value (unwrapArray (nameof array) array) +// ---- Properties ---- - [] - let exists predicate (array : FlatList<'T>) = Array.exists predicate (unwrapArray (nameof array) array) +[] +let isEmpty (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.IsEmpty - [] - let exists2 predicate (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = - Array.exists2 predicate (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) +[] +let length (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.Length - [] - let find predicate (array : FlatList<'T>) = Array.find predicate (unwrapArray (nameof array) array) +// ---- Access ---- - [] - let findBack predicate (array : FlatList<'T>) = Array.findBack predicate (unwrapArray (nameof array) array) +[] +let exactlyOne (array : FlatList<'T>) = Array.exactlyOne (unwrapArray (nameof array) array) - [] - let findIndex predicate (array : FlatList<'T>) = Array.findIndex predicate (unwrapArray (nameof array) array) +[] +let head (array : FlatList<'T>) = Array.head (unwrapArray (nameof array) array) - [] - let findIndexBack predicate (array : FlatList<'T>) = Array.findIndexBack predicate (unwrapArray (nameof array) array) +[] +let item index (array : FlatList<'T>) = Array.item index (unwrapArray (nameof array) array) - [] - let forall predicate (array : FlatList<'T>) = Array.forall predicate (unwrapArray (nameof array) array) +[] +let last (array : FlatList<'T>) = Array.last (unwrapArray (nameof array) array) - [] - let forall2 predicate (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = - Array.forall2 predicate (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) +[] +let tail (array : FlatList<'T>) = Array.tail (unwrapArray (nameof array) array) |> wrapArray - [] - let indexOf (value : 'T) (array : FlatList<'T>) = - checkNotDefault (nameof array) array - array.IndexOf value +[] +let tryExactlyOne (array : FlatList<'T>) = + checkNotDefault (nameof array) array + if array.Length = 1 then ValueSome array[0] else ValueNone - [] - let indexOfWith (comparer : IEqualityComparer<'T>) (value : 'T) (array : FlatList<'T>) = - checkNotDefault (nameof array) array - array.IndexOf (value, 0, array.Length, comparer) +[] +let tryHead (array : FlatList<'T>) = + checkNotDefault (nameof array) array + if array.IsEmpty then ValueNone else ValueSome array[0] - [] - let lastIndexOf (value : 'T) (array : FlatList<'T>) = - checkNotDefault (nameof array) array - array.LastIndexOf value +[] +let tryItem index (array : FlatList<'T>) = + checkNotDefault (nameof array) array - [] - let lastIndexOfWith (comparer : IEqualityComparer<'T>) (value : 'T) (array : FlatList<'T>) = - checkNotDefault (nameof array) array + if index < 0 || index >= array.Length then + ValueNone + else + ValueSome array[index] - // The BCL rejects the start index -1 of an empty array, so an empty array answers without asking it - if array.IsEmpty then - -1 - else - array.LastIndexOf (value, array.Length - 1, array.Length, comparer) +[] +let tryLast (array : FlatList<'T>) = + checkNotDefault (nameof array) array - [] - let pick (chooser : 'T -> 'U voption) (array : FlatList<'T>) = - let source = unwrapArray (nameof array) array - let mutable result = ValueNone - let mutable index = 0 + if array.IsEmpty then + ValueNone + else + ValueSome array[array.Length - 1] - while result.IsNone && index < source.Length do - result <- chooser source[index] - index <- index + 1 +[] +let tryTail (array : FlatList<'T>) = + let source = unwrapArray (nameof array) array - match result with - | ValueSome value -> value - | ValueNone -> Raise.indexNotFound () + if source.Length = 0 then + ValueNone + else + ValueSome (Array.tail source |> wrapArray) - [] - let tryFind predicate (array : FlatList<'T>) = - let source = unwrapArray (nameof array) array - let mutable result = ValueNone - let mutable index = 0 +// ---- Search ---- - while result.IsNone && index < source.Length do - if predicate source[index] then - result <- ValueSome source[index] +[] +let binarySearch (value : 'T) (array : FlatList<'T>) = + checkNotDefault (nameof array) array + ImmutableArray.BinarySearch (array, value, DefaultComparer<'T>.Instance) - index <- index + 1 +[] +let contains (value : 'T) (array : FlatList<'T>) = Array.contains value (unwrapArray (nameof array) array) - result +[] +let exists predicate (array : FlatList<'T>) = Array.exists predicate (unwrapArray (nameof array) array) - [] - let tryFindBack predicate (array : FlatList<'T>) = - let source = unwrapArray (nameof array) array - let mutable result = ValueNone - let mutable index = source.Length - 1 +[] +let exists2 predicate (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = + Array.exists2 predicate (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) - while result.IsNone && index >= 0 do - if predicate source[index] then - result <- ValueSome source[index] +[] +let find predicate (array : FlatList<'T>) = Array.find predicate (unwrapArray (nameof array) array) - index <- index - 1 +[] +let findBack predicate (array : FlatList<'T>) = Array.findBack predicate (unwrapArray (nameof array) array) - result +[] +let findIndex predicate (array : FlatList<'T>) = Array.findIndex predicate (unwrapArray (nameof array) array) - [] - let tryFindIndex predicate (array : FlatList<'T>) = - let source = unwrapArray (nameof array) array - let mutable result = ValueNone - let mutable index = 0 +[] +let findIndexBack predicate (array : FlatList<'T>) = Array.findIndexBack predicate (unwrapArray (nameof array) array) - while result.IsNone && index < source.Length do - if predicate source[index] then - result <- ValueSome index +[] +let forall predicate (array : FlatList<'T>) = Array.forall predicate (unwrapArray (nameof array) array) - index <- index + 1 - - result +[] +let forall2 predicate (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = + Array.forall2 predicate (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) - [] - let tryFindIndexBack predicate (array : FlatList<'T>) = - let source = unwrapArray (nameof array) array - let mutable result = ValueNone - let mutable index = source.Length - 1 - - while result.IsNone && index >= 0 do - if predicate source[index] then - result <- ValueSome index - - index <- index - 1 - - result - - [] - let tryPick (chooser : 'T -> 'U voption) (array : FlatList<'T>) = - let source = unwrapArray (nameof array) array - let mutable result = ValueNone - let mutable index = 0 - - while result.IsNone && index < source.Length do - result <- chooser source[index] - index <- index + 1 - - result - - // ---- Transformation ---- - - [] - let add (value : 'T) (array : FlatList<'T>) = - checkNotDefault (nameof array) array - array.Add value - - [] - let allPairs (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = - Arrays.allPairs structPair (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) - |> wrapArray - - [] - let append (array1 : FlatList<'T>) (array2 : FlatList<'T>) = - checkNotDefault (nameof array1) array1 - checkNotDefault (nameof array2) array2 - array1.AddRange array2 - - [] - let choose (chooser : 'T -> 'U voption) (array : FlatList<'T>) = - let source = unwrapArray (nameof array) array - let builder = ImmutableArray.CreateBuilder<'U>() - - for item in source do - match chooser item with - | ValueSome value -> builder.Add value - | ValueNone -> () - - builder.DrainToImmutable () - - [] - let chunkBySize chunkSize (array : FlatList<'T>) = - Array.chunkBySize chunkSize (unwrapArray (nameof array) array) - |> Array.map wrapArray - |> wrapArray - - [] - let collect (mapping : 'T -> FlatList<'U>) (array : FlatList<'T>) = - let source = unwrapArray (nameof array) array - let builder = ImmutableArray.CreateBuilder<'U>() - - for item in source do - let items = mapping item - // Array.collect dereferences a null result; a default result is a default FlatList, rejected as in concat - checkNotDefault (nameof mapping) items - builder.AddRange items - - builder.DrainToImmutable () - - [] - let concat (arrays : seq>) = - checkNotNull (nameof arrays) arrays - - arrays - |> Seq.map (fun array -> unwrapArray (nameof arrays) array) - |> Array.concat - |> wrapArray - - [] - let filter predicate (array : FlatList<'T>) = - Array.filter predicate (unwrapArray (nameof array) array) - |> wrapArray - - [] - let indexed (array : FlatList<'T>) = - Arrays.indexed structPair (unwrapArray (nameof array) array) - |> wrapArray - - [] - let insertAt index (value : 'T) (source : FlatList<'T>) = - Array.insertAt index value (unwrapArray (nameof source) source) - |> wrapArray - - [] - let insertManyAt index (values : seq<'T>) (source : FlatList<'T>) = - Array.insertManyAt index values (unwrapArray (nameof source) source) - |> wrapArray - - [] - let map (mapping : 'T -> 'U) (array : FlatList<'T>) = - Array.map mapping (unwrapArray (nameof array) array) - |> wrapArray - - [] - let map2 (mapping : 'T1 -> 'T2 -> 'U) (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = - Array.map2 mapping (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) - |> wrapArray - - [] - let map3 (mapping : 'T1 -> 'T2 -> 'T3 -> 'U) (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) (array3 : FlatList<'T3>) = - Array.map3 - mapping - (unwrapArray (nameof array1) array1) - (unwrapArray (nameof array2) array2) - (unwrapArray (nameof array3) array3) - |> wrapArray - - [] - let mapi (mapping : int -> 'T -> 'U) (array : FlatList<'T>) = - Array.mapi mapping (unwrapArray (nameof array) array) - |> wrapArray - - [] - let mapi2 (mapping : int -> 'T1 -> 'T2 -> 'U) (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = - Array.mapi2 mapping (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) - |> wrapArray - - [] - let pairwise (array : FlatList<'T>) = - Arrays.pairwise structPair (unwrapArray (nameof array) array) - |> wrapArray - - [] - let permute indexMap (array : FlatList<'T>) = - Array.permute indexMap (unwrapArray (nameof array) array) - |> wrapArray - - [] - let removeAt index (source : FlatList<'T>) = - Array.removeAt index (unwrapArray (nameof source) source) - |> wrapArray - - [] - let removeManyAt index count (source : FlatList<'T>) = - Array.removeManyAt index count (unwrapArray (nameof source) source) - |> wrapArray - - [] - let rev (array : FlatList<'T>) = Array.rev (unwrapArray (nameof array) array) |> wrapArray - - [] - let transpose (arrays : seq>) = - checkNotNull (nameof arrays) arrays - - arrays - |> Seq.map (fun array -> unwrapArray (nameof arrays) array) - |> Array.transpose - |> Array.map wrapArray - |> wrapArray - - [] - let unzip (array : FlatList) = - let struct (first, second) = Arrays.unzip id (unwrapArray (nameof array) array) - struct (wrapArray first, wrapArray second) - - [] - let unzip3 (array : FlatList) = - let struct (first, second, third) = Arrays.unzip3 id (unwrapArray (nameof array) array) - struct (wrapArray first, wrapArray second, wrapArray third) - - [] - let updateAt index (value : 'T) (source : FlatList<'T>) = - Array.updateAt index value (unwrapArray (nameof source) source) - |> wrapArray - - [] - let where predicate (array : FlatList<'T>) = - Array.filter predicate (unwrapArray (nameof array) array) - |> wrapArray - - [] - let windowed windowSize (array : FlatList<'T>) = - Array.windowed windowSize (unwrapArray (nameof array) array) - |> Array.map wrapArray - |> wrapArray - - [] - let zip (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = - let source1 = unwrapArray (nameof array1) array1 - let source2 = unwrapArray (nameof array2) array2 - ArrayChecks.sameLengths (nameof array1) source1 (nameof array2) source2 - Arrays.zip structPair source1 source2 |> wrapArray - - [] - let zip3 (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) (array3 : FlatList<'T3>) = - let source1 = unwrapArray (nameof array1) array1 - let source2 = unwrapArray (nameof array2) array2 - let source3 = unwrapArray (nameof array3) array3 - ArrayChecks.sameLengths3 (nameof array1) source1 (nameof array2) source2 (nameof array3) source3 - - Arrays.zip3 structTriple source1 source2 source3 - |> wrapArray - - // ---- Folding ---- - - // The inline functions can use public members only, so they check and unwrap the array themselves - [] - let inline average (array : FlatList< ^T >) : ^T = - if array.IsDefault then - nullArg (nameof array) - - Array.average ( - ImmutableCollectionsMarshal.AsArray array - |> Unchecked.nonNull - ) - - [] - let inline averageBy ([] projection : 'T -> ^U) (array : FlatList<'T>) : ^U = - if array.IsDefault then - nullArg (nameof array) - - Array.averageBy - projection - (ImmutableCollectionsMarshal.AsArray array - |> Unchecked.nonNull) - - [] - let compareWith comparer (array1 : FlatList<'T>) (array2 : FlatList<'T>) = - Array.compareWith comparer (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) - - [] - let fold (folder : 'State -> 'T -> 'State) (state : 'State) (array : FlatList<'T>) = - Array.fold folder state (unwrapArray (nameof array) array) - - [] - let fold2 (folder : 'State -> 'T1 -> 'T2 -> 'State) (state : 'State) (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = - Array.fold2 folder state (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) - - [] - let foldBack (folder : 'T -> 'State -> 'State) (array : FlatList<'T>) (state : 'State) = - Array.foldBack folder (unwrapArray (nameof array) array) state - - [] - let foldBack2 (folder : 'T1 -> 'T2 -> 'State -> 'State) (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) (state : 'State) = - Array.foldBack2 folder (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) state - - [] - let iter action (array : FlatList<'T>) = Array.iter action (unwrapArray (nameof array) array) - - [] - let iter2 action (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = - Array.iter2 action (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) - - [] - let iteri action (array : FlatList<'T>) = Array.iteri action (unwrapArray (nameof array) array) - - [] - let iteri2 action (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = - Array.iteri2 action (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) - - [] - let mapFold (mapping : 'State -> 'T -> struct ('Result * 'State)) (state : 'State) (array : FlatList<'T>) = - let source = unwrapArray (nameof array) array - let results = Array.zeroCreate source.Length - let mutable state = state - - for index = 0 to source.Length - 1 do - let struct (result, next) = mapping state source[index] - results[index] <- result - state <- next +[] +let indexOf (value : 'T) (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.IndexOf value - struct (wrapArray results, state) +[] +let indexOfWith (comparer : IEqualityComparer<'T>) (value : 'T) (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.IndexOf (value, 0, array.Length, comparer) - [] - let mapFoldBack (mapping : 'T -> 'State -> struct ('Result * 'State)) (array : FlatList<'T>) (state : 'State) = - let source = unwrapArray (nameof array) array - let results = Array.zeroCreate source.Length - let mutable state = state +[] +let lastIndexOf (value : 'T) (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.LastIndexOf value - for index = source.Length - 1 downto 0 do - let struct (result, next) = mapping source[index] state - results[index] <- result - state <- next +[] +let lastIndexOfWith (comparer : IEqualityComparer<'T>) (value : 'T) (array : FlatList<'T>) = + checkNotDefault (nameof array) array - struct (wrapArray results, state) + // The BCL rejects the start index -1 of an empty array, so an empty array answers without asking it + if array.IsEmpty then + -1 + else + array.LastIndexOf (value, array.Length - 1, array.Length, comparer) - [] - let max (array : FlatList<'T>) = Array.max (unwrapArray (nameof array) array) +[] +let pick (chooser : 'T -> 'U voption) (array : FlatList<'T>) = + let source = unwrapArray (nameof array) array + let mutable result = ValueNone + let mutable index = 0 - [] - let maxBy (projection : 'T -> 'U) (array : FlatList<'T>) = Array.maxBy projection (unwrapArray (nameof array) array) + while result.IsNone && index < source.Length do + result <- chooser source[index] + index <- index + 1 - [] - let min (array : FlatList<'T>) = Array.min (unwrapArray (nameof array) array) + match result with + | ValueSome value -> value + | ValueNone -> Raise.indexNotFound () - [] - let minBy (projection : 'T -> 'U) (array : FlatList<'T>) = Array.minBy projection (unwrapArray (nameof array) array) +[] +let tryFind predicate (array : FlatList<'T>) = + let source = unwrapArray (nameof array) array + let mutable result = ValueNone + let mutable index = 0 - [] - let reduce reduction (array : FlatList<'T>) = Array.reduce reduction (unwrapArray (nameof array) array) + while result.IsNone && index < source.Length do + if predicate source[index] then + result <- ValueSome source[index] - [] - let reduceBack reduction (array : FlatList<'T>) = Array.reduceBack reduction (unwrapArray (nameof array) array) + index <- index + 1 - [] - let scan (folder : 'State -> 'T -> 'State) (state : 'State) (array : FlatList<'T>) = - Array.scan folder state (unwrapArray (nameof array) array) - |> wrapArray + result - [] - let scanBack (folder : 'T -> 'State -> 'State) (array : FlatList<'T>) (state : 'State) = - Array.scanBack folder (unwrapArray (nameof array) array) state - |> wrapArray +[] +let tryFindBack predicate (array : FlatList<'T>) = + let source = unwrapArray (nameof array) array + let mutable result = ValueNone + let mutable index = source.Length - 1 - [] - let inline sum (array : FlatList< ^T >) : ^T = - if array.IsDefault then - nullArg (nameof array) + while result.IsNone && index >= 0 do + if predicate source[index] then + result <- ValueSome source[index] - Array.sum ( - ImmutableCollectionsMarshal.AsArray array - |> Unchecked.nonNull - ) + index <- index - 1 - [] - let inline sumBy ([] projection : 'T -> ^U) (array : FlatList<'T>) : ^U = - if array.IsDefault then - nullArg (nameof array) + result - Array.sumBy - projection - (ImmutableCollectionsMarshal.AsArray array - |> Unchecked.nonNull) +[] +let tryFindIndex predicate (array : FlatList<'T>) = + let source = unwrapArray (nameof array) array + let mutable result = ValueNone + let mutable index = 0 - // ---- Partitioning and grouping ---- + while result.IsNone && index < source.Length do + if predicate source[index] then + result <- ValueSome index - [] - let countBy (projection : 'T -> 'Key) (array : FlatList<'T>) = - Arrays.countBy structPair projection (unwrapArray (nameof array) array) - |> wrapArray + index <- index + 1 + + result - [] - let distinct (array : FlatList<'T>) = - Array.distinct (unwrapArray (nameof array) array) - |> wrapArray +[] +let tryFindIndexBack predicate (array : FlatList<'T>) = + let source = unwrapArray (nameof array) array + let mutable result = ValueNone + let mutable index = source.Length - 1 + + while result.IsNone && index >= 0 do + if predicate source[index] then + result <- ValueSome index + + index <- index - 1 + + result + +[] +let tryPick (chooser : 'T -> 'U voption) (array : FlatList<'T>) = + let source = unwrapArray (nameof array) array + let mutable result = ValueNone + let mutable index = 0 + + while result.IsNone && index < source.Length do + result <- chooser source[index] + index <- index + 1 + + result + +// ---- Transformation ---- + +[] +let add (value : 'T) (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.Add value + +[] +let allPairs (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = + Arrays.allPairs structPair (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) + |> wrapArray + +[] +let append (array1 : FlatList<'T>) (array2 : FlatList<'T>) = + checkNotDefault (nameof array1) array1 + checkNotDefault (nameof array2) array2 + array1.AddRange array2 + +[] +let choose (chooser : 'T -> 'U voption) (array : FlatList<'T>) = + let source = unwrapArray (nameof array) array + let builder = ImmutableArray.CreateBuilder<'U>() + + for item in source do + match chooser item with + | ValueSome value -> builder.Add value + | ValueNone -> () + + builder.DrainToImmutable () + +[] +let chunkBySize chunkSize (array : FlatList<'T>) = + Array.chunkBySize chunkSize (unwrapArray (nameof array) array) + |> Array.map wrapArray + |> wrapArray + +[] +let collect (mapping : 'T -> FlatList<'U>) (array : FlatList<'T>) = + let source = unwrapArray (nameof array) array + let builder = ImmutableArray.CreateBuilder<'U>() + + for item in source do + let items = mapping item + // Array.collect dereferences a null result; a default result is a default FlatList, rejected as in concat + checkNotDefault (nameof mapping) items + builder.AddRange items + + builder.DrainToImmutable () + +[] +let concat (arrays : seq>) = + checkNotNull (nameof arrays) arrays + + arrays + |> Seq.map (fun array -> unwrapArray (nameof arrays) array) + |> Array.concat + |> wrapArray + +[] +let filter predicate (array : FlatList<'T>) = + Array.filter predicate (unwrapArray (nameof array) array) + |> wrapArray + +[] +let indexed (array : FlatList<'T>) = + Arrays.indexed structPair (unwrapArray (nameof array) array) + |> wrapArray + +[] +let insertAt index (value : 'T) (source : FlatList<'T>) = + Array.insertAt index value (unwrapArray (nameof source) source) + |> wrapArray + +[] +let insertManyAt index (values : seq<'T>) (source : FlatList<'T>) = + Array.insertManyAt index values (unwrapArray (nameof source) source) + |> wrapArray + +[] +let map (mapping : 'T -> 'U) (array : FlatList<'T>) = + Array.map mapping (unwrapArray (nameof array) array) + |> wrapArray + +[] +let map2 (mapping : 'T1 -> 'T2 -> 'U) (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = + Array.map2 mapping (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) + |> wrapArray + +[] +let map3 (mapping : 'T1 -> 'T2 -> 'T3 -> 'U) (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) (array3 : FlatList<'T3>) = + Array.map3 + mapping + (unwrapArray (nameof array1) array1) + (unwrapArray (nameof array2) array2) + (unwrapArray (nameof array3) array3) + |> wrapArray + +[] +let mapi (mapping : int -> 'T -> 'U) (array : FlatList<'T>) = + Array.mapi mapping (unwrapArray (nameof array) array) + |> wrapArray + +[] +let mapi2 (mapping : int -> 'T1 -> 'T2 -> 'U) (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = + Array.mapi2 mapping (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) + |> wrapArray + +[] +let pairwise (array : FlatList<'T>) = + Arrays.pairwise structPair (unwrapArray (nameof array) array) + |> wrapArray + +[] +let permute indexMap (array : FlatList<'T>) = + Array.permute indexMap (unwrapArray (nameof array) array) + |> wrapArray + +[] +let removeAt index (source : FlatList<'T>) = + Array.removeAt index (unwrapArray (nameof source) source) + |> wrapArray + +[] +let removeManyAt index count (source : FlatList<'T>) = + Array.removeManyAt index count (unwrapArray (nameof source) source) + |> wrapArray + +[] +let rev (array : FlatList<'T>) = Array.rev (unwrapArray (nameof array) array) |> wrapArray + +[] +let transpose (arrays : seq>) = + checkNotNull (nameof arrays) arrays + + arrays + |> Seq.map (fun array -> unwrapArray (nameof arrays) array) + |> Array.transpose + |> Array.map wrapArray + |> wrapArray + +[] +let unzip (array : FlatList) = + let struct (first, second) = Arrays.unzip id (unwrapArray (nameof array) array) + struct (wrapArray first, wrapArray second) + +[] +let unzip3 (array : FlatList) = + let struct (first, second, third) = Arrays.unzip3 id (unwrapArray (nameof array) array) + struct (wrapArray first, wrapArray second, wrapArray third) + +[] +let updateAt index (value : 'T) (source : FlatList<'T>) = + Array.updateAt index value (unwrapArray (nameof source) source) + |> wrapArray + +[] +let where predicate (array : FlatList<'T>) = + Array.filter predicate (unwrapArray (nameof array) array) + |> wrapArray + +[] +let windowed windowSize (array : FlatList<'T>) = + Array.windowed windowSize (unwrapArray (nameof array) array) + |> Array.map wrapArray + |> wrapArray + +[] +let zip (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = + let source1 = unwrapArray (nameof array1) array1 + let source2 = unwrapArray (nameof array2) array2 + ArrayChecks.sameLengths (nameof array1) source1 (nameof array2) source2 + Arrays.zip structPair source1 source2 |> wrapArray + +[] +let zip3 (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) (array3 : FlatList<'T3>) = + let source1 = unwrapArray (nameof array1) array1 + let source2 = unwrapArray (nameof array2) array2 + let source3 = unwrapArray (nameof array3) array3 + ArrayChecks.sameLengths3 (nameof array1) source1 (nameof array2) source2 (nameof array3) source3 + + Arrays.zip3 structTriple source1 source2 source3 + |> wrapArray + +// ---- Folding ---- + +// The inline functions can use public members only, so they check and unwrap the array themselves +[] +let inline average (array : FlatList< ^T >) : ^T = + if array.IsDefault then + nullArg (nameof array) + + Array.average ( + ImmutableCollectionsMarshal.AsArray array + |> Unchecked.nonNull + ) + +[] +let inline averageBy ([] projection : 'T -> ^U) (array : FlatList<'T>) : ^U = + if array.IsDefault then + nullArg (nameof array) + + Array.averageBy + projection + (ImmutableCollectionsMarshal.AsArray array + |> Unchecked.nonNull) + +[] +let compareWith comparer (array1 : FlatList<'T>) (array2 : FlatList<'T>) = + Array.compareWith comparer (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) + +[] +let fold (folder : 'State -> 'T -> 'State) (state : 'State) (array : FlatList<'T>) = + Array.fold folder state (unwrapArray (nameof array) array) + +[] +let fold2 (folder : 'State -> 'T1 -> 'T2 -> 'State) (state : 'State) (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = + Array.fold2 folder state (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) + +[] +let foldBack (folder : 'T -> 'State -> 'State) (array : FlatList<'T>) (state : 'State) = + Array.foldBack folder (unwrapArray (nameof array) array) state + +[] +let foldBack2 (folder : 'T1 -> 'T2 -> 'State -> 'State) (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) (state : 'State) = + Array.foldBack2 folder (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) state + +[] +let iter action (array : FlatList<'T>) = Array.iter action (unwrapArray (nameof array) array) + +[] +let iter2 action (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = + Array.iter2 action (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) + +[] +let iteri action (array : FlatList<'T>) = Array.iteri action (unwrapArray (nameof array) array) + +[] +let iteri2 action (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = + Array.iteri2 action (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) + +[] +let mapFold (mapping : 'State -> 'T -> struct ('Result * 'State)) (state : 'State) (array : FlatList<'T>) = + let source = unwrapArray (nameof array) array + let results = Array.zeroCreate source.Length + let mutable state = state + + for index = 0 to source.Length - 1 do + let struct (result, next) = mapping state source[index] + results[index] <- result + state <- next + + struct (wrapArray results, state) + +[] +let mapFoldBack (mapping : 'T -> 'State -> struct ('Result * 'State)) (array : FlatList<'T>) (state : 'State) = + let source = unwrapArray (nameof array) array + let results = Array.zeroCreate source.Length + let mutable state = state + + for index = source.Length - 1 downto 0 do + let struct (result, next) = mapping source[index] state + results[index] <- result + state <- next + + struct (wrapArray results, state) + +[] +let max (array : FlatList<'T>) = Array.max (unwrapArray (nameof array) array) + +[] +let maxBy (projection : 'T -> 'U) (array : FlatList<'T>) = Array.maxBy projection (unwrapArray (nameof array) array) + +[] +let min (array : FlatList<'T>) = Array.min (unwrapArray (nameof array) array) + +[] +let minBy (projection : 'T -> 'U) (array : FlatList<'T>) = Array.minBy projection (unwrapArray (nameof array) array) + +[] +let reduce reduction (array : FlatList<'T>) = Array.reduce reduction (unwrapArray (nameof array) array) + +[] +let reduceBack reduction (array : FlatList<'T>) = Array.reduceBack reduction (unwrapArray (nameof array) array) + +[] +let scan (folder : 'State -> 'T -> 'State) (state : 'State) (array : FlatList<'T>) = + Array.scan folder state (unwrapArray (nameof array) array) + |> wrapArray + +[] +let scanBack (folder : 'T -> 'State -> 'State) (array : FlatList<'T>) (state : 'State) = + Array.scanBack folder (unwrapArray (nameof array) array) state + |> wrapArray + +[] +let inline sum (array : FlatList< ^T >) : ^T = + if array.IsDefault then + nullArg (nameof array) + + Array.sum ( + ImmutableCollectionsMarshal.AsArray array + |> Unchecked.nonNull + ) - [] - let distinctBy (projection : 'T -> 'Key) (array : FlatList<'T>) = - Array.distinctBy projection (unwrapArray (nameof array) array) - |> wrapArray +[] +let inline sumBy ([] projection : 'T -> ^U) (array : FlatList<'T>) : ^U = + if array.IsDefault then + nullArg (nameof array) - [] - let except (itemsToExclude : seq<'T>) (array : FlatList<'T>) = - // FSharp.Core checks the items to exclude first - checkNotNull (nameof itemsToExclude) itemsToExclude - Array.except itemsToExclude (unwrapArray (nameof array) array) - |> wrapArray + Array.sumBy + projection + (ImmutableCollectionsMarshal.AsArray array + |> Unchecked.nonNull) - [] - let groupBy (projection : 'T -> 'Key) (array : FlatList<'T>) = - Arrays.groupBy (fun key items -> struct (key, wrapArray items)) projection (unwrapArray (nameof array) array) - |> wrapArray +// ---- Partitioning and grouping ---- - [] - let partition predicate (array : FlatList<'T>) = - let accepted, rejected = Array.partition predicate (unwrapArray (nameof array) array) - struct (wrapArray accepted, wrapArray rejected) +[] +let countBy (projection : 'T -> 'Key) (array : FlatList<'T>) = + Arrays.countBy structPair projection (unwrapArray (nameof array) array) + |> wrapArray - [] - let partitionWith (partitioner : 'T -> Choice<'T1, 'T2>) (array : FlatList<'T>) = - let first, second = Array.partitionWith partitioner (unwrapArray (nameof array) array) - struct (wrapArray first, wrapArray second) +[] +let distinct (array : FlatList<'T>) = + Array.distinct (unwrapArray (nameof array) array) + |> wrapArray - // The range functions repeat the checks of Array.skip, Array.take and Array.splitAt and then slice: a slice copies the - // selected elements, unless the range is empty or covers the whole array (ADR 0001, D5) +[] +let distinctBy (projection : 'T -> 'Key) (array : FlatList<'T>) = + Array.distinctBy projection (unwrapArray (nameof array) array) + |> wrapArray + +[] +let except (itemsToExclude : seq<'T>) (array : FlatList<'T>) = + // FSharp.Core checks the items to exclude first + checkNotNull (nameof itemsToExclude) itemsToExclude + Array.except itemsToExclude (unwrapArray (nameof array) array) + |> wrapArray + +[] +let groupBy (projection : 'T -> 'Key) (array : FlatList<'T>) = + Arrays.groupBy (fun key items -> struct (key, wrapArray items)) projection (unwrapArray (nameof array) array) + |> wrapArray + +[] +let partition predicate (array : FlatList<'T>) = + let accepted, rejected = Array.partition predicate (unwrapArray (nameof array) array) + struct (wrapArray accepted, wrapArray rejected) + +[] +let partitionWith (partitioner : 'T -> Choice<'T1, 'T2>) (array : FlatList<'T>) = + let first, second = Array.partitionWith partitioner (unwrapArray (nameof array) array) + struct (wrapArray first, wrapArray second) - [] - let skip count (array : FlatList<'T>) = - checkNotDefault (nameof array) array +// The range functions repeat the checks of Array.skip, Array.take and Array.splitAt and then slice: a slice copies the +// selected elements, unless the range is empty or covers the whole array (ADR 0001, D5) + +[] +let skip count (array : FlatList<'T>) = + checkNotDefault (nameof array) array + + if count > array.Length then + Raise.invalidArgOutOfRange (nameof count) count "array.Length" array.Length - if count > array.Length then - Raise.invalidArgOutOfRange (nameof count) count "array.Length" array.Length + if count = array.Length then + empty + else + let count = Operators.max count 0 + array.Slice (count, array.Length - count) - if count = array.Length then - empty - else - let count = Operators.max count 0 - array.Slice (count, array.Length - count) +[] +let skipLast count (array : FlatList<'T>) = + checkNotDefault (nameof array) array + + if count > array.Length then + Raise.invalidArgOutOfRange (nameof count) count "array.Length" array.Length + + if count = array.Length then + empty + else + array.Slice (0, array.Length - Operators.max count 0) + +[] +let skipWhile predicate (array : FlatList<'T>) = + let source = unwrapArray (nameof array) array + let mutable index = 0 + + while index < source.Length && predicate source[index] do + index <- index + 1 + + array.Slice (index, source.Length - index) + +[] +let splitAt index (array : FlatList<'T>) = + checkNotDefault (nameof array) array + + if index < 0 then + Raise.invalidArgInputMustBeNonNegative (nameof index) index + + if array.Length < index then + Raise.notEnoughElements () + + struct (array.Slice (0, index), array.Slice (index, array.Length - index)) + +[] +let splitInto count (array : FlatList<'T>) = + Array.splitInto count (unwrapArray (nameof array) array) + |> Array.map wrapArray + |> wrapArray - [] - let skipLast count (array : FlatList<'T>) = - checkNotDefault (nameof array) array +[] +let take count (array : FlatList<'T>) = + checkNotDefault (nameof array) array + if count < 0 then + Raise.invalidArgInputMustBeNonNegative (nameof count) count + + if count = 0 then + empty + else if count > array.Length then - Raise.invalidArgOutOfRange (nameof count) count "array.Length" array.Length - - if count = array.Length then - empty - else - array.Slice (0, array.Length - Operators.max count 0) - - [] - let skipWhile predicate (array : FlatList<'T>) = - let source = unwrapArray (nameof array) array - let mutable index = 0 - - while index < source.Length && predicate source[index] do - index <- index + 1 - - array.Slice (index, source.Length - index) - - [] - let splitAt index (array : FlatList<'T>) = - checkNotDefault (nameof array) array - - if index < 0 then - Raise.invalidArgInputMustBeNonNegative (nameof index) index - - if array.Length < index then Raise.notEnoughElements () - struct (array.Slice (0, index), array.Slice (index, array.Length - index)) - - [] - let splitInto count (array : FlatList<'T>) = - Array.splitInto count (unwrapArray (nameof array) array) - |> Array.map wrapArray - |> wrapArray - - [] - let take count (array : FlatList<'T>) = - checkNotDefault (nameof array) array + array.Slice (0, count) - if count < 0 then - Raise.invalidArgInputMustBeNonNegative (nameof count) count +[] +let takeLast count (array : FlatList<'T>) = + checkNotDefault (nameof array) array - if count = 0 then - empty - else - if count > array.Length then - Raise.notEnoughElements () + if count < 0 then + Raise.invalidArgInputMustBeNonNegative (nameof count) count - array.Slice (0, count) + if count = 0 then + empty + else + if count > array.Length then + Raise.notEnoughElements () - [] - let takeLast count (array : FlatList<'T>) = - checkNotDefault (nameof array) array + array.Slice (array.Length - count, count) - if count < 0 then - Raise.invalidArgInputMustBeNonNegative (nameof count) count +[] +let takeWhile predicate (array : FlatList<'T>) = + let source = unwrapArray (nameof array) array + let mutable count = 0 + + while count < source.Length && predicate source[count] do + count <- count + 1 - if count = 0 then - empty - else - if count > array.Length then - Raise.notEnoughElements () + array.Slice (0, count) - array.Slice (array.Length - count, count) +[] +let truncate count (array : FlatList<'T>) = + checkNotDefault (nameof array) array - [] - let takeWhile predicate (array : FlatList<'T>) = - let source = unwrapArray (nameof array) array - let mutable count = 0 + if count <= 0 then + empty + else + array.Slice (0, Operators.min count array.Length) - while count < source.Length && predicate source[count] do - count <- count + 1 +// ---- Sorting ---- + +[] +let sort (array : FlatList<'T>) = Array.sort (unwrapArray (nameof array) array) |> wrapArray - array.Slice (0, count) +[] +let sortBy (projection : 'T -> 'Key) (array : FlatList<'T>) = + Array.sortBy projection (unwrapArray (nameof array) array) + |> wrapArray - [] - let truncate count (array : FlatList<'T>) = - checkNotDefault (nameof array) array - - if count <= 0 then - empty - else - array.Slice (0, Operators.min count array.Length) - - // ---- Sorting ---- - - [] - let sort (array : FlatList<'T>) = Array.sort (unwrapArray (nameof array) array) |> wrapArray - - [] - let sortBy (projection : 'T -> 'Key) (array : FlatList<'T>) = - Array.sortBy projection (unwrapArray (nameof array) array) - |> wrapArray - - [] - let sortByDescending (projection : 'T -> 'Key) (array : FlatList<'T>) = - Array.sortByDescending projection (unwrapArray (nameof array) array) - |> wrapArray - - [] - let sortDescending (array : FlatList<'T>) = - Array.sortDescending (unwrapArray (nameof array) array) - |> wrapArray +[] +let sortByDescending (projection : 'T -> 'Key) (array : FlatList<'T>) = + Array.sortByDescending projection (unwrapArray (nameof array) array) + |> wrapArray - [] - let sortWith comparer (array : FlatList<'T>) = - Array.sortWith comparer (unwrapArray (nameof array) array) - |> wrapArray +[] +let sortDescending (array : FlatList<'T>) = + Array.sortDescending (unwrapArray (nameof array) array) + |> wrapArray - [] - let sortWithComparer (comparer : IComparer<'T>) (array : FlatList<'T>) = - checkNotDefault (nameof array) array - array.Sort comparer +[] +let sortWith comparer (array : FlatList<'T>) = + Array.sortWith comparer (unwrapArray (nameof array) array) + |> wrapArray - // ---- Random ---- +[] +let sortWithComparer (comparer : IComparer<'T>) (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.Sort comparer - [] - let randomChoice (source : FlatList<'T>) = Array.randomChoice (unwrapArray (nameof source) source) +// ---- Random ---- - [] - let randomChoiceBy randomizer (source : FlatList<'T>) = Array.randomChoiceBy randomizer (unwrapArray (nameof source) source) +[] +let randomChoice (source : FlatList<'T>) = Array.randomChoice (unwrapArray (nameof source) source) - [] - let randomChoiceWith (random : Random) (source : FlatList<'T>) = - Array.randomChoiceWith random (unwrapArray (nameof source) source) +[] +let randomChoiceBy randomizer (source : FlatList<'T>) = Array.randomChoiceBy randomizer (unwrapArray (nameof source) source) - [] - let randomChoices count (source : FlatList<'T>) = - Array.randomChoices count (unwrapArray (nameof source) source) - |> wrapArray +[] +let randomChoiceWith (random : Random) (source : FlatList<'T>) = + Array.randomChoiceWith random (unwrapArray (nameof source) source) - [] - let randomChoicesBy randomizer count (source : FlatList<'T>) = - Array.randomChoicesBy randomizer count (unwrapArray (nameof source) source) - |> wrapArray +[] +let randomChoices count (source : FlatList<'T>) = + Array.randomChoices count (unwrapArray (nameof source) source) + |> wrapArray - [] - let randomChoicesWith (random : Random) count (source : FlatList<'T>) = - Array.randomChoicesWith random count (unwrapArray (nameof source) source) - |> wrapArray +[] +let randomChoicesBy randomizer count (source : FlatList<'T>) = + Array.randomChoicesBy randomizer count (unwrapArray (nameof source) source) + |> wrapArray - [] - let randomSample count (source : FlatList<'T>) = - Array.randomSample count (unwrapArray (nameof source) source) - |> wrapArray +[] +let randomChoicesWith (random : Random) count (source : FlatList<'T>) = + Array.randomChoicesWith random count (unwrapArray (nameof source) source) + |> wrapArray - [] - let randomSampleBy randomizer count (source : FlatList<'T>) = - Array.randomSampleBy randomizer count (unwrapArray (nameof source) source) - |> wrapArray +[] +let randomSample count (source : FlatList<'T>) = + Array.randomSample count (unwrapArray (nameof source) source) + |> wrapArray - [] - let randomSampleWith (random : Random) count (source : FlatList<'T>) = - Array.randomSampleWith random count (unwrapArray (nameof source) source) - |> wrapArray +[] +let randomSampleBy randomizer count (source : FlatList<'T>) = + Array.randomSampleBy randomizer count (unwrapArray (nameof source) source) + |> wrapArray - [] - let randomShuffle (source : FlatList<'T>) = - Array.randomShuffle (unwrapArray (nameof source) source) - |> wrapArray +[] +let randomSampleWith (random : Random) count (source : FlatList<'T>) = + Array.randomSampleWith random count (unwrapArray (nameof source) source) + |> wrapArray - [] - let randomShuffleBy randomizer (source : FlatList<'T>) = - Array.randomShuffleBy randomizer (unwrapArray (nameof source) source) - |> wrapArray +[] +let randomShuffle (source : FlatList<'T>) = + Array.randomShuffle (unwrapArray (nameof source) source) + |> wrapArray - [] - let randomShuffleWith (random : Random) (source : FlatList<'T>) = - Array.randomShuffleWith random (unwrapArray (nameof source) source) - |> wrapArray +[] +let randomShuffleBy randomizer (source : FlatList<'T>) = + Array.randomShuffleBy randomizer (unwrapArray (nameof source) source) + |> wrapArray + +[] +let randomShuffleWith (random : Random) (source : FlatList<'T>) = + Array.randomShuffleWith random (unwrapArray (nameof source) source) + |> wrapArray - // ---- Conversion ---- +// ---- Conversion ---- - [] - let asMemory (array : FlatList<'T>) = - checkNotDefault (nameof array) array - array.AsMemory () - - [] - let asSpan (array : FlatList<'T>) = - checkNotDefault (nameof array) array - array.AsSpan () +[] +let asMemory (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.AsMemory () + +[] +let asSpan (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.AsSpan () - [] - let blit (source : FlatList<'T>) sourceIndex (target : 'T array) targetIndex count = - Array.blit (unwrapArray (nameof source) source) sourceIndex target targetIndex count - - [] - let toArray (array : FlatList<'T>) = Array.copy (unwrapArray (nameof array) array) - - [] - let toList (array : FlatList<'T>) = Array.toList (unwrapArray (nameof array) array) - - [] - let toSeq (array : FlatList<'T>) = - checkNotDefault (nameof array) array - array :> seq<'T> - - // ---- Type-specific ---- - - [] - let isDefault (array : FlatList<'T>) = array.IsDefault +[] +let blit (source : FlatList<'T>) sourceIndex (target : 'T array) targetIndex count = + Array.blit (unwrapArray (nameof source) source) sourceIndex target targetIndex count + +[] +let toArray (array : FlatList<'T>) = Array.copy (unwrapArray (nameof array) array) + +[] +let toList (array : FlatList<'T>) = Array.toList (unwrapArray (nameof array) array) + +[] +let toSeq (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array :> seq<'T> + +// ---- Type-specific ---- + +[] +let isDefault (array : FlatList<'T>) = array.IsDefault - [] - let isDefaultOrEmpty (array : FlatList<'T>) = array.IsDefaultOrEmpty +[] +let isDefaultOrEmpty (array : FlatList<'T>) = array.IsDefaultOrEmpty diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi b/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi index ff9ab9b3..9e17357b 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi +++ b/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi @@ -1,13 +1,3 @@ -namespace FSharp.Collections.Immutable - -open System -open System.Collections.Generic -open System.Collections.Immutable - -/// An immutable array, . -/// The name comes from a similar construct in the sources of the F# compiler. -type FlatList<'T> = ImmutableArray<'T> - /// Functions for , the immutable array. /// /// @@ -31,1283 +21,1287 @@ type FlatList<'T> = ImmutableArray<'T> /// /// [] -module FlatList = - - // ---- Creation ---- - - /// Creates a flat list whose elements are all the given value. - /// The length of the flat list. - /// The value of every element. - /// The created flat list. - /// Thrown when is negative. - /// O(n). - [] - val create : count : int -> value : 'T -> FlatList<'T> - - /// The empty flat list. - /// O(1). The value is . - [] - [] - val empty<'T> : FlatList<'T> - - /// Creates a flat list from a function of the index. - /// The length of the flat list. - /// The function that computes the element at each index, called in index order. - /// The created flat list. - /// Thrown when is negative. - /// O(n). - [] - val init : count : int -> initializer : (int -> 'T) -> FlatList<'T> - - /// Creates a flat list from a copy of an array. - /// The input array. - /// The flat list of the elements of the array. - /// Thrown when is . - /// O(n). Later changes to the array do not change the flat list. - [] - val ofArray : array : 'T array -> FlatList<'T> - - /// Creates a flat list from a list. - /// The input list. - /// The flat list of the elements of the list. - /// O(n). - [] - val ofList : list : 'T list -> FlatList<'T> - - /// Creates a flat list from a copy of a read-only span. - /// The input span. - /// The flat list of the elements of the span. - /// O(n). The function wraps . - [] - val ofReadOnlySpan : span : ReadOnlySpan<'T> -> FlatList<'T> - - /// Creates a flat list from a sequence. - /// The input sequence. - /// The flat list of the elements of the sequence. - /// Thrown when is or a default flat list. - /// O(n). A flat list is returned as it is, without a copy. - [] - val ofSeq : source : seq<'T> -> FlatList<'T> - - /// Creates a flat list from a copy of a span. - /// The input span. - /// The flat list of the elements of the span. - /// O(n). The function wraps . - [] - val ofSpan : span : Span<'T> -> FlatList<'T> - - /// Creates a flat list whose elements are all the given value. - /// The length of the flat list. - /// The value of every element. - /// The created flat list. - /// Thrown when is negative. - /// O(n). - [] - val replicate : count : int -> initial : 'T -> FlatList<'T> - - /// Creates a flat list of one element. - /// The element. - /// The flat list of one element. - /// O(1). - [] - val singleton : value : 'T -> FlatList<'T> - - /// Creates a flat list from a generator, which returns the next element and the next state. - /// The function that returns the next element and state, or ValueNone to stop. - /// The initial state. - /// The generated flat list. - /// O(n). - [] - val unfold : generator : ('State -> struct ('T * 'State) voption) -> state : 'State -> FlatList<'T> - - // ---- Builder ---- - - /// Creates a flat list with a builder that an action fills. - /// The action that adds the elements to the builder. - /// The flat list of the elements that the action added. - /// O(n). - [] - val build : action : (FlatList<'T>.Builder -> unit) -> FlatList<'T> - - /// Creates an empty builder. - /// A builder of capacity 8. - /// O(1). The function wraps . - [] - val builder : unit -> FlatList<'T>.Builder - - /// Creates an empty builder of a given capacity. - /// The initial capacity. - /// The builder. - /// Thrown when is negative. - /// O(capacity). The function wraps . - [] - val builderWith : capacity : int -> FlatList<'T>.Builder - - /// Returns the contents of a full builder as a flat list without a copy and empties the builder. - /// The builder, whose count must equal its capacity. - /// The flat list of the elements of the builder. - /// Thrown when is . - /// Thrown when the count of the builder differs from its capacity. - /// O(1). The function wraps . - [] - val moveFromBuilder : builder : FlatList<'T>.Builder -> FlatList<'T> - - /// Returns a copy of the contents of a builder as a flat list. - /// The builder, which stays unchanged. - /// The flat list of the elements of the builder. - /// Thrown when is . - /// O(n). The function wraps . - [] - val ofBuilder : builder : FlatList<'T>.Builder -> FlatList<'T> - - /// Creates a builder that contains a copy of the elements of a flat list. - /// The input flat list. - /// The builder. - /// O(n). The function wraps . - [] - val toBuilder : array : FlatList<'T> -> FlatList<'T>.Builder - - /// Creates a flat list from a builder over the elements of a flat list, after an action changes the builder. - /// The action that changes the builder. - /// The input flat list, which stays unchanged. - /// The flat list of the elements of the changed builder. - /// O(n). - [] - val update : action : (FlatList<'T>.Builder -> unit) -> array : FlatList<'T> -> FlatList<'T> - - // ---- Properties ---- - - /// Returns whether the flat list has no elements. - /// The input flat list. - /// when the flat list is empty. - /// O(1). - [] - val isEmpty : array : FlatList<'T> -> bool - - /// Returns the number of elements of the flat list. - /// The input flat list. - /// The length of the flat list. - /// O(1). - [] - val length : array : FlatList<'T> -> int - - // ---- Access ---- - - /// Returns the only element of the flat list. - /// The input flat list. - /// The only element. - /// Thrown when the flat list does not have exactly one element. - /// O(1). - [] - val exactlyOne : array : FlatList<'T> -> 'T - - /// Returns the first element of the flat list. - /// The input flat list. - /// The first element. - /// Thrown when the flat list is empty. - /// O(1). - [] - val head : array : FlatList<'T> -> 'T - - /// Returns the element at an index. - /// The index of the element. - /// The input flat list. - /// The element at the index. - /// Thrown when the index is negative or not less than the length. - /// O(1). - [] - val item : index : int -> array : FlatList<'T> -> 'T - - /// Returns the last element of the flat list. - /// The input flat list. - /// The last element. - /// Thrown when the flat list is empty. - /// O(1). - [] - val last : array : FlatList<'T> -> 'T - - /// Returns the flat list without its first element. - /// The input flat list. - /// A copy of the elements after the first one. - /// Thrown when the flat list is empty. - /// O(n). - [] - val tail : array : FlatList<'T> -> FlatList<'T> - - /// Returns the only element of the flat list, or ValueNone when it does not have exactly one element. - /// The input flat list. - /// The only element, or ValueNone. - /// O(1). - [] - val tryExactlyOne : array : FlatList<'T> -> 'T voption - - /// Returns the first element of the flat list, or ValueNone when it is empty. - /// The input flat list. - /// The first element, or ValueNone. - /// O(1). - [] - val tryHead : array : FlatList<'T> -> 'T voption - - /// Returns the element at an index, or ValueNone when the index is out of range. - /// The index of the element. - /// The input flat list. - /// The element at the index, or ValueNone. - /// O(1). - [] - val tryItem : index : int -> array : FlatList<'T> -> 'T voption - - /// Returns the last element of the flat list, or ValueNone when it is empty. - /// The input flat list. - /// The last element, or ValueNone. - /// O(1). - [] - val tryLast : array : FlatList<'T> -> 'T voption - - /// Returns the flat list without its first element, or ValueNone when it is empty. - /// The input flat list. - /// A copy of the elements after the first one, or ValueNone. - /// O(n). - [] - val tryTail : array : FlatList<'T> -> FlatList<'T> voption - - // ---- Search ---- - - /// Searches a sorted flat list for a value with the binary search algorithm. - /// The value to search for. - /// The input flat list, sorted in the order of F# generic comparison. - /// - /// The index of the value, or the bitwise complement of the index of the first larger element, or the bitwise - /// complement of the length when no element is larger. - /// - /// - /// O(log n). The function compares with , - /// the order that produces. - /// - [] - val binarySearch : value : 'T -> array : FlatList<'T> -> int when 'T : comparison - - /// Returns whether the flat list contains an element equal to a value, in F# structural equality. - /// The value to find. - /// The input flat list. - /// when an element equals the value. - /// O(n). - [] - val contains : value : 'T -> array : FlatList<'T> -> bool when 'T : equality - - /// Returns whether any element satisfies the predicate. - /// The function to test the elements. - /// The input flat list. - /// when an element satisfies the predicate. - /// O(n). - [] - val exists : predicate : ('T -> bool) -> array : FlatList<'T> -> bool - - /// Returns whether any pair of elements at the same index satisfies the predicate. - /// The function to test the pairs. - /// The first flat list. - /// The second flat list. - /// when a pair satisfies the predicate. - /// Thrown when the flat lists have different lengths. - /// O(n). - [] - val exists2 : predicate : ('T1 -> 'T2 -> bool) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> bool - - /// Returns the first element that satisfies the predicate. - /// The function to test the elements. - /// The input flat list. - /// The first element that satisfies the predicate. - /// Thrown when no element satisfies the predicate. - /// O(n). - [] - val find : predicate : ('T -> bool) -> array : FlatList<'T> -> 'T - - /// Returns the last element that satisfies the predicate. - /// The function to test the elements. - /// The input flat list. - /// The last element that satisfies the predicate. - /// Thrown when no element satisfies the predicate. - /// O(n). - [] - val findBack : predicate : ('T -> bool) -> array : FlatList<'T> -> 'T - - /// Returns the index of the first element that satisfies the predicate. - /// The function to test the elements. - /// The input flat list. - /// The index of the first element that satisfies the predicate. - /// Thrown when no element satisfies the predicate. - /// O(n). - [] - val findIndex : predicate : ('T -> bool) -> array : FlatList<'T> -> int - - /// Returns the index of the last element that satisfies the predicate. - /// The function to test the elements. - /// The input flat list. - /// The index of the last element that satisfies the predicate. - /// Thrown when no element satisfies the predicate. - /// O(n). - [] - val findIndexBack : predicate : ('T -> bool) -> array : FlatList<'T> -> int - - /// Returns whether all elements satisfy the predicate. - /// The function to test the elements. - /// The input flat list. - /// when every element satisfies the predicate. - /// O(n). - [] - val forall : predicate : ('T -> bool) -> array : FlatList<'T> -> bool - - /// Returns whether all pairs of elements at the same index satisfy the predicate. - /// The function to test the pairs. - /// The first flat list. - /// The second flat list. - /// when every pair satisfies the predicate. - /// Thrown when the flat lists have different lengths. - /// O(n). - [] - val forall2 : predicate : ('T1 -> 'T2 -> bool) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> bool - - /// Returns the index of the first element equal to a value, in . - /// The value to find. - /// The input flat list. - /// The index of the first equal element, or -1. - /// O(n). The function wraps . - [] - val indexOf : value : 'T -> array : FlatList<'T> -> int - - /// Returns the index of the first element equal to a value in a given equality comparer. - /// The equality comparer. - /// The value to find. - /// The input flat list. - /// The index of the first equal element, or -1. - /// - /// O(n). The function wraps - /// . - /// - [] - val indexOfWith : comparer : IEqualityComparer<'T> -> value : 'T -> array : FlatList<'T> -> int - - /// Returns the index of the last element equal to a value, in . - /// The value to find. - /// The input flat list. - /// The index of the last equal element, or -1. - /// O(n). The function wraps . - [] - val lastIndexOf : value : 'T -> array : FlatList<'T> -> int - - /// Returns the index of the last element equal to a value in a given equality comparer. - /// The equality comparer. - /// The value to find. - /// The input flat list. - /// The index of the last equal element, or -1. - /// - /// O(n). The function wraps - /// . - /// - [] - val lastIndexOfWith : comparer : IEqualityComparer<'T> -> value : 'T -> array : FlatList<'T> -> int - - /// Returns the first value that the chooser returns for the elements. - /// The function that returns a value, or ValueNone to go on. - /// The input flat list. - /// The first chosen value. - /// Thrown when the chooser returns ValueNone for every element. - /// O(n). - [] - val pick : chooser : ('T -> 'U voption) -> array : FlatList<'T> -> 'U - - /// Returns the first element that satisfies the predicate, or ValueNone. - /// The function to test the elements. - /// The input flat list. - /// The first element that satisfies the predicate, or ValueNone. - /// O(n). - [] - val tryFind : predicate : ('T -> bool) -> array : FlatList<'T> -> 'T voption - - /// Returns the last element that satisfies the predicate, or ValueNone. - /// The function to test the elements. - /// The input flat list. - /// The last element that satisfies the predicate, or ValueNone. - /// O(n). - [] - val tryFindBack : predicate : ('T -> bool) -> array : FlatList<'T> -> 'T voption - - /// Returns the index of the first element that satisfies the predicate, or ValueNone. - /// The function to test the elements. - /// The input flat list. - /// The index of the first element that satisfies the predicate, or ValueNone. - /// O(n). - [] - val tryFindIndex : predicate : ('T -> bool) -> array : FlatList<'T> -> int voption - - /// Returns the index of the last element that satisfies the predicate, or ValueNone. - /// The function to test the elements. - /// The input flat list. - /// The index of the last element that satisfies the predicate, or ValueNone. - /// O(n). - [] - val tryFindIndexBack : predicate : ('T -> bool) -> array : FlatList<'T> -> int voption - - /// Returns the first value that the chooser returns for the elements, or ValueNone. - /// The function that returns a value, or ValueNone to go on. - /// The input flat list. - /// The first chosen value, or ValueNone. - /// O(n). - [] - val tryPick : chooser : ('T -> 'U voption) -> array : FlatList<'T> -> 'U voption - - // ---- Transformation ---- - - /// Returns a flat list with a value added after the last element. - /// The value to add. - /// The input flat list. - /// A flat list of the elements and the value. - /// O(n). The function wraps . - [] - val add : value : 'T -> array : FlatList<'T> -> FlatList<'T> - - /// Returns every element of the first flat list paired with every element of the second one. - /// The first flat list. - /// The second flat list. - /// The pairs, in the order of the first flat list and then of the second one. - /// O(n × m). - [] - val allPairs : array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> FlatList - - /// Returns a flat list of the elements of the first flat list followed by the elements of the second one. - /// The first flat list. - /// The second flat list. - /// The concatenation. - /// O(n + m). When one of the flat lists is empty, the function returns the other one. - [] - val append : array1 : FlatList<'T> -> array2 : FlatList<'T> -> FlatList<'T> - - /// Returns the values that the chooser returns for the elements. - /// The function that returns a value, or ValueNone to skip the element. - /// The input flat list. - /// The chosen values. - /// O(n). - [] - val choose : chooser : ('T -> 'U voption) -> array : FlatList<'T> -> FlatList<'U> - - /// Divides the flat list into chunks of a given size. - /// The maximum size of a chunk. - /// The input flat list. - /// The chunks; the last one can be smaller. - /// Thrown when is not positive. - /// O(n). The chunks are copies. - [] - val chunkBySize : chunkSize : int -> array : FlatList<'T> -> FlatList> - - /// Concatenates the flat lists that a function returns for the elements. - /// The function that returns a flat list for each element. - /// The input flat list. - /// The concatenation of the returned flat lists. - /// Thrown when the mapping returns a default flat list. - /// O(n + the total length of the returned flat lists). - [] - val collect : mapping : ('T -> FlatList<'U>) -> array : FlatList<'T> -> FlatList<'U> - - /// Concatenates a sequence of flat lists. - /// The flat lists. - /// The concatenation. - /// Thrown when is . - /// O(the total length). - [] - val concat : arrays : seq> -> FlatList<'T> - - /// Returns the elements that satisfy the predicate. - /// The function to test the elements. - /// The input flat list. - /// The elements that satisfy the predicate, in their order. - /// O(n). - [] - val filter : predicate : ('T -> bool) -> array : FlatList<'T> -> FlatList<'T> - - /// Returns every element with its index. - /// The input flat list. - /// The pairs of index and element. - /// O(n). - [] - val indexed : array : FlatList<'T> -> FlatList - - /// Returns a flat list with a value inserted at an index. - /// The index of the inserted value, from 0 to the length. - /// The value to insert. - /// The input flat list. - /// The flat list with the value. - /// Thrown when the index is negative or greater than the length. - /// O(n). - [] - val insertAt : index : int -> value : 'T -> source : FlatList<'T> -> FlatList<'T> - - /// Returns a flat list with values inserted at an index. - /// The index of the first inserted value, from 0 to the length. - /// The values to insert. - /// The input flat list. - /// The flat list with the values. - /// Thrown when the index is negative or greater than the length. - /// O(n + m). - [] - val insertManyAt : index : int -> values : seq<'T> -> source : FlatList<'T> -> FlatList<'T> - - /// Returns the results of a function applied to each element. - /// The function to transform the elements. - /// The input flat list. - /// The results, in the order of the elements. - /// O(n). - [] - val map : mapping : ('T -> 'U) -> array : FlatList<'T> -> FlatList<'U> - - /// Returns the results of a function applied to the pairs of elements at the same index. - /// The function to transform the pairs. - /// The first flat list. - /// The second flat list. - /// The results. - /// Thrown when the flat lists have different lengths. - /// O(n). - [] - val map2 : mapping : ('T1 -> 'T2 -> 'U) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> FlatList<'U> - - /// Returns the results of a function applied to the triples of elements at the same index. - /// The function to transform the triples. - /// The first flat list. - /// The second flat list. - /// The third flat list. - /// The results. - /// Thrown when the flat lists have different lengths. - /// O(n). - [] - val map3 : - mapping : ('T1 -> 'T2 -> 'T3 -> 'U) -> - array1 : FlatList<'T1> -> - array2 : FlatList<'T2> -> - array3 : FlatList<'T3> -> - FlatList<'U> - - /// Returns the results of a function applied to each element and its index. - /// The function to transform the elements. - /// The input flat list. - /// The results. - /// O(n). - [] - val mapi : mapping : (int -> 'T -> 'U) -> array : FlatList<'T> -> FlatList<'U> - - /// Returns the results of a function applied to the index and the pair of elements at that index. - /// The function to transform the pairs. - /// The first flat list. - /// The second flat list. - /// The results. - /// Thrown when the flat lists have different lengths. - /// O(n). - [] - val mapi2 : mapping : (int -> 'T1 -> 'T2 -> 'U) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> FlatList<'U> - - /// Returns every element with its successor. - /// The input flat list. - /// The pairs of neighbours; empty for fewer than two elements. - /// O(n). - [] - val pairwise : array : FlatList<'T> -> FlatList - - /// Returns the elements moved to the indices that a function computes. - /// The function that maps the index of each element to its new index. - /// The input flat list. - /// The permuted flat list. - /// Thrown when the function does not compute a permutation. - /// O(n). - [] - val permute : indexMap : (int -> int) -> array : FlatList<'T> -> FlatList<'T> - - /// Returns a flat list without the element at an index. - /// The index of the removed element. - /// The input flat list. - /// The flat list without the element. - /// Thrown when the index is out of range. - /// O(n). - [] - val removeAt : index : int -> source : FlatList<'T> -> FlatList<'T> - - /// Returns a flat list without a range of elements. - /// The index of the first removed element. - /// The number of removed elements. - /// The input flat list. - /// The flat list without the range. - /// Thrown when the range is out of range. - /// O(n). - [] - val removeManyAt : index : int -> count : int -> source : FlatList<'T> -> FlatList<'T> - - /// Returns the elements in reverse order. - /// The input flat list. - /// The reversed flat list. - /// O(n). - [] - val rev : array : FlatList<'T> -> FlatList<'T> - - /// Returns the transpose of a sequence of flat lists of the same length. - /// The flat lists. - /// A flat list whose element i holds the element i of every input flat list. - /// Thrown when the flat lists have different lengths. - /// O(the total length). - [] - val transpose : arrays : seq> -> FlatList> - - /// Splits a flat list of pairs into two flat lists. - /// The input flat list. - /// The first components and the second components. - /// O(n). - [] - val unzip : array : FlatList -> struct (FlatList<'T1> * FlatList<'T2>) - - /// Splits a flat list of triples into three flat lists. - /// The input flat list. - /// The first, the second and the third components. - /// O(n). - [] - val unzip3 : array : FlatList -> struct (FlatList<'T1> * FlatList<'T2> * FlatList<'T3>) - - /// Returns a flat list with the element at an index replaced by a value. - /// The index of the replaced element. - /// The new value. - /// The input flat list. - /// The flat list with the new value. - /// Thrown when the index is out of range. - /// O(n). - [] - val updateAt : index : int -> value : 'T -> source : FlatList<'T> -> FlatList<'T> - - /// Returns the elements that satisfy the predicate. - /// The function to test the elements. - /// The input flat list. - /// The elements that satisfy the predicate, in their order. - /// O(n). A synonym of . - [] - val where : predicate : ('T -> bool) -> array : FlatList<'T> -> FlatList<'T> - - /// Returns the windows of a given size that slide over the flat list. - /// The size of a window. - /// The input flat list. - /// The windows; empty when the flat list is shorter than a window. - /// Thrown when is not positive. - /// O(n × windowSize). The windows are copies. - [] - val windowed : windowSize : int -> array : FlatList<'T> -> FlatList> - - /// Combines two flat lists into a flat list of pairs. - /// The first flat list. - /// The second flat list. - /// The pairs of elements at the same index. - /// Thrown when the flat lists have different lengths. - /// O(n). - [] - val zip : array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> FlatList - - /// Combines three flat lists into a flat list of triples. - /// The first flat list. - /// The second flat list. - /// The third flat list. - /// The triples of elements at the same index. - /// Thrown when the flat lists have different lengths. - /// O(n). - [] - val zip3 : array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> array3 : FlatList<'T3> -> FlatList - - // ---- Folding ---- - - /// Returns the average of the elements. - /// The input flat list. - /// The average. - /// Thrown when the flat list is empty. - /// O(n). - [] - val inline average : - array : FlatList< ^T > -> ^T - when ^T : (static member (+) : ^T * ^T -> ^T) - and ^T : (static member DivideByInt : ^T * int -> ^T) - and ^T : (static member Zero : ^T) - - /// Returns the average of the results of a function applied to each element. - /// The function to transform the elements. - /// The input flat list. - /// The average of the results. - /// Thrown when the flat list is empty. - /// O(n). - [] - val inline averageBy : - projection : ('T -> ^U) -> array : FlatList<'T> -> ^U - when ^U : (static member (+) : ^U * ^U -> ^U) - and ^U : (static member DivideByInt : ^U * int -> ^U) - and ^U : (static member Zero : ^U) - - /// Compares two flat lists element by element with a comparer. - /// The function that compares two elements. - /// The first flat list. - /// The second flat list. - /// - /// The first non-zero result of the comparer, or the comparison of the lengths when every common element compares equal. - /// - /// O(n). - [] - val compareWith : comparer : ('T -> 'T -> int) -> array1 : FlatList<'T> -> array2 : FlatList<'T> -> int - - /// Applies a function to a state and each element, from the first element to the last one. - /// The function that computes the next state. - /// The initial state. - /// The input flat list. - /// The final state. - /// O(n). - [] - val fold : folder : ('State -> 'T -> 'State) -> state : 'State -> array : FlatList<'T> -> 'State - - /// Applies a function to a state and each pair of elements at the same index, from the first index to the last one. - /// The function that computes the next state. - /// The initial state. - /// The first flat list. - /// The second flat list. - /// The final state. - /// Thrown when the flat lists have different lengths. - /// O(n). - [] - val fold2 : - folder : ('State -> 'T1 -> 'T2 -> 'State) -> state : 'State -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> 'State - - /// Applies a function to each element and a state, from the last element to the first one. - /// The function that computes the next state. - /// The input flat list. - /// The initial state. - /// The final state. - /// O(n). - [] - val foldBack : folder : ('T -> 'State -> 'State) -> array : FlatList<'T> -> state : 'State -> 'State - - /// Applies a function to each pair of elements at the same index and a state, from the last index to the first one. - /// The function that computes the next state. - /// The first flat list. - /// The second flat list. - /// The initial state. - /// The final state. - /// Thrown when the flat lists have different lengths. - /// O(n). - [] - val foldBack2 : - folder : ('T1 -> 'T2 -> 'State -> 'State) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> state : 'State -> 'State - - /// Applies an action to each element. - /// The action. - /// The input flat list. - /// O(n). - [] - val iter : action : ('T -> unit) -> array : FlatList<'T> -> unit - - /// Applies an action to each pair of elements at the same index. - /// The action. - /// The first flat list. - /// The second flat list. - /// Thrown when the flat lists have different lengths. - /// O(n). - [] - val iter2 : action : ('T1 -> 'T2 -> unit) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> unit - - /// Applies an action to each element and its index. - /// The action. - /// The input flat list. - /// O(n). - [] - val iteri : action : (int -> 'T -> unit) -> array : FlatList<'T> -> unit - - /// Applies an action to the index and the pair of elements at that index. - /// The action. - /// The first flat list. - /// The second flat list. - /// Thrown when the flat lists have different lengths. - /// O(n). - [] - val iteri2 : action : (int -> 'T1 -> 'T2 -> unit) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> unit - - /// Maps each element and threads a state through the mapping, from the first element to the last one. - /// The function that returns the result for an element and the next state. - /// The initial state. - /// The input flat list. - /// The results and the final state. - /// O(n). - [] - val mapFold : - mapping : ('State -> 'T -> struct ('Result * 'State)) -> - state : 'State -> - array : FlatList<'T> -> - struct (FlatList<'Result> * 'State) - - /// Maps each element and threads a state through the mapping, from the last element to the first one. - /// The function that returns the result for an element and the next state. - /// The input flat list. - /// The initial state. - /// The results, in the order of the elements, and the final state. - /// O(n). - [] - val mapFoldBack : - mapping : ('T -> 'State -> struct ('Result * 'State)) -> - array : FlatList<'T> -> - state : 'State -> - struct (FlatList<'Result> * 'State) - - /// Returns the greatest element, in F# generic comparison. - /// The input flat list. - /// The greatest element. - /// Thrown when the flat list is empty. - /// O(n). - [] - val max : array : FlatList<'T> -> 'T when 'T : comparison - - /// Returns the element with the greatest result of a projection. - /// The function to transform the elements for the comparison. - /// The input flat list. - /// The first element with the greatest result. - /// Thrown when the flat list is empty. - /// O(n). - [] - val maxBy : projection : ('T -> 'U) -> array : FlatList<'T> -> 'T when 'U : comparison - - /// Returns the smallest element, in F# generic comparison. - /// The input flat list. - /// The smallest element. - /// Thrown when the flat list is empty. - /// O(n). - [] - val min : array : FlatList<'T> -> 'T when 'T : comparison - - /// Returns the element with the smallest result of a projection. - /// The function to transform the elements for the comparison. - /// The input flat list. - /// The first element with the smallest result. - /// Thrown when the flat list is empty. - /// O(n). - [] - val minBy : projection : ('T -> 'U) -> array : FlatList<'T> -> 'T when 'U : comparison - - /// Combines the elements with a function, from the first element to the last one. - /// The function that combines the result so far with the next element. - /// The input flat list. - /// The combined value. - /// Thrown when the flat list is empty. - /// O(n). - [] - val reduce : reduction : ('T -> 'T -> 'T) -> array : FlatList<'T> -> 'T - - /// Combines the elements with a function, from the last element to the first one. - /// The function that combines an element with the result so far. - /// The input flat list. - /// The combined value. - /// Thrown when the flat list is empty. - /// O(n). - [] - val reduceBack : reduction : ('T -> 'T -> 'T) -> array : FlatList<'T> -> 'T - - /// Returns every state of a fold from the first element to the last one, the initial state first. - /// The function that computes the next state. - /// The initial state. - /// The input flat list. - /// The states; one more than the elements. - /// O(n). - [] - val scan : folder : ('State -> 'T -> 'State) -> state : 'State -> array : FlatList<'T> -> FlatList<'State> - - /// Returns every state of a fold from the last element to the first one, the initial state last. - /// The function that computes the next state. - /// The input flat list. - /// The initial state. - /// The states; one more than the elements. - /// O(n). - [] - val scanBack : folder : ('T -> 'State -> 'State) -> array : FlatList<'T> -> state : 'State -> FlatList<'State> - - /// Returns the sum of the elements. - /// The input flat list. - /// The sum; zero for an empty flat list. - /// O(n). - [] - val inline sum : array : FlatList< ^T > -> ^T when ^T : (static member (+) : ^T * ^T -> ^T) and ^T : (static member Zero : ^T) - - /// Returns the sum of the results of a function applied to each element. - /// The function to transform the elements. - /// The input flat list. - /// The sum of the results; zero for an empty flat list. - /// O(n). - [] - val inline sumBy : - projection : ('T -> ^U) -> array : FlatList<'T> -> ^U - when ^U : (static member (+) : ^U * ^U -> ^U) and ^U : (static member Zero : ^U) - - // ---- Partitioning and grouping ---- - - /// Counts the elements of each key of a projection. - /// The function that returns the key of an element. - /// The input flat list. - /// The keys, in the order of their first occurrence, with the number of their elements. - /// O(n). The keys compare in F# structural equality. - [] - val countBy : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList when 'Key : equality - - /// Returns the elements without duplicates, in F# structural equality. - /// The input flat list. - /// The first occurrence of each element, in their order. - /// O(n). - [] - val distinct : array : FlatList<'T> -> FlatList<'T> when 'T : equality - - /// Returns the elements without duplicates of the result of a projection. - /// The function that returns the key of an element. - /// The input flat list. - /// The first element of each key, in their order. - /// O(n). - [] - val distinctBy : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList<'T> when 'Key : equality - - /// Returns the distinct elements that do not equal any of the excluded items. - /// The items to remove. - /// The input flat list. - /// The distinct remaining elements, in their order. - /// Thrown when is . - /// O(n + m). - [] - val except : itemsToExclude : seq<'T> -> array : FlatList<'T> -> FlatList<'T> when 'T : equality - - /// Groups the elements by the key of a projection. - /// The function that returns the key of an element. - /// The input flat list. - /// The keys, in the order of their first occurrence, with their elements in their order. - /// O(n). The keys compare in F# structural equality. - [] - val groupBy : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList)> when 'Key : equality - - /// Splits the elements by a predicate. - /// The function to test the elements. - /// The input flat list. - /// The elements that satisfy the predicate and the other ones, both in their order. - /// O(n). - [] - val partition : predicate : ('T -> bool) -> array : FlatList<'T> -> struct (FlatList<'T> * FlatList<'T>) - - /// Splits the elements by the choice that a function returns for them. - /// The function that returns Choice1Of2 or Choice2Of2 for an element. - /// The input flat list. - /// The values of the first choices and of the second choices, both in the order of the elements. - /// O(n). - [] - val partitionWith : partitioner : ('T -> Choice<'T1, 'T2>) -> array : FlatList<'T> -> struct (FlatList<'T1> * FlatList<'T2>) - - /// Returns the flat list without its first elements. - /// The number of elements to skip; a negative count skips nothing. - /// The input flat list. - /// The remaining elements. - /// Thrown when the count exceeds the length. - /// O(n). The result is a copy, unless it is empty or the whole flat list. - [] - val skip : count : int -> array : FlatList<'T> -> FlatList<'T> - - /// Returns the flat list without its last elements. - /// The number of elements to skip; a negative count skips nothing. - /// The input flat list. - /// The remaining elements. - /// Thrown when the count exceeds the length. - /// O(n). The checks follow . The result is a copy, unless it is empty or the whole flat list. - [] - val skipLast : count : int -> array : FlatList<'T> -> FlatList<'T> - - /// Returns the flat list without its first elements that satisfy the predicate. - /// The function to test the elements. - /// The input flat list. - /// The elements from the first one that does not satisfy the predicate. - /// O(n). The result is a copy, unless it is empty or the whole flat list. - [] - val skipWhile : predicate : ('T -> bool) -> array : FlatList<'T> -> FlatList<'T> - - /// Splits the flat list at an index. - /// The index of the first element of the second part. - /// The input flat list. - /// The elements before the index and the elements from the index. - /// Thrown when the index is negative. - /// Thrown when the index exceeds the length. - /// O(n). The parts are copies, unless they are empty or the whole flat list. - [] - val splitAt : index : int -> array : FlatList<'T> -> struct (FlatList<'T> * FlatList<'T>) - - /// Splits the flat list into at most a given number of parts of nearly equal length. - /// The maximum number of parts. - /// The input flat list. - /// The parts; the first ones are one element longer when the length does not divide evenly. - /// Thrown when is not positive. - /// O(n). The parts are copies. - [] - val splitInto : count : int -> array : FlatList<'T> -> FlatList> - - /// Returns the first elements of the flat list. - /// The number of elements to take. - /// The input flat list. - /// The first elements. - /// Thrown when the count is negative. - /// Thrown when the count exceeds the length. - /// O(n). The result is a copy, unless it is empty or the whole flat list. - [] - val take : count : int -> array : FlatList<'T> -> FlatList<'T> - - /// Returns the last elements of the flat list. - /// The number of elements to take. - /// The input flat list. - /// The last elements. - /// Thrown when the count is negative. - /// Thrown when the count exceeds the length. - /// O(n). The checks follow . The result is a copy, unless it is empty or the whole flat list. - [] - val takeLast : count : int -> array : FlatList<'T> -> FlatList<'T> - - /// Returns the first elements of the flat list that satisfy the predicate. - /// The function to test the elements. - /// The input flat list. - /// The elements before the first one that does not satisfy the predicate. - /// O(n). The result is a copy, unless it is empty or the whole flat list. - [] - val takeWhile : predicate : ('T -> bool) -> array : FlatList<'T> -> FlatList<'T> - - /// Returns at most a given number of first elements. - /// The maximum number of elements; a count that is not positive returns an empty flat list. - /// The input flat list. - /// The first elements. - /// O(n). The result is a copy, unless it is empty or the whole flat list. - [] - val truncate : count : int -> array : FlatList<'T> -> FlatList<'T> - - // ---- Sorting ---- - - /// Sorts the elements in the order of F# generic comparison. - /// The input flat list. - /// The sorted flat list. - /// O(n log n). This is not a stable sort, as . - [] - val sort : array : FlatList<'T> -> FlatList<'T> when 'T : comparison - - /// Sorts the elements by the result of a projection, in the order of F# generic comparison. - /// The function that returns the key of an element. - /// The input flat list. - /// The sorted flat list. - /// O(n log n). This is not a stable sort. - [] - val sortBy : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList<'T> when 'Key : comparison - - /// Sorts the elements by the result of a projection, in descending order. - /// The function that returns the key of an element. - /// The input flat list. - /// The sorted flat list. - /// O(n log n). This is not a stable sort. - [] - val sortByDescending : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList<'T> when 'Key : comparison - - /// Sorts the elements in descending order of F# generic comparison. - /// The input flat list. - /// The sorted flat list. - /// O(n log n). This is not a stable sort. - [] - val sortDescending : array : FlatList<'T> -> FlatList<'T> when 'T : comparison - - /// Sorts the elements with a comparison function. - /// The function that compares two elements. - /// The input flat list. - /// The sorted flat list. - /// O(n log n). This is not a stable sort. - [] - val sortWith : comparer : ('T -> 'T -> int) -> array : FlatList<'T> -> FlatList<'T> - - /// Sorts the elements with a comparer. - /// The comparer. - /// The input flat list. - /// The sorted flat list; the input itself when it is already sorted. - /// - /// O(n log n). This is not a stable sort. The function wraps - /// . - /// - [] - val sortWithComparer : comparer : IComparer<'T> -> array : FlatList<'T> -> FlatList<'T> - - // ---- Random ---- - - /// Returns a random element, chosen with . - /// The input flat list. - /// A random element. - /// Thrown when the flat list is empty. - /// O(1). - [] - val randomChoice : source : FlatList<'T> -> 'T - - /// Returns a random element, chosen with a function that returns numbers in [0, 1). - /// The function that returns a random number in [0, 1). - /// The input flat list. - /// A random element. - /// Thrown when the flat list is empty. - /// Thrown when the randomizer returns a number outside [0, 1). - /// O(1). - [] - val randomChoiceBy : randomizer : (unit -> float) -> source : FlatList<'T> -> 'T - - /// Returns a random element, chosen with a given random number generator. - /// The random number generator. - /// The input flat list. - /// A random element. - /// Thrown when is . - /// Thrown when the flat list is empty. - /// O(1). - [] - val randomChoiceWith : random : Random -> source : FlatList<'T> -> 'T - - /// Returns random elements, chosen with ; an element can repeat. - /// The number of elements to choose. - /// The input flat list. - /// The chosen elements. - /// Thrown when the count is negative or the flat list is empty. - /// O(count). - [] - val randomChoices : count : int -> source : FlatList<'T> -> FlatList<'T> - - /// Returns random elements, chosen with a function that returns numbers in [0, 1); an element can repeat. - /// The function that returns a random number in [0, 1). - /// The number of elements to choose. - /// The input flat list. - /// The chosen elements. - /// Thrown when the count is negative or the flat list is empty. - /// Thrown when the randomizer returns a number outside [0, 1). - /// O(count). - [] - val randomChoicesBy : randomizer : (unit -> float) -> count : int -> source : FlatList<'T> -> FlatList<'T> - - /// Returns random elements, chosen with a given random number generator; an element can repeat. - /// The random number generator. - /// The number of elements to choose. - /// The input flat list. - /// The chosen elements. - /// Thrown when is . - /// Thrown when the count is negative or the flat list is empty. - /// O(count). - [] - val randomChoicesWith : random : Random -> count : int -> source : FlatList<'T> -> FlatList<'T> - - /// Returns random elements at distinct indices, chosen with . - /// The number of elements to choose. - /// The input flat list. - /// The chosen elements. - /// Thrown when the count is negative or exceeds the length. - /// O(count). - [] - val randomSample : count : int -> source : FlatList<'T> -> FlatList<'T> - - /// Returns random elements at distinct indices, chosen with a function that returns numbers in [0, 1). - /// The function that returns a random number in [0, 1). - /// The number of elements to choose. - /// The input flat list. - /// The chosen elements. - /// Thrown when the count is negative or exceeds the length. - /// Thrown when the randomizer returns a number outside [0, 1). - /// O(count). - [] - val randomSampleBy : randomizer : (unit -> float) -> count : int -> source : FlatList<'T> -> FlatList<'T> - - /// Returns random elements at distinct indices, chosen with a given random number generator. - /// The random number generator. - /// The number of elements to choose. - /// The input flat list. - /// The chosen elements. - /// Thrown when is . - /// Thrown when the count is negative or exceeds the length. - /// O(count). - [] - val randomSampleWith : random : Random -> count : int -> source : FlatList<'T> -> FlatList<'T> - - /// Returns the elements in a random order, chosen with . - /// The input flat list. - /// The shuffled flat list. - /// O(n). - [] - val randomShuffle : source : FlatList<'T> -> FlatList<'T> - - /// Returns the elements in a random order, chosen with a function that returns numbers in [0, 1). - /// The function that returns a random number in [0, 1). - /// The input flat list. - /// The shuffled flat list. - /// Thrown when the randomizer returns a number outside [0, 1). - /// O(n). - [] - val randomShuffleBy : randomizer : (unit -> float) -> source : FlatList<'T> -> FlatList<'T> - - /// Returns the elements in a random order, chosen with a given random number generator. - /// The random number generator. - /// The input flat list. - /// The shuffled flat list. - /// Thrown when is . - /// O(n). - [] - val randomShuffleWith : random : Random -> source : FlatList<'T> -> FlatList<'T> - - // ---- Conversion ---- - - /// Returns a read-only memory region over the elements, without a copy. - /// The input flat list. - /// The memory region. - /// O(1). The function wraps . - [] - val asMemory : array : FlatList<'T> -> ReadOnlyMemory<'T> - - /// Returns a read-only span over the elements, without a copy. - /// The input flat list. - /// The span. - /// - /// O(1). The function wraps . A range of the span is a - /// view of a range of the flat list that copies nothing. - /// - [] - val asSpan : array : FlatList<'T> -> ReadOnlySpan<'T> - - /// Copies a range of the flat list into an array. - /// The input flat list. - /// The index of the first element to copy. - /// The array to copy to. - /// The index in the target array of the first copied element. - /// The number of elements to copy. - /// Thrown when is . - /// Thrown when a range is outside its flat list or array. - /// O(count). - [] - val blit : source : FlatList<'T> -> sourceIndex : int -> target : 'T array -> targetIndex : int -> count : int -> unit - - /// Returns a new array with the elements. - /// The input flat list. - /// A copy of the elements. - /// O(n). - [] - val toArray : array : FlatList<'T> -> 'T array - - /// Returns a list of the elements. - /// The input flat list. - /// The list of the elements. - /// O(n). - [] - val toList : array : FlatList<'T> -> 'T list - - /// Returns the flat list as a sequence. - /// The input flat list. - /// The sequence of the elements. - /// O(1). - [] - val toSeq : array : FlatList<'T> -> seq<'T> - - // ---- Type-specific ---- - - /// Returns whether the immutable array is a default value without an underlying array. - /// The input flat list, which can be a default value. - /// for a default value. - /// O(1). The function wraps . - [] - val isDefault : array : FlatList<'T> -> bool - - /// Returns whether the immutable array is a default value or empty. - /// The input flat list, which can be a default value. - /// for a default value or an empty flat list. - /// O(1). The function wraps . - [] - val isDefaultOrEmpty : array : FlatList<'T> -> bool +module FSharp.Collections.Immutable.FlatList + +open System +open System.Collections.Generic +open System.Collections.Immutable + +// ---- Creation ---- + +/// Creates a flat list whose elements are all the given value. +/// The length of the flat list. +/// The value of every element. +/// The created flat list. +/// Thrown when is negative. +/// O(n). +[] +val create : count : int -> value : 'T -> FlatList<'T> + +/// The empty flat list. +/// O(1). The value is . +[] +[] +val empty<'T> : FlatList<'T> + +/// Creates a flat list from a function of the index. +/// The length of the flat list. +/// The function that computes the element at each index, called in index order. +/// The created flat list. +/// Thrown when is negative. +/// O(n). +[] +val init : count : int -> initializer : (int -> 'T) -> FlatList<'T> + +/// Creates a flat list from a copy of an array. +/// The input array. +/// The flat list of the elements of the array. +/// Thrown when is . +/// O(n). Later changes to the array do not change the flat list. +[] +val ofArray : array : 'T array -> FlatList<'T> + +/// Creates a flat list from a list. +/// The input list. +/// The flat list of the elements of the list. +/// O(n). +[] +val ofList : list : 'T list -> FlatList<'T> + +/// Creates a flat list from a copy of a read-only span. +/// The input span. +/// The flat list of the elements of the span. +/// O(n). The function wraps . +[] +val ofReadOnlySpan : span : ReadOnlySpan<'T> -> FlatList<'T> + +/// Creates a flat list from a sequence. +/// The input sequence. +/// The flat list of the elements of the sequence. +/// Thrown when is or a default flat list. +/// O(n). A flat list is returned as it is, without a copy. +[] +val ofSeq : source : seq<'T> -> FlatList<'T> + +/// Creates a flat list from a copy of a span. +/// The input span. +/// The flat list of the elements of the span. +/// O(n). The function wraps . +[] +val ofSpan : span : Span<'T> -> FlatList<'T> + +/// Creates a flat list whose elements are all the given value. +/// The length of the flat list. +/// The value of every element. +/// The created flat list. +/// Thrown when is negative. +/// O(n). +[] +val replicate : count : int -> initial : 'T -> FlatList<'T> + +/// Creates a flat list of one element. +/// The element. +/// The flat list of one element. +/// O(1). +[] +val singleton : value : 'T -> FlatList<'T> + +/// Creates a flat list from a generator, which returns the next element and the next state. +/// The function that returns the next element and state, or ValueNone to stop. +/// The initial state. +/// The generated flat list. +/// O(n). +[] +val unfold : generator : ('State -> struct ('T * 'State) voption) -> state : 'State -> FlatList<'T> + +// ---- Builder ---- + +/// Creates a flat list with a builder that an action fills. +/// The action that adds the elements to the builder. +/// The flat list of the elements that the action added. +/// O(n). +[] +val build : action : (FlatList<'T>.Builder -> unit) -> FlatList<'T> + +/// Creates an empty builder. +/// A builder of capacity 8. +/// O(1). The function wraps . +[] +val builder : unit -> FlatList<'T>.Builder + +/// Creates an empty builder of a given capacity. +/// The initial capacity. +/// The builder. +/// Thrown when is negative. +/// O(capacity). The function wraps . +[] +val builderWith : capacity : int -> FlatList<'T>.Builder + +/// Returns the contents of a full builder as a flat list without a copy and empties the builder. +/// The builder, whose count must equal its capacity. +/// The flat list of the elements of the builder. +/// Thrown when is . +/// Thrown when the count of the builder differs from its capacity. +/// O(1). The function wraps . +[] +val moveFromBuilder : builder : FlatList<'T>.Builder -> FlatList<'T> + +/// Returns a copy of the contents of a builder as a flat list. +/// The builder, which stays unchanged. +/// The flat list of the elements of the builder. +/// Thrown when is . +/// O(n). The function wraps . +[] +val ofBuilder : builder : FlatList<'T>.Builder -> FlatList<'T> + +/// Creates a builder that contains a copy of the elements of a flat list. +/// The input flat list. +/// The builder. +/// O(n). The function wraps . +[] +val toBuilder : array : FlatList<'T> -> FlatList<'T>.Builder + +/// Creates a flat list from a builder over the elements of a flat list, after an action changes the builder. +/// The action that changes the builder. +/// The input flat list, which stays unchanged. +/// The flat list of the elements of the changed builder. +/// O(n). +[] +val update : action : (FlatList<'T>.Builder -> unit) -> array : FlatList<'T> -> FlatList<'T> + +// ---- Properties ---- + +/// Returns whether the flat list has no elements. +/// The input flat list. +/// when the flat list is empty. +/// O(1). +[] +val isEmpty : array : FlatList<'T> -> bool + +/// Returns the number of elements of the flat list. +/// The input flat list. +/// The length of the flat list. +/// O(1). +[] +val length : array : FlatList<'T> -> int + +// ---- Access ---- + +/// Returns the only element of the flat list. +/// The input flat list. +/// The only element. +/// Thrown when the flat list does not have exactly one element. +/// O(1). +[] +val exactlyOne : array : FlatList<'T> -> 'T + +/// Returns the first element of the flat list. +/// The input flat list. +/// The first element. +/// Thrown when the flat list is empty. +/// O(1). +[] +val head : array : FlatList<'T> -> 'T + +/// Returns the element at an index. +/// The index of the element. +/// The input flat list. +/// The element at the index. +/// Thrown when the index is negative or not less than the length. +/// O(1). +[] +val item : index : int -> array : FlatList<'T> -> 'T + +/// Returns the last element of the flat list. +/// The input flat list. +/// The last element. +/// Thrown when the flat list is empty. +/// O(1). +[] +val last : array : FlatList<'T> -> 'T + +/// Returns the flat list without its first element. +/// The input flat list. +/// A copy of the elements after the first one. +/// Thrown when the flat list is empty. +/// O(n). +[] +val tail : array : FlatList<'T> -> FlatList<'T> + +/// Returns the only element of the flat list, or ValueNone when it does not have exactly one element. +/// The input flat list. +/// The only element, or ValueNone. +/// O(1). +[] +val tryExactlyOne : array : FlatList<'T> -> 'T voption + +/// Returns the first element of the flat list, or ValueNone when it is empty. +/// The input flat list. +/// The first element, or ValueNone. +/// O(1). +[] +val tryHead : array : FlatList<'T> -> 'T voption + +/// Returns the element at an index, or ValueNone when the index is out of range. +/// The index of the element. +/// The input flat list. +/// The element at the index, or ValueNone. +/// O(1). +[] +val tryItem : index : int -> array : FlatList<'T> -> 'T voption + +/// Returns the last element of the flat list, or ValueNone when it is empty. +/// The input flat list. +/// The last element, or ValueNone. +/// O(1). +[] +val tryLast : array : FlatList<'T> -> 'T voption + +/// Returns the flat list without its first element, or ValueNone when it is empty. +/// The input flat list. +/// A copy of the elements after the first one, or ValueNone. +/// O(n). +[] +val tryTail : array : FlatList<'T> -> FlatList<'T> voption + +// ---- Search ---- + +/// Searches a sorted flat list for a value with the binary search algorithm. +/// The value to search for. +/// The input flat list, sorted in the order of F# generic comparison. +/// +/// The index of the value, or the bitwise complement of the index of the first larger element, or the bitwise +/// complement of the length when no element is larger. +/// +/// +/// O(log n). The function compares with , +/// the order that produces. +/// +[] +val binarySearch : value : 'T -> array : FlatList<'T> -> int when 'T : comparison + +/// Returns whether the flat list contains an element equal to a value, in F# structural equality. +/// The value to find. +/// The input flat list. +/// when an element equals the value. +/// O(n). +[] +val contains : value : 'T -> array : FlatList<'T> -> bool when 'T : equality + +/// Returns whether any element satisfies the predicate. +/// The function to test the elements. +/// The input flat list. +/// when an element satisfies the predicate. +/// O(n). +[] +val exists : predicate : ('T -> bool) -> array : FlatList<'T> -> bool + +/// Returns whether any pair of elements at the same index satisfies the predicate. +/// The function to test the pairs. +/// The first flat list. +/// The second flat list. +/// when a pair satisfies the predicate. +/// Thrown when the flat lists have different lengths. +/// O(n). +[] +val exists2 : predicate : ('T1 -> 'T2 -> bool) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> bool + +/// Returns the first element that satisfies the predicate. +/// The function to test the elements. +/// The input flat list. +/// The first element that satisfies the predicate. +/// Thrown when no element satisfies the predicate. +/// O(n). +[] +val find : predicate : ('T -> bool) -> array : FlatList<'T> -> 'T + +/// Returns the last element that satisfies the predicate. +/// The function to test the elements. +/// The input flat list. +/// The last element that satisfies the predicate. +/// Thrown when no element satisfies the predicate. +/// O(n). +[] +val findBack : predicate : ('T -> bool) -> array : FlatList<'T> -> 'T + +/// Returns the index of the first element that satisfies the predicate. +/// The function to test the elements. +/// The input flat list. +/// The index of the first element that satisfies the predicate. +/// Thrown when no element satisfies the predicate. +/// O(n). +[] +val findIndex : predicate : ('T -> bool) -> array : FlatList<'T> -> int + +/// Returns the index of the last element that satisfies the predicate. +/// The function to test the elements. +/// The input flat list. +/// The index of the last element that satisfies the predicate. +/// Thrown when no element satisfies the predicate. +/// O(n). +[] +val findIndexBack : predicate : ('T -> bool) -> array : FlatList<'T> -> int + +/// Returns whether all elements satisfy the predicate. +/// The function to test the elements. +/// The input flat list. +/// when every element satisfies the predicate. +/// O(n). +[] +val forall : predicate : ('T -> bool) -> array : FlatList<'T> -> bool + +/// Returns whether all pairs of elements at the same index satisfy the predicate. +/// The function to test the pairs. +/// The first flat list. +/// The second flat list. +/// when every pair satisfies the predicate. +/// Thrown when the flat lists have different lengths. +/// O(n). +[] +val forall2 : predicate : ('T1 -> 'T2 -> bool) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> bool + +/// Returns the index of the first element equal to a value, in . +/// The value to find. +/// The input flat list. +/// The index of the first equal element, or -1. +/// O(n). The function wraps . +[] +val indexOf : value : 'T -> array : FlatList<'T> -> int + +/// Returns the index of the first element equal to a value in a given equality comparer. +/// The equality comparer. +/// The value to find. +/// The input flat list. +/// The index of the first equal element, or -1. +/// +/// O(n). The function wraps +/// . +/// +[] +val indexOfWith : comparer : IEqualityComparer<'T> -> value : 'T -> array : FlatList<'T> -> int + +/// Returns the index of the last element equal to a value, in . +/// The value to find. +/// The input flat list. +/// The index of the last equal element, or -1. +/// O(n). The function wraps . +[] +val lastIndexOf : value : 'T -> array : FlatList<'T> -> int + +/// Returns the index of the last element equal to a value in a given equality comparer. +/// The equality comparer. +/// The value to find. +/// The input flat list. +/// The index of the last equal element, or -1. +/// +/// O(n). The function wraps +/// . +/// +[] +val lastIndexOfWith : comparer : IEqualityComparer<'T> -> value : 'T -> array : FlatList<'T> -> int + +/// Returns the first value that the chooser returns for the elements. +/// The function that returns a value, or ValueNone to go on. +/// The input flat list. +/// The first chosen value. +/// Thrown when the chooser returns ValueNone for every element. +/// O(n). +[] +val pick : chooser : ('T -> 'U voption) -> array : FlatList<'T> -> 'U + +/// Returns the first element that satisfies the predicate, or ValueNone. +/// The function to test the elements. +/// The input flat list. +/// The first element that satisfies the predicate, or ValueNone. +/// O(n). +[] +val tryFind : predicate : ('T -> bool) -> array : FlatList<'T> -> 'T voption + +/// Returns the last element that satisfies the predicate, or ValueNone. +/// The function to test the elements. +/// The input flat list. +/// The last element that satisfies the predicate, or ValueNone. +/// O(n). +[] +val tryFindBack : predicate : ('T -> bool) -> array : FlatList<'T> -> 'T voption + +/// Returns the index of the first element that satisfies the predicate, or ValueNone. +/// The function to test the elements. +/// The input flat list. +/// The index of the first element that satisfies the predicate, or ValueNone. +/// O(n). +[] +val tryFindIndex : predicate : ('T -> bool) -> array : FlatList<'T> -> int voption + +/// Returns the index of the last element that satisfies the predicate, or ValueNone. +/// The function to test the elements. +/// The input flat list. +/// The index of the last element that satisfies the predicate, or ValueNone. +/// O(n). +[] +val tryFindIndexBack : predicate : ('T -> bool) -> array : FlatList<'T> -> int voption + +/// Returns the first value that the chooser returns for the elements, or ValueNone. +/// The function that returns a value, or ValueNone to go on. +/// The input flat list. +/// The first chosen value, or ValueNone. +/// O(n). +[] +val tryPick : chooser : ('T -> 'U voption) -> array : FlatList<'T> -> 'U voption + +// ---- Transformation ---- + +/// Returns a flat list with a value added after the last element. +/// The value to add. +/// The input flat list. +/// A flat list of the elements and the value. +/// O(n). The function wraps . +[] +val add : value : 'T -> array : FlatList<'T> -> FlatList<'T> + +/// Returns every element of the first flat list paired with every element of the second one. +/// The first flat list. +/// The second flat list. +/// The pairs, in the order of the first flat list and then of the second one. +/// O(n × m). +[] +val allPairs : array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> FlatList + +/// Returns a flat list of the elements of the first flat list followed by the elements of the second one. +/// The first flat list. +/// The second flat list. +/// The concatenation. +/// O(n + m). When one of the flat lists is empty, the function returns the other one. +[] +val append : array1 : FlatList<'T> -> array2 : FlatList<'T> -> FlatList<'T> + +/// Returns the values that the chooser returns for the elements. +/// The function that returns a value, or ValueNone to skip the element. +/// The input flat list. +/// The chosen values. +/// O(n). +[] +val choose : chooser : ('T -> 'U voption) -> array : FlatList<'T> -> FlatList<'U> + +/// Divides the flat list into chunks of a given size. +/// The maximum size of a chunk. +/// The input flat list. +/// The chunks; the last one can be smaller. +/// Thrown when is not positive. +/// O(n). The chunks are copies. +[] +val chunkBySize : chunkSize : int -> array : FlatList<'T> -> FlatList> + +/// Concatenates the flat lists that a function returns for the elements. +/// The function that returns a flat list for each element. +/// The input flat list. +/// The concatenation of the returned flat lists. +/// Thrown when the mapping returns a default flat list. +/// O(n + the total length of the returned flat lists). +[] +val collect : mapping : ('T -> FlatList<'U>) -> array : FlatList<'T> -> FlatList<'U> + +/// Concatenates a sequence of flat lists. +/// The flat lists. +/// The concatenation. +/// Thrown when is . +/// O(the total length). +[] +val concat : arrays : seq> -> FlatList<'T> + +/// Returns the elements that satisfy the predicate. +/// The function to test the elements. +/// The input flat list. +/// The elements that satisfy the predicate, in their order. +/// O(n). +[] +val filter : predicate : ('T -> bool) -> array : FlatList<'T> -> FlatList<'T> + +/// Returns every element with its index. +/// The input flat list. +/// The pairs of index and element. +/// O(n). +[] +val indexed : array : FlatList<'T> -> FlatList + +/// Returns a flat list with a value inserted at an index. +/// The index of the inserted value, from 0 to the length. +/// The value to insert. +/// The input flat list. +/// The flat list with the value. +/// Thrown when the index is negative or greater than the length. +/// O(n). +[] +val insertAt : index : int -> value : 'T -> source : FlatList<'T> -> FlatList<'T> + +/// Returns a flat list with values inserted at an index. +/// The index of the first inserted value, from 0 to the length. +/// The values to insert. +/// The input flat list. +/// The flat list with the values. +/// Thrown when the index is negative or greater than the length. +/// O(n + m). +[] +val insertManyAt : index : int -> values : seq<'T> -> source : FlatList<'T> -> FlatList<'T> + +/// Returns the results of a function applied to each element. +/// The function to transform the elements. +/// The input flat list. +/// The results, in the order of the elements. +/// O(n). +[] +val map : mapping : ('T -> 'U) -> array : FlatList<'T> -> FlatList<'U> + +/// Returns the results of a function applied to the pairs of elements at the same index. +/// The function to transform the pairs. +/// The first flat list. +/// The second flat list. +/// The results. +/// Thrown when the flat lists have different lengths. +/// O(n). +[] +val map2 : mapping : ('T1 -> 'T2 -> 'U) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> FlatList<'U> + +/// Returns the results of a function applied to the triples of elements at the same index. +/// The function to transform the triples. +/// The first flat list. +/// The second flat list. +/// The third flat list. +/// The results. +/// Thrown when the flat lists have different lengths. +/// O(n). +[] +val map3 : + mapping : ('T1 -> 'T2 -> 'T3 -> 'U) -> + array1 : FlatList<'T1> -> + array2 : FlatList<'T2> -> + array3 : FlatList<'T3> -> + FlatList<'U> + +/// Returns the results of a function applied to each element and its index. +/// The function to transform the elements. +/// The input flat list. +/// The results. +/// O(n). +[] +val mapi : mapping : (int -> 'T -> 'U) -> array : FlatList<'T> -> FlatList<'U> + +/// Returns the results of a function applied to the index and the pair of elements at that index. +/// The function to transform the pairs. +/// The first flat list. +/// The second flat list. +/// The results. +/// Thrown when the flat lists have different lengths. +/// O(n). +[] +val mapi2 : mapping : (int -> 'T1 -> 'T2 -> 'U) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> FlatList<'U> + +/// Returns every element with its successor. +/// The input flat list. +/// The pairs of neighbours; empty for fewer than two elements. +/// O(n). +[] +val pairwise : array : FlatList<'T> -> FlatList + +/// Returns the elements moved to the indices that a function computes. +/// The function that maps the index of each element to its new index. +/// The input flat list. +/// The permuted flat list. +/// Thrown when the function does not compute a permutation. +/// O(n). +[] +val permute : indexMap : (int -> int) -> array : FlatList<'T> -> FlatList<'T> + +/// Returns a flat list without the element at an index. +/// The index of the removed element. +/// The input flat list. +/// The flat list without the element. +/// Thrown when the index is out of range. +/// O(n). +[] +val removeAt : index : int -> source : FlatList<'T> -> FlatList<'T> + +/// Returns a flat list without a range of elements. +/// The index of the first removed element. +/// The number of removed elements. +/// The input flat list. +/// The flat list without the range. +/// Thrown when the range is out of range. +/// O(n). +[] +val removeManyAt : index : int -> count : int -> source : FlatList<'T> -> FlatList<'T> + +/// Returns the elements in reverse order. +/// The input flat list. +/// The reversed flat list. +/// O(n). +[] +val rev : array : FlatList<'T> -> FlatList<'T> + +/// Returns the transpose of a sequence of flat lists of the same length. +/// The flat lists. +/// A flat list whose element i holds the element i of every input flat list. +/// Thrown when the flat lists have different lengths. +/// O(the total length). +[] +val transpose : arrays : seq> -> FlatList> + +/// Splits a flat list of pairs into two flat lists. +/// The input flat list. +/// The first components and the second components. +/// O(n). +[] +val unzip : array : FlatList -> struct (FlatList<'T1> * FlatList<'T2>) + +/// Splits a flat list of triples into three flat lists. +/// The input flat list. +/// The first, the second and the third components. +/// O(n). +[] +val unzip3 : array : FlatList -> struct (FlatList<'T1> * FlatList<'T2> * FlatList<'T3>) + +/// Returns a flat list with the element at an index replaced by a value. +/// The index of the replaced element. +/// The new value. +/// The input flat list. +/// The flat list with the new value. +/// Thrown when the index is out of range. +/// O(n). +[] +val updateAt : index : int -> value : 'T -> source : FlatList<'T> -> FlatList<'T> + +/// Returns the elements that satisfy the predicate. +/// The function to test the elements. +/// The input flat list. +/// The elements that satisfy the predicate, in their order. +/// O(n). A synonym of . +[] +val where : predicate : ('T -> bool) -> array : FlatList<'T> -> FlatList<'T> + +/// Returns the windows of a given size that slide over the flat list. +/// The size of a window. +/// The input flat list. +/// The windows; empty when the flat list is shorter than a window. +/// Thrown when is not positive. +/// O(n × windowSize). The windows are copies. +[] +val windowed : windowSize : int -> array : FlatList<'T> -> FlatList> + +/// Combines two flat lists into a flat list of pairs. +/// The first flat list. +/// The second flat list. +/// The pairs of elements at the same index. +/// Thrown when the flat lists have different lengths. +/// O(n). +[] +val zip : array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> FlatList + +/// Combines three flat lists into a flat list of triples. +/// The first flat list. +/// The second flat list. +/// The third flat list. +/// The triples of elements at the same index. +/// Thrown when the flat lists have different lengths. +/// O(n). +[] +val zip3 : array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> array3 : FlatList<'T3> -> FlatList + +// ---- Folding ---- + +/// Returns the average of the elements. +/// The input flat list. +/// The average. +/// Thrown when the flat list is empty. +/// O(n). +[] +val inline average : + array : FlatList< ^T > -> ^T + when ^T : (static member (+) : ^T * ^T -> ^T) + and ^T : (static member DivideByInt : ^T * int -> ^T) + and ^T : (static member Zero : ^T) + +/// Returns the average of the results of a function applied to each element. +/// The function to transform the elements. +/// The input flat list. +/// The average of the results. +/// Thrown when the flat list is empty. +/// O(n). +[] +val inline averageBy : + projection : ('T -> ^U) -> array : FlatList<'T> -> ^U + when ^U : (static member (+) : ^U * ^U -> ^U) + and ^U : (static member DivideByInt : ^U * int -> ^U) + and ^U : (static member Zero : ^U) + +/// Compares two flat lists element by element with a comparer. +/// The function that compares two elements. +/// The first flat list. +/// The second flat list. +/// +/// The first non-zero result of the comparer, or the comparison of the lengths when every common element compares equal. +/// +/// O(n). +[] +val compareWith : comparer : ('T -> 'T -> int) -> array1 : FlatList<'T> -> array2 : FlatList<'T> -> int + +/// Applies a function to a state and each element, from the first element to the last one. +/// The function that computes the next state. +/// The initial state. +/// The input flat list. +/// The final state. +/// O(n). +[] +val fold : folder : ('State -> 'T -> 'State) -> state : 'State -> array : FlatList<'T> -> 'State + +/// Applies a function to a state and each pair of elements at the same index, from the first index to the last one. +/// The function that computes the next state. +/// The initial state. +/// The first flat list. +/// The second flat list. +/// The final state. +/// Thrown when the flat lists have different lengths. +/// O(n). +[] +val fold2 : + folder : ('State -> 'T1 -> 'T2 -> 'State) -> state : 'State -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> 'State + +/// Applies a function to each element and a state, from the last element to the first one. +/// The function that computes the next state. +/// The input flat list. +/// The initial state. +/// The final state. +/// O(n). +[] +val foldBack : folder : ('T -> 'State -> 'State) -> array : FlatList<'T> -> state : 'State -> 'State + +/// Applies a function to each pair of elements at the same index and a state, from the last index to the first one. +/// The function that computes the next state. +/// The first flat list. +/// The second flat list. +/// The initial state. +/// The final state. +/// Thrown when the flat lists have different lengths. +/// O(n). +[] +val foldBack2 : + folder : ('T1 -> 'T2 -> 'State -> 'State) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> state : 'State -> 'State + +/// Applies an action to each element. +/// The action. +/// The input flat list. +/// O(n). +[] +val iter : action : ('T -> unit) -> array : FlatList<'T> -> unit + +/// Applies an action to each pair of elements at the same index. +/// The action. +/// The first flat list. +/// The second flat list. +/// Thrown when the flat lists have different lengths. +/// O(n). +[] +val iter2 : action : ('T1 -> 'T2 -> unit) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> unit + +/// Applies an action to each element and its index. +/// The action. +/// The input flat list. +/// O(n). +[] +val iteri : action : (int -> 'T -> unit) -> array : FlatList<'T> -> unit + +/// Applies an action to the index and the pair of elements at that index. +/// The action. +/// The first flat list. +/// The second flat list. +/// Thrown when the flat lists have different lengths. +/// O(n). +[] +val iteri2 : action : (int -> 'T1 -> 'T2 -> unit) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> unit + +/// Maps each element and threads a state through the mapping, from the first element to the last one. +/// The function that returns the result for an element and the next state. +/// The initial state. +/// The input flat list. +/// The results and the final state. +/// O(n). +[] +val mapFold : + mapping : ('State -> 'T -> struct ('Result * 'State)) -> + state : 'State -> + array : FlatList<'T> -> + struct (FlatList<'Result> * 'State) + +/// Maps each element and threads a state through the mapping, from the last element to the first one. +/// The function that returns the result for an element and the next state. +/// The input flat list. +/// The initial state. +/// The results, in the order of the elements, and the final state. +/// O(n). +[] +val mapFoldBack : + mapping : ('T -> 'State -> struct ('Result * 'State)) -> + array : FlatList<'T> -> + state : 'State -> + struct (FlatList<'Result> * 'State) + +/// Returns the greatest element, in F# generic comparison. +/// The input flat list. +/// The greatest element. +/// Thrown when the flat list is empty. +/// O(n). +[] +val max : array : FlatList<'T> -> 'T when 'T : comparison + +/// Returns the element with the greatest result of a projection. +/// The function to transform the elements for the comparison. +/// The input flat list. +/// The first element with the greatest result. +/// Thrown when the flat list is empty. +/// O(n). +[] +val maxBy : projection : ('T -> 'U) -> array : FlatList<'T> -> 'T when 'U : comparison + +/// Returns the smallest element, in F# generic comparison. +/// The input flat list. +/// The smallest element. +/// Thrown when the flat list is empty. +/// O(n). +[] +val min : array : FlatList<'T> -> 'T when 'T : comparison + +/// Returns the element with the smallest result of a projection. +/// The function to transform the elements for the comparison. +/// The input flat list. +/// The first element with the smallest result. +/// Thrown when the flat list is empty. +/// O(n). +[] +val minBy : projection : ('T -> 'U) -> array : FlatList<'T> -> 'T when 'U : comparison + +/// Combines the elements with a function, from the first element to the last one. +/// The function that combines the result so far with the next element. +/// The input flat list. +/// The combined value. +/// Thrown when the flat list is empty. +/// O(n). +[] +val reduce : reduction : ('T -> 'T -> 'T) -> array : FlatList<'T> -> 'T + +/// Combines the elements with a function, from the last element to the first one. +/// The function that combines an element with the result so far. +/// The input flat list. +/// The combined value. +/// Thrown when the flat list is empty. +/// O(n). +[] +val reduceBack : reduction : ('T -> 'T -> 'T) -> array : FlatList<'T> -> 'T + +/// Returns every state of a fold from the first element to the last one, the initial state first. +/// The function that computes the next state. +/// The initial state. +/// The input flat list. +/// The states; one more than the elements. +/// O(n). +[] +val scan : folder : ('State -> 'T -> 'State) -> state : 'State -> array : FlatList<'T> -> FlatList<'State> + +/// Returns every state of a fold from the last element to the first one, the initial state last. +/// The function that computes the next state. +/// The input flat list. +/// The initial state. +/// The states; one more than the elements. +/// O(n). +[] +val scanBack : folder : ('T -> 'State -> 'State) -> array : FlatList<'T> -> state : 'State -> FlatList<'State> + +/// Returns the sum of the elements. +/// The input flat list. +/// The sum; zero for an empty flat list. +/// O(n). +[] +val inline sum : array : FlatList< ^T > -> ^T when ^T : (static member (+) : ^T * ^T -> ^T) and ^T : (static member Zero : ^T) + +/// Returns the sum of the results of a function applied to each element. +/// The function to transform the elements. +/// The input flat list. +/// The sum of the results; zero for an empty flat list. +/// O(n). +[] +val inline sumBy : + projection : ('T -> ^U) -> array : FlatList<'T> -> ^U + when ^U : (static member (+) : ^U * ^U -> ^U) and ^U : (static member Zero : ^U) + +// ---- Partitioning and grouping ---- + +/// Counts the elements of each key of a projection. +/// The function that returns the key of an element. +/// The input flat list. +/// The keys, in the order of their first occurrence, with the number of their elements. +/// O(n). The keys compare in F# structural equality. +[] +val countBy : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList when 'Key : equality + +/// Returns the elements without duplicates, in F# structural equality. +/// The input flat list. +/// The first occurrence of each element, in their order. +/// O(n). +[] +val distinct : array : FlatList<'T> -> FlatList<'T> when 'T : equality + +/// Returns the elements without duplicates of the result of a projection. +/// The function that returns the key of an element. +/// The input flat list. +/// The first element of each key, in their order. +/// O(n). +[] +val distinctBy : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList<'T> when 'Key : equality + +/// Returns the distinct elements that do not equal any of the excluded items. +/// The items to remove. +/// The input flat list. +/// The distinct remaining elements, in their order. +/// Thrown when is . +/// O(n + m). +[] +val except : itemsToExclude : seq<'T> -> array : FlatList<'T> -> FlatList<'T> when 'T : equality + +/// Groups the elements by the key of a projection. +/// The function that returns the key of an element. +/// The input flat list. +/// The keys, in the order of their first occurrence, with their elements in their order. +/// O(n). The keys compare in F# structural equality. +[] +val groupBy : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList)> when 'Key : equality + +/// Splits the elements by a predicate. +/// The function to test the elements. +/// The input flat list. +/// The elements that satisfy the predicate and the other ones, both in their order. +/// O(n). +[] +val partition : predicate : ('T -> bool) -> array : FlatList<'T> -> struct (FlatList<'T> * FlatList<'T>) + +/// Splits the elements by the choice that a function returns for them. +/// The function that returns Choice1Of2 or Choice2Of2 for an element. +/// The input flat list. +/// The values of the first choices and of the second choices, both in the order of the elements. +/// O(n). +[] +val partitionWith : partitioner : ('T -> Choice<'T1, 'T2>) -> array : FlatList<'T> -> struct (FlatList<'T1> * FlatList<'T2>) + +/// Returns the flat list without its first elements. +/// The number of elements to skip; a negative count skips nothing. +/// The input flat list. +/// The remaining elements. +/// Thrown when the count exceeds the length. +/// O(n). The result is a copy, unless it is empty or the whole flat list. +[] +val skip : count : int -> array : FlatList<'T> -> FlatList<'T> + +/// Returns the flat list without its last elements. +/// The number of elements to skip; a negative count skips nothing. +/// The input flat list. +/// The remaining elements. +/// Thrown when the count exceeds the length. +/// O(n). The checks follow . The result is a copy, unless it is empty or the whole flat list. +[] +val skipLast : count : int -> array : FlatList<'T> -> FlatList<'T> + +/// Returns the flat list without its first elements that satisfy the predicate. +/// The function to test the elements. +/// The input flat list. +/// The elements from the first one that does not satisfy the predicate. +/// O(n). The result is a copy, unless it is empty or the whole flat list. +[] +val skipWhile : predicate : ('T -> bool) -> array : FlatList<'T> -> FlatList<'T> + +/// Splits the flat list at an index. +/// The index of the first element of the second part. +/// The input flat list. +/// The elements before the index and the elements from the index. +/// Thrown when the index is negative. +/// Thrown when the index exceeds the length. +/// O(n). The parts are copies, unless they are empty or the whole flat list. +[] +val splitAt : index : int -> array : FlatList<'T> -> struct (FlatList<'T> * FlatList<'T>) + +/// Splits the flat list into at most a given number of parts of nearly equal length. +/// The maximum number of parts. +/// The input flat list. +/// The parts; the first ones are one element longer when the length does not divide evenly. +/// Thrown when is not positive. +/// O(n). The parts are copies. +[] +val splitInto : count : int -> array : FlatList<'T> -> FlatList> + +/// Returns the first elements of the flat list. +/// The number of elements to take. +/// The input flat list. +/// The first elements. +/// Thrown when the count is negative. +/// Thrown when the count exceeds the length. +/// O(n). The result is a copy, unless it is empty or the whole flat list. +[] +val take : count : int -> array : FlatList<'T> -> FlatList<'T> + +/// Returns the last elements of the flat list. +/// The number of elements to take. +/// The input flat list. +/// The last elements. +/// Thrown when the count is negative. +/// Thrown when the count exceeds the length. +/// O(n). The checks follow . The result is a copy, unless it is empty or the whole flat list. +[] +val takeLast : count : int -> array : FlatList<'T> -> FlatList<'T> + +/// Returns the first elements of the flat list that satisfy the predicate. +/// The function to test the elements. +/// The input flat list. +/// The elements before the first one that does not satisfy the predicate. +/// O(n). The result is a copy, unless it is empty or the whole flat list. +[] +val takeWhile : predicate : ('T -> bool) -> array : FlatList<'T> -> FlatList<'T> + +/// Returns at most a given number of first elements. +/// The maximum number of elements; a count that is not positive returns an empty flat list. +/// The input flat list. +/// The first elements. +/// O(n). The result is a copy, unless it is empty or the whole flat list. +[] +val truncate : count : int -> array : FlatList<'T> -> FlatList<'T> + +// ---- Sorting ---- + +/// Sorts the elements in the order of F# generic comparison. +/// The input flat list. +/// The sorted flat list. +/// O(n log n). This is not a stable sort, as . +[] +val sort : array : FlatList<'T> -> FlatList<'T> when 'T : comparison + +/// Sorts the elements by the result of a projection, in the order of F# generic comparison. +/// The function that returns the key of an element. +/// The input flat list. +/// The sorted flat list. +/// O(n log n). This is not a stable sort. +[] +val sortBy : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList<'T> when 'Key : comparison + +/// Sorts the elements by the result of a projection, in descending order. +/// The function that returns the key of an element. +/// The input flat list. +/// The sorted flat list. +/// O(n log n). This is not a stable sort. +[] +val sortByDescending : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList<'T> when 'Key : comparison + +/// Sorts the elements in descending order of F# generic comparison. +/// The input flat list. +/// The sorted flat list. +/// O(n log n). This is not a stable sort. +[] +val sortDescending : array : FlatList<'T> -> FlatList<'T> when 'T : comparison + +/// Sorts the elements with a comparison function. +/// The function that compares two elements. +/// The input flat list. +/// The sorted flat list. +/// O(n log n). This is not a stable sort. +[] +val sortWith : comparer : ('T -> 'T -> int) -> array : FlatList<'T> -> FlatList<'T> + +/// Sorts the elements with a comparer. +/// The comparer. +/// The input flat list. +/// The sorted flat list; the input itself when it is already sorted. +/// +/// O(n log n). This is not a stable sort. The function wraps +/// . +/// +[] +val sortWithComparer : comparer : IComparer<'T> -> array : FlatList<'T> -> FlatList<'T> + +// ---- Random ---- + +/// Returns a random element, chosen with . +/// The input flat list. +/// A random element. +/// Thrown when the flat list is empty. +/// O(1). +[] +val randomChoice : source : FlatList<'T> -> 'T + +/// Returns a random element, chosen with a function that returns numbers in [0, 1). +/// The function that returns a random number in [0, 1). +/// The input flat list. +/// A random element. +/// Thrown when the flat list is empty. +/// Thrown when the randomizer returns a number outside [0, 1). +/// O(1). +[] +val randomChoiceBy : randomizer : (unit -> float) -> source : FlatList<'T> -> 'T + +/// Returns a random element, chosen with a given random number generator. +/// The random number generator. +/// The input flat list. +/// A random element. +/// Thrown when is . +/// Thrown when the flat list is empty. +/// O(1). +[] +val randomChoiceWith : random : Random -> source : FlatList<'T> -> 'T + +/// Returns random elements, chosen with ; an element can repeat. +/// The number of elements to choose. +/// The input flat list. +/// The chosen elements. +/// Thrown when the count is negative or the flat list is empty. +/// O(count). +[] +val randomChoices : count : int -> source : FlatList<'T> -> FlatList<'T> + +/// Returns random elements, chosen with a function that returns numbers in [0, 1); an element can repeat. +/// The function that returns a random number in [0, 1). +/// The number of elements to choose. +/// The input flat list. +/// The chosen elements. +/// Thrown when the count is negative or the flat list is empty. +/// Thrown when the randomizer returns a number outside [0, 1). +/// O(count). +[] +val randomChoicesBy : randomizer : (unit -> float) -> count : int -> source : FlatList<'T> -> FlatList<'T> + +/// Returns random elements, chosen with a given random number generator; an element can repeat. +/// The random number generator. +/// The number of elements to choose. +/// The input flat list. +/// The chosen elements. +/// Thrown when is . +/// Thrown when the count is negative or the flat list is empty. +/// O(count). +[] +val randomChoicesWith : random : Random -> count : int -> source : FlatList<'T> -> FlatList<'T> + +/// Returns random elements at distinct indices, chosen with . +/// The number of elements to choose. +/// The input flat list. +/// The chosen elements. +/// Thrown when the count is negative or exceeds the length. +/// O(count). +[] +val randomSample : count : int -> source : FlatList<'T> -> FlatList<'T> + +/// Returns random elements at distinct indices, chosen with a function that returns numbers in [0, 1). +/// The function that returns a random number in [0, 1). +/// The number of elements to choose. +/// The input flat list. +/// The chosen elements. +/// Thrown when the count is negative or exceeds the length. +/// Thrown when the randomizer returns a number outside [0, 1). +/// O(count). +[] +val randomSampleBy : randomizer : (unit -> float) -> count : int -> source : FlatList<'T> -> FlatList<'T> + +/// Returns random elements at distinct indices, chosen with a given random number generator. +/// The random number generator. +/// The number of elements to choose. +/// The input flat list. +/// The chosen elements. +/// Thrown when is . +/// Thrown when the count is negative or exceeds the length. +/// O(count). +[] +val randomSampleWith : random : Random -> count : int -> source : FlatList<'T> -> FlatList<'T> + +/// Returns the elements in a random order, chosen with . +/// The input flat list. +/// The shuffled flat list. +/// O(n). +[] +val randomShuffle : source : FlatList<'T> -> FlatList<'T> + +/// Returns the elements in a random order, chosen with a function that returns numbers in [0, 1). +/// The function that returns a random number in [0, 1). +/// The input flat list. +/// The shuffled flat list. +/// Thrown when the randomizer returns a number outside [0, 1). +/// O(n). +[] +val randomShuffleBy : randomizer : (unit -> float) -> source : FlatList<'T> -> FlatList<'T> + +/// Returns the elements in a random order, chosen with a given random number generator. +/// The random number generator. +/// The input flat list. +/// The shuffled flat list. +/// Thrown when is . +/// O(n). +[] +val randomShuffleWith : random : Random -> source : FlatList<'T> -> FlatList<'T> + +// ---- Conversion ---- + +/// Returns a read-only memory region over the elements, without a copy. +/// The input flat list. +/// The memory region. +/// O(1). The function wraps . +[] +val asMemory : array : FlatList<'T> -> ReadOnlyMemory<'T> + +/// Returns a read-only span over the elements, without a copy. +/// The input flat list. +/// The span. +/// +/// O(1). The function wraps . A range of the span is a +/// view of a range of the flat list that copies nothing. +/// +[] +val asSpan : array : FlatList<'T> -> ReadOnlySpan<'T> + +/// Copies a range of the flat list into an array. +/// The input flat list. +/// The index of the first element to copy. +/// The array to copy to. +/// The index in the target array of the first copied element. +/// The number of elements to copy. +/// Thrown when is . +/// Thrown when a range is outside its flat list or array. +/// O(count). +[] +val blit : source : FlatList<'T> -> sourceIndex : int -> target : 'T array -> targetIndex : int -> count : int -> unit + +/// Returns a new array with the elements. +/// The input flat list. +/// A copy of the elements. +/// O(n). +[] +val toArray : array : FlatList<'T> -> 'T array + +/// Returns a list of the elements. +/// The input flat list. +/// The list of the elements. +/// O(n). +[] +val toList : array : FlatList<'T> -> 'T list + +/// Returns the flat list as a sequence. +/// The input flat list. +/// The sequence of the elements. +/// O(1). +[] +val toSeq : array : FlatList<'T> -> seq<'T> + +// ---- Type-specific ---- + +/// Returns whether the immutable array is a default value without an underlying array. +/// The input flat list, which can be a default value. +/// for a default value. +/// O(1). The function wraps . +[] +val isDefault : array : FlatList<'T> -> bool + +/// Returns whether the immutable array is a default value or empty. +/// The input flat list, which can be a default value. +/// for a default value or an empty flat list. +/// O(1). The function wraps . +[] +val isDefaultOrEmpty : array : FlatList<'T> -> bool diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs b/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs index 9f1fd471..f28b09e3 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs @@ -1,9 +1,3 @@ -namespace FSharp.Collections.Immutable - -open System -open System.Collections.Generic -open System.Collections.Immutable - /// /// Functions for the builder of a flat list, . /// @@ -14,181 +8,185 @@ open System.Collections.Immutable /// returns its contents as a flat list. /// [] -module FlatListBuilder = - - // ---- Properties ---- - - /// Returns the number of elements that the builder can hold without growing. - /// The builder. - /// The capacity. - [] - let capacity (builder : FlatList<'T>.Builder) = builder.Capacity - - /// Returns the number of elements in the builder. - /// The builder. - /// The number of elements. - [] - let length (builder : FlatList<'T>.Builder) = builder.Count - - // ---- Access ---- - - /// Returns the element at an index. - /// The index of the element. - /// The builder. - /// The element. - /// Thrown when the index is out of range. - [] - let item index (builder : FlatList<'T>.Builder) = builder[index] - - // ---- Search ---- - - /// Returns whether the builder contains a value, in . - /// The value to find. - /// The builder. - /// when an element equals the value. - [] - let contains (value : 'T) (builder : FlatList<'T>.Builder) = builder.Contains value - - /// Returns the index of the first element equal to a value, or -1. - /// The value to find. - /// The builder. - /// The index, or -1. - [] - let indexOf (value : 'T) (builder : FlatList<'T>.Builder) = builder.IndexOf value - - /// Returns the index of the last element equal to a value, or -1. - /// The value to find. - /// The builder. - /// The index, or -1. - [] - let lastIndexOf (value : 'T) (builder : FlatList<'T>.Builder) = builder.LastIndexOf value - - // ---- Transformation ---- - - /// Adds a value after the last element. - /// The value to add. - /// The builder. - /// The same builder. - [] - let add (value : 'T) (builder : FlatList<'T>.Builder) = - builder.Add value - builder - - /// Adds values after the last element. - /// The values to add. - /// The builder. - /// The same builder. - [] - let addRange (values : seq<'T>) (builder : FlatList<'T>.Builder) = - builder.AddRange values - builder - - /// Removes every element. - /// The builder. - /// The same builder. - [] - let clear (builder : FlatList<'T>.Builder) = - builder.Clear () - builder - - /// Inserts a value at an index. - /// The index of the inserted value, from 0 to the count. - /// The value to insert. - /// The builder. - /// The same builder. - /// Thrown when the index is out of range. - [] - let insertAt index (value : 'T) (builder : FlatList<'T>.Builder) = - builder.Insert (index, value) - builder - - /// Removes the first element equal to a value, in . - /// The value to remove. - /// The builder. - /// when an element was removed. - [] - let remove (value : 'T) (builder : FlatList<'T>.Builder) = builder.Remove value - - /// Removes the element at an index. - /// The index of the removed element. - /// The builder. - /// The same builder. - /// Thrown when the index is out of range. - [] - let removeAt index (builder : FlatList<'T>.Builder) = - builder.RemoveAt index - builder - - /// Reverses the order of the elements. - /// The builder. - /// The same builder. - [] - let rev (builder : FlatList<'T>.Builder) = - builder.Reverse () - builder - - /// Sets the number of elements that the builder can hold without growing. - /// The new capacity, not less than the count. - /// The builder. - /// The same builder. - /// Thrown when the capacity is less than the count. - [] - let setCapacity capacity (builder : FlatList<'T>.Builder) = - builder.Capacity <- capacity - builder - - /// Replaces the element at an index. - /// The index of the replaced element. - /// The new value. - /// The builder. - /// The same builder. - /// Thrown when the index is out of range. - [] - let updateAt index (value : 'T) (builder : FlatList<'T>.Builder) = - builder[index] <- value - builder - - // ---- Sorting ---- - - /// Sorts the elements in the order of F# generic comparison. - /// The builder. - /// The same builder. - /// This is not a stable sort. - [] - let sort (builder : FlatList<'T>.Builder) = - builder.Sort LanguagePrimitives.FastGenericComparer<'T> - builder - - /// Sorts the elements with a comparison function. - /// The function that compares two elements. - /// The builder. - /// The same builder. - /// This is not a stable sort. - [] - let sortWith (comparer : 'T -> 'T -> int) (builder : FlatList<'T>.Builder) = - builder.Sort (Comparison<'T> comparer) - builder - - /// Sorts the elements with a comparer. - /// The comparer. - /// The builder. - /// The same builder. - /// This is not a stable sort. - [] - let sortWithComparer (comparer : IComparer<'T>) (builder : FlatList<'T>.Builder) = - builder.Sort comparer - builder - - // ---- Conversion ---- - - /// Returns the contents of the builder as a flat list and empties the builder. - /// The builder. - /// The flat list of the elements; without a copy when the count equals the capacity. - [] - let drainToFlatList (builder : FlatList<'T>.Builder) : FlatList<'T> = builder.DrainToImmutable () - - /// Returns a new array with the elements of the builder. - /// The builder. - /// A copy of the elements. - [] - let toArray (builder : FlatList<'T>.Builder) = builder.ToArray () +module FSharp.Collections.Immutable.FlatListBuilder + +open System +open System.Collections.Generic +open System.Collections.Immutable + +// ---- Properties ---- + +/// Returns the number of elements that the builder can hold without growing. +/// The builder. +/// The capacity. +[] +let capacity (builder : FlatList<'T>.Builder) = builder.Capacity + +/// Returns the number of elements in the builder. +/// The builder. +/// The number of elements. +[] +let length (builder : FlatList<'T>.Builder) = builder.Count + +// ---- Access ---- + +/// Returns the element at an index. +/// The index of the element. +/// The builder. +/// The element. +/// Thrown when the index is out of range. +[] +let item index (builder : FlatList<'T>.Builder) = builder[index] + +// ---- Search ---- + +/// Returns whether the builder contains a value, in . +/// The value to find. +/// The builder. +/// when an element equals the value. +[] +let contains (value : 'T) (builder : FlatList<'T>.Builder) = builder.Contains value + +/// Returns the index of the first element equal to a value, or -1. +/// The value to find. +/// The builder. +/// The index, or -1. +[] +let indexOf (value : 'T) (builder : FlatList<'T>.Builder) = builder.IndexOf value + +/// Returns the index of the last element equal to a value, or -1. +/// The value to find. +/// The builder. +/// The index, or -1. +[] +let lastIndexOf (value : 'T) (builder : FlatList<'T>.Builder) = builder.LastIndexOf value + +// ---- Transformation ---- + +/// Adds a value after the last element. +/// The value to add. +/// The builder. +/// The same builder. +[] +let add (value : 'T) (builder : FlatList<'T>.Builder) = + builder.Add value + builder + +/// Adds values after the last element. +/// The values to add. +/// The builder. +/// The same builder. +[] +let addRange (values : seq<'T>) (builder : FlatList<'T>.Builder) = + builder.AddRange values + builder + +/// Removes every element. +/// The builder. +/// The same builder. +[] +let clear (builder : FlatList<'T>.Builder) = + builder.Clear () + builder + +/// Inserts a value at an index. +/// The index of the inserted value, from 0 to the count. +/// The value to insert. +/// The builder. +/// The same builder. +/// Thrown when the index is out of range. +[] +let insertAt index (value : 'T) (builder : FlatList<'T>.Builder) = + builder.Insert (index, value) + builder + +/// Removes the first element equal to a value, in . +/// The value to remove. +/// The builder. +/// when an element was removed. +[] +let remove (value : 'T) (builder : FlatList<'T>.Builder) = builder.Remove value + +/// Removes the element at an index. +/// The index of the removed element. +/// The builder. +/// The same builder. +/// Thrown when the index is out of range. +[] +let removeAt index (builder : FlatList<'T>.Builder) = + builder.RemoveAt index + builder + +/// Reverses the order of the elements. +/// The builder. +/// The same builder. +[] +let rev (builder : FlatList<'T>.Builder) = + builder.Reverse () + builder + +/// Sets the number of elements that the builder can hold without growing. +/// The new capacity, not less than the count. +/// The builder. +/// The same builder. +/// Thrown when the capacity is less than the count. +[] +let setCapacity capacity (builder : FlatList<'T>.Builder) = + builder.Capacity <- capacity + builder + +/// Replaces the element at an index. +/// The index of the replaced element. +/// The new value. +/// The builder. +/// The same builder. +/// Thrown when the index is out of range. +[] +let updateAt index (value : 'T) (builder : FlatList<'T>.Builder) = + builder[index] <- value + builder + +// ---- Sorting ---- + +/// Sorts the elements in the order of F# generic comparison. +/// The builder. +/// The same builder. +/// This is not a stable sort. +[] +let sort (builder : FlatList<'T>.Builder) = + builder.Sort DefaultComparer<'T>.Instance + builder + +/// Sorts the elements with a comparison function. +/// The function that compares two elements. +/// The builder. +/// The same builder. +/// This is not a stable sort. +[] +let sortWith (comparer : 'T -> 'T -> int) (builder : FlatList<'T>.Builder) = + builder.Sort (Comparison<'T> comparer) + builder + +/// Sorts the elements with a comparer. +/// The comparer. +/// The builder. +/// The same builder. +/// This is not a stable sort. +[] +let sortWithComparer (comparer : IComparer<'T>) (builder : FlatList<'T>.Builder) = + builder.Sort comparer + builder + +// ---- Conversion ---- + +/// Returns the contents of the builder as a flat list and empties the builder. +/// The builder. +/// The flat list of the elements; without a copy when the count equals the capacity. +[] +let drainToFlatList (builder : FlatList<'T>.Builder) : FlatList<'T> = builder.DrainToImmutable () + +/// Returns a new array with the elements of the builder. +/// The builder. +/// A copy of the elements. +[] +let toArray (builder : FlatList<'T>.Builder) = builder.ToArray () diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListOption.fs b/src/FSharp.Collections.Immutable/FlatList/FlatListOption.fs index 87856235..b510cf8b 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListOption.fs +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListOption.fs @@ -1,59 +1,58 @@ -namespace FSharp.Collections.Immutable.Option +[] +module FSharp.Collections.Immutable.Option.FlatList open FSharp.Collections.Immutable // The functions call the FSharp.Core Array functions, which take and return options, over the underlying array, or // convert the result of the default module where it implements the function itself. -[] -module FlatList = - // ---- Creation ---- +// ---- Creation ---- - [] - let unfold (generator : 'State -> ('T * 'State) option) (state : 'State) = Array.unfold generator state |> wrapArray +[] +let unfold (generator : 'State -> ('T * 'State) option) (state : 'State) = Array.unfold generator state |> wrapArray - // ---- Access ---- +// ---- Access ---- - [] - let tryExactlyOne (array : FlatList<'T>) = Array.tryExactlyOne (unwrapArray (nameof array) array) +[] +let tryExactlyOne (array : FlatList<'T>) = Array.tryExactlyOne (unwrapArray (nameof array) array) - [] - let tryHead (array : FlatList<'T>) = Array.tryHead (unwrapArray (nameof array) array) +[] +let tryHead (array : FlatList<'T>) = Array.tryHead (unwrapArray (nameof array) array) - [] - let tryItem index (array : FlatList<'T>) = Array.tryItem index (unwrapArray (nameof array) array) +[] +let tryItem index (array : FlatList<'T>) = Array.tryItem index (unwrapArray (nameof array) array) - [] - let tryLast (array : FlatList<'T>) = Array.tryLast (unwrapArray (nameof array) array) +[] +let tryLast (array : FlatList<'T>) = Array.tryLast (unwrapArray (nameof array) array) - [] - let tryTail (array : FlatList<'T>) = - FSharp.Collections.Immutable.FlatList.tryTail array - |> ValueOption.toOption +[] +let tryTail (array : FlatList<'T>) = + FSharp.Collections.Immutable.FlatList.tryTail array + |> ValueOption.toOption - // ---- Search ---- +// ---- Search ---- - [] - let pick (chooser : 'T -> 'U option) (array : FlatList<'T>) = Array.pick chooser (unwrapArray (nameof array) array) +[] +let pick (chooser : 'T -> 'U option) (array : FlatList<'T>) = Array.pick chooser (unwrapArray (nameof array) array) - [] - let tryFind predicate (array : FlatList<'T>) = Array.tryFind predicate (unwrapArray (nameof array) array) +[] +let tryFind predicate (array : FlatList<'T>) = Array.tryFind predicate (unwrapArray (nameof array) array) - [] - let tryFindBack predicate (array : FlatList<'T>) = Array.tryFindBack predicate (unwrapArray (nameof array) array) +[] +let tryFindBack predicate (array : FlatList<'T>) = Array.tryFindBack predicate (unwrapArray (nameof array) array) - [] - let tryFindIndex predicate (array : FlatList<'T>) = Array.tryFindIndex predicate (unwrapArray (nameof array) array) +[] +let tryFindIndex predicate (array : FlatList<'T>) = Array.tryFindIndex predicate (unwrapArray (nameof array) array) - [] - let tryFindIndexBack predicate (array : FlatList<'T>) = Array.tryFindIndexBack predicate (unwrapArray (nameof array) array) +[] +let tryFindIndexBack predicate (array : FlatList<'T>) = Array.tryFindIndexBack predicate (unwrapArray (nameof array) array) - [] - let tryPick (chooser : 'T -> 'U option) (array : FlatList<'T>) = Array.tryPick chooser (unwrapArray (nameof array) array) +[] +let tryPick (chooser : 'T -> 'U option) (array : FlatList<'T>) = Array.tryPick chooser (unwrapArray (nameof array) array) - // ---- Transformation ---- +// ---- Transformation ---- - [] - let choose (chooser : 'T -> 'U option) (array : FlatList<'T>) = - Array.choose chooser (unwrapArray (nameof array) array) - |> wrapArray +[] +let choose (chooser : 'T -> 'U option) (array : FlatList<'T>) = + Array.choose chooser (unwrapArray (nameof array) array) + |> wrapArray diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListOption.fsi b/src/FSharp.Collections.Immutable/FlatList/FlatListOption.fsi index 12544d3e..bfb30ffe 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListOption.fsi +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListOption.fsi @@ -1,7 +1,3 @@ -namespace FSharp.Collections.Immutable.Option - -open FSharp.Collections.Immutable - /// /// The functions of that take or return an optional /// value, with instead of @@ -13,113 +9,115 @@ open FSharp.Collections.Immutable /// name in where that module has one. /// [] -module FlatList = - - // ---- Creation ---- - - /// Creates a flat list from a generator, which returns the next element and the next state. - /// The function that returns the next element and state, or None to stop. - /// The initial state. - /// The generated flat list. - /// O(n). - [] - val unfold : generator : ('State -> ('T * 'State) option) -> state : 'State -> FlatList<'T> - - // ---- Access ---- - - /// Returns the only element of the flat list, or None when it does not have exactly one element. - /// The input flat list. - /// The only element, or None. - /// O(1). - [] - val tryExactlyOne : array : FlatList<'T> -> 'T option - - /// Returns the first element of the flat list, or None when it is empty. - /// The input flat list. - /// The first element, or None. - /// O(1). - [] - val tryHead : array : FlatList<'T> -> 'T option - - /// Returns the element at an index, or None when the index is out of range. - /// The index of the element. - /// The input flat list. - /// The element at the index, or None. - /// O(1). - [] - val tryItem : index : int -> array : FlatList<'T> -> 'T option - - /// Returns the last element of the flat list, or None when it is empty. - /// The input flat list. - /// The last element, or None. - /// O(1). - [] - val tryLast : array : FlatList<'T> -> 'T option - - /// Returns the flat list without its first element, or None when it is empty. - /// The input flat list. - /// A copy of the elements after the first one, or None. - /// O(n). - [] - val tryTail : array : FlatList<'T> -> FlatList<'T> option - - // ---- Search ---- - - /// Returns the first value that the chooser returns for the elements. - /// The function that returns a value, or None to go on. - /// The input flat list. - /// The first chosen value. - /// Thrown when the chooser returns None for every element. - /// O(n). - [] - val pick : chooser : ('T -> 'U option) -> array : FlatList<'T> -> 'U - - /// Returns the first element that satisfies the predicate, or None. - /// The function to test the elements. - /// The input flat list. - /// The first element that satisfies the predicate, or None. - /// O(n). - [] - val tryFind : predicate : ('T -> bool) -> array : FlatList<'T> -> 'T option - - /// Returns the last element that satisfies the predicate, or None. - /// The function to test the elements. - /// The input flat list. - /// The last element that satisfies the predicate, or None. - /// O(n). - [] - val tryFindBack : predicate : ('T -> bool) -> array : FlatList<'T> -> 'T option - - /// Returns the index of the first element that satisfies the predicate, or None. - /// The function to test the elements. - /// The input flat list. - /// The index of the first element that satisfies the predicate, or None. - /// O(n). - [] - val tryFindIndex : predicate : ('T -> bool) -> array : FlatList<'T> -> int option - - /// Returns the index of the last element that satisfies the predicate, or None. - /// The function to test the elements. - /// The input flat list. - /// The index of the last element that satisfies the predicate, or None. - /// O(n). - [] - val tryFindIndexBack : predicate : ('T -> bool) -> array : FlatList<'T> -> int option - - /// Returns the first value that the chooser returns for the elements, or None. - /// The function that returns a value, or None to go on. - /// The input flat list. - /// The first chosen value, or None. - /// O(n). - [] - val tryPick : chooser : ('T -> 'U option) -> array : FlatList<'T> -> 'U option - - // ---- Transformation ---- - - /// Returns the values that the chooser returns for the elements. - /// The function that returns a value, or None to skip the element. - /// The input flat list. - /// The chosen values. - /// O(n). - [] - val choose : chooser : ('T -> 'U option) -> array : FlatList<'T> -> FlatList<'U> +module FSharp.Collections.Immutable.Option.FlatList + +open FSharp.Collections.Immutable + +// ---- Creation ---- + +/// Creates a flat list from a generator, which returns the next element and the next state. +/// The function that returns the next element and state, or None to stop. +/// The initial state. +/// The generated flat list. +/// O(n). +[] +val unfold : generator : ('State -> ('T * 'State) option) -> state : 'State -> FlatList<'T> + +// ---- Access ---- + +/// Returns the only element of the flat list, or None when it does not have exactly one element. +/// The input flat list. +/// The only element, or None. +/// O(1). +[] +val tryExactlyOne : array : FlatList<'T> -> 'T option + +/// Returns the first element of the flat list, or None when it is empty. +/// The input flat list. +/// The first element, or None. +/// O(1). +[] +val tryHead : array : FlatList<'T> -> 'T option + +/// Returns the element at an index, or None when the index is out of range. +/// The index of the element. +/// The input flat list. +/// The element at the index, or None. +/// O(1). +[] +val tryItem : index : int -> array : FlatList<'T> -> 'T option + +/// Returns the last element of the flat list, or None when it is empty. +/// The input flat list. +/// The last element, or None. +/// O(1). +[] +val tryLast : array : FlatList<'T> -> 'T option + +/// Returns the flat list without its first element, or None when it is empty. +/// The input flat list. +/// A copy of the elements after the first one, or None. +/// O(n). +[] +val tryTail : array : FlatList<'T> -> FlatList<'T> option + +// ---- Search ---- + +/// Returns the first value that the chooser returns for the elements. +/// The function that returns a value, or None to go on. +/// The input flat list. +/// The first chosen value. +/// Thrown when the chooser returns None for every element. +/// O(n). +[] +val pick : chooser : ('T -> 'U option) -> array : FlatList<'T> -> 'U + +/// Returns the first element that satisfies the predicate, or None. +/// The function to test the elements. +/// The input flat list. +/// The first element that satisfies the predicate, or None. +/// O(n). +[] +val tryFind : predicate : ('T -> bool) -> array : FlatList<'T> -> 'T option + +/// Returns the last element that satisfies the predicate, or None. +/// The function to test the elements. +/// The input flat list. +/// The last element that satisfies the predicate, or None. +/// O(n). +[] +val tryFindBack : predicate : ('T -> bool) -> array : FlatList<'T> -> 'T option + +/// Returns the index of the first element that satisfies the predicate, or None. +/// The function to test the elements. +/// The input flat list. +/// The index of the first element that satisfies the predicate, or None. +/// O(n). +[] +val tryFindIndex : predicate : ('T -> bool) -> array : FlatList<'T> -> int option + +/// Returns the index of the last element that satisfies the predicate, or None. +/// The function to test the elements. +/// The input flat list. +/// The index of the last element that satisfies the predicate, or None. +/// O(n). +[] +val tryFindIndexBack : predicate : ('T -> bool) -> array : FlatList<'T> -> int option + +/// Returns the first value that the chooser returns for the elements, or None. +/// The function that returns a value, or None to go on. +/// The input flat list. +/// The first chosen value, or None. +/// O(n). +[] +val tryPick : chooser : ('T -> 'U option) -> array : FlatList<'T> -> 'U option + +// ---- Transformation ---- + +/// Returns the values that the chooser returns for the elements. +/// The function that returns a value, or None to skip the element. +/// The input flat list. +/// The chosen values. +/// O(n). +[] +val choose : chooser : ('T -> 'U option) -> array : FlatList<'T> -> FlatList<'U> diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fs b/src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fs index e285c4ef..7425d044 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fs +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fs @@ -1,113 +1,112 @@ -namespace FSharp.Collections.Immutable.Tuple +[] +module FSharp.Collections.Immutable.Tuple.FlatList open FSharp.Collections.Immutable // The functions that build a tuple per element share their algorithm with the default module and only pass another // tuple constructor; the others delegate to the default module and repack the tuples, which costs no second pass. -[] -module FlatList = - - // ---- Creation ---- - - [] - let unfold (generator : 'State -> ('T * 'State) voption) (state : 'State) = - let generateStruct state = - match generator state with - | ValueSome (item, next) -> ValueSome (struct (item, next)) - | ValueNone -> ValueNone - - FSharp.Collections.Immutable.FlatList.unfold generateStruct state - - // ---- Transformation ---- - - [] - let allPairs (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = - Arrays.allPairs referencePair (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) - |> wrapArray - - [] - let indexed (array : FlatList<'T>) = - Arrays.indexed referencePair (unwrapArray (nameof array) array) - |> wrapArray - - [] - let pairwise (array : FlatList<'T>) = - Arrays.pairwise referencePair (unwrapArray (nameof array) array) - |> wrapArray - - [] - let unzip (array : FlatList<'T1 * 'T2>) = - let struct (first, second) = - Arrays.unzip (fun (item1, item2) -> struct (item1, item2)) (unwrapArray (nameof array) array) - - wrapArray first, wrapArray second - - [] - let unzip3 (array : FlatList<'T1 * 'T2 * 'T3>) = - let struct (first, second, third) = - Arrays.unzip3 (fun (item1, item2, item3) -> struct (item1, item2, item3)) (unwrapArray (nameof array) array) - - wrapArray first, wrapArray second, wrapArray third - - [] - let zip (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = - let source1 = unwrapArray (nameof array1) array1 - let source2 = unwrapArray (nameof array2) array2 - ArrayChecks.sameLengths (nameof array1) source1 (nameof array2) source2 - Arrays.zip referencePair source1 source2 |> wrapArray - - [] - let zip3 (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) (array3 : FlatList<'T3>) = - let source1 = unwrapArray (nameof array1) array1 - let source2 = unwrapArray (nameof array2) array2 - let source3 = unwrapArray (nameof array3) array3 - ArrayChecks.sameLengths3 (nameof array1) source1 (nameof array2) source2 (nameof array3) source3 - Arrays.zip3 referenceTriple source1 source2 source3 - |> wrapArray - - // ---- Folding ---- - - [] - let mapFold (mapping : 'State -> 'T -> 'Result * 'State) (state : 'State) (array : FlatList<'T>) = - let mapStruct state item = - let result, next = mapping state item - struct (result, next) - - let struct (results, final) = FSharp.Collections.Immutable.FlatList.mapFold mapStruct state array - results, final - - [] - let mapFoldBack (mapping : 'T -> 'State -> 'Result * 'State) (array : FlatList<'T>) (state : 'State) = - let mapStruct item state = - let result, next = mapping item state - struct (result, next) - - let struct (results, final) = FSharp.Collections.Immutable.FlatList.mapFoldBack mapStruct array state - results, final - - // ---- Partitioning and grouping ---- - - [] - let countBy (projection : 'T -> 'Key) (array : FlatList<'T>) = - Arrays.countBy referencePair projection (unwrapArray (nameof array) array) - |> wrapArray - - [] - let groupBy (projection : 'T -> 'Key) (array : FlatList<'T>) = - Arrays.groupBy (fun key items -> key, wrapArray items) projection (unwrapArray (nameof array) array) - |> wrapArray - - [] - let partition predicate (array : FlatList<'T>) = - let struct (accepted, rejected) = FSharp.Collections.Immutable.FlatList.partition predicate array - accepted, rejected - - [] - let partitionWith (partitioner : 'T -> Choice<'T1, 'T2>) (array : FlatList<'T>) = - let struct (first, second) = FSharp.Collections.Immutable.FlatList.partitionWith partitioner array - first, second - - [] - let splitAt index (array : FlatList<'T>) = - let struct (before, after) = FSharp.Collections.Immutable.FlatList.splitAt index array - before, after + +// ---- Creation ---- + +[] +let unfold (generator : 'State -> ('T * 'State) voption) (state : 'State) = + let generateStruct state = + match generator state with + | ValueSome (item, next) -> ValueSome (struct (item, next)) + | ValueNone -> ValueNone + + FSharp.Collections.Immutable.FlatList.unfold generateStruct state + +// ---- Transformation ---- + +[] +let allPairs (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = + Arrays.allPairs referencePair (unwrapArray (nameof array1) array1) (unwrapArray (nameof array2) array2) + |> wrapArray + +[] +let indexed (array : FlatList<'T>) = + Arrays.indexed referencePair (unwrapArray (nameof array) array) + |> wrapArray + +[] +let pairwise (array : FlatList<'T>) = + Arrays.pairwise referencePair (unwrapArray (nameof array) array) + |> wrapArray + +[] +let unzip (array : FlatList<'T1 * 'T2>) = + let struct (first, second) = + Arrays.unzip (fun (item1, item2) -> struct (item1, item2)) (unwrapArray (nameof array) array) + + wrapArray first, wrapArray second + +[] +let unzip3 (array : FlatList<'T1 * 'T2 * 'T3>) = + let struct (first, second, third) = + Arrays.unzip3 (fun (item1, item2, item3) -> struct (item1, item2, item3)) (unwrapArray (nameof array) array) + + wrapArray first, wrapArray second, wrapArray third + +[] +let zip (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) = + let source1 = unwrapArray (nameof array1) array1 + let source2 = unwrapArray (nameof array2) array2 + ArrayChecks.sameLengths (nameof array1) source1 (nameof array2) source2 + Arrays.zip referencePair source1 source2 |> wrapArray + +[] +let zip3 (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) (array3 : FlatList<'T3>) = + let source1 = unwrapArray (nameof array1) array1 + let source2 = unwrapArray (nameof array2) array2 + let source3 = unwrapArray (nameof array3) array3 + ArrayChecks.sameLengths3 (nameof array1) source1 (nameof array2) source2 (nameof array3) source3 + Arrays.zip3 referenceTriple source1 source2 source3 + |> wrapArray + +// ---- Folding ---- + +[] +let mapFold (mapping : 'State -> 'T -> 'Result * 'State) (state : 'State) (array : FlatList<'T>) = + let mapStruct state item = + let result, next = mapping state item + struct (result, next) + + let struct (results, final) = FSharp.Collections.Immutable.FlatList.mapFold mapStruct state array + results, final + +[] +let mapFoldBack (mapping : 'T -> 'State -> 'Result * 'State) (array : FlatList<'T>) (state : 'State) = + let mapStruct item state = + let result, next = mapping item state + struct (result, next) + + let struct (results, final) = FSharp.Collections.Immutable.FlatList.mapFoldBack mapStruct array state + results, final + +// ---- Partitioning and grouping ---- + +[] +let countBy (projection : 'T -> 'Key) (array : FlatList<'T>) = + Arrays.countBy referencePair projection (unwrapArray (nameof array) array) + |> wrapArray + +[] +let groupBy (projection : 'T -> 'Key) (array : FlatList<'T>) = + Arrays.groupBy (fun key items -> key, wrapArray items) projection (unwrapArray (nameof array) array) + |> wrapArray + +[] +let partition predicate (array : FlatList<'T>) = + let struct (accepted, rejected) = FSharp.Collections.Immutable.FlatList.partition predicate array + accepted, rejected + +[] +let partitionWith (partitioner : 'T -> Choice<'T1, 'T2>) (array : FlatList<'T>) = + let struct (first, second) = FSharp.Collections.Immutable.FlatList.partitionWith partitioner array + first, second + +[] +let splitAt index (array : FlatList<'T>) = + let struct (before, after) = FSharp.Collections.Immutable.FlatList.splitAt index array + before, after diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fsi b/src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fsi index 307127c7..cd04092a 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fsi +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fsi @@ -1,7 +1,3 @@ -namespace FSharp.Collections.Immutable.Tuple - -open FSharp.Collections.Immutable - /// /// The functions of that take or return tuples, with /// reference tuples instead of struct tuples. @@ -14,137 +10,138 @@ open FSharp.Collections.Immutable /// . /// [] -module FlatList = - - // ---- Creation ---- - - /// Creates a flat list from a generator, which returns the next element and the next state. - /// The function that returns the next element and state, or ValueNone to stop. - /// The initial state. - /// The generated flat list. - /// O(n). - [] - val unfold : generator : ('State -> ('T * 'State) voption) -> state : 'State -> FlatList<'T> - - // ---- Transformation ---- - - /// Returns every element of the first flat list paired with every element of the second one. - /// The first flat list. - /// The second flat list. - /// The pairs, in the order of the first flat list and then of the second one. - /// O(n × m). - [] - val allPairs : array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> FlatList<'T1 * 'T2> - - /// Returns every element with its index. - /// The input flat list. - /// The pairs of index and element. - /// O(n). - [] - val indexed : array : FlatList<'T> -> FlatList - - /// Returns every element with its successor. - /// The input flat list. - /// The pairs of neighbours; empty for fewer than two elements. - /// O(n). - [] - val pairwise : array : FlatList<'T> -> FlatList<'T * 'T> - - /// Splits a flat list of pairs into two flat lists. - /// The input flat list. - /// The first components and the second components. - /// O(n). - [] - val unzip : array : FlatList<'T1 * 'T2> -> FlatList<'T1> * FlatList<'T2> - - /// Splits a flat list of triples into three flat lists. - /// The input flat list. - /// The first, the second and the third components. - /// O(n). - [] - val unzip3 : array : FlatList<'T1 * 'T2 * 'T3> -> FlatList<'T1> * FlatList<'T2> * FlatList<'T3> - - /// Combines two flat lists into a flat list of pairs. - /// The first flat list. - /// The second flat list. - /// The pairs of elements at the same index. - /// Thrown when the flat lists have different lengths. - /// O(n). - [] - val zip : array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> FlatList<'T1 * 'T2> - - /// Combines three flat lists into a flat list of triples. - /// The first flat list. - /// The second flat list. - /// The third flat list. - /// The triples of elements at the same index. - /// Thrown when the flat lists have different lengths. - /// O(n). - [] - val zip3 : array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> array3 : FlatList<'T3> -> FlatList<'T1 * 'T2 * 'T3> - - // ---- Folding ---- - - /// Maps each element and threads a state through the mapping, from the first element to the last one. - /// The function that returns the result for an element and the next state. - /// The initial state. - /// The input flat list. - /// The results and the final state. - /// O(n). - [] - val mapFold : - mapping : ('State -> 'T -> 'Result * 'State) -> state : 'State -> array : FlatList<'T> -> FlatList<'Result> * 'State - - /// Maps each element and threads a state through the mapping, from the last element to the first one. - /// The function that returns the result for an element and the next state. - /// The input flat list. - /// The initial state. - /// The results, in the order of the elements, and the final state. - /// O(n). - [] - val mapFoldBack : - mapping : ('T -> 'State -> 'Result * 'State) -> array : FlatList<'T> -> state : 'State -> FlatList<'Result> * 'State - - // ---- Partitioning and grouping ---- - - /// Counts the elements of each key of a projection. - /// The function that returns the key of an element. - /// The input flat list. - /// The keys, in the order of their first occurrence, with the number of their elements. - /// O(n). The keys compare in F# structural equality. - [] - val countBy : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList<'Key * int> when 'Key : equality - - /// Groups the elements by the key of a projection. - /// The function that returns the key of an element. - /// The input flat list. - /// The keys, in the order of their first occurrence, with their elements in their order. - /// O(n). The keys compare in F# structural equality. - [] - val groupBy : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList<'Key * FlatList<'T>> when 'Key : equality - - /// Splits the elements by a predicate. - /// The function to test the elements. - /// The input flat list. - /// The elements that satisfy the predicate and the other ones, both in their order. - /// O(n). - [] - val partition : predicate : ('T -> bool) -> array : FlatList<'T> -> FlatList<'T> * FlatList<'T> - - /// Splits the elements by the choice that a function returns for them. - /// The function that returns Choice1Of2 or Choice2Of2 for an element. - /// The input flat list. - /// The values of the first choices and of the second choices, both in the order of the elements. - /// O(n). - [] - val partitionWith : partitioner : ('T -> Choice<'T1, 'T2>) -> array : FlatList<'T> -> FlatList<'T1> * FlatList<'T2> - - /// Splits the flat list at an index. - /// The index of the first element of the second part. - /// The input flat list. - /// The elements before the index and the elements from the index. - /// Thrown when the index is negative. - /// Thrown when the index exceeds the length. - /// O(n). The parts are copies, unless they are empty or the whole flat list. - [] - val splitAt : index : int -> array : FlatList<'T> -> FlatList<'T> * FlatList<'T> +module FSharp.Collections.Immutable.Tuple.FlatList + +open FSharp.Collections.Immutable + +// ---- Creation ---- + +/// Creates a flat list from a generator, which returns the next element and the next state. +/// The function that returns the next element and state, or ValueNone to stop. +/// The initial state. +/// The generated flat list. +/// O(n). +[] +val unfold : generator : ('State -> ('T * 'State) voption) -> state : 'State -> FlatList<'T> + +// ---- Transformation ---- + +/// Returns every element of the first flat list paired with every element of the second one. +/// The first flat list. +/// The second flat list. +/// The pairs, in the order of the first flat list and then of the second one. +/// O(n × m). +[] +val allPairs : array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> FlatList<'T1 * 'T2> + +/// Returns every element with its index. +/// The input flat list. +/// The pairs of index and element. +/// O(n). +[] +val indexed : array : FlatList<'T> -> FlatList + +/// Returns every element with its successor. +/// The input flat list. +/// The pairs of neighbours; empty for fewer than two elements. +/// O(n). +[] +val pairwise : array : FlatList<'T> -> FlatList<'T * 'T> + +/// Splits a flat list of pairs into two flat lists. +/// The input flat list. +/// The first components and the second components. +/// O(n). +[] +val unzip : array : FlatList<'T1 * 'T2> -> FlatList<'T1> * FlatList<'T2> + +/// Splits a flat list of triples into three flat lists. +/// The input flat list. +/// The first, the second and the third components. +/// O(n). +[] +val unzip3 : array : FlatList<'T1 * 'T2 * 'T3> -> FlatList<'T1> * FlatList<'T2> * FlatList<'T3> + +/// Combines two flat lists into a flat list of pairs. +/// The first flat list. +/// The second flat list. +/// The pairs of elements at the same index. +/// Thrown when the flat lists have different lengths. +/// O(n). +[] +val zip : array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> FlatList<'T1 * 'T2> + +/// Combines three flat lists into a flat list of triples. +/// The first flat list. +/// The second flat list. +/// The third flat list. +/// The triples of elements at the same index. +/// Thrown when the flat lists have different lengths. +/// O(n). +[] +val zip3 : array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> array3 : FlatList<'T3> -> FlatList<'T1 * 'T2 * 'T3> + +// ---- Folding ---- + +/// Maps each element and threads a state through the mapping, from the first element to the last one. +/// The function that returns the result for an element and the next state. +/// The initial state. +/// The input flat list. +/// The results and the final state. +/// O(n). +[] +val mapFold : mapping : ('State -> 'T -> 'Result * 'State) -> state : 'State -> array : FlatList<'T> -> FlatList<'Result> * 'State + +/// Maps each element and threads a state through the mapping, from the last element to the first one. +/// The function that returns the result for an element and the next state. +/// The input flat list. +/// The initial state. +/// The results, in the order of the elements, and the final state. +/// O(n). +[] +val mapFoldBack : + mapping : ('T -> 'State -> 'Result * 'State) -> array : FlatList<'T> -> state : 'State -> FlatList<'Result> * 'State + +// ---- Partitioning and grouping ---- + +/// Counts the elements of each key of a projection. +/// The function that returns the key of an element. +/// The input flat list. +/// The keys, in the order of their first occurrence, with the number of their elements. +/// O(n). The keys compare in F# structural equality. +[] +val countBy : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList<'Key * int> when 'Key : equality + +/// Groups the elements by the key of a projection. +/// The function that returns the key of an element. +/// The input flat list. +/// The keys, in the order of their first occurrence, with their elements in their order. +/// O(n). The keys compare in F# structural equality. +[] +val groupBy : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList<'Key * FlatList<'T>> when 'Key : equality + +/// Splits the elements by a predicate. +/// The function to test the elements. +/// The input flat list. +/// The elements that satisfy the predicate and the other ones, both in their order. +/// O(n). +[] +val partition : predicate : ('T -> bool) -> array : FlatList<'T> -> FlatList<'T> * FlatList<'T> + +/// Splits the elements by the choice that a function returns for them. +/// The function that returns Choice1Of2 or Choice2Of2 for an element. +/// The input flat list. +/// The values of the first choices and of the second choices, both in the order of the elements. +/// O(n). +[] +val partitionWith : partitioner : ('T -> Choice<'T1, 'T2>) -> array : FlatList<'T> -> FlatList<'T1> * FlatList<'T2> + +/// Splits the flat list at an index. +/// The index of the first element of the second part. +/// The input flat list. +/// The elements before the index and the elements from the index. +/// Thrown when the index is negative. +/// Thrown when the index exceeds the length. +/// O(n). The parts are copies, unless they are empty or the whole flat list. +[] +val splitAt : index : int -> array : FlatList<'T> -> FlatList<'T> * FlatList<'T> diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListTypes.fs b/src/FSharp.Collections.Immutable/FlatList/FlatListTypes.fs new file mode 100644 index 00000000..3315cc3f --- /dev/null +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListTypes.fs @@ -0,0 +1,10 @@ +namespace FSharp.Collections.Immutable + +open System.Collections.Immutable + +// The abbreviation has its own file because the module of the folder is a root module, which cannot declare a type of +// the namespace; a file of abbreviations needs no signature file (ADR 0001, D2). + +/// An immutable array, . +/// The name comes from a similar construct in the sources of the F# compiler. +type FlatList<'T> = ImmutableArray<'T> diff --git a/src/FSharp.Collections.Immutable/Helper.fs b/src/FSharp.Collections.Immutable/Helper.fs index 37be54aa..cf658ced 100644 --- a/src/FSharp.Collections.Immutable/Helper.fs +++ b/src/FSharp.Collections.Immutable/Helper.fs @@ -59,6 +59,34 @@ module internal ImmutableCollectionUtil = /// The constructor of a reference triple, for the algorithms shared by the default and the `Tuple` modules. let inline referenceTriple (first : 'T1) (second : 'T2) (third : 'T3) = (first, second, third) +/// The default comparers of the collections (ADR 0001, D5). +[] +module internal Comparers = + + /// + /// The comparer of F# generic comparison for a type: the comparer of the sorts, of binarySearch and of the + /// sorted collections that are created without a comparer. + /// + /// + /// + /// The class resolves once per type, in + /// generic code, where FSharp.Core returns the comparer that it keeps for the type. + /// + /// + /// A call of FastGenericComparer at a concrete type that is not primitive creates a new comparer each time, and + /// the F# optimizer creates such calls when it inlines a small function of the library into its caller. Collections of + /// one type would then have different comparers, and the BCL could not take its shortcuts for two collections with the + /// same comparer, such as the one of ImmutableSortedSet.Union. A static field cannot be inlined. + /// + /// + [] + type DefaultComparer<'T when 'T : comparison> private () = + + static let comparer = LanguagePrimitives.FastGenericComparer<'T> + + /// The comparer of F# generic comparison for the type. + static member Instance : IComparer<'T> = comparer + /// The exception messages of FSharp.Core (FSCore.resx), in English. /// /// The modules throw what the FSharp.Core function that they mirror throws (ADR 0001, D6). FSharp.Core keeps its resources From 0bab4fe287c89c189cab7310ff8adee91220b8da Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 15:27:00 +0200 Subject: [PATCH 07/27] fixup! test: shared contracts of the sequence-like modules, run for `FlatList` Test methods carry no categories: the classes carry them (ADR 0001, D8). `FlatListOps` is the root module of its file. Co-Authored-By: Claude Opus 5.5 --- .../Contracts/IndexedContract.fs | 36 +- .../Contracts/SeqCreationContract.fs | 28 +- .../SeqLike/SeqLikeAccessContract.fs | 28 +- .../Contracts/SeqLike/SeqLikeContract.fs | 6 +- .../SeqLike/SeqLikeFoldingContract.fs | 70 ++-- .../SeqLike/SeqLikePartitioningContract.fs | 46 +-- .../SeqLike/SeqLikeRandomContract.fs | 34 +- .../SeqLike/SeqLikeSearchContract.fs | 44 +- .../SeqLike/SeqLikeSortingContract.fs | 12 +- .../SeqLike/SeqLikeTransformationContract.fs | 64 +-- .../FlatList/FlatListBuilderTests.fs | 40 +- .../FlatListComputationExpressionTests.fs | 21 +- .../FlatList/FlatListOps.fs | 378 +++++++++--------- .../FlatList/FlatListOptionTests.fs | 28 +- .../FlatList/FlatListTests.fs | 34 +- .../FlatList/FlatListTupleTests.fs | 34 +- 16 files changed, 450 insertions(+), 453 deletions(-) diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/IndexedContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/IndexedContract.fs index 42aea500..81266540 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/IndexedContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/IndexedContract.fs @@ -24,14 +24,14 @@ type IndexedContract<'C> (ops : IndexedOps<'C>) = let collection = ops.OfSeq Inputs.several struct (collection, ops.ToList collection) - [] + [] member _.``item returns the element at the index`` () = let struct (collection, elements) = several () for index in 0 .. elements.Length - 1 do Assert.AreEqual(List.item index elements, ops.Item index collection, $"%s{ops.Name}.item %d{index}") - [] + [] member this.``item throws for an index out of range as FSharp.Core does`` () = let struct (collection, elements) = several () @@ -43,7 +43,7 @@ type IndexedContract<'C> (ops : IndexedOps<'C>) = $"item %d{index} must throw" ) - [] + [] member _.``tryItem returns the element at the index or nothing`` () = let struct (collection, elements) = several () @@ -54,7 +54,7 @@ type IndexedContract<'C> (ops : IndexedOps<'C>) = $"%s{ops.Name}.tryItem %d{index}" ) - [] + [] member _.``indexOf returns the index of the first equal element or -1`` () = let struct (collection, elements) = several () @@ -66,7 +66,7 @@ type IndexedContract<'C> (ops : IndexedOps<'C>) = $"%s{ops.Name}.indexOf %d{value}" ) - [] + [] member _.``indexOfWith compares with the equality comparer`` () = let struct (collection, elements) = several () @@ -78,7 +78,7 @@ type IndexedContract<'C> (ops : IndexedOps<'C>) = $"%s{ops.Name}.indexOfWith %d{value}" ) - [] + [] member _.``lastIndexOf returns the index of the last equal element or -1`` () = let struct (collection, elements) = several () @@ -92,7 +92,7 @@ type IndexedContract<'C> (ops : IndexedOps<'C>) = Assert.AreEqual(-1, ops.LastIndexOf 5 (ops.OfSeq Inputs.empty), $"%s{ops.Name}.lastIndexOf in an empty collection") - [] + [] member _.``lastIndexOfWith compares with the equality comparer`` () = let struct (collection, elements) = several () @@ -110,7 +110,7 @@ type IndexedContract<'C> (ops : IndexedOps<'C>) = $"%s{ops.Name}.lastIndexOfWith in an empty collection" ) - [] + [] member _.``add appends the value`` () = for input in Inputs.all do let collection = ops.OfSeq input @@ -121,7 +121,7 @@ type IndexedContract<'C> (ops : IndexedOps<'C>) = $"%s{ops.Name}.add to %A{input}" ) - [] + [] member _.``insertAt inserts the value at the index`` () = let struct (collection, elements) = several () @@ -132,7 +132,7 @@ type IndexedContract<'C> (ops : IndexedOps<'C>) = $"%s{ops.Name}.insertAt %d{index}" ) - [] + [] member this.``insertAt throws for an index out of range as FSharp.Core does`` () = let struct (collection, elements) = several () @@ -144,7 +144,7 @@ type IndexedContract<'C> (ops : IndexedOps<'C>) = $"insertAt %d{index} must throw" ) - [] + [] member _.``insertManyAt inserts the values at the index`` () = let struct (collection, elements) = several () @@ -156,7 +156,7 @@ type IndexedContract<'C> (ops : IndexedOps<'C>) = $"%s{ops.Name}.insertManyAt %d{index} %A{values}" ) - [] + [] member this.``insertManyAt throws for an index out of range as FSharp.Core does`` () = let struct (collection, elements) = several () @@ -168,7 +168,7 @@ type IndexedContract<'C> (ops : IndexedOps<'C>) = $"insertManyAt %d{index} must throw" ) - [] + [] member _.``removeAt removes the element at the index`` () = let struct (collection, elements) = several () @@ -179,7 +179,7 @@ type IndexedContract<'C> (ops : IndexedOps<'C>) = $"%s{ops.Name}.removeAt %d{index}" ) - [] + [] member this.``removeAt throws for an index out of range as FSharp.Core does`` () = let struct (collection, elements) = several () @@ -191,7 +191,7 @@ type IndexedContract<'C> (ops : IndexedOps<'C>) = $"removeAt %d{index} must throw" ) - [] + [] member _.``removeManyAt removes the elements of the range`` () = let struct (collection, elements) = several () @@ -202,7 +202,7 @@ type IndexedContract<'C> (ops : IndexedOps<'C>) = $"%s{ops.Name}.removeManyAt %d{index} %d{count}" ) - [] + [] member this.``removeManyAt throws for a range out of range as FSharp.Core does`` () = let struct (collection, elements) = several () @@ -214,7 +214,7 @@ type IndexedContract<'C> (ops : IndexedOps<'C>) = $"removeManyAt %d{index} %d{count} must throw" ) - [] + [] member _.``updateAt replaces the element at the index`` () = let struct (collection, elements) = several () @@ -225,7 +225,7 @@ type IndexedContract<'C> (ops : IndexedOps<'C>) = $"%s{ops.Name}.updateAt %d{index}" ) - [] + [] member this.``updateAt throws for an index out of range as FSharp.Core does`` () = let struct (collection, elements) = several () diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqCreationContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqCreationContract.fs index a634a381..07173168 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqCreationContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqCreationContract.fs @@ -11,11 +11,11 @@ open Microsoft.VisualStudio.TestTools.UnitTesting type SeqCreationContract<'C> (ops : SeqCreationOps<'C>) = inherit ContractBase (ops.Name, ops.Mirror, ops.ParamName) - [] + [] member _.``empty has no elements`` () = Assert.IsEmpty (ops.ToList (ops.Empty ()), $"%s{ops.Name}.empty must have no elements") - [] + [] member _.``init creates the elements in index order`` () = for count in [ 0; 1; 5 ] do Assert.AreEqual( @@ -24,7 +24,7 @@ type SeqCreationContract<'C> (ops : SeqCreationOps<'C>) = $"%s{ops.Name}.init %d{count}" ) - [] + [] member this.``init throws for a negative count as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.init -1 id), @@ -33,7 +33,7 @@ type SeqCreationContract<'C> (ops : SeqCreationOps<'C>) = "init -1 must throw" ) - [] + [] member _.``ofArray copies the elements in order`` () = for input in Inputs.all do let source = List.toArray input @@ -44,7 +44,7 @@ type SeqCreationContract<'C> (ops : SeqCreationOps<'C>) = Assert.AreEqual(input, ops.ToList collection, $"%s{ops.Name}.ofArray must copy %A{input}") - [] + [] member this.``ofArray throws for a null array as FSharp.Core does`` () = let source = Unchecked.defaultof @@ -55,12 +55,12 @@ type SeqCreationContract<'C> (ops : SeqCreationOps<'C>) = "ofArray of a null array must throw" ) - [] + [] member _.``ofList keeps the elements in order`` () = for input in Inputs.all do Assert.AreEqual(input, ops.ToList (ops.OfList input), $"%s{ops.Name}.ofList of %A{input}") - [] + [] member _.``ofReadOnlySpan copies the elements in order`` () = for input in Inputs.all do Assert.AreEqual( @@ -69,7 +69,7 @@ type SeqCreationContract<'C> (ops : SeqCreationOps<'C>) = $"%s{ops.Name}.ofReadOnlySpan of %A{input}" ) - [] + [] member _.``ofSeq keeps the elements in order`` () = for input in Inputs.all do Assert.AreEqual(input, ops.ToList (ops.OfSeq input), $"%s{ops.Name}.ofSeq of the list %A{input}") @@ -86,7 +86,7 @@ type SeqCreationContract<'C> (ops : SeqCreationOps<'C>) = $"%s{ops.Name}.ofSeq of a lazy sequence of %A{input}" ) - [] + [] member this.``ofSeq throws for a null sequence as FSharp.Core does`` () = let source = Unchecked.defaultof> @@ -97,12 +97,12 @@ type SeqCreationContract<'C> (ops : SeqCreationOps<'C>) = "ofSeq of a null sequence must throw" ) - [] + [] member _.``ofSpan copies the elements in order`` () = for input in Inputs.all do Assert.AreEqual(input, ops.ToList (ops.OfSpan (List.toArray input)), $"%s{ops.Name}.ofSpan of %A{input}") - [] + [] member _.``replicate repeats the value`` () = for count in [ 0; 1; 4 ] do Assert.AreEqual( @@ -111,7 +111,7 @@ type SeqCreationContract<'C> (ops : SeqCreationOps<'C>) = $"%s{ops.Name}.replicate %d{count}" ) - [] + [] member this.``replicate throws for a negative count as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.replicate -1 7), @@ -120,11 +120,11 @@ type SeqCreationContract<'C> (ops : SeqCreationOps<'C>) = "replicate -1 must throw" ) - [] + [] member _.``singleton has one element`` () = Assert.AreEqual([ 42 ], ops.ToList (ops.Singleton 42), $"%s{ops.Name}.singleton must have one element") - [] + [] member _.``unfold generates the elements until the generator stops`` () = let generator (state : int) = if state > 0 then diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeAccessContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeAccessContract.fs index f7d25c42..bf559a75 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeAccessContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeAccessContract.fs @@ -25,7 +25,7 @@ type SeqLikeAccessContract<'C> (ops : SeqOps<'C>) = // ---- Properties ---- - [] + [] member this.``isEmpty is true only for a collection without elements`` () = this.ForEach ( Inputs.all, @@ -33,7 +33,7 @@ type SeqLikeAccessContract<'C> (ops : SeqOps<'C>) = Assert.AreEqual(List.isEmpty elements, ops.IsEmpty collection, $"%s{ops.Name}.isEmpty of %A{elements}") ) - [] + [] member this.``length counts the elements`` () = this.ForEach ( Inputs.all, @@ -43,7 +43,7 @@ type SeqLikeAccessContract<'C> (ops : SeqOps<'C>) = // ---- Access ---- - [] + [] member this.``exactlyOne returns the only element`` () = let collection = ops.OfSeq Inputs.singleton Assert.AreEqual( @@ -52,7 +52,7 @@ type SeqLikeAccessContract<'C> (ops : SeqOps<'C>) = $"%s{ops.Name}.exactlyOne of one element" ) - [] + [] member this.``exactlyOne throws for no element or several elements as FSharp.Core does`` () = for input in [ Inputs.empty; Inputs.several ] do this.ThrowsLikeFSharpCore ( @@ -62,7 +62,7 @@ type SeqLikeAccessContract<'C> (ops : SeqOps<'C>) = $"exactlyOne of %A{input} must throw" ) - [] + [] member this.``head returns the first element`` () = this.ForEach ( Inputs.nonEmpty, @@ -70,7 +70,7 @@ type SeqLikeAccessContract<'C> (ops : SeqOps<'C>) = Assert.AreEqual(List.head elements, ops.Head collection, $"%s{ops.Name}.head of %A{elements}") ) - [] + [] member this.``head throws for an empty collection as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.head Array.empty), @@ -79,7 +79,7 @@ type SeqLikeAccessContract<'C> (ops : SeqOps<'C>) = "head of an empty collection must throw" ) - [] + [] member this.``last returns the last element`` () = this.ForEach ( Inputs.nonEmpty, @@ -87,7 +87,7 @@ type SeqLikeAccessContract<'C> (ops : SeqOps<'C>) = Assert.AreEqual(List.last elements, ops.Last collection, $"%s{ops.Name}.last of %A{elements}") ) - [] + [] member this.``last throws for an empty collection as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.last Array.empty), @@ -96,7 +96,7 @@ type SeqLikeAccessContract<'C> (ops : SeqOps<'C>) = "last of an empty collection must throw" ) - [] + [] member this.``tail returns the elements after the first one`` () = this.ForEach ( Inputs.nonEmpty, @@ -108,7 +108,7 @@ type SeqLikeAccessContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``tail throws for an empty collection as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.tail Array.empty), @@ -117,7 +117,7 @@ type SeqLikeAccessContract<'C> (ops : SeqOps<'C>) = "tail of an empty collection must throw" ) - [] + [] member this.``tryExactlyOne returns the only element or nothing`` () = this.ForEach ( Inputs.all, @@ -129,7 +129,7 @@ type SeqLikeAccessContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``tryHead returns the first element or nothing`` () = this.ForEach ( Inputs.all, @@ -141,7 +141,7 @@ type SeqLikeAccessContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``tryLast returns the last element or nothing`` () = this.ForEach ( Inputs.all, @@ -153,7 +153,7 @@ type SeqLikeAccessContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``tryTail returns the elements after the first one or nothing`` () = this.ForEach ( Inputs.all, diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeContract.fs index 45fc4aa3..e4c33950 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeContract.fs @@ -14,7 +14,7 @@ open Microsoft.VisualStudio.TestTools.UnitTesting type SeqLikeContract<'C> (ops : SeqOps<'C>) = inherit SeqLikeRandomContract<'C> (ops) - [] + [] member this.``toArray returns the elements in order`` () = this.ForEach ( Inputs.all, @@ -22,7 +22,7 @@ type SeqLikeContract<'C> (ops : SeqOps<'C>) = CollectionAssert.AreEqual (List.toArray elements, ops.ToArray collection, $"%s{ops.Name}.toArray of %A{elements}") ) - [] + [] member _.``toArray returns a new array`` () = let collection = ops.OfSeq Inputs.several let array = ops.ToArray collection @@ -34,7 +34,7 @@ type SeqLikeContract<'C> (ops : SeqOps<'C>) = $"%s{ops.Name}.toArray must return a copy that does not change the collection" ) - [] + [] member this.``toSeq returns the elements in order`` () = this.ForEach ( Inputs.all, diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeFoldingContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeFoldingContract.fs index 193e8365..4d15034e 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeFoldingContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeFoldingContract.fs @@ -14,12 +14,12 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = /// A folder from the end whose state records the order in which it saw the elements. static let recordBack (item : int) (state : int list) = item :: state - [] + [] member _.``average returns the average of the elements`` () = for input in [ [ 1.5 ]; [ 1.0; 2.0; 4.5; -3.0 ] ] do Assert.AreEqual(List.average input, ops.Average input, $"%s{ops.Name}.average of %A{input}") - [] + [] member this.``average throws for an empty collection as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.average Array.empty), @@ -28,7 +28,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = "average of an empty collection must throw" ) - [] + [] member this.``averageBy returns the average of the projections`` () = this.ForEach ( Inputs.nonEmpty, @@ -40,7 +40,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``averageBy throws for an empty collection as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.averageBy float Array.empty), @@ -49,7 +49,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = "averageBy of an empty collection must throw" ) - [] + [] member _.``compareWith compares element by element and then by length`` () = for first in Inputs.all do for second in Inputs.all @ [ [ 5; -3 ]; [ 5; 0 ] ] do @@ -63,7 +63,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = $"%s{ops.Name}.compareWith of %A{first} and %A{second}" ) - [] + [] member this.``fold visits the elements from the first one`` () = this.ForEach ( Inputs.all, @@ -75,7 +75,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member _.``fold2 visits the pairs from the first index`` () = let collection = ops.OfSeq Inputs.several let other = ops.OfSeq Inputs.other @@ -87,7 +87,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = $"%s{ops.Name}.fold2 over two collections" ) - [] + [] member this.``fold2 throws for different lengths as FSharp.Core does`` () = let folder (state : int list) (_ : int) (_ : int) = state @@ -98,7 +98,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = "fold2 of collections of different lengths must throw" ) - [] + [] member this.``foldBack visits the elements from the last one`` () = this.ForEach ( Inputs.all, @@ -110,7 +110,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member _.``foldBack2 visits the pairs from the last index`` () = let collection = ops.OfSeq Inputs.several let other = ops.OfSeq Inputs.other @@ -122,7 +122,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = $"%s{ops.Name}.foldBack2 over two collections" ) - [] + [] member this.``foldBack2 throws for different lengths as FSharp.Core does`` () = let folder (_ : int) (_ : int) (state : int list) = state @@ -133,7 +133,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = "foldBack2 of collections of different lengths must throw" ) - [] + [] member this.``iter visits every element in order`` () = this.ForEach ( Inputs.all, @@ -143,7 +143,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = Assert.AreEqual(elements, List.ofSeq visited, $"%s{ops.Name}.iter over %A{elements}") ) - [] + [] member _.``iter2 visits every pair in order`` () = let collection = ops.OfSeq Inputs.several let other = ops.OfSeq Inputs.other @@ -157,7 +157,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = $"%s{ops.Name}.iter2 over two collections" ) - [] + [] member this.``iter2 throws for different lengths as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.iter2 (fun _ _ -> ()) (List.toArray Inputs.singleton) (List.toArray Inputs.several)), @@ -166,7 +166,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = "iter2 of collections of different lengths must throw" ) - [] + [] member this.``iteri visits every element with its index`` () = this.ForEach ( Inputs.all, @@ -182,7 +182,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member _.``iteri2 visits every pair with its index`` () = let collection = ops.OfSeq Inputs.several let other = ops.OfSeq Inputs.other @@ -196,7 +196,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = $"%s{ops.Name}.iteri2 over two collections" ) - [] + [] member this.``iteri2 throws for different lengths as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.iteri2 (fun _ _ _ -> ()) (List.toArray Inputs.singleton) (List.toArray Inputs.several)), @@ -205,7 +205,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = "iteri2 of collections of different lengths must throw" ) - [] + [] member this.``mapFold maps the elements and threads the state from the first one`` () = this.ForEach ( Inputs.all, @@ -217,7 +217,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = Assert.AreEqual(expectedState, state, $"%s{ops.Name}.mapFold state over %A{elements}") ) - [] + [] member this.``mapFoldBack maps the elements and threads the state from the last one`` () = this.ForEach ( Inputs.all, @@ -231,7 +231,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = Assert.AreEqual(expectedState, state, $"%s{ops.Name}.mapFoldBack state over %A{elements}") ) - [] + [] member this.``max returns the greatest element`` () = this.ForEach ( Inputs.nonEmpty, @@ -239,7 +239,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = Assert.AreEqual(List.max elements, ops.Max collection, $"%s{ops.Name}.max of %A{elements}") ) - [] + [] member this.``max throws for an empty collection as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.max Array.empty), @@ -248,7 +248,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = "max of an empty collection must throw" ) - [] + [] member this.``maxBy returns the first element with the greatest projection`` () = this.ForEach ( Inputs.nonEmpty, @@ -256,7 +256,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = Assert.AreEqual(List.maxBy abs elements, ops.MaxBy abs collection, $"%s{ops.Name}.maxBy of %A{elements}") ) - [] + [] member this.``maxBy throws for an empty collection as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.maxBy abs Array.empty), @@ -265,7 +265,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = "maxBy of an empty collection must throw" ) - [] + [] member this.``min returns the smallest element`` () = this.ForEach ( Inputs.nonEmpty, @@ -273,7 +273,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = Assert.AreEqual(List.min elements, ops.Min collection, $"%s{ops.Name}.min of %A{elements}") ) - [] + [] member this.``min throws for an empty collection as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.min Array.empty), @@ -282,7 +282,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = "min of an empty collection must throw" ) - [] + [] member this.``minBy returns the first element with the smallest projection`` () = this.ForEach ( Inputs.nonEmpty, @@ -290,7 +290,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = Assert.AreEqual(List.minBy abs elements, ops.MinBy abs collection, $"%s{ops.Name}.minBy of %A{elements}") ) - [] + [] member this.``minBy throws for an empty collection as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.minBy abs Array.empty), @@ -299,7 +299,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = "minBy of an empty collection must throw" ) - [] + [] member this.``reduce combines the elements from the first one`` () = this.ForEach ( Inputs.nonEmpty, @@ -311,7 +311,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``reduce throws for an empty collection as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.reduce (+) Array.empty), @@ -320,7 +320,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = "reduce of an empty collection must throw" ) - [] + [] member this.``reduceBack combines the elements from the last one`` () = this.ForEach ( Inputs.nonEmpty, @@ -332,7 +332,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``reduceBack throws for an empty collection as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.reduceBack (+) Array.empty), @@ -341,7 +341,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = "reduceBack of an empty collection must throw" ) - [] + [] member this.``scan returns every state from the first element`` () = this.ForEach ( Inputs.all, @@ -353,7 +353,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``scanBack returns every state from the last element`` () = this.ForEach ( Inputs.all, @@ -365,7 +365,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``sum adds the elements`` () = this.ForEach ( Inputs.all, @@ -373,7 +373,7 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = Assert.AreEqual(List.sum elements, ops.Sum collection, $"%s{ops.Name}.sum of %A{elements}") ) - [] + [] member this.``sumBy adds the projections`` () = this.ForEach ( Inputs.all, diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikePartitioningContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikePartitioningContract.fs index c0675609..33b8d6d8 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikePartitioningContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikePartitioningContract.fs @@ -11,7 +11,7 @@ type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = /// The counts to try on a collection of the given length: negative, zero, inside and the whole length. static let countsFor (length : int) = List.distinct [ -1; 0; min 3 length; length ] - [] + [] member this.``countBy counts the elements of each key in the order of the first occurrence`` () = this.ForEach ( Inputs.all, @@ -24,7 +24,7 @@ type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``distinct keeps the first occurrence of each element`` () = this.ForEach ( Inputs.all, @@ -36,7 +36,7 @@ type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``distinctBy keeps the first element of each key`` () = this.ForEach ( Inputs.all, @@ -48,7 +48,7 @@ type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``except removes the excluded items and the duplicates`` () = this.ForEach ( Inputs.all, @@ -60,7 +60,7 @@ type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``except throws for null items to exclude as FSharp.Core does`` () = let excluded = Unchecked.defaultof> @@ -71,7 +71,7 @@ type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = "except of null items must throw" ) - [] + [] member this.``groupBy groups the elements by key in the order of the first occurrence`` () = this.ForEach ( Inputs.all, @@ -84,7 +84,7 @@ type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``partition splits the elements by the predicate`` () = this.ForEach ( Inputs.all, @@ -103,7 +103,7 @@ type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``partitionWith splits the elements by the choice`` () = this.ForEach ( Inputs.all, @@ -119,7 +119,7 @@ type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = Assert.AreEqual(expectedSecond, second, $"%s{ops.Name}.partitionWith second choices of %A{elements}") ) - [] + [] member this.``skip drops the first elements`` () = this.ForEach ( Inputs.all, @@ -132,7 +132,7 @@ type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``skip throws for a count beyond the length as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.skip 9 (List.toArray Inputs.several)), @@ -141,7 +141,7 @@ type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = "skip beyond the length must throw" ) - [] + [] member this.``skipLast drops the last elements`` () = this.ForEach ( Inputs.all, @@ -154,7 +154,7 @@ type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``skipLast throws for a count beyond the length as skip does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.skip 9 (List.toArray Inputs.several)), @@ -163,7 +163,7 @@ type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = "skipLast beyond the length must throw" ) - [] + [] member this.``skipWhile drops the first elements that satisfy the predicate`` () = this.ForEach ( Inputs.all, @@ -176,7 +176,7 @@ type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``splitAt splits the elements at the index`` () = this.ForEach ( Inputs.all, @@ -198,7 +198,7 @@ type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``splitAt throws for a negative index or one beyond the length as FSharp.Core does`` () = for index in [ -1; 9 ] do this.ThrowsLikeFSharpCore ( @@ -208,7 +208,7 @@ type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = $"splitAt %d{index} must throw" ) - [] + [] member this.``splitInto splits the elements into parts of nearly equal length`` () = this.ForEach ( Inputs.all, @@ -221,7 +221,7 @@ type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``splitInto throws for a count that is not positive as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.splitInto 0 (List.toArray Inputs.several)), @@ -230,7 +230,7 @@ type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = "splitInto 0 must throw" ) - [] + [] member this.``take returns the first elements`` () = this.ForEach ( Inputs.all, @@ -243,7 +243,7 @@ type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``take throws for a negative count or one beyond the length as FSharp.Core does`` () = for count in [ -1; 9 ] do this.ThrowsLikeFSharpCore ( @@ -253,7 +253,7 @@ type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = $"take %d{count} must throw" ) - [] + [] member this.``takeLast returns the last elements`` () = this.ForEach ( Inputs.all, @@ -266,7 +266,7 @@ type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``takeLast throws for a negative count or one beyond the length as take does`` () = for count in [ -1; 9 ] do this.ThrowsLikeFSharpCore ( @@ -276,7 +276,7 @@ type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = $"takeLast %d{count} must throw" ) - [] + [] member this.``takeWhile returns the first elements that satisfy the predicate`` () = this.ForEach ( Inputs.all, @@ -289,7 +289,7 @@ type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``truncate returns at most the given number of first elements`` () = this.ForEach ( Inputs.all, diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeRandomContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeRandomContract.fs index f2a30a24..b5a26695 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeRandomContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeRandomContract.fs @@ -23,7 +23,7 @@ type SeqLikeRandomContract<'C> (ops : SeqOps<'C>) = /// The elements of a result as a sorted list, for the comparison of multisets. static let sorted (items : int list) = List.sort items - [] + [] member this.``randomChoice returns an element`` () = this.ForEach ( Inputs.nonEmpty, @@ -32,7 +32,7 @@ type SeqLikeRandomContract<'C> (ops : SeqOps<'C>) = Assert.Contains (chosen, elements, $"%s{ops.Name}.randomChoice must return an element of %A{elements}") ) - [] + [] member this.``randomChoice throws for an empty collection as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.randomChoice Array.empty), @@ -41,7 +41,7 @@ type SeqLikeRandomContract<'C> (ops : SeqOps<'C>) = "randomChoice of an empty collection must throw" ) - [] + [] member this.``randomChoiceBy chooses as FSharp.Core does with the same randomizer`` () = this.ForEach ( Inputs.nonEmpty, @@ -59,7 +59,7 @@ type SeqLikeRandomContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``randomChoiceBy throws for a randomizer outside the range as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.randomChoiceBy (fun () -> 1.0) (List.toArray Inputs.several)), @@ -68,7 +68,7 @@ type SeqLikeRandomContract<'C> (ops : SeqOps<'C>) = "randomChoiceBy with a randomizer that returns 1.0 must throw" ) - [] + [] member this.``randomChoiceWith chooses as FSharp.Core does with the same generator`` () = this.ForEach ( Inputs.nonEmpty, @@ -86,7 +86,7 @@ type SeqLikeRandomContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``randomChoiceWith throws for a null generator as FSharp.Core does`` () = let random = Unchecked.defaultof @@ -97,7 +97,7 @@ type SeqLikeRandomContract<'C> (ops : SeqOps<'C>) = "randomChoiceWith of a null generator must throw" ) - [] + [] member this.``randomChoices returns the given number of elements`` () = this.ForEach ( Inputs.nonEmpty, @@ -109,7 +109,7 @@ type SeqLikeRandomContract<'C> (ops : SeqOps<'C>) = Assert.Contains (item, elements, $"%s{ops.Name}.randomChoices must return elements of %A{elements}") ) - [] + [] member this.``randomChoices throws for a negative count or an empty collection as FSharp.Core does`` () = for struct (count, input) in [ struct (-1, Inputs.several); struct (3, Inputs.empty) ] do this.ThrowsLikeFSharpCore ( @@ -119,7 +119,7 @@ type SeqLikeRandomContract<'C> (ops : SeqOps<'C>) = $"randomChoices %d{count} of %A{input} must throw" ) - [] + [] member this.``randomChoicesBy chooses as FSharp.Core does with the same randomizer`` () = this.ForEach ( Inputs.nonEmpty, @@ -140,7 +140,7 @@ type SeqLikeRandomContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``randomChoicesWith chooses as FSharp.Core does with the same generator`` () = this.ForEach ( Inputs.nonEmpty, @@ -161,7 +161,7 @@ type SeqLikeRandomContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``randomSample returns elements at distinct indices`` () = this.ForEach ( Inputs.nonEmpty, @@ -180,7 +180,7 @@ type SeqLikeRandomContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``randomSample throws for a negative count or one beyond the length as FSharp.Core does`` () = for count in [ -1; 9 ] do this.ThrowsLikeFSharpCore ( @@ -190,7 +190,7 @@ type SeqLikeRandomContract<'C> (ops : SeqOps<'C>) = $"randomSample %d{count} must throw" ) - [] + [] member this.``randomSampleBy samples as FSharp.Core does with the same randomizer`` () = this.ForEach ( Inputs.nonEmpty, @@ -213,7 +213,7 @@ type SeqLikeRandomContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``randomSampleWith samples as FSharp.Core does with the same generator`` () = this.ForEach ( Inputs.nonEmpty, @@ -236,7 +236,7 @@ type SeqLikeRandomContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``randomShuffle returns a permutation of the elements`` () = this.ForEach ( Inputs.all, @@ -248,7 +248,7 @@ type SeqLikeRandomContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``randomShuffleBy shuffles as FSharp.Core does with the same randomizer`` () = this.ForEach ( Inputs.all, @@ -269,7 +269,7 @@ type SeqLikeRandomContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``randomShuffleWith shuffles as FSharp.Core does with the same generator`` () = this.ForEach ( Inputs.all, diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSearchContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSearchContract.fs index 0b00376f..e3960c26 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSearchContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSearchContract.fs @@ -18,7 +18,7 @@ type SeqLikeSearchContract<'C> (ops : SeqOps<'C>) = /// The same chooser as `negatedIfNegative`, for the FSharp.Core oracle. static let negatedIfNegativeOption (item : int) = if item < 0 then Some (-item) else None - [] + [] member this.``contains finds an element equal to the value`` () = this.ForEach ( Inputs.all, @@ -31,7 +31,7 @@ type SeqLikeSearchContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``exists finds an element that satisfies the predicate`` () = this.ForEach ( Inputs.all, @@ -44,7 +44,7 @@ type SeqLikeSearchContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``exists2 tests the pairs at the same index`` () = let collection = ops.OfSeq Inputs.several let other = ops.OfSeq Inputs.other @@ -58,7 +58,7 @@ type SeqLikeSearchContract<'C> (ops : SeqOps<'C>) = $"%s{ops.Name}.exists2 over %A{elements} and %A{otherElements}" ) - [] + [] member this.``exists2 throws for different lengths as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.exists2 (fun _ _ -> false) (List.toArray Inputs.singleton) (List.toArray Inputs.several)), @@ -67,7 +67,7 @@ type SeqLikeSearchContract<'C> (ops : SeqOps<'C>) = "exists2 of collections of different lengths must throw" ) - [] + [] member this.``find returns the first element that satisfies the predicate`` () = this.ForEach ( [ Inputs.several ], @@ -80,7 +80,7 @@ type SeqLikeSearchContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``find throws when no element satisfies the predicate as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.find never (List.toArray Inputs.several)), @@ -89,7 +89,7 @@ type SeqLikeSearchContract<'C> (ops : SeqOps<'C>) = "find without a match must throw" ) - [] + [] member this.``findBack returns the last element that satisfies the predicate`` () = this.ForEach ( [ Inputs.several ], @@ -102,7 +102,7 @@ type SeqLikeSearchContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``findBack throws when no element satisfies the predicate as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.findBack never (List.toArray Inputs.several)), @@ -111,7 +111,7 @@ type SeqLikeSearchContract<'C> (ops : SeqOps<'C>) = "findBack without a match must throw" ) - [] + [] member this.``findIndex returns the index of the first element that satisfies the predicate`` () = this.ForEach ( [ Inputs.several ], @@ -124,7 +124,7 @@ type SeqLikeSearchContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``findIndex throws when no element satisfies the predicate as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.findIndex never (List.toArray Inputs.several)), @@ -133,7 +133,7 @@ type SeqLikeSearchContract<'C> (ops : SeqOps<'C>) = "findIndex without a match must throw" ) - [] + [] member this.``findIndexBack returns the index of the last element that satisfies the predicate`` () = this.ForEach ( [ Inputs.several ], @@ -146,7 +146,7 @@ type SeqLikeSearchContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``findIndexBack throws when no element satisfies the predicate as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.findIndexBack never (List.toArray Inputs.several)), @@ -155,7 +155,7 @@ type SeqLikeSearchContract<'C> (ops : SeqOps<'C>) = "findIndexBack without a match must throw" ) - [] + [] member this.``forall checks that every element satisfies the predicate`` () = this.ForEach ( Inputs.all, @@ -168,7 +168,7 @@ type SeqLikeSearchContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``forall2 tests every pair at the same index`` () = let collection = ops.OfSeq Inputs.several let other = ops.OfSeq Inputs.other @@ -182,7 +182,7 @@ type SeqLikeSearchContract<'C> (ops : SeqOps<'C>) = $"%s{ops.Name}.forall2 over %A{elements} and %A{otherElements}" ) - [] + [] member this.``forall2 throws for different lengths as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.forall2 (fun _ _ -> true) (List.toArray Inputs.singleton) (List.toArray Inputs.several)), @@ -191,7 +191,7 @@ type SeqLikeSearchContract<'C> (ops : SeqOps<'C>) = "forall2 of collections of different lengths must throw" ) - [] + [] member this.``pick returns the first chosen value`` () = this.ForEach ( [ Inputs.several ], @@ -203,7 +203,7 @@ type SeqLikeSearchContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``pick throws when the chooser returns nothing as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.pick (fun _ -> None) (List.toArray Inputs.several) : int), @@ -212,7 +212,7 @@ type SeqLikeSearchContract<'C> (ops : SeqOps<'C>) = "pick without a choice must throw" ) - [] + [] member this.``tryFind returns the first element that satisfies the predicate or nothing`` () = this.ForEach ( Inputs.all, @@ -225,7 +225,7 @@ type SeqLikeSearchContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``tryFindBack returns the last element that satisfies the predicate or nothing`` () = this.ForEach ( Inputs.all, @@ -238,7 +238,7 @@ type SeqLikeSearchContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``tryFindIndex returns the index of the first element that satisfies the predicate or nothing`` () = this.ForEach ( Inputs.all, @@ -251,7 +251,7 @@ type SeqLikeSearchContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``tryFindIndexBack returns the index of the last element that satisfies the predicate or nothing`` () = this.ForEach ( Inputs.all, @@ -265,7 +265,7 @@ type SeqLikeSearchContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``tryPick returns the first chosen value or nothing`` () = this.ForEach ( Inputs.all, diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSortingContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSortingContract.fs index cd8e646c..683bd1fb 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSortingContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSortingContract.fs @@ -13,7 +13,7 @@ open Microsoft.VisualStudio.TestTools.UnitTesting type SeqLikeSortingContract<'C> (ops : SeqOps<'C>) = inherit SeqLikePartitioningContract<'C> (ops) - [] + [] member this.``sort orders the elements ascending`` () = this.ForEach ( Inputs.all, @@ -25,7 +25,7 @@ type SeqLikeSortingContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``sortBy orders the elements by the projection`` () = this.ForEach ( Inputs.all, @@ -37,7 +37,7 @@ type SeqLikeSortingContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``sortByDescending orders the elements by the projection descending`` () = this.ForEach ( Inputs.all, @@ -49,7 +49,7 @@ type SeqLikeSortingContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``sortDescending orders the elements descending`` () = this.ForEach ( Inputs.all, @@ -61,7 +61,7 @@ type SeqLikeSortingContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``sortWith orders the elements with the comparison function`` () = let descending (left : int) (right : int) = compare right left @@ -75,7 +75,7 @@ type SeqLikeSortingContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``sortWithComparer orders the elements with the comparer`` () = let descending = Comparer.Create (fun left right -> compare right left) diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeTransformationContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeTransformationContract.fs index ca5fd415..2b3de026 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeTransformationContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeTransformationContract.fs @@ -16,7 +16,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = triples |> List.map (fun (first, second, third) -> struct (first, second, third)) - [] + [] member this.``allPairs pairs every element with every element of the other collection`` () = let other = ops.OfSeq [ 1; 2; 3 ] let otherElements = ops.ToList other @@ -31,7 +31,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``append concatenates two collections`` () = for first in Inputs.all do for second in Inputs.all do @@ -44,7 +44,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = $"%s{ops.Name}.append of %A{first} and %A{second}" ) - [] + [] member this.``choose keeps the chosen values in order`` () = this.ForEach ( Inputs.all, @@ -59,7 +59,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``chunkBySize divides the elements into chunks`` () = this.ForEach ( Inputs.all, @@ -72,7 +72,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``chunkBySize throws for a size that is not positive as FSharp.Core does`` () = for size in [ 0; -1 ] do this.ThrowsLikeFSharpCore ( @@ -82,7 +82,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = $"chunkBySize %d{size} must throw" ) - [] + [] member this.``collect concatenates the collections of the elements`` () = this.ForEach ( Inputs.all, @@ -94,7 +94,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``concat concatenates a sequence of collections`` () = let collections = Inputs.all |> List.map (fun input -> ops.OfSeq input) let elements = collections |> List.map ops.ToList @@ -106,7 +106,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = ) Assert.AreEqual([], ops.ToList (ops.Concat []), $"%s{ops.Name}.concat of no collections") - [] + [] member this.``filter keeps the elements that satisfy the predicate`` () = this.ForEach ( Inputs.all, @@ -118,7 +118,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``indexed pairs every element with its index`` () = this.ForEach ( Inputs.all, @@ -130,7 +130,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``map transforms every element`` () = this.ForEach ( Inputs.all, @@ -142,7 +142,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``map2 transforms the pairs at the same index`` () = let collection = ops.OfSeq Inputs.several let other = ops.OfSeq Inputs.other @@ -153,7 +153,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = $"%s{ops.Name}.map2 of two collections" ) - [] + [] member this.``map2 throws for different lengths as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.map2 (+) (List.toArray Inputs.singleton) (List.toArray Inputs.several)), @@ -162,7 +162,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = "map2 of collections of different lengths must throw" ) - [] + [] member this.``map3 transforms the triples at the same index`` () = let collection = ops.OfSeq Inputs.several let other = ops.OfSeq Inputs.other @@ -174,7 +174,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = $"%s{ops.Name}.map3 of three collections" ) - [] + [] member this.``map3 throws for different lengths as FSharp.Core does`` () = let shortArray = List.toArray Inputs.singleton let longArray = List.toArray Inputs.several @@ -192,7 +192,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = "map3 of collections of different lengths must throw" ) - [] + [] member this.``mapi transforms every element with its index`` () = this.ForEach ( Inputs.all, @@ -204,7 +204,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``mapi2 transforms the pairs at the same index with the index`` () = let collection = ops.OfSeq Inputs.several let other = ops.OfSeq Inputs.other @@ -215,7 +215,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = $"%s{ops.Name}.mapi2 of two collections" ) - [] + [] member this.``mapi2 throws for different lengths as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.mapi2 (fun _ a b -> a + b) (List.toArray Inputs.singleton) (List.toArray Inputs.several)), @@ -224,7 +224,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = "mapi2 of collections of different lengths must throw" ) - [] + [] member this.``pairwise pairs every element with its successor`` () = this.ForEach ( Inputs.all, @@ -236,7 +236,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``permute moves every element to its new index`` () = this.ForEach ( Inputs.nonEmpty, @@ -251,7 +251,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``permute throws for a function that is not a permutation as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.permute (fun _ -> 0) (List.toArray Inputs.several)), @@ -260,7 +260,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = "permute with a function that is not a permutation must throw" ) - [] + [] member this.``rev reverses the elements`` () = this.ForEach ( Inputs.all, @@ -268,7 +268,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = Assert.AreEqual(List.rev elements, ops.ToList (ops.Rev collection), $"%s{ops.Name}.rev of %A{elements}") ) - [] + [] member this.``transpose turns the collections into their columns`` () = let collections = [ Inputs.several; Inputs.other; Inputs.third ] @@ -281,7 +281,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = $"%s{ops.Name}.transpose of %A{elements}" ) - [] + [] member this.``transpose throws for collections of different lengths as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.transpose [ List.toArray Inputs.several; List.toArray Inputs.singleton ]), @@ -290,7 +290,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = "transpose of collections of different lengths must throw" ) - [] + [] member _.``unzip splits the pairs`` () = let pairs = List.zip Inputs.several Inputs.other let struct (first, second) = ops.Unzip (toStructPairs pairs) @@ -298,7 +298,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = Assert.AreEqual(expectedFirst, first, $"%s{ops.Name}.unzip must return the first components") Assert.AreEqual(expectedSecond, second, $"%s{ops.Name}.unzip must return the second components") - [] + [] member _.``unzip3 splits the triples`` () = let triples = List.zip3 Inputs.several Inputs.other Inputs.third let struct (first, second, third) = ops.Unzip3 (toStructTriples triples) @@ -307,7 +307,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = Assert.AreEqual(expectedSecond, second, $"%s{ops.Name}.unzip3 must return the second components") Assert.AreEqual(expectedThird, third, $"%s{ops.Name}.unzip3 must return the third components") - [] + [] member this.``where keeps the elements that satisfy the predicate`` () = this.ForEach ( Inputs.all, @@ -319,7 +319,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``windowed returns the windows that slide over the elements`` () = this.ForEach ( Inputs.all, @@ -332,7 +332,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = ) ) - [] + [] member this.``windowed throws for a size that is not positive as FSharp.Core does`` () = for size in [ 0; -1 ] do this.ThrowsLikeFSharpCore ( @@ -342,7 +342,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = $"windowed %d{size} must throw" ) - [] + [] member _.``zip pairs the elements at the same index`` () = let collection = ops.OfSeq Inputs.several let other = ops.OfSeq Inputs.other @@ -354,7 +354,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = $"%s{ops.Name}.zip of two collections" ) - [] + [] member this.``zip throws for different lengths as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.zip (List.toArray Inputs.singleton) (List.toArray Inputs.several)), @@ -363,7 +363,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = "zip of collections of different lengths must throw" ) - [] + [] member _.``zip3 combines the elements at the same index`` () = let collection = ops.OfSeq Inputs.several let other = ops.OfSeq Inputs.other @@ -376,7 +376,7 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = $"%s{ops.Name}.zip3 of three collections" ) - [] + [] member this.``zip3 throws for different lengths as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.zip3 (List.toArray Inputs.several) (List.toArray Inputs.singleton) (List.toArray Inputs.several)), diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListBuilderTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListBuilderTests.fs index 4eb8a12f..32ad942c 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListBuilderTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListBuilderTests.fs @@ -15,23 +15,23 @@ type FlatListBuilderTests () = builder.AddRange Inputs.several builder - [] + [] member _.``build returns the elements that the action adds`` () = let array = FlatList.build (fun builder -> builder.AddRange Inputs.several) Assert.AreEqual(Inputs.several, FlatList.toList array, "FlatList.build must return the added elements") - [] + [] member _.``builder starts empty`` () = let builder = FlatList.builder() Assert.AreEqual(0, builder.Count, "FlatList.builder must start without elements") - [] + [] member _.``builderWith reserves the capacity`` () = let builder = FlatList.builderWith 10 Assert.AreEqual(10, builder.Capacity, "FlatList.builderWith must reserve the capacity") Assert.AreEqual(0, builder.Count, "FlatList.builderWith must start without elements") - [] + [] member _.``builderWith throws for a negative capacity`` () = Assert.ThrowsExactly( Action (fun () -> FlatList.builderWith -1 |> ignore), @@ -39,7 +39,7 @@ type FlatListBuilderTests () = ) |> ignore - [] + [] member _.``moveFromBuilder takes the contents of a full builder`` () = let builder = FlatList.builderWith 3 builder.AddRange [ 1; 2; 3 ] @@ -47,7 +47,7 @@ type FlatListBuilderTests () = Assert.AreEqual([ 1; 2; 3 ], FlatList.toList array, "FlatList.moveFromBuilder must return the elements") Assert.AreEqual(0, builder.Count, "FlatList.moveFromBuilder must empty the builder") - [] + [] member _.``moveFromBuilder throws for a builder that is not full`` () = let builder = FlatList.builderWith 3 builder.Add 1 @@ -58,7 +58,7 @@ type FlatListBuilderTests () = ) |> ignore - [] + [] member _.``ofBuilder copies the contents and keeps the builder`` () = let builder = filled () let array = FlatList.ofBuilder builder @@ -66,7 +66,7 @@ type FlatListBuilderTests () = Assert.AreEqual(Inputs.several, FlatList.toList array, "FlatList.ofBuilder must copy the elements") Assert.AreEqual(Inputs.several.Length + 1, builder.Count, "FlatList.ofBuilder must keep the builder usable") - [] + [] member _.``ofBuilder throws for a null builder`` () = let ex = Assert.ThrowsExactly( @@ -79,7 +79,7 @@ type FlatListBuilderTests () = Assert.AreEqual("builder", ex.ParamName, "FlatList.ofBuilder must name the builder parameter") - [] + [] member _.``toBuilder copies the elements into a new builder`` () = let array = FlatList.ofList Inputs.several let builder = FlatList.toBuilder array @@ -87,14 +87,14 @@ type FlatListBuilderTests () = Assert.AreEqual(Inputs.several @ [ 99 ], List.ofSeq builder, "FlatList.toBuilder must hold the elements") Assert.AreEqual(Inputs.several, FlatList.toList array, "FlatList.toBuilder must not change the flat list") - [] + [] member _.``update applies the action to a copy`` () = let array = FlatList.ofList Inputs.several let updated = FlatList.update (fun builder -> builder.RemoveAt 0) array Assert.AreEqual(List.tail Inputs.several, FlatList.toList updated, "FlatList.update must apply the action") Assert.AreEqual(Inputs.several, FlatList.toList array, "FlatList.update must not change the input") - [] + [] member _.``add, addRange and insertAt return the same builder`` () = let builder = FlatList.builder() let result = @@ -105,7 +105,7 @@ type FlatListBuilderTests () = Assert.AreSame (builder, result, "The functions that change a builder must return it") Assert.AreEqual([ 0; 1; 2; 3 ], List.ofSeq builder, "add, addRange and insertAt must change the builder") - [] + [] member _.``capacity, setCapacity and length report the builder`` () = let builder = FlatList.builderWith 4 @@ -114,12 +114,12 @@ type FlatListBuilderTests () = Assert.AreEqual(10, FlatListBuilder.capacity builder, "FlatListBuilder.capacity must return the capacity") Assert.AreEqual(1, FlatListBuilder.length builder, "FlatListBuilder.length must count the elements") - [] + [] member _.``clear removes every element`` () = let builder = filled () |> FlatListBuilder.clear Assert.AreEqual(0, FlatListBuilder.length builder, "FlatListBuilder.clear must remove every element") - [] + [] member _.``contains, indexOf and lastIndexOf search the builder`` () = let builder = filled () Assert.IsTrue (FlatListBuilder.contains 8 builder, "FlatListBuilder.contains must find 8") @@ -127,7 +127,7 @@ type FlatListBuilderTests () = Assert.AreEqual(5, FlatListBuilder.lastIndexOf -3 builder, "FlatListBuilder.lastIndexOf must return the last index") Assert.AreEqual(-1, FlatListBuilder.indexOf 1000 builder, "FlatListBuilder.indexOf must return -1 when absent") - [] + [] member _.``drainToFlatList returns the elements and empties the builder`` () = let builder = filled () let array = FlatListBuilder.drainToFlatList builder @@ -138,12 +138,12 @@ type FlatListBuilderTests () = ) Assert.AreEqual(0, FlatListBuilder.length builder, "FlatListBuilder.drainToFlatList must empty the builder") - [] + [] member _.``item and updateAt read and replace an element`` () = let builder = filled () |> FlatListBuilder.updateAt 2 99 Assert.AreEqual(99, FlatListBuilder.item 2 builder, "FlatListBuilder.updateAt must replace the element") - [] + [] member _.``remove and removeAt remove elements`` () = let builder = filled () Assert.IsTrue (FlatListBuilder.remove -3 builder, "FlatListBuilder.remove must report a removed element") @@ -151,12 +151,12 @@ type FlatListBuilderTests () = FlatListBuilder.removeAt 0 builder |> ignore Assert.AreEqual([ 8; 5; 0; -3; 12; 7 ], List.ofSeq builder, "remove and removeAt must remove the elements") - [] + [] member _.``rev reverses the builder`` () = let builder = filled () |> FlatListBuilder.rev Assert.AreEqual(List.rev Inputs.several, List.ofSeq builder, "FlatListBuilder.rev must reverse the elements") - [] + [] member _.``sort, sortWith and sortWithComparer order the builder`` () = Assert.AreEqual( List.sort Inputs.several, @@ -182,7 +182,7 @@ type FlatListBuilderTests () = "FlatListBuilder.sortWithComparer must use the comparer" ) - [] + [] member _.``toArray copies the elements`` () = let builder = filled () CollectionAssert.AreEqual ( diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs index fdd9f09f..f2eea4f5 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs @@ -8,12 +8,12 @@ open Microsoft.VisualStudio.TestTools.UnitTesting [] type FlatListComputationExpressionTests () = - [] + [] member _.``an expression without elements is empty`` () = let array : FlatList = flatList { () } Assert.IsTrue (FlatList.isEmpty array, "flatList { () } must be empty") - [] + [] member _.``yield adds the elements in order`` () = let array = flatList { yield 1 @@ -23,7 +23,7 @@ type FlatListComputationExpressionTests () = Assert.AreEqual([ 1; 2; 3 ], FlatList.toList array, "flatList must keep the order of yield") - [] + [] member _.``implicit yields add the elements in order`` () = let array = flatList { 1 @@ -33,8 +33,7 @@ type FlatListComputationExpressionTests () = Assert.AreEqual([ 1; 2; 3 ], FlatList.toList array, "flatList must keep the order of implicit yields") - // nameof cannot name the overloaded YieldFrom, so the category is the expression itself - [] + [] member _.``yield! adds a sequence, an array, a flat list and another expression`` () = let inner = flatList { yield 7 } @@ -47,7 +46,7 @@ type FlatListComputationExpressionTests () = Assert.AreEqual([ 1; 2; 3; 4; 5; 6; 7 ], FlatList.toList array, "flatList must add the elements of yield!") - [] + [] member _.``for adds the elements of the body for each item`` () = let array = flatList { for item in 1..3 do @@ -56,7 +55,7 @@ type FlatListComputationExpressionTests () = Assert.AreEqual([ 10; 20; 30 ], FlatList.toList array, "flatList must run the body of for for each item") - [] + [] member _.``while adds the elements while the guard holds`` () = let mutable count = 0 @@ -68,7 +67,7 @@ type FlatListComputationExpressionTests () = Assert.AreEqual([ 1; 2; 3 ], FlatList.toList array, "flatList must run the body of while while the guard holds") - [] + [] member _.``if without else and following elements combine`` () = let array = flatList { if false then @@ -82,7 +81,7 @@ type FlatListComputationExpressionTests () = Assert.AreEqual([ 1; 2 ], FlatList.toList array, "flatList must combine conditional and plain elements") - [] + [] member _.``try with adds the elements of the handler after an exception`` () = let array = flatList { try @@ -98,7 +97,7 @@ type FlatListComputationExpressionTests () = "flatList must keep the elements before the exception and add the handler's" ) - [] + [] member _.``try finally runs the compensation`` () = let mutable compensated = false @@ -112,7 +111,7 @@ type FlatListComputationExpressionTests () = Assert.IsTrue (compensated, "flatList must run the compensation of try finally") Assert.AreEqual([ 1 ], FlatList.toList array, "flatList must keep the elements of the body of try finally") - [] + [] member _.``use disposes the resource after the body`` () = let mutable disposed = false diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOps.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOps.fs index d3597e32..012e4b0f 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOps.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOps.fs @@ -1,194 +1,192 @@ -namespace FSharp.Collections.Immutable.Tests +/// The functions of the `FlatList` module for the contract tests. +[] +module FSharp.Collections.Immutable.Tests.FlatListOps open System open FSharp.Collections.Immutable -/// The functions of the `FlatList` module for the contract tests. -[] -module FlatListOps = - - let private toLists (lists : FlatList>) = lists |> FlatList.toList |> List.map FlatList.toList - - let private toListPair (struct (first : FlatList<'T1>, second : FlatList<'T2>)) = - struct (FlatList.toList first, FlatList.toList second) - - /// The functions of the sequence-like facet. `FlatList` names its parameters as the `Array` module does. - let seqLike : SeqOps> = { - Name = nameof FlatList - Mirror = ArrayMirror - ParamName = id - OfSeq = FlatList.ofSeq - ToList = FlatList.toList - - IsEmpty = FlatList.isEmpty - Length = FlatList.length - - ExactlyOne = FlatList.exactlyOne - Head = FlatList.head - Last = FlatList.last - Tail = FlatList.tail - TryExactlyOne = FlatList.tryExactlyOne - TryHead = FlatList.tryHead - TryLast = FlatList.tryLast - TryTail = FlatList.tryTail - - Contains = FlatList.contains - Exists = FlatList.exists - Exists2 = FlatList.exists2 - Find = FlatList.find - FindBack = FlatList.findBack - FindIndex = FlatList.findIndex - FindIndexBack = FlatList.findIndexBack - Forall = FlatList.forall - Forall2 = FlatList.forall2 - Pick = FlatList.pick - TryFind = FlatList.tryFind - TryFindBack = FlatList.tryFindBack - TryFindIndex = FlatList.tryFindIndex - TryFindIndexBack = FlatList.tryFindIndexBack - TryPick = FlatList.tryPick - - AllPairs = fun array1 array2 -> FlatList.allPairs array1 array2 |> FlatList.toList - Append = FlatList.append - Choose = FlatList.choose - ChunkBySize = fun chunkSize array -> FlatList.chunkBySize chunkSize array |> toLists - Collect = FlatList.collect - Concat = FlatList.concat - Filter = FlatList.filter - Indexed = FlatList.indexed >> FlatList.toList - Map = FlatList.map - Map2 = FlatList.map2 - Map3 = FlatList.map3 - Mapi = FlatList.mapi - Mapi2 = FlatList.mapi2 - Pairwise = FlatList.pairwise >> FlatList.toList - Permute = FlatList.permute - Rev = FlatList.rev - Transpose = fun arrays -> FlatList.transpose arrays |> toLists - Unzip = fun pairs -> FlatList.unzip (FlatList.ofList pairs) |> toListPair - Unzip3 = - fun triples -> - let struct (first, second, third) = FlatList.unzip3 (FlatList.ofList triples) - struct (FlatList.toList first, FlatList.toList second, FlatList.toList third) - Where = FlatList.where - Windowed = fun windowSize array -> FlatList.windowed windowSize array |> toLists - Zip = fun array1 array2 -> FlatList.zip array1 array2 |> FlatList.toList - Zip3 = fun array1 array2 array3 -> FlatList.zip3 array1 array2 array3 |> FlatList.toList - - Average = fun items -> FlatList.average (FlatList.ofList items) - AverageBy = FlatList.averageBy - CompareWith = FlatList.compareWith - Fold = FlatList.fold - Fold2 = FlatList.fold2 - FoldBack = FlatList.foldBack - FoldBack2 = FlatList.foldBack2 - Iter = FlatList.iter - Iter2 = FlatList.iter2 - Iteri = FlatList.iteri - Iteri2 = FlatList.iteri2 - MapFold = - fun mapping state array -> - let struct (results, final) = FlatList.mapFold mapping state array - struct (FlatList.toList results, final) - MapFoldBack = - fun mapping array state -> - let struct (results, final) = FlatList.mapFoldBack mapping array state - struct (FlatList.toList results, final) - Max = FlatList.max - MaxBy = FlatList.maxBy - Min = FlatList.min - MinBy = FlatList.minBy - Reduce = FlatList.reduce - ReduceBack = FlatList.reduceBack - Scan = fun folder state array -> FlatList.scan folder state array |> FlatList.toList - ScanBack = fun folder array state -> FlatList.scanBack folder array state |> FlatList.toList - Sum = FlatList.sum - SumBy = FlatList.sumBy - - CountBy = fun projection array -> FlatList.countBy projection array |> FlatList.toList - Distinct = FlatList.distinct - DistinctBy = FlatList.distinctBy - Except = FlatList.except - GroupBy = - fun projection array -> - FlatList.groupBy projection array - |> FlatList.toList - |> List.map (fun struct (key, items) -> struct (key, FlatList.toList items)) - Partition = FlatList.partition - PartitionWith = fun partitioner array -> FlatList.partitionWith partitioner array |> toListPair - Skip = FlatList.skip - SkipLast = FlatList.skipLast - SkipWhile = FlatList.skipWhile - SplitAt = FlatList.splitAt - SplitInto = fun count array -> FlatList.splitInto count array |> toLists - Take = FlatList.take - TakeLast = FlatList.takeLast - TakeWhile = FlatList.takeWhile - Truncate = FlatList.truncate - - Sort = FlatList.sort - SortBy = FlatList.sortBy - SortByDescending = FlatList.sortByDescending - SortDescending = FlatList.sortDescending - SortWith = FlatList.sortWith - SortWithComparer = FlatList.sortWithComparer - - RandomChoice = FlatList.randomChoice - RandomChoiceBy = FlatList.randomChoiceBy - RandomChoiceWith = FlatList.randomChoiceWith - RandomChoices = FlatList.randomChoices - RandomChoicesBy = FlatList.randomChoicesBy - RandomChoicesWith = FlatList.randomChoicesWith - RandomSample = FlatList.randomSample - RandomSampleBy = FlatList.randomSampleBy - RandomSampleWith = FlatList.randomSampleWith - RandomShuffle = FlatList.randomShuffle - RandomShuffleBy = FlatList.randomShuffleBy - RandomShuffleWith = FlatList.randomShuffleWith - - ToArray = FlatList.toArray - ToSeq = FlatList.toSeq - } - - /// The functions of the creation facet. - let creation : SeqCreationOps> = { - Name = nameof FlatList - Mirror = ArrayMirror - ParamName = id - ToList = FlatList.toList - - Empty = fun () -> FlatList.empty - Init = FlatList.init - OfArray = FlatList.ofArray - OfList = FlatList.ofList - OfReadOnlySpan = fun array -> FlatList.ofReadOnlySpan (ReadOnlySpan array) - OfSeq = FlatList.ofSeq - OfSpan = fun array -> FlatList.ofSpan (Span array) - Replicate = FlatList.replicate - Singleton = FlatList.singleton - Unfold = FlatList.unfold - } - - /// The functions of the index-based facet. - let indexed : IndexedOps> = { - Name = nameof FlatList - Mirror = ArrayMirror - ParamName = id - OfSeq = FlatList.ofSeq - ToList = FlatList.toList - - Item = FlatList.item - TryItem = FlatList.tryItem - - IndexOf = FlatList.indexOf - IndexOfWith = FlatList.indexOfWith - LastIndexOf = FlatList.lastIndexOf - LastIndexOfWith = FlatList.lastIndexOfWith - - Add = FlatList.add - InsertAt = FlatList.insertAt - InsertManyAt = FlatList.insertManyAt - RemoveAt = FlatList.removeAt - RemoveManyAt = FlatList.removeManyAt - UpdateAt = FlatList.updateAt - } +let private toLists (lists : FlatList>) = lists |> FlatList.toList |> List.map FlatList.toList + +let private toListPair (struct (first : FlatList<'T1>, second : FlatList<'T2>)) = + struct (FlatList.toList first, FlatList.toList second) + +/// The functions of the sequence-like facet. `FlatList` names its parameters as the `Array` module does. +let seqLike : SeqOps> = { + Name = nameof FlatList + Mirror = ArrayMirror + ParamName = id + OfSeq = FlatList.ofSeq + ToList = FlatList.toList + + IsEmpty = FlatList.isEmpty + Length = FlatList.length + + ExactlyOne = FlatList.exactlyOne + Head = FlatList.head + Last = FlatList.last + Tail = FlatList.tail + TryExactlyOne = FlatList.tryExactlyOne + TryHead = FlatList.tryHead + TryLast = FlatList.tryLast + TryTail = FlatList.tryTail + + Contains = FlatList.contains + Exists = FlatList.exists + Exists2 = FlatList.exists2 + Find = FlatList.find + FindBack = FlatList.findBack + FindIndex = FlatList.findIndex + FindIndexBack = FlatList.findIndexBack + Forall = FlatList.forall + Forall2 = FlatList.forall2 + Pick = FlatList.pick + TryFind = FlatList.tryFind + TryFindBack = FlatList.tryFindBack + TryFindIndex = FlatList.tryFindIndex + TryFindIndexBack = FlatList.tryFindIndexBack + TryPick = FlatList.tryPick + + AllPairs = fun array1 array2 -> FlatList.allPairs array1 array2 |> FlatList.toList + Append = FlatList.append + Choose = FlatList.choose + ChunkBySize = fun chunkSize array -> FlatList.chunkBySize chunkSize array |> toLists + Collect = FlatList.collect + Concat = FlatList.concat + Filter = FlatList.filter + Indexed = FlatList.indexed >> FlatList.toList + Map = FlatList.map + Map2 = FlatList.map2 + Map3 = FlatList.map3 + Mapi = FlatList.mapi + Mapi2 = FlatList.mapi2 + Pairwise = FlatList.pairwise >> FlatList.toList + Permute = FlatList.permute + Rev = FlatList.rev + Transpose = fun arrays -> FlatList.transpose arrays |> toLists + Unzip = fun pairs -> FlatList.unzip (FlatList.ofList pairs) |> toListPair + Unzip3 = + fun triples -> + let struct (first, second, third) = FlatList.unzip3 (FlatList.ofList triples) + struct (FlatList.toList first, FlatList.toList second, FlatList.toList third) + Where = FlatList.where + Windowed = fun windowSize array -> FlatList.windowed windowSize array |> toLists + Zip = fun array1 array2 -> FlatList.zip array1 array2 |> FlatList.toList + Zip3 = fun array1 array2 array3 -> FlatList.zip3 array1 array2 array3 |> FlatList.toList + + Average = fun items -> FlatList.average (FlatList.ofList items) + AverageBy = FlatList.averageBy + CompareWith = FlatList.compareWith + Fold = FlatList.fold + Fold2 = FlatList.fold2 + FoldBack = FlatList.foldBack + FoldBack2 = FlatList.foldBack2 + Iter = FlatList.iter + Iter2 = FlatList.iter2 + Iteri = FlatList.iteri + Iteri2 = FlatList.iteri2 + MapFold = + fun mapping state array -> + let struct (results, final) = FlatList.mapFold mapping state array + struct (FlatList.toList results, final) + MapFoldBack = + fun mapping array state -> + let struct (results, final) = FlatList.mapFoldBack mapping array state + struct (FlatList.toList results, final) + Max = FlatList.max + MaxBy = FlatList.maxBy + Min = FlatList.min + MinBy = FlatList.minBy + Reduce = FlatList.reduce + ReduceBack = FlatList.reduceBack + Scan = fun folder state array -> FlatList.scan folder state array |> FlatList.toList + ScanBack = fun folder array state -> FlatList.scanBack folder array state |> FlatList.toList + Sum = FlatList.sum + SumBy = FlatList.sumBy + + CountBy = fun projection array -> FlatList.countBy projection array |> FlatList.toList + Distinct = FlatList.distinct + DistinctBy = FlatList.distinctBy + Except = FlatList.except + GroupBy = + fun projection array -> + FlatList.groupBy projection array + |> FlatList.toList + |> List.map (fun struct (key, items) -> struct (key, FlatList.toList items)) + Partition = FlatList.partition + PartitionWith = fun partitioner array -> FlatList.partitionWith partitioner array |> toListPair + Skip = FlatList.skip + SkipLast = FlatList.skipLast + SkipWhile = FlatList.skipWhile + SplitAt = FlatList.splitAt + SplitInto = fun count array -> FlatList.splitInto count array |> toLists + Take = FlatList.take + TakeLast = FlatList.takeLast + TakeWhile = FlatList.takeWhile + Truncate = FlatList.truncate + + Sort = FlatList.sort + SortBy = FlatList.sortBy + SortByDescending = FlatList.sortByDescending + SortDescending = FlatList.sortDescending + SortWith = FlatList.sortWith + SortWithComparer = FlatList.sortWithComparer + + RandomChoice = FlatList.randomChoice + RandomChoiceBy = FlatList.randomChoiceBy + RandomChoiceWith = FlatList.randomChoiceWith + RandomChoices = FlatList.randomChoices + RandomChoicesBy = FlatList.randomChoicesBy + RandomChoicesWith = FlatList.randomChoicesWith + RandomSample = FlatList.randomSample + RandomSampleBy = FlatList.randomSampleBy + RandomSampleWith = FlatList.randomSampleWith + RandomShuffle = FlatList.randomShuffle + RandomShuffleBy = FlatList.randomShuffleBy + RandomShuffleWith = FlatList.randomShuffleWith + + ToArray = FlatList.toArray + ToSeq = FlatList.toSeq +} + +/// The functions of the creation facet. +let creation : SeqCreationOps> = { + Name = nameof FlatList + Mirror = ArrayMirror + ParamName = id + ToList = FlatList.toList + + Empty = fun () -> FlatList.empty + Init = FlatList.init + OfArray = FlatList.ofArray + OfList = FlatList.ofList + OfReadOnlySpan = fun array -> FlatList.ofReadOnlySpan (ReadOnlySpan array) + OfSeq = FlatList.ofSeq + OfSpan = fun array -> FlatList.ofSpan (Span array) + Replicate = FlatList.replicate + Singleton = FlatList.singleton + Unfold = FlatList.unfold +} + +/// The functions of the index-based facet. +let indexed : IndexedOps> = { + Name = nameof FlatList + Mirror = ArrayMirror + ParamName = id + OfSeq = FlatList.ofSeq + ToList = FlatList.toList + + Item = FlatList.item + TryItem = FlatList.tryItem + + IndexOf = FlatList.indexOf + IndexOfWith = FlatList.indexOfWith + LastIndexOf = FlatList.lastIndexOf + LastIndexOfWith = FlatList.lastIndexOfWith + + Add = FlatList.add + InsertAt = FlatList.insertAt + InsertManyAt = FlatList.insertManyAt + RemoveAt = FlatList.removeAt + RemoveManyAt = FlatList.removeManyAt + UpdateAt = FlatList.updateAt +} diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOptionTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOptionTests.fs index 239924ff..c7779f50 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOptionTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOptionTests.fs @@ -16,7 +16,7 @@ type FlatListOptionTests () = static let negatedIfNegative (item : int) = if item < 0 then Some (-item) else None - [] + [] member _.``unfold takes a generator of options of reference tuples`` () = let generator (state : int) = if state > 0 then Some (state, state - 1) else None let generated : FlatList = FlatList.unfold generator 3 @@ -26,7 +26,7 @@ type FlatListOptionTests () = "Option.FlatList.unfold must generate as List.unfold does" ) - [] + [] member _.``tryExactlyOne returns an option`` () = let found : int option = FlatList.tryExactlyOne (FlatList.singleton 5) @@ -35,7 +35,7 @@ type FlatListOptionTests () = found "Option.FlatList.tryExactlyOne must match the default after conversion" - [] + [] member _.``tryHead returns an option`` () = let found : int option = FlatList.tryHead array OverrideAssert.optionMatches @@ -43,7 +43,7 @@ type FlatListOptionTests () = found "Option.FlatList.tryHead must match the default" - [] + [] member _.``tryItem returns an option`` () = for index in [ -1; 2; 8 ] do let found : int option = FlatList.tryItem index array @@ -53,7 +53,7 @@ type FlatListOptionTests () = found $"Option.FlatList.tryItem %d{index} must match the default" - [] + [] member _.``tryLast returns an option`` () = let found : int option = FlatList.tryLast array OverrideAssert.optionMatches @@ -61,7 +61,7 @@ type FlatListOptionTests () = found "Option.FlatList.tryLast must match the default" - [] + [] member _.``tryTail returns an option`` () = let found : FlatList option = FlatList.tryTail array @@ -73,7 +73,7 @@ type FlatListOptionTests () = "Option.FlatList.tryTail must match the default" ) - [] + [] member _.``pick takes a chooser of options`` () = let picked : int = FlatList.pick negatedIfNegative array Assert.AreEqual( @@ -82,7 +82,7 @@ type FlatListOptionTests () = "Option.FlatList.pick must pick as List.pick does" ) - [] + [] member _.``tryFind returns an option`` () = let found : int option = FlatList.tryFind (fun item -> item > 6) array @@ -91,7 +91,7 @@ type FlatListOptionTests () = found "Option.FlatList.tryFind must match the default" - [] + [] member _.``tryFindBack returns an option`` () = let found : int option = FlatList.tryFindBack (fun item -> item > 6) array @@ -100,7 +100,7 @@ type FlatListOptionTests () = found "Option.FlatList.tryFindBack must match the default" - [] + [] member _.``tryFindIndex returns an option`` () = let found : int option = FlatList.tryFindIndex (fun item -> item > 6) array @@ -109,7 +109,7 @@ type FlatListOptionTests () = found "Option.FlatList.tryFindIndex must match the default" - [] + [] member _.``tryFindIndexBack returns an option`` () = let found : int option = FlatList.tryFindIndexBack (fun item -> item > 6) array @@ -118,7 +118,7 @@ type FlatListOptionTests () = found "Option.FlatList.tryFindIndexBack must match the default" - [] + [] member _.``tryPick takes a chooser of options and returns an option`` () = let found : int option = FlatList.tryPick negatedIfNegative array Assert.AreEqual( @@ -127,7 +127,7 @@ type FlatListOptionTests () = "Option.FlatList.tryPick must pick as List.tryPick does" ) - [] + [] member _.``choose takes a chooser of options`` () = let chosen : FlatList = FlatList.choose negatedIfNegative array @@ -137,7 +137,7 @@ type FlatListOptionTests () = "Option.FlatList.choose must choose as List.choose does" ) - [] + [] member _.``the functions without an option keep resolving to the default module`` () = let mapped : FlatList = FlatList.map (fun item -> item * 2) array let zipped : FlatList = FlatList.zip array array diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs index f8a6bbb1..5b32b397 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs @@ -17,12 +17,12 @@ type FlatListTests () = /// The array under a flat list, to tell whether two flat lists share their storage. static let underlying (array : FlatList) = ImmutableCollectionsMarshal.AsArray array - [] + [] member _.``create repeats the value`` () = Assert.AreEqual([ 7; 7; 7 ], FlatList.toList (FlatList.create 3 7), "FlatList.create must repeat the value") Assert.IsEmpty (FlatList.create 0 7, "FlatList.create 0 must be empty") - [] + [] member _.``create throws for a negative count as Array does`` () = ExceptionAssert.same id @@ -30,7 +30,7 @@ type FlatListTests () = (fun () -> FlatList.create -1 7 |> ignore) "FlatList.create -1 must throw as Array.create does" - [] + [] member _.``isDefault and isDefaultOrEmpty tell a default value from an empty one`` () = Assert.IsTrue (FlatList.isDefault defaultArray, "isDefault must be true for a default value") Assert.IsFalse (FlatList.isDefault FlatList.empty, "isDefault must be false for an empty flat list") @@ -41,7 +41,7 @@ type FlatListTests () = "isDefaultOrEmpty must be false for a non-empty flat list" ) - [] + [] member _.``functions reject a default flat list as Array rejects a null array`` () = let cases : struct (string * (unit -> unit) * (unit -> unit)) list = [ struct (nameof FlatList.allPairs, @@ -182,7 +182,7 @@ type FlatListTests () = for struct (name, oracle, actual) in cases do ExceptionAssert.same id oracle actual $"FlatList.%s{name} must reject a default flat list" - [] + [] member _.``functions without an Array counterpart reject a default flat list`` () = let cases : struct (string * string * (unit -> unit)) list = [ struct (nameof FlatList.add, "array", (fun () -> FlatList.add 1 defaultArray |> ignore)) @@ -239,7 +239,7 @@ type FlatListTests () = $"FlatList.%s{name} must name the parameter of the default flat list" ) - [] + [] member _.``countBy counts null keys like any other key`` () = let array = FlatList.ofList Inputs.several let keyOf (item : int) = if item > 0 then Some (item % 2) else None @@ -251,13 +251,13 @@ type FlatListTests () = "FlatList.countBy must count the key None, which is null, as List.countBy does" ) - [] + [] member _.``asSpan views the elements`` () = let array = FlatList.ofList Inputs.several let span = FlatList.asSpan array CollectionAssert.AreEqual (List.toArray Inputs.several, span.ToArray (), "FlatList.asSpan must view the elements") - [] + [] member _.``asMemory views the underlying array without a copy`` () = let array = FlatList.ofList Inputs.several let memory = FlatList.asMemory array @@ -266,7 +266,7 @@ type FlatListTests () = Assert.AreSame (underlying array, segment.Array, "FlatList.asMemory must view the underlying array") Assert.HasCount (Inputs.several.Length, segment, "FlatList.asMemory must view every element") - [] + [] member _.``binarySearch finds a value or the complement of its insertion point`` () = let array = FlatList.ofList [ 1; 3; 5; 7 ] Assert.AreEqual(2, FlatList.binarySearch 5 array, "FlatList.binarySearch must find 5 at index 2") @@ -281,7 +281,7 @@ type FlatListTests () = "FlatList.binarySearch must return the complement of the length for 9" ) - [] + [] member _.``binarySearch uses the order of sort, which is ordinal for strings`` () = let array = FlatList.ofList [ "b"; "A"; "a"; "B" ] |> FlatList.sort Assert.AreEqual([ "A"; "B"; "a"; "b" ], FlatList.toList array, "FlatList.sort must order strings ordinally") @@ -293,13 +293,13 @@ type FlatListTests () = $"FlatList.binarySearch must find %s{array[index]}" ) - [] + [] member _.``blit copies a range into an array`` () = let target = Array.zeroCreate 5 FlatList.blit (FlatList.ofList Inputs.several) 2 target 1 3 CollectionAssert.AreEqual ([| 0; 8; 5; 0; 0 |], target, "FlatList.blit must copy three elements from index 2 to index 1") - [] + [] member _.``blit throws for a range out of range as Array does`` () = let source = List.toArray Inputs.several @@ -309,12 +309,12 @@ type FlatListTests () = (fun () -> FlatList.blit (FlatList.ofArray source) 6 (Array.zeroCreate 5) 0 3) "FlatList.blit beyond the source must throw as Array.blit does" - [] + [] member _.``ofSeq of a flat list shares its storage`` () = let array = FlatList.ofList Inputs.several Assert.AreSame (underlying array, underlying (FlatList.ofSeq array), "FlatList.ofSeq of a flat list must not copy it") - [] + [] member _.``range functions return the input itself for the whole range`` () = let array = FlatList.ofList Inputs.several @@ -331,7 +331,7 @@ type FlatListTests () = for name, result in results do Assert.AreSame (underlying array, underlying result, $"FlatList.%s{name} of the whole range must return the input") - [] + [] member _.``range functions copy a partial range`` () = let array = FlatList.ofList Inputs.several let struct (before, after) = FlatList.splitAt 3 array @@ -345,7 +345,7 @@ type FlatListTests () = ] do Assert.AreNotSame (underlying array, underlying result, $"FlatList.%s{name} of a partial range must copy it") - [] + [] member _.``append returns the other flat list when one is empty`` () = let array = FlatList.ofList Inputs.several Assert.AreSame ( @@ -359,7 +359,7 @@ type FlatListTests () = "FlatList.append of an empty first flat list must return the second one" ) - [] + [] member _.``sortWithComparer returns a sorted input itself`` () = let array = FlatList.ofList [ 1; 2; 3 ] Assert.AreSame ( diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTupleTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTupleTests.fs index bc2b2cf5..177b13a5 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTupleTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTupleTests.fs @@ -16,7 +16,7 @@ type FlatListTupleTests () = static let third = FlatList.ofList Inputs.third - [] + [] member _.``unfold takes a generator of value options of reference tuples`` () = let generator (state : int) = if state > 0 then @@ -26,7 +26,7 @@ type FlatListTupleTests () = let generated : FlatList = FlatList.unfold generator 3 Assert.AreEqual([ 3; 2; 1 ], FlatList.toList generated, "Tuple.FlatList.unfold must generate the elements") - [] + [] member _.``allPairs returns reference tuples`` () = let pairs : FlatList = FlatList.allPairs array other @@ -36,7 +36,7 @@ type FlatListTupleTests () = "Tuple.FlatList.allPairs must pair as List.allPairs does" ) - [] + [] member _.``indexed returns reference tuples`` () = let pairs : FlatList = FlatList.indexed array Assert.AreEqual<(int * int) list>( @@ -45,7 +45,7 @@ type FlatListTupleTests () = "Tuple.FlatList.indexed must index as List.indexed does" ) - [] + [] member _.``pairwise returns reference tuples`` () = let pairs : FlatList = FlatList.pairwise array @@ -55,13 +55,13 @@ type FlatListTupleTests () = "Tuple.FlatList.pairwise must pair as List.pairwise does" ) - [] + [] member _.``unzip takes and returns reference tuples`` () = let (first : FlatList), (second : FlatList) = FlatList.unzip (FlatList.zip array other) Assert.AreEqual(Inputs.several, FlatList.toList first, "Tuple.FlatList.unzip must return the first components") Assert.AreEqual(Inputs.other, FlatList.toList second, "Tuple.FlatList.unzip must return the second components") - [] + [] member _.``unzip3 takes and returns reference tuples`` () = let (first : FlatList), (second : FlatList), (last : FlatList) = FlatList.unzip3 (FlatList.zip3 array other third) @@ -69,7 +69,7 @@ type FlatListTupleTests () = Assert.AreEqual(Inputs.other, FlatList.toList second, "Tuple.FlatList.unzip3 must return the second components") Assert.AreEqual(Inputs.third, FlatList.toList last, "Tuple.FlatList.unzip3 must return the third components") - [] + [] member _.``zip returns reference tuples`` () = let pairs : FlatList = FlatList.zip array other Assert.AreEqual<(int * int) list>( @@ -78,7 +78,7 @@ type FlatListTupleTests () = "Tuple.FlatList.zip must zip as List.zip does" ) - [] + [] member _.``zip throws for different lengths as the default does`` () = ExceptionAssert.same id @@ -89,7 +89,7 @@ type FlatListTupleTests () = (fun () -> FlatList.zip array (FlatList.singleton 1) |> ignore) "Tuple.FlatList.zip must throw as the default zip does" - [] + [] member _.``zip3 returns reference tuples`` () = let triples : FlatList = FlatList.zip3 array other third @@ -99,7 +99,7 @@ type FlatListTupleTests () = "Tuple.FlatList.zip3 must zip as List.zip3 does" ) - [] + [] member _.``mapFold takes and returns reference tuples`` () = let (results : FlatList), (state : int) = FlatList.mapFold (fun state item -> item * state, state + 1) 1 array @@ -112,7 +112,7 @@ type FlatListTupleTests () = ) Assert.AreEqual(expectedState, state, "Tuple.FlatList.mapFold must thread the state as List.mapFold does") - [] + [] member _.``mapFoldBack takes and returns reference tuples`` () = let (results : FlatList), (state : int) = FlatList.mapFoldBack (fun item state -> item * state, state + 1) array 1 @@ -125,7 +125,7 @@ type FlatListTupleTests () = ) Assert.AreEqual(expectedState, state, "Tuple.FlatList.mapFoldBack must thread the state as List.mapFoldBack does") - [] + [] member _.``countBy returns reference tuples`` () = let counts : FlatList = FlatList.countBy (fun item -> item % 3) array @@ -135,7 +135,7 @@ type FlatListTupleTests () = "Tuple.FlatList.countBy must count as List.countBy does" ) - [] + [] member _.``groupBy returns reference tuples`` () = let groups : FlatList> = FlatList.groupBy (fun item -> item % 3) array @@ -147,7 +147,7 @@ type FlatListTupleTests () = "Tuple.FlatList.groupBy must group as List.groupBy does" ) - [] + [] member _.``partition returns a reference tuple`` () = let (accepted : FlatList), (rejected : FlatList) = FlatList.partition (fun item -> item > 0) array @@ -163,7 +163,7 @@ type FlatListTupleTests () = "Tuple.FlatList.partition must return the rejected elements" ) - [] + [] member _.``partitionWith returns a reference tuple`` () = let partitioner (item : int) = if item > 0 then @@ -183,14 +183,14 @@ type FlatListTupleTests () = "Tuple.FlatList.partitionWith must return the second choices" ) - [] + [] member _.``splitAt returns a reference tuple`` () = let (before : FlatList), (after : FlatList) = FlatList.splitAt 3 array let expectedBefore, expectedAfter = List.splitAt 3 Inputs.several Assert.AreEqual(expectedBefore, FlatList.toList before, "Tuple.FlatList.splitAt must return the first part") Assert.AreEqual(expectedAfter, FlatList.toList after, "Tuple.FlatList.splitAt must return the second part") - [] + [] member _.``the functions without a tuple keep resolving to the default module`` () = let found : int voption = FlatList.tryFind (fun item -> item > 6) array Assert.AreEqual(ValueSome 8, found, "FlatList.tryFind must resolve to the default module") From 2925ceb1541e2744387d6e364c5581e2d36fdb28 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 15:32:18 +0200 Subject: [PATCH 08/27] fixup! feat: `FlatList` module with the vocabulary of the FSharp.Core `Array` module Two crefs name the right members: `ImmutableArray.Empty` is a field, and the ID of `ofBuilder` writes the nested generic builder as `ImmutableArray.Builder{``0}`. Co-Authored-By: Claude Opus 5.5 --- src/FSharp.Collections.Immutable/FlatList/FlatList.fsi | 2 +- src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi b/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi index 9e17357b..19f9f4da 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi +++ b/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi @@ -39,7 +39,7 @@ open System.Collections.Immutable val create : count : int -> value : 'T -> FlatList<'T> /// The empty flat list. -/// O(1). The value is . +/// O(1). The value is . [] [] val empty<'T> : FlatList<'T> diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs b/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs index f28b09e3..50533a5d 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs @@ -4,7 +4,7 @@ /// /// The builder is mutable. The functions that change it return the same builder, so that the calls chain with the pipe /// operator. creates a builder, and -/// +/// /// returns its contents as a flat list. /// [] From f946e17db89dc7db7da7e2d5dc763fcfb4689ae8 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 16:56:20 +0200 Subject: [PATCH 09/27] fixup! test: shared contracts of the sequence-like modules, run for `FlatList` The tests compare arrays and take their cases as data rows, so every case runs and reports as a test of its own (708 tests per target framework instead of 267): - The ops records observe a collection with its `toArray`, and results of another element type are arrays. `SeqOps.ToArray` is both the observation and the `toArray` under test; `toList` gets a field and a test of its own, because it no longer observes the collections. `Concat` and `Transpose` take the sequence that the module takes; `Average`, `Unzip` and `Unzip3` take arrays. - `SeqContractBase.ForEach` is gone: a test takes its input from `DynamicData (nameof Inputs.All, typeof)` and builds the collection and its elements with `Create`. Loops inside a test become rows from a source next to it (`InputsAndSizes`, `InputsAndCounts`, ...) or `DataRow` constants. - A row holds printable data, because MSTest names a row by its values: predicates are the cases of `ElementPredicate` and `PairPredicate`, whose `Matches` the test takes, and the tables of `FlatListTests` give their names as rows. - Expectations come from the FSharp.Core `Array` functions; exceptions still come from the `Array` or the `List` oracle, the latter on `List.ofArray`. Lists remain only as the oracles, as the input of `ofList`, the list case of `ofSeq` and the list inputs of the functions under test. - Tuples and value options that hold arrays are compared by their parts. - The empty collection of `lastIndexOf` and `lastIndexOfWith` is a test of its own instead of a check repeated by every row. Co-Authored-By: Claude Opus 5.5 --- .../Contracts/IndexedContract.fs | 370 +++++++------ .../Contracts/SeqCreationContract.fs | 136 ++--- .../SeqLike/SeqLikeAccessContract.fs | 202 ++++--- .../Contracts/SeqLike/SeqLikeContract.fs | 46 +- .../SeqLike/SeqLikeFoldingContract.fs | 449 ++++++++-------- .../SeqLike/SeqLikePartitioningContract.fs | 464 ++++++++-------- .../SeqLike/SeqLikeRandomContract.fs | 411 +++++++------- .../SeqLike/SeqLikeSearchContract.fs | 442 ++++++++------- .../SeqLike/SeqLikeSortingContract.fs | 106 ++-- .../SeqLike/SeqLikeTransformationContract.fs | 501 +++++++++--------- .../Contracts/SeqOps.fs | 63 +-- .../FlatList/FlatListBuilderTests.fs | 66 ++- .../FlatListComputationExpressionTests.fs | 28 +- .../FlatList/FlatListOps.fs | 56 +- .../FlatList/FlatListOptionTests.fs | 74 +-- .../FlatList/FlatListTests.fs | 241 +++++---- .../FlatList/FlatListTupleTests.fs | 147 ++--- 17 files changed, 1971 insertions(+), 1831 deletions(-) diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/IndexedContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/IndexedContract.fs index 81266540..d8da95a5 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/IndexedContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/IndexedContract.fs @@ -8,6 +8,7 @@ open Microsoft.VisualStudio.TestTools.UnitTesting /// The contract of the index-based functions of FlatList, IndexedList and IIndexedList /// (ADR 0001, Appendix A). /// +/// The tests run on the input with several elements; the indices of their rows are computed from its length. [] type IndexedContract<'C> (ops : IndexedOps<'C>) = inherit ContractBase (ops.Name, ops.Mirror, ops.ParamName) @@ -21,218 +22,271 @@ type IndexedContract<'C> (ops : IndexedOps<'C>) = /// The collection of the input with several elements and the elements that it enumerates. let several () = - let collection = ops.OfSeq Inputs.several - struct (collection, ops.ToList collection) + let collection = ops.OfSeq Inputs.Several + struct (collection, ops.ToArray collection) - [] - member _.``item returns the element at the index`` () = - let struct (collection, elements) = several () + /// Every index of an element. + static member Indices = seq { 0 .. Inputs.Several.Length - 1 } - for index in 0 .. elements.Length - 1 do - Assert.AreEqual(List.item index elements, ops.Item index collection, $"%s{ops.Name}.item %d{index}") + /// The indices next to the elements: before the first one and after the last one. + static member IndicesOutOfRange = seq { + -1 + Inputs.Several.Length + } - [] - member this.``item throws for an index out of range as FSharp.Core does`` () = - let struct (collection, elements) = several () + /// Every index of an element and the indices next to them. + static member IndicesAndOutOfRange = seq { -1 .. Inputs.Several.Length } - for index in [ -1; elements.Length ] do - this.ThrowsLikeFSharpCore ( - (fun () -> Array.item index (List.toArray elements)), - (fun () -> List.item index elements), - (fun () -> ops.Item index collection), - $"item %d{index} must throw" - ) + /// The indices to insert at: before the first element, inside and after the last one. + static member InsertionIndices = seq { + 0 + 3 + Inputs.Several.Length + } - [] - member _.``tryItem returns the element at the index or nothing`` () = + /// The indices to insert at that are out of range: before the first element and beyond the one after the last. + static member InsertionIndicesOutOfRange = seq { + -1 + Inputs.Several.Length + 1 + } + + /// Each index to insert at with no values and with two values. + static member InsertionIndicesAndValues = seq { + for index in IndexedContract<'C>.InsertionIndices do + for values in [| [||]; [| 98; 99 |] |] do + struct (index, values) + } + + /// The indices to remove at: the first element, one inside and the last one. + static member RemovalIndices = seq { + 0 + 3 + Inputs.Several.Length - 1 + } + + /// The ranges to remove, as an index and a count: from the first element, inside, the last element, and no element. + static member RemovalRanges = seq { + struct (0, 2) + struct (3, 3) + struct (Inputs.Several.Length - 1, 1) + struct (2, 0) + } + + /// The ranges that are out of range, as an index and a count: before the first element, beyond the last one, and a + /// negative count. + static member RemovalRangesOutOfRange = seq { + struct (-1, 1) + struct (Inputs.Several.Length - 1, 2) + struct (0, -1) + } + + /// The indices of the first and the last element. + static member FirstAndLastIndices = seq { + 0 + Inputs.Several.Length - 1 + } + + [.Indices)>] + member _.``item returns the element at the index`` (index : int) = + let struct (collection, elements) = several () + Assert.AreEqual( + Array.item index elements, + ops.Item index collection, + $"%s{ops.Name}.item must return the element at the index" + ) + + [.IndicesOutOfRange)>] + member this.``item throws for an index out of range as FSharp.Core does`` (index : int) = let struct (collection, elements) = several () - for index in -1 .. elements.Length do - Assert.AreEqual( - List.tryItem index elements |> ValueOption.ofOption, - ops.TryItem index collection, - $"%s{ops.Name}.tryItem %d{index}" - ) + this.ThrowsLikeFSharpCore ( + (fun () -> Array.item index elements), + (fun () -> List.item index (List.ofArray elements)), + (fun () -> ops.Item index collection), + "item at an index out of range must throw" + ) - [] - member _.``indexOf returns the index of the first equal element or -1`` () = + [.IndicesAndOutOfRange)>] + member _.``tryItem returns the element at the index or nothing`` (index : int) = let struct (collection, elements) = several () - for value in [ 5; -3; 7; 1000 ] do - Assert.AreEqual( - List.tryFindIndex ((=) value) elements - |> Option.defaultValue -1, - ops.IndexOf value collection, - $"%s{ops.Name}.indexOf %d{value}" - ) + Assert.AreEqual( + Array.tryItem index elements |> ValueOption.ofOption, + ops.TryItem index collection, + $"%s{ops.Name}.tryItem must return the element at the index or nothing" + ) - [] - member _.``indexOfWith compares with the equality comparer`` () = + [] + member _.``indexOf returns the index of the first equal element or -1`` (value : int) = let struct (collection, elements) = several () - for value in [ -5; 3; -12; 1000 ] do - Assert.AreEqual( - List.tryFindIndex (fun item -> abs item = abs value) elements - |> Option.defaultValue -1, - ops.IndexOfWith absoluteEquality value collection, - $"%s{ops.Name}.indexOfWith %d{value}" - ) + Assert.AreEqual( + Array.tryFindIndex ((=) value) elements + |> Option.defaultValue -1, + ops.IndexOf value collection, + $"%s{ops.Name}.indexOf must return the index of the first equal element or -1" + ) - [] - member _.``lastIndexOf returns the index of the last equal element or -1`` () = + [] + member _.``indexOfWith compares with the equality comparer`` (value : int) = let struct (collection, elements) = several () - for value in [ 5; -3; 7; 1000 ] do - Assert.AreEqual( - List.tryFindIndexBack ((=) value) elements - |> Option.defaultValue -1, - ops.LastIndexOf value collection, - $"%s{ops.Name}.lastIndexOf %d{value}" - ) + Assert.AreEqual( + Array.tryFindIndex (fun item -> abs item = abs value) elements + |> Option.defaultValue -1, + ops.IndexOfWith absoluteEquality value collection, + $"%s{ops.Name}.indexOfWith must compare with the equality comparer" + ) - Assert.AreEqual(-1, ops.LastIndexOf 5 (ops.OfSeq Inputs.empty), $"%s{ops.Name}.lastIndexOf in an empty collection") + [] + member _.``lastIndexOf returns the index of the last equal element or -1`` (value : int) = + let struct (collection, elements) = several () + + Assert.AreEqual( + Array.tryFindIndexBack ((=) value) elements + |> Option.defaultValue -1, + ops.LastIndexOf value collection, + $"%s{ops.Name}.lastIndexOf must return the index of the last equal element or -1" + ) [] - member _.``lastIndexOfWith compares with the equality comparer`` () = + member _.``lastIndexOf returns -1 in an empty collection`` () = + Assert.AreEqual( + -1, + ops.LastIndexOf 5 (ops.OfSeq Inputs.Empty), + $"%s{ops.Name}.lastIndexOf must return -1 in an empty collection" + ) + + [] + member _.``lastIndexOfWith compares with the equality comparer`` (value : int) = let struct (collection, elements) = several () - for value in [ -5; 3; -12; 1000 ] do - Assert.AreEqual( - List.tryFindIndexBack (fun item -> abs item = abs value) elements - |> Option.defaultValue -1, - ops.LastIndexOfWith absoluteEquality value collection, - $"%s{ops.Name}.lastIndexOfWith %d{value}" - ) + Assert.AreEqual( + Array.tryFindIndexBack (fun item -> abs item = abs value) elements + |> Option.defaultValue -1, + ops.LastIndexOfWith absoluteEquality value collection, + $"%s{ops.Name}.lastIndexOfWith must compare with the equality comparer" + ) + [] + member _.``lastIndexOfWith returns -1 in an empty collection`` () = Assert.AreEqual( -1, - ops.LastIndexOfWith absoluteEquality 5 (ops.OfSeq Inputs.empty), - $"%s{ops.Name}.lastIndexOfWith in an empty collection" + ops.LastIndexOfWith absoluteEquality 5 (ops.OfSeq Inputs.Empty), + $"%s{ops.Name}.lastIndexOfWith must return -1 in an empty collection" ) - [] - member _.``add appends the value`` () = - for input in Inputs.all do - let collection = ops.OfSeq input + [)>] + member _.``add appends the value`` (input : int array) = + let collection = ops.OfSeq input - Assert.AreEqual( - ops.ToList collection @ [ 99 ], - ops.ToList (ops.Add 99 collection), - $"%s{ops.Name}.add to %A{input}" - ) + CollectionAssert.AreEqual ( + Array.append (ops.ToArray collection) [| 99 |], + ops.ToArray (ops.Add 99 collection), + $"%s{ops.Name}.add must append the value" + ) - [] - member _.``insertAt inserts the value at the index`` () = + [.InsertionIndices)>] + member _.``insertAt inserts the value at the index`` (index : int) = let struct (collection, elements) = several () - for index in [ 0; 3; elements.Length ] do - Assert.AreEqual( - List.insertAt index 99 elements, - ops.ToList (ops.InsertAt index 99 collection), - $"%s{ops.Name}.insertAt %d{index}" - ) + CollectionAssert.AreEqual ( + Array.insertAt index 99 elements, + ops.ToArray (ops.InsertAt index 99 collection), + $"%s{ops.Name}.insertAt must insert the value at the index" + ) - [] - member this.``insertAt throws for an index out of range as FSharp.Core does`` () = + [.InsertionIndicesOutOfRange)>] + member this.``insertAt throws for an index out of range as FSharp.Core does`` (index : int) = let struct (collection, elements) = several () - for index in [ -1; elements.Length + 1 ] do - this.ThrowsLikeFSharpCore ( - (fun () -> Array.insertAt index 99 (List.toArray elements)), - (fun () -> List.insertAt index 99 elements), - (fun () -> ops.InsertAt index 99 collection), - $"insertAt %d{index} must throw" - ) + this.ThrowsLikeFSharpCore ( + (fun () -> Array.insertAt index 99 elements), + (fun () -> List.insertAt index 99 (List.ofArray elements)), + (fun () -> ops.InsertAt index 99 collection), + "insertAt at an index out of range must throw" + ) - [] - member _.``insertManyAt inserts the values at the index`` () = + [.InsertionIndicesAndValues)>] + member _.``insertManyAt inserts the values at the index`` (index : int, values : int array) = let struct (collection, elements) = several () - for index in [ 0; 3; elements.Length ] do - for values in [ []; [ 98; 99 ] ] do - Assert.AreEqual( - List.insertManyAt index values elements, - ops.ToList (ops.InsertManyAt index values collection), - $"%s{ops.Name}.insertManyAt %d{index} %A{values}" - ) + CollectionAssert.AreEqual ( + Array.insertManyAt index values elements, + ops.ToArray (ops.InsertManyAt index values collection), + $"%s{ops.Name}.insertManyAt must insert the values at the index" + ) - [] - member this.``insertManyAt throws for an index out of range as FSharp.Core does`` () = + [.InsertionIndicesOutOfRange)>] + member this.``insertManyAt throws for an index out of range as FSharp.Core does`` (index : int) = let struct (collection, elements) = several () - for index in [ -1; elements.Length + 1 ] do - this.ThrowsLikeFSharpCore ( - (fun () -> Array.insertManyAt index [ 99 ] (List.toArray elements)), - (fun () -> List.insertManyAt index [ 99 ] elements), - (fun () -> ops.InsertManyAt index [ 99 ] collection), - $"insertManyAt %d{index} must throw" - ) + this.ThrowsLikeFSharpCore ( + (fun () -> Array.insertManyAt index [ 99 ] elements), + (fun () -> List.insertManyAt index [ 99 ] (List.ofArray elements)), + (fun () -> ops.InsertManyAt index [ 99 ] collection), + "insertManyAt at an index out of range must throw" + ) - [] - member _.``removeAt removes the element at the index`` () = + [.RemovalIndices)>] + member _.``removeAt removes the element at the index`` (index : int) = let struct (collection, elements) = several () - for index in [ 0; 3; elements.Length - 1 ] do - Assert.AreEqual( - List.removeAt index elements, - ops.ToList (ops.RemoveAt index collection), - $"%s{ops.Name}.removeAt %d{index}" - ) + CollectionAssert.AreEqual ( + Array.removeAt index elements, + ops.ToArray (ops.RemoveAt index collection), + $"%s{ops.Name}.removeAt must remove the element at the index" + ) - [] - member this.``removeAt throws for an index out of range as FSharp.Core does`` () = + [.IndicesOutOfRange)>] + member this.``removeAt throws for an index out of range as FSharp.Core does`` (index : int) = let struct (collection, elements) = several () - for index in [ -1; elements.Length ] do - this.ThrowsLikeFSharpCore ( - (fun () -> Array.removeAt index (List.toArray elements)), - (fun () -> List.removeAt index elements), - (fun () -> ops.RemoveAt index collection), - $"removeAt %d{index} must throw" - ) + this.ThrowsLikeFSharpCore ( + (fun () -> Array.removeAt index elements), + (fun () -> List.removeAt index (List.ofArray elements)), + (fun () -> ops.RemoveAt index collection), + "removeAt at an index out of range must throw" + ) - [] - member _.``removeManyAt removes the elements of the range`` () = + [.RemovalRanges)>] + member _.``removeManyAt removes the elements of the range`` (index : int, count : int) = let struct (collection, elements) = several () - for struct (index, count) in [ struct (0, 2); struct (3, 3); struct (elements.Length - 1, 1); struct (2, 0) ] do - Assert.AreEqual( - List.removeManyAt index count elements, - ops.ToList (ops.RemoveManyAt index count collection), - $"%s{ops.Name}.removeManyAt %d{index} %d{count}" - ) + CollectionAssert.AreEqual ( + Array.removeManyAt index count elements, + ops.ToArray (ops.RemoveManyAt index count collection), + $"%s{ops.Name}.removeManyAt must remove the elements of the range" + ) - [] - member this.``removeManyAt throws for a range out of range as FSharp.Core does`` () = + [.RemovalRangesOutOfRange)>] + member this.``removeManyAt throws for a range out of range as FSharp.Core does`` (index : int, count : int) = let struct (collection, elements) = several () - for struct (index, count) in [ struct (-1, 1); struct (elements.Length - 1, 2); struct (0, -1) ] do - this.ThrowsLikeFSharpCore ( - (fun () -> Array.removeManyAt index count (List.toArray elements)), - (fun () -> List.removeManyAt index count elements), - (fun () -> ops.RemoveManyAt index count collection), - $"removeManyAt %d{index} %d{count} must throw" - ) + this.ThrowsLikeFSharpCore ( + (fun () -> Array.removeManyAt index count elements), + (fun () -> List.removeManyAt index count (List.ofArray elements)), + (fun () -> ops.RemoveManyAt index count collection), + "removeManyAt of a range out of range must throw" + ) - [] - member _.``updateAt replaces the element at the index`` () = + [.FirstAndLastIndices)>] + member _.``updateAt replaces the element at the index`` (index : int) = let struct (collection, elements) = several () - for index in [ 0; elements.Length - 1 ] do - Assert.AreEqual( - List.updateAt index 99 elements, - ops.ToList (ops.UpdateAt index 99 collection), - $"%s{ops.Name}.updateAt %d{index}" - ) + CollectionAssert.AreEqual ( + Array.updateAt index 99 elements, + ops.ToArray (ops.UpdateAt index 99 collection), + $"%s{ops.Name}.updateAt must replace the element at the index" + ) - [] - member this.``updateAt throws for an index out of range as FSharp.Core does`` () = + [.IndicesOutOfRange)>] + member this.``updateAt throws for an index out of range as FSharp.Core does`` (index : int) = let struct (collection, elements) = several () - for index in [ -1; elements.Length ] do - this.ThrowsLikeFSharpCore ( - (fun () -> Array.updateAt index 99 (List.toArray elements)), - (fun () -> List.updateAt index 99 elements), - (fun () -> ops.UpdateAt index 99 collection), - $"updateAt %d{index} must throw" - ) + this.ThrowsLikeFSharpCore ( + (fun () -> Array.updateAt index 99 elements), + (fun () -> List.updateAt index 99 (List.ofArray elements)), + (fun () -> ops.UpdateAt index 99 collection), + "updateAt at an index out of range must throw" + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqCreationContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqCreationContract.fs index 07173168..1169acd3 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqCreationContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqCreationContract.fs @@ -13,16 +13,15 @@ type SeqCreationContract<'C> (ops : SeqCreationOps<'C>) = [] member _.``empty has no elements`` () = - Assert.IsEmpty (ops.ToList (ops.Empty ()), $"%s{ops.Name}.empty must have no elements") - - [] - member _.``init creates the elements in index order`` () = - for count in [ 0; 1; 5 ] do - Assert.AreEqual( - List.init count (fun index -> index * index), - ops.ToList (ops.Init count (fun index -> index * index)), - $"%s{ops.Name}.init %d{count}" - ) + Assert.IsEmpty (ops.ToArray (ops.Empty ()), $"%s{ops.Name}.empty must have no elements") + + [] + member _.``init creates the elements in index order`` (count : int) = + CollectionAssert.AreEqual ( + Array.init count (fun index -> index * index), + ops.ToArray (ops.Init count (fun index -> index * index)), + $"%s{ops.Name}.init must create the elements in index order" + ) [] member this.``init throws for a negative count as FSharp.Core does`` () = @@ -33,16 +32,16 @@ type SeqCreationContract<'C> (ops : SeqCreationOps<'C>) = "init -1 must throw" ) - [] - member _.``ofArray copies the elements in order`` () = - for input in Inputs.all do - let source = List.toArray input - let collection = ops.OfArray source + [)>] + member _.``ofArray copies the elements in order`` (input : int array) = + // The test changes the source after the creation, so it changes a copy of its row + let source = Array.copy input + let collection = ops.OfArray source - if source.Length > 0 then - source[0] <- 1000 + if source.Length > 0 then + source[0] <- 1000 - Assert.AreEqual(input, ops.ToList collection, $"%s{ops.Name}.ofArray must copy %A{input}") + CollectionAssert.AreEqual (input, ops.ToArray collection, $"%s{ops.Name}.ofArray must copy the elements in order") [] member this.``ofArray throws for a null array as FSharp.Core does`` () = @@ -55,36 +54,41 @@ type SeqCreationContract<'C> (ops : SeqCreationOps<'C>) = "ofArray of a null array must throw" ) - [] - member _.``ofList keeps the elements in order`` () = - for input in Inputs.all do - Assert.AreEqual(input, ops.ToList (ops.OfList input), $"%s{ops.Name}.ofList of %A{input}") + [)>] + member _.``ofList keeps the elements in order`` (input : int array) = + CollectionAssert.AreEqual ( + input, + ops.ToArray (ops.OfList (List.ofArray input)), + $"%s{ops.Name}.ofList must keep the elements in order" + ) - [] - member _.``ofReadOnlySpan copies the elements in order`` () = - for input in Inputs.all do - Assert.AreEqual( - input, - ops.ToList (ops.OfReadOnlySpan (List.toArray input)), - $"%s{ops.Name}.ofReadOnlySpan of %A{input}" - ) + [)>] + member _.``ofReadOnlySpan copies the elements in order`` (input : int array) = + CollectionAssert.AreEqual ( + input, + ops.ToArray (ops.OfReadOnlySpan input), + $"%s{ops.Name}.ofReadOnlySpan must copy the elements in order" + ) - [] - member _.``ofSeq keeps the elements in order`` () = - for input in Inputs.all do - Assert.AreEqual(input, ops.ToList (ops.OfSeq input), $"%s{ops.Name}.ofSeq of the list %A{input}") - - Assert.AreEqual( - input, - ops.ToList (ops.OfSeq (List.toArray input)), - $"%s{ops.Name}.ofSeq of the array %A{input}" - ) - - Assert.AreEqual( - input, - ops.ToList (ops.OfSeq (Seq.map id input)), - $"%s{ops.Name}.ofSeq of a lazy sequence of %A{input}" - ) + [)>] + member _.``ofSeq keeps the elements in order`` (input : int array) = + CollectionAssert.AreEqual ( + input, + ops.ToArray (ops.OfSeq (List.ofArray input)), + $"%s{ops.Name}.ofSeq of a list must keep the elements in order" + ) + + CollectionAssert.AreEqual ( + input, + ops.ToArray (ops.OfSeq input), + $"%s{ops.Name}.ofSeq of an array must keep the elements in order" + ) + + CollectionAssert.AreEqual ( + input, + ops.ToArray (ops.OfSeq (Seq.map id input)), + $"%s{ops.Name}.ofSeq of a lazy sequence must keep the elements in order" + ) [] member this.``ofSeq throws for a null sequence as FSharp.Core does`` () = @@ -97,19 +101,17 @@ type SeqCreationContract<'C> (ops : SeqCreationOps<'C>) = "ofSeq of a null sequence must throw" ) - [] - member _.``ofSpan copies the elements in order`` () = - for input in Inputs.all do - Assert.AreEqual(input, ops.ToList (ops.OfSpan (List.toArray input)), $"%s{ops.Name}.ofSpan of %A{input}") + [)>] + member _.``ofSpan copies the elements in order`` (input : int array) = + CollectionAssert.AreEqual (input, ops.ToArray (ops.OfSpan input), $"%s{ops.Name}.ofSpan must copy the elements in order") - [] - member _.``replicate repeats the value`` () = - for count in [ 0; 1; 4 ] do - Assert.AreEqual( - List.replicate count 7, - ops.ToList (ops.Replicate count 7), - $"%s{ops.Name}.replicate %d{count}" - ) + [] + member _.``replicate repeats the value`` (count : int) = + CollectionAssert.AreEqual ( + Array.replicate count 7, + ops.ToArray (ops.Replicate count 7), + $"%s{ops.Name}.replicate must repeat the value" + ) [] member this.``replicate throws for a negative count as FSharp.Core does`` () = @@ -122,20 +124,20 @@ type SeqCreationContract<'C> (ops : SeqCreationOps<'C>) = [] member _.``singleton has one element`` () = - Assert.AreEqual([ 42 ], ops.ToList (ops.Singleton 42), $"%s{ops.Name}.singleton must have one element") + CollectionAssert.AreEqual ([| 42 |], ops.ToArray (ops.Singleton 42), $"%s{ops.Name}.singleton must have one element") - [] - member _.``unfold generates the elements until the generator stops`` () = + [] + member _.``unfold generates the elements until the generator stops`` (start : int) = let generator (state : int) = if state > 0 then ValueSome (struct (state * 10, state - 1)) else ValueNone + let generatorOption (state : int) = if state > 0 then Some (state * 10, state - 1) else None - for start in [ 0; 1; 5 ] do - Assert.AreEqual( - List.unfold generatorOption start, - ops.ToList (ops.Unfold generator start), - $"%s{ops.Name}.unfold from %d{start}" - ) + CollectionAssert.AreEqual ( + Array.unfold generatorOption start, + ops.ToArray (ops.Unfold generator start), + $"%s{ops.Name}.unfold must generate the elements until the generator stops" + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeAccessContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeAccessContract.fs index bf559a75..0fff13c1 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeAccessContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeAccessContract.fs @@ -5,107 +5,103 @@ open Microsoft.VisualStudio.TestTools.UnitTesting /// The helpers that the contract bases of the sequence-like modules share. /// -/// The contract tests compute their expectations with the FSharp.Core List module over the elements in the order +/// The contract tests compute their expectations with the FSharp.Core Array module over the elements in the order /// in which the collection enumerates them, so one test body serves FIFO, LIFO and indexed collections (ADR 0001, D8). /// [] type SeqContractBase<'C> (ops : SeqOps<'C>) = inherit ContractBase (ops.Name, ops.Mirror, ops.ParamName) - /// Runs a check on every given input: the collection of its elements and the elements that the collection enumerates. - member _.ForEach (inputs : int list list, check : 'C -> int list -> unit) = - for input in inputs do - let collection = ops.OfSeq input - check collection (ops.ToList collection) + /// The collection of the elements of an input and the elements in the order in which the collection enumerates them. + member _.Create (input : int array) = + let collection = ops.OfSeq input + struct (collection, ops.ToArray collection) /// The contract of the properties and the access functions of a sequence-like module. [] type SeqLikeAccessContract<'C> (ops : SeqOps<'C>) = inherit SeqContractBase<'C> (ops) + /// The inputs that do not have exactly one element. + static member NotSingletons = seq { + Inputs.Empty + Inputs.Several + } + // ---- Properties ---- - [] - member this.``isEmpty is true only for a collection without elements`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual(List.isEmpty elements, ops.IsEmpty collection, $"%s{ops.Name}.isEmpty of %A{elements}") - ) + [)>] + member this.``isEmpty is true only for a collection without elements`` (input : int array) = + let struct (collection, elements) = this.Create input - [] - member this.``length counts the elements`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual(List.length elements, ops.Length collection, $"%s{ops.Name}.length of %A{elements}") + Assert.AreEqual( + Array.isEmpty elements, + ops.IsEmpty collection, + $"%s{ops.Name}.isEmpty must be true only for a collection without elements" ) + [)>] + member this.``length counts the elements`` (input : int array) = + let struct (collection, elements) = this.Create input + Assert.AreEqual(Array.length elements, ops.Length collection, $"%s{ops.Name}.length must count the elements") + // ---- Access ---- [] - member this.``exactlyOne returns the only element`` () = - let collection = ops.OfSeq Inputs.singleton + member _.``exactlyOne returns the only element`` () = + let collection = ops.OfSeq Inputs.Singleton + Assert.AreEqual( - List.exactlyOne Inputs.singleton, + Array.exactlyOne Inputs.Singleton, ops.ExactlyOne collection, - $"%s{ops.Name}.exactlyOne of one element" + $"%s{ops.Name}.exactlyOne must return the only element" ) - [] - member this.``exactlyOne throws for no element or several elements as FSharp.Core does`` () = - for input in [ Inputs.empty; Inputs.several ] do - this.ThrowsLikeFSharpCore ( - (fun () -> Array.exactlyOne (List.toArray input)), - (fun () -> List.exactlyOne input), - (fun () -> ops.ExactlyOne (ops.OfSeq input)), - $"exactlyOne of %A{input} must throw" - ) - - [] - member this.``head returns the first element`` () = - this.ForEach ( - Inputs.nonEmpty, - fun collection elements -> - Assert.AreEqual(List.head elements, ops.Head collection, $"%s{ops.Name}.head of %A{elements}") + [.NotSingletons)>] + member this.``exactlyOne throws for no element or several elements as FSharp.Core does`` (input : int array) = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.exactlyOne input), + (fun () -> List.exactlyOne (List.ofArray input)), + (fun () -> ops.ExactlyOne (ops.OfSeq input)), + "exactlyOne of no element or of several elements must throw" ) + [)>] + member this.``head returns the first element`` (input : int array) = + let struct (collection, elements) = this.Create input + Assert.AreEqual(Array.head elements, ops.Head collection, $"%s{ops.Name}.head must return the first element") + [] member this.``head throws for an empty collection as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.head Array.empty), (fun () -> List.head List.empty), - (fun () -> ops.Head (ops.OfSeq Inputs.empty)), + (fun () -> ops.Head (ops.OfSeq Inputs.Empty)), "head of an empty collection must throw" ) - [] - member this.``last returns the last element`` () = - this.ForEach ( - Inputs.nonEmpty, - fun collection elements -> - Assert.AreEqual(List.last elements, ops.Last collection, $"%s{ops.Name}.last of %A{elements}") - ) + [)>] + member this.``last returns the last element`` (input : int array) = + let struct (collection, elements) = this.Create input + Assert.AreEqual(Array.last elements, ops.Last collection, $"%s{ops.Name}.last must return the last element") [] member this.``last throws for an empty collection as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.last Array.empty), (fun () -> List.last List.empty), - (fun () -> ops.Last (ops.OfSeq Inputs.empty)), + (fun () -> ops.Last (ops.OfSeq Inputs.Empty)), "last of an empty collection must throw" ) - [] - member this.``tail returns the elements after the first one`` () = - this.ForEach ( - Inputs.nonEmpty, - fun collection elements -> - Assert.AreEqual( - List.tail elements, - ops.ToList (ops.Tail collection), - $"%s{ops.Name}.tail of %A{elements}" - ) + [)>] + member this.``tail returns the elements after the first one`` (input : int array) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.tail elements, + ops.ToArray (ops.Tail collection), + $"%s{ops.Name}.tail must return the elements after the first one" ) [] @@ -113,59 +109,57 @@ type SeqLikeAccessContract<'C> (ops : SeqOps<'C>) = this.ThrowsLikeFSharpCore ( (fun () -> Array.tail Array.empty), (fun () -> List.tail List.empty), - (fun () -> ops.Tail (ops.OfSeq Inputs.empty)), + (fun () -> ops.Tail (ops.OfSeq Inputs.Empty)), "tail of an empty collection must throw" ) - [] - member this.``tryExactlyOne returns the only element or nothing`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.tryExactlyOne elements |> ValueOption.ofOption, - ops.TryExactlyOne collection, - $"%s{ops.Name}.tryExactlyOne of %A{elements}" - ) + [)>] + member this.``tryExactlyOne returns the only element or nothing`` (input : int array) = + let struct (collection, elements) = this.Create input + + Assert.AreEqual( + Array.tryExactlyOne elements |> ValueOption.ofOption, + ops.TryExactlyOne collection, + $"%s{ops.Name}.tryExactlyOne must return the only element or nothing" ) - [] - member this.``tryHead returns the first element or nothing`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.tryHead elements |> ValueOption.ofOption, - ops.TryHead collection, - $"%s{ops.Name}.tryHead of %A{elements}" - ) + [)>] + member this.``tryHead returns the first element or nothing`` (input : int array) = + let struct (collection, elements) = this.Create input + + Assert.AreEqual( + Array.tryHead elements |> ValueOption.ofOption, + ops.TryHead collection, + $"%s{ops.Name}.tryHead must return the first element or nothing" ) - [] - member this.``tryLast returns the last element or nothing`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.tryLast elements |> ValueOption.ofOption, - ops.TryLast collection, - $"%s{ops.Name}.tryLast of %A{elements}" - ) + [)>] + member this.``tryLast returns the last element or nothing`` (input : int array) = + let struct (collection, elements) = this.Create input + + Assert.AreEqual( + Array.tryLast elements |> ValueOption.ofOption, + ops.TryLast collection, + $"%s{ops.Name}.tryLast must return the last element or nothing" ) - [] - member this.``tryTail returns the elements after the first one or nothing`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - let expected = - match elements with - | [] -> ValueNone - | _ :: rest -> ValueSome rest - - Assert.AreEqual( - expected, - ops.TryTail collection |> ValueOption.map ops.ToList, - $"%s{ops.Name}.tryTail of %A{elements}" - ) + [)>] + member this.``tryTail returns the elements after the first one or nothing`` (input : int array) = + let struct (collection, elements) = this.Create input + let tail = ops.TryTail collection + + Assert.AreEqual( + not (Array.isEmpty elements), + tail.IsSome, + $"%s{ops.Name}.tryTail must return a tail exactly for a collection with elements" ) + + // CollectionAssert compares the elements of the tail and reports the first one that differs + match tail with + | ValueSome tail -> + CollectionAssert.AreEqual ( + Array.tail elements, + ops.ToArray tail, + $"%s{ops.Name}.tryTail must return the elements after the first one" + ) + | ValueNone -> () diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeContract.fs index e4c33950..613eeee7 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeContract.fs @@ -14,30 +14,44 @@ open Microsoft.VisualStudio.TestTools.UnitTesting type SeqLikeContract<'C> (ops : SeqOps<'C>) = inherit SeqLikeRandomContract<'C> (ops) - [] - member this.``toArray returns the elements in order`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - CollectionAssert.AreEqual (List.toArray elements, ops.ToArray collection, $"%s{ops.Name}.toArray of %A{elements}") + [)>] + member _.``toArray returns the elements in order`` (input : int array) = + // The other tests observe a collection with toArray, so toArray is checked against the sequence that the collection + // was created from, which ofSeq keeps in its order + CollectionAssert.AreEqual ( + input, + ops.ToArray (ops.OfSeq input), + $"%s{ops.Name}.toArray must return the elements in the order of the sequence that the collection was created from" ) [] member _.``toArray returns a new array`` () = - let collection = ops.OfSeq Inputs.several + let collection = ops.OfSeq Inputs.Several let array = ops.ToArray collection array[0] <- 1000 - Assert.AreEqual( - Inputs.several, - ops.ToList collection, + CollectionAssert.AreEqual ( + Inputs.Several, + ops.ToArray collection, $"%s{ops.Name}.toArray must return a copy that does not change the collection" ) - [] - member this.``toSeq returns the elements in order`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual(elements, List.ofSeq (ops.ToSeq collection), $"%s{ops.Name}.toSeq of %A{elements}") + [)>] + member this.``toList returns the elements in order`` (input : int array) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + elements, + List.toArray (ops.ToList collection), + $"%s{ops.Name}.toList must return the elements in the order in which the collection enumerates them" + ) + + [)>] + member this.``toSeq returns the elements in order`` (input : int array) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + elements, + Seq.toArray (ops.ToSeq collection), + $"%s{ops.Name}.toSeq must return the elements in the order in which the collection enumerates them" ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeFoldingContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeFoldingContract.fs index 4d15034e..308cdf50 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeFoldingContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeFoldingContract.fs @@ -9,35 +9,51 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = inherit SeqLikeTransformationContract<'C> (ops) /// A folder whose state records the order in which it saw the elements. - static let record (state : int list) (item : int) = item :: state + static let record (visited : int array) (item : int) = Array.append visited [| item |] /// A folder from the end whose state records the order in which it saw the elements. - static let recordBack (item : int) (state : int list) = item :: state + static let recordBack (item : int) (visited : int array) = Array.append visited [| item |] - [] - member _.``average returns the average of the elements`` () = - for input in [ [ 1.5 ]; [ 1.0; 2.0; 4.5; -3.0 ] ] do - Assert.AreEqual(List.average input, ops.Average input, $"%s{ops.Name}.average of %A{input}") + /// Every input with every input, with a prefix of the input with several elements and with an input that differs from + /// that one at the second element. + static member ComparedInputs = + let others = seq { + yield! Inputs.All + [| 5; -3 |] + [| 5; 0 |] + } + + seq { + for first in Inputs.All do + for second in others do + struct (first, second) + } + + [] + member _.``average returns the average of the elements`` (input : float array) = + Assert.AreEqual( + Array.average input, + ops.Average input, + $"%s{ops.Name}.average must return the average of the elements" + ) [] member this.``average throws for an empty collection as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.average Array.empty), (fun () -> List.average List.empty), - (fun () -> ops.Average []), + (fun () -> ops.Average Array.empty), "average of an empty collection must throw" ) - [] - member this.``averageBy returns the average of the projections`` () = - this.ForEach ( - Inputs.nonEmpty, - fun collection elements -> - Assert.AreEqual( - List.averageBy float elements, - ops.AverageBy float collection, - $"%s{ops.Name}.averageBy of %A{elements}" - ) + [)>] + member this.``averageBy returns the average of the projections`` (input : int array) = + let struct (collection, elements) = this.Create input + + Assert.AreEqual( + Array.averageBy float elements, + ops.AverageBy float collection, + $"%s{ops.Name}.averageBy must return the average of the projections" ) [] @@ -45,215 +61,195 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = this.ThrowsLikeFSharpCore ( (fun () -> Array.averageBy float Array.empty), (fun () -> List.averageBy float List.empty), - (fun () -> ops.AverageBy float (ops.OfSeq Inputs.empty)), + (fun () -> ops.AverageBy float (ops.OfSeq Inputs.Empty)), "averageBy of an empty collection must throw" ) - [] - member _.``compareWith compares element by element and then by length`` () = - for first in Inputs.all do - for second in Inputs.all @ [ [ 5; -3 ]; [ 5; 0 ] ] do - let collection = ops.OfSeq first - let other = ops.OfSeq second - - Assert.AreEqual( - List.compareWith compare (ops.ToList collection) (ops.ToList other) - |> sign, - ops.CompareWith compare collection other |> sign, - $"%s{ops.Name}.compareWith of %A{first} and %A{second}" - ) + [.ComparedInputs)>] + member this.``compareWith compares element by element and then by length`` (first : int array, second : int array) = + let struct (collection, elements) = this.Create first + let struct (other, otherElements) = this.Create second - [] - member this.``fold visits the elements from the first one`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.fold record [] elements, - ops.Fold record [] collection, - $"%s{ops.Name}.fold over %A{elements}" - ) + Assert.AreEqual( + Array.compareWith compare elements otherElements |> sign, + ops.CompareWith compare collection other |> sign, + $"%s{ops.Name}.compareWith must compare element by element and then by length" + ) + + [)>] + member this.``fold visits the elements from the first one`` (input : int array) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.fold record [||] elements, + ops.Fold record [||] collection, + $"%s{ops.Name}.fold must visit the elements from the first one" ) [] - member _.``fold2 visits the pairs from the first index`` () = - let collection = ops.OfSeq Inputs.several - let other = ops.OfSeq Inputs.other - let folder (state : int list) a b = a * 100 + b :: state - - Assert.AreEqual( - List.fold2 folder [] (ops.ToList collection) (ops.ToList other), - ops.Fold2 folder [] collection other, - $"%s{ops.Name}.fold2 over two collections" + member this.``fold2 visits the pairs from the first index`` () = + let struct (collection, elements) = this.Create Inputs.Several + let struct (other, otherElements) = this.Create Inputs.Other + let folder (visited : int array) a b = Array.append visited [| a * 100 + b |] + + CollectionAssert.AreEqual ( + Array.fold2 folder [||] elements otherElements, + ops.Fold2 folder [||] collection other, + $"%s{ops.Name}.fold2 must visit the pairs from the first index" ) [] member this.``fold2 throws for different lengths as FSharp.Core does`` () = - let folder (state : int list) (_ : int) (_ : int) = state + let folder (state : int array) (_ : int) (_ : int) = state this.ThrowsLikeFSharpCore ( - (fun () -> Array.fold2 folder [] (List.toArray Inputs.singleton) (List.toArray Inputs.several)), - (fun () -> List.fold2 folder [] Inputs.singleton Inputs.several), - (fun () -> ops.Fold2 folder [] (ops.OfSeq Inputs.singleton) (ops.OfSeq Inputs.several)), + (fun () -> Array.fold2 folder [||] Inputs.Singleton Inputs.Several), + (fun () -> List.fold2 folder [||] (List.ofArray Inputs.Singleton) (List.ofArray Inputs.Several)), + (fun () -> ops.Fold2 folder [||] (ops.OfSeq Inputs.Singleton) (ops.OfSeq Inputs.Several)), "fold2 of collections of different lengths must throw" ) - [] - member this.``foldBack visits the elements from the last one`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.foldBack recordBack elements [], - ops.FoldBack recordBack collection [], - $"%s{ops.Name}.foldBack over %A{elements}" - ) + [)>] + member this.``foldBack visits the elements from the last one`` (input : int array) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.foldBack recordBack elements [||], + ops.FoldBack recordBack collection [||], + $"%s{ops.Name}.foldBack must visit the elements from the last one" ) [] - member _.``foldBack2 visits the pairs from the last index`` () = - let collection = ops.OfSeq Inputs.several - let other = ops.OfSeq Inputs.other - let folder a b (state : int list) = a * 100 + b :: state - - Assert.AreEqual( - List.foldBack2 folder (ops.ToList collection) (ops.ToList other) [], - ops.FoldBack2 folder collection other [], - $"%s{ops.Name}.foldBack2 over two collections" + member this.``foldBack2 visits the pairs from the last index`` () = + let struct (collection, elements) = this.Create Inputs.Several + let struct (other, otherElements) = this.Create Inputs.Other + let folder a b (visited : int array) = Array.append visited [| a * 100 + b |] + + CollectionAssert.AreEqual ( + Array.foldBack2 folder elements otherElements [||], + ops.FoldBack2 folder collection other [||], + $"%s{ops.Name}.foldBack2 must visit the pairs from the last index" ) [] member this.``foldBack2 throws for different lengths as FSharp.Core does`` () = - let folder (_ : int) (_ : int) (state : int list) = state + let folder (_ : int) (_ : int) (state : int array) = state this.ThrowsLikeFSharpCore ( - (fun () -> Array.foldBack2 folder (List.toArray Inputs.singleton) (List.toArray Inputs.several) []), - (fun () -> List.foldBack2 folder Inputs.singleton Inputs.several []), - (fun () -> ops.FoldBack2 folder (ops.OfSeq Inputs.singleton) (ops.OfSeq Inputs.several) []), + (fun () -> Array.foldBack2 folder Inputs.Singleton Inputs.Several [||]), + (fun () -> List.foldBack2 folder (List.ofArray Inputs.Singleton) (List.ofArray Inputs.Several) [||]), + (fun () -> ops.FoldBack2 folder (ops.OfSeq Inputs.Singleton) (ops.OfSeq Inputs.Several) [||]), "foldBack2 of collections of different lengths must throw" ) - [] - member this.``iter visits every element in order`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - let visited = ResizeArray() - ops.Iter visited.Add collection - Assert.AreEqual(elements, List.ofSeq visited, $"%s{ops.Name}.iter over %A{elements}") - ) + [)>] + member this.``iter visits every element in order`` (input : int array) = + let struct (collection, elements) = this.Create input + let visited = ResizeArray() + ops.Iter visited.Add collection + CollectionAssert.AreEqual (elements, visited.ToArray (), $"%s{ops.Name}.iter must visit every element in order") [] - member _.``iter2 visits every pair in order`` () = - let collection = ops.OfSeq Inputs.several - let other = ops.OfSeq Inputs.other + member this.``iter2 visits every pair in order`` () = + let struct (collection, elements) = this.Create Inputs.Several + let struct (other, otherElements) = this.Create Inputs.Other let visited = ResizeArray() ops.Iter2 (fun a b -> visited.Add (struct (a, b))) collection other - Assert.AreEqual( - List.zip (ops.ToList collection) (ops.ToList other) - |> List.map (fun (a, b) -> struct (a, b)), - List.ofSeq visited, - $"%s{ops.Name}.iter2 over two collections" + CollectionAssert.AreEqual ( + Array.zip elements otherElements + |> Array.map (fun (a, b) -> struct (a, b)), + visited.ToArray (), + $"%s{ops.Name}.iter2 must visit every pair in order" ) [] member this.``iter2 throws for different lengths as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( - (fun () -> Array.iter2 (fun _ _ -> ()) (List.toArray Inputs.singleton) (List.toArray Inputs.several)), - (fun () -> List.iter2 (fun _ _ -> ()) Inputs.singleton Inputs.several), - (fun () -> ops.Iter2 (fun _ _ -> ()) (ops.OfSeq Inputs.singleton) (ops.OfSeq Inputs.several)), + (fun () -> Array.iter2 (fun _ _ -> ()) Inputs.Singleton Inputs.Several), + (fun () -> List.iter2 (fun _ _ -> ()) (List.ofArray Inputs.Singleton) (List.ofArray Inputs.Several)), + (fun () -> ops.Iter2 (fun _ _ -> ()) (ops.OfSeq Inputs.Singleton) (ops.OfSeq Inputs.Several)), "iter2 of collections of different lengths must throw" ) - [] - member this.``iteri visits every element with its index`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - let visited = ResizeArray() - ops.Iteri (fun index item -> visited.Add (struct (index, item))) collection - - Assert.AreEqual( - List.indexed elements - |> List.map (fun (index, item) -> struct (index, item)), - List.ofSeq visited, - $"%s{ops.Name}.iteri over %A{elements}" - ) + [)>] + member this.``iteri visits every element with its index`` (input : int array) = + let struct (collection, elements) = this.Create input + let visited = ResizeArray() + ops.Iteri (fun index item -> visited.Add (struct (index, item))) collection + + CollectionAssert.AreEqual ( + Array.indexed elements + |> Array.map (fun (index, item) -> struct (index, item)), + visited.ToArray (), + $"%s{ops.Name}.iteri must visit every element with its index" ) [] - member _.``iteri2 visits every pair with its index`` () = - let collection = ops.OfSeq Inputs.several - let other = ops.OfSeq Inputs.other + member this.``iteri2 visits every pair with its index`` () = + let struct (collection, elements) = this.Create Inputs.Several + let struct (other, otherElements) = this.Create Inputs.Other let visited = ResizeArray() ops.Iteri2 (fun index a b -> visited.Add (struct (index, a, b))) collection other - Assert.AreEqual( - List.zip (ops.ToList collection) (ops.ToList other) - |> List.mapi (fun index (a, b) -> struct (index, a, b)), - List.ofSeq visited, - $"%s{ops.Name}.iteri2 over two collections" + CollectionAssert.AreEqual ( + Array.zip elements otherElements + |> Array.mapi (fun index (a, b) -> struct (index, a, b)), + visited.ToArray (), + $"%s{ops.Name}.iteri2 must visit every pair with its index" ) [] member this.``iteri2 throws for different lengths as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( - (fun () -> Array.iteri2 (fun _ _ _ -> ()) (List.toArray Inputs.singleton) (List.toArray Inputs.several)), - (fun () -> List.iteri2 (fun _ _ _ -> ()) Inputs.singleton Inputs.several), - (fun () -> ops.Iteri2 (fun _ _ _ -> ()) (ops.OfSeq Inputs.singleton) (ops.OfSeq Inputs.several)), + (fun () -> Array.iteri2 (fun _ _ _ -> ()) Inputs.Singleton Inputs.Several), + (fun () -> List.iteri2 (fun _ _ _ -> ()) (List.ofArray Inputs.Singleton) (List.ofArray Inputs.Several)), + (fun () -> ops.Iteri2 (fun _ _ _ -> ()) (ops.OfSeq Inputs.Singleton) (ops.OfSeq Inputs.Several)), "iteri2 of collections of different lengths must throw" ) - [] - member this.``mapFold maps the elements and threads the state from the first one`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - let expectedResults, expectedState = List.mapFold (fun state item -> item * state, state + 1) 1 elements - let struct (results, state) = - ops.MapFold (fun state item -> struct (item * state, state + 1)) 1 collection - Assert.AreEqual(expectedResults, results, $"%s{ops.Name}.mapFold results over %A{elements}") - Assert.AreEqual(expectedState, state, $"%s{ops.Name}.mapFold state over %A{elements}") - ) - - [] - member this.``mapFoldBack maps the elements and threads the state from the last one`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - let expectedResults, expectedState = - List.mapFoldBack (fun item state -> item * state, state + 1) elements 1 - - let struct (results, state) = - ops.MapFoldBack (fun item state -> struct (item * state, state + 1)) collection 1 - Assert.AreEqual(expectedResults, results, $"%s{ops.Name}.mapFoldBack results over %A{elements}") - Assert.AreEqual(expectedState, state, $"%s{ops.Name}.mapFoldBack state over %A{elements}") - ) - - [] - member this.``max returns the greatest element`` () = - this.ForEach ( - Inputs.nonEmpty, - fun collection elements -> - Assert.AreEqual(List.max elements, ops.Max collection, $"%s{ops.Name}.max of %A{elements}") - ) + [)>] + member this.``mapFold maps the elements and threads the state from the first one`` (input : int array) = + let struct (collection, elements) = this.Create input + let expectedResults, expectedState = + Array.mapFold (fun state item -> item * state, state + 1) 1 elements + let struct (results, state) = + ops.MapFold (fun state item -> struct (item * state, state + 1)) 1 collection + CollectionAssert.AreEqual (expectedResults, results, $"%s{ops.Name}.mapFold must map the elements from the first one") + Assert.AreEqual(expectedState, state, $"%s{ops.Name}.mapFold must thread the state from the first element") + + [)>] + member this.``mapFoldBack maps the elements and threads the state from the last one`` (input : int array) = + let struct (collection, elements) = this.Create input + let expectedResults, expectedState = + Array.mapFoldBack (fun item state -> item * state, state + 1) elements 1 + let struct (results, state) = + ops.MapFoldBack (fun item state -> struct (item * state, state + 1)) collection 1 + CollectionAssert.AreEqual (expectedResults, results, $"%s{ops.Name}.mapFoldBack must map the elements from the last one") + Assert.AreEqual(expectedState, state, $"%s{ops.Name}.mapFoldBack must thread the state from the last element") + + [)>] + member this.``max returns the greatest element`` (input : int array) = + let struct (collection, elements) = this.Create input + Assert.AreEqual(Array.max elements, ops.Max collection, $"%s{ops.Name}.max must return the greatest element") [] member this.``max throws for an empty collection as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.max Array.empty), (fun () -> List.max List.empty), - (fun () -> ops.Max (ops.OfSeq Inputs.empty)), + (fun () -> ops.Max (ops.OfSeq Inputs.Empty)), "max of an empty collection must throw" ) - [] - member this.``maxBy returns the first element with the greatest projection`` () = - this.ForEach ( - Inputs.nonEmpty, - fun collection elements -> - Assert.AreEqual(List.maxBy abs elements, ops.MaxBy abs collection, $"%s{ops.Name}.maxBy of %A{elements}") + [)>] + member this.``maxBy returns the first element with the greatest projection`` (input : int array) = + let struct (collection, elements) = this.Create input + + Assert.AreEqual( + Array.maxBy abs elements, + ops.MaxBy abs collection, + $"%s{ops.Name}.maxBy must return the first element with the greatest projection" ) [] @@ -261,33 +257,32 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = this.ThrowsLikeFSharpCore ( (fun () -> Array.maxBy abs Array.empty), (fun () -> List.maxBy abs List.empty), - (fun () -> ops.MaxBy abs (ops.OfSeq Inputs.empty)), + (fun () -> ops.MaxBy abs (ops.OfSeq Inputs.Empty)), "maxBy of an empty collection must throw" ) - [] - member this.``min returns the smallest element`` () = - this.ForEach ( - Inputs.nonEmpty, - fun collection elements -> - Assert.AreEqual(List.min elements, ops.Min collection, $"%s{ops.Name}.min of %A{elements}") - ) + [)>] + member this.``min returns the smallest element`` (input : int array) = + let struct (collection, elements) = this.Create input + Assert.AreEqual(Array.min elements, ops.Min collection, $"%s{ops.Name}.min must return the smallest element") [] member this.``min throws for an empty collection as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.min Array.empty), (fun () -> List.min List.empty), - (fun () -> ops.Min (ops.OfSeq Inputs.empty)), + (fun () -> ops.Min (ops.OfSeq Inputs.Empty)), "min of an empty collection must throw" ) - [] - member this.``minBy returns the first element with the smallest projection`` () = - this.ForEach ( - Inputs.nonEmpty, - fun collection elements -> - Assert.AreEqual(List.minBy abs elements, ops.MinBy abs collection, $"%s{ops.Name}.minBy of %A{elements}") + [)>] + member this.``minBy returns the first element with the smallest projection`` (input : int array) = + let struct (collection, elements) = this.Create input + + Assert.AreEqual( + Array.minBy abs elements, + ops.MinBy abs collection, + $"%s{ops.Name}.minBy must return the first element with the smallest projection" ) [] @@ -295,20 +290,18 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = this.ThrowsLikeFSharpCore ( (fun () -> Array.minBy abs Array.empty), (fun () -> List.minBy abs List.empty), - (fun () -> ops.MinBy abs (ops.OfSeq Inputs.empty)), + (fun () -> ops.MinBy abs (ops.OfSeq Inputs.Empty)), "minBy of an empty collection must throw" ) - [] - member this.``reduce combines the elements from the first one`` () = - this.ForEach ( - Inputs.nonEmpty, - fun collection elements -> - Assert.AreEqual( - List.reduce (fun a b -> a * 2 - b) elements, - ops.Reduce (fun a b -> a * 2 - b) collection, - $"%s{ops.Name}.reduce of %A{elements}" - ) + [)>] + member this.``reduce combines the elements from the first one`` (input : int array) = + let struct (collection, elements) = this.Create input + + Assert.AreEqual( + Array.reduce (fun a b -> a * 2 - b) elements, + ops.Reduce (fun a b -> a * 2 - b) collection, + $"%s{ops.Name}.reduce must combine the elements from the first one" ) [] @@ -316,20 +309,18 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = this.ThrowsLikeFSharpCore ( (fun () -> Array.reduce (+) Array.empty), (fun () -> List.reduce (+) List.empty), - (fun () -> ops.Reduce (+) (ops.OfSeq Inputs.empty)), + (fun () -> ops.Reduce (+) (ops.OfSeq Inputs.Empty)), "reduce of an empty collection must throw" ) - [] - member this.``reduceBack combines the elements from the last one`` () = - this.ForEach ( - Inputs.nonEmpty, - fun collection elements -> - Assert.AreEqual( - List.reduceBack (fun a b -> a * 2 - b) elements, - ops.ReduceBack (fun a b -> a * 2 - b) collection, - $"%s{ops.Name}.reduceBack of %A{elements}" - ) + [)>] + member this.``reduceBack combines the elements from the last one`` (input : int array) = + let struct (collection, elements) = this.Create input + + Assert.AreEqual( + Array.reduceBack (fun a b -> a * 2 - b) elements, + ops.ReduceBack (fun a b -> a * 2 - b) collection, + $"%s{ops.Name}.reduceBack must combine the elements from the last one" ) [] @@ -337,46 +328,36 @@ type SeqLikeFoldingContract<'C> (ops : SeqOps<'C>) = this.ThrowsLikeFSharpCore ( (fun () -> Array.reduceBack (+) Array.empty), (fun () -> List.reduceBack (+) List.empty), - (fun () -> ops.ReduceBack (+) (ops.OfSeq Inputs.empty)), + (fun () -> ops.ReduceBack (+) (ops.OfSeq Inputs.Empty)), "reduceBack of an empty collection must throw" ) - [] - member this.``scan returns every state from the first element`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.scan record [] elements, - ops.Scan record [] collection, - $"%s{ops.Name}.scan over %A{elements}" - ) - ) + [)>] + member this.``scan returns every state from the first element`` (input : int array) = + let struct (collection, elements) = this.Create input - [] - member this.``scanBack returns every state from the last element`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.scanBack recordBack elements [], - ops.ScanBack recordBack collection [], - $"%s{ops.Name}.scanBack over %A{elements}" - ) + CollectionAssert.AreEqual ( + Array.scan record [||] elements, + ops.Scan record [||] collection, + $"%s{ops.Name}.scan must return every state from the first element" ) - [] - member this.``sum adds the elements`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual(List.sum elements, ops.Sum collection, $"%s{ops.Name}.sum of %A{elements}") - ) + [)>] + member this.``scanBack returns every state from the last element`` (input : int array) = + let struct (collection, elements) = this.Create input - [] - member this.``sumBy adds the projections`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual(List.sumBy abs elements, ops.SumBy abs collection, $"%s{ops.Name}.sumBy of %A{elements}") + CollectionAssert.AreEqual ( + Array.scanBack recordBack elements [||], + ops.ScanBack recordBack collection [||], + $"%s{ops.Name}.scanBack must return every state from the last element" ) + + [)>] + member this.``sum adds the elements`` (input : int array) = + let struct (collection, elements) = this.Create input + Assert.AreEqual(Array.sum elements, ops.Sum collection, $"%s{ops.Name}.sum must add the elements") + + [)>] + member this.``sumBy adds the projections`` (input : int array) = + let struct (collection, elements) = this.Create input + Assert.AreEqual(Array.sumBy abs elements, ops.SumBy abs collection, $"%s{ops.Name}.sumBy must add the projections") diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikePartitioningContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikePartitioningContract.fs index 33b8d6d8..ffc03149 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikePartitioningContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikePartitioningContract.fs @@ -9,55 +9,83 @@ type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = inherit SeqLikeFoldingContract<'C> (ops) /// The counts to try on a collection of the given length: negative, zero, inside and the whole length. - static let countsFor (length : int) = List.distinct [ -1; 0; min 3 length; length ] + static let countsFor (length : int) = Array.distinct [| -1; 0; min 3 length; length |] - [] - member this.``countBy counts the elements of each key in the order of the first occurrence`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.countBy (fun item -> item % 3) elements - |> List.map (fun (key, count) -> struct (key, count)), - ops.CountBy (fun item -> item % 3) collection, - $"%s{ops.Name}.countBy of %A{elements}" - ) + /// Every input with each count to try on it: negative, zero, inside and the whole length. + static member InputsAndCounts = seq { + for input in Inputs.All do + for count in countsFor input.Length do + struct (input, count) + } + + /// Every input with each count from zero to its length: zero, inside and the whole length. + static member InputsAndCountsWithin = seq { + for input in Inputs.All do + for count in Array.distinct [| 0; min 3 input.Length; input.Length |] do + struct (input, count) + } + + /// Every input with each count to try on it and a count beyond its length. + static member InputsAndCountsBeyond = seq { + for input in Inputs.All do + for count in Array.append (countsFor input.Length) [| input.Length + 5 |] do + struct (input, count) + } + + /// Every input with each limit of the predicates of skipWhile and takeWhile: below every element, between them and + /// above every element. + static member InputsAndLimits = seq { + for input in Inputs.All do + for limit in [| -10; 6; 100 |] do + struct (input, limit) + } + + /// Every input with each number of parts: one, inside the length, the length and beyond it. + static member InputsAndPartCounts = seq { + for input in Inputs.All do + for count in [| 1; 3; 8; 9 |] do + struct (input, count) + } + + [)>] + member this.``countBy counts the elements of each key in the order of the first occurrence`` (input : int array) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.countBy (fun item -> item % 3) elements + |> Array.map (fun (key, count) -> struct (key, count)), + ops.CountBy (fun item -> item % 3) collection, + $"%s{ops.Name}.countBy must count the elements of each key in the order of the first occurrence" ) - [] - member this.``distinct keeps the first occurrence of each element`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.distinct elements, - ops.ToList (ops.Distinct collection), - $"%s{ops.Name}.distinct of %A{elements}" - ) + [)>] + member this.``distinct keeps the first occurrence of each element`` (input : int array) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.distinct elements, + ops.ToArray (ops.Distinct collection), + $"%s{ops.Name}.distinct must keep the first occurrence of each element" ) - [] - member this.``distinctBy keeps the first element of each key`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.distinctBy abs elements, - ops.ToList (ops.DistinctBy abs collection), - $"%s{ops.Name}.distinctBy of %A{elements}" - ) + [)>] + member this.``distinctBy keeps the first element of each key`` (input : int array) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.distinctBy abs elements, + ops.ToArray (ops.DistinctBy abs collection), + $"%s{ops.Name}.distinctBy must keep the first element of each key" ) - [] - member this.``except removes the excluded items and the duplicates`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.except [ 5; 12 ] elements, - ops.ToList (ops.Except [ 5; 12 ] collection), - $"%s{ops.Name}.except of %A{elements}" - ) + [)>] + member this.``except removes the excluded items and the duplicates`` (input : int array) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.except [ 5; 12 ] elements, + ops.ToArray (ops.Except [ 5; 12 ] collection), + $"%s{ops.Name}.except must remove the excluded items and the duplicates" ) [] @@ -65,239 +93,213 @@ type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = let excluded = Unchecked.defaultof> this.ThrowsLikeFSharpCore ( - (fun () -> Array.except excluded (List.toArray Inputs.several)), - (fun () -> List.except excluded Inputs.several), - (fun () -> ops.Except excluded (ops.OfSeq Inputs.several)), + (fun () -> Array.except excluded Inputs.Several), + (fun () -> List.except excluded (List.ofArray Inputs.Several)), + (fun () -> ops.Except excluded (ops.OfSeq Inputs.Several)), "except of null items must throw" ) - [] - member this.``groupBy groups the elements by key in the order of the first occurrence`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.groupBy (fun item -> item % 3) elements - |> List.map (fun (key, items) -> struct (key, items)), - ops.GroupBy (fun item -> item % 3) collection, - $"%s{ops.Name}.groupBy of %A{elements}" - ) + [)>] + member this.``groupBy groups the elements by key in the order of the first occurrence`` (input : int array) = + let struct (collection, elements) = this.Create input + let expected = Array.groupBy (fun item -> item % 3) elements + let groups = ops.GroupBy (fun item -> item % 3) collection + + // A group pairs its key with an array, which a pair compares by reference, so the keys and the elements are + // compared apart + CollectionAssert.AreEqual ( + Array.map fst expected, + groups |> Array.map (fun struct (key, _) -> key), + $"%s{ops.Name}.groupBy must return the keys in the order of their first occurrence" ) - [] - member this.``partition splits the elements by the predicate`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - let expectedAccepted, expectedRejected = List.partition (fun item -> item > 0) elements - let struct (accepted, rejected) = ops.Partition (fun item -> item > 0) collection - Assert.AreEqual( - expectedAccepted, - ops.ToList accepted, - $"%s{ops.Name}.partition accepted of %A{elements}" - ) - Assert.AreEqual( - expectedRejected, - ops.ToList rejected, - $"%s{ops.Name}.partition rejected of %A{elements}" - ) + CollectionAssert.AreEqual ( + Array.map snd expected, + groups |> Array.map (fun struct (_, items) -> items), + $"%s{ops.Name}.groupBy must group the elements of each key in their order" ) - [] - member this.``partitionWith splits the elements by the choice`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - let partitioner (item : int) = - if item > 0 then - Choice1Of2 item - else - Choice2Of2 (string item) - let expectedFirst, expectedSecond = List.partitionWith partitioner elements - let struct (first, second) = ops.PartitionWith partitioner collection - Assert.AreEqual(expectedFirst, first, $"%s{ops.Name}.partitionWith first choices of %A{elements}") - Assert.AreEqual(expectedSecond, second, $"%s{ops.Name}.partitionWith second choices of %A{elements}") + [)>] + member this.``partition splits the elements by the predicate`` (input : int array) = + let struct (collection, elements) = this.Create input + let expectedAccepted, expectedRejected = Array.partition (fun item -> item > 0) elements + let struct (accepted, rejected) = ops.Partition (fun item -> item > 0) collection + + CollectionAssert.AreEqual ( + expectedAccepted, + ops.ToArray accepted, + $"%s{ops.Name}.partition must return the elements that satisfy the predicate" ) - [] - member this.``skip drops the first elements`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - for count in countsFor elements.Length do - Assert.AreEqual( - List.skip (max count 0) elements, - ops.ToList (ops.Skip count collection), - $"%s{ops.Name}.skip %d{count} of %A{elements}" - ) + CollectionAssert.AreEqual ( + expectedRejected, + ops.ToArray rejected, + $"%s{ops.Name}.partition must return the elements that do not satisfy the predicate" + ) + + [)>] + member this.``partitionWith splits the elements by the choice`` (input : int array) = + let struct (collection, elements) = this.Create input + + let partitioner (item : int) = + if item > 0 then + Choice1Of2 item + else + Choice2Of2 (string item) + + let expectedFirst, expectedSecond = Array.partitionWith partitioner elements + let struct (first, second) = ops.PartitionWith partitioner collection + CollectionAssert.AreEqual (expectedFirst, first, $"%s{ops.Name}.partitionWith must return the first choices in order") + CollectionAssert.AreEqual (expectedSecond, second, $"%s{ops.Name}.partitionWith must return the second choices in order") + + [.InputsAndCounts)>] + member this.``skip drops the first elements`` (input : int array, count : int) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.skip (max count 0) elements, + ops.ToArray (ops.Skip count collection), + $"%s{ops.Name}.skip must drop the first elements" ) [] member this.``skip throws for a count beyond the length as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( - (fun () -> Array.skip 9 (List.toArray Inputs.several)), - (fun () -> List.skip 9 Inputs.several), - (fun () -> ops.Skip 9 (ops.OfSeq Inputs.several)), + (fun () -> Array.skip 9 Inputs.Several), + (fun () -> List.skip 9 (List.ofArray Inputs.Several)), + (fun () -> ops.Skip 9 (ops.OfSeq Inputs.Several)), "skip beyond the length must throw" ) - [] - member this.``skipLast drops the last elements`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - for count in countsFor elements.Length do - Assert.AreEqual( - List.take (elements.Length - max count 0) elements, - ops.ToList (ops.SkipLast count collection), - $"%s{ops.Name}.skipLast %d{count} of %A{elements}" - ) + [.InputsAndCounts)>] + member this.``skipLast drops the last elements`` (input : int array, count : int) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.take (elements.Length - max count 0) elements, + ops.ToArray (ops.SkipLast count collection), + $"%s{ops.Name}.skipLast must drop the last elements" ) [] member this.``skipLast throws for a count beyond the length as skip does`` () = this.ThrowsLikeFSharpCore ( - (fun () -> Array.skip 9 (List.toArray Inputs.several)), - (fun () -> List.skip 9 Inputs.several), - (fun () -> ops.SkipLast 9 (ops.OfSeq Inputs.several)), + (fun () -> Array.skip 9 Inputs.Several), + (fun () -> List.skip 9 (List.ofArray Inputs.Several)), + (fun () -> ops.SkipLast 9 (ops.OfSeq Inputs.Several)), "skipLast beyond the length must throw" ) - [] - member this.``skipWhile drops the first elements that satisfy the predicate`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - for limit in [ -10; 6; 100 ] do - Assert.AreEqual( - List.skipWhile (fun item -> item < limit) elements, - ops.ToList (ops.SkipWhile (fun item -> item < limit) collection), - $"%s{ops.Name}.skipWhile below %d{limit} of %A{elements}" - ) + [.InputsAndLimits)>] + member this.``skipWhile drops the first elements that satisfy the predicate`` (input : int array, limit : int) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.skipWhile (fun item -> item < limit) elements, + ops.ToArray (ops.SkipWhile (fun item -> item < limit) collection), + $"%s{ops.Name}.skipWhile must drop the first elements that satisfy the predicate" ) - [] - member this.``splitAt splits the elements at the index`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - for index in List.distinct [ 0; min 3 elements.Length; elements.Length ] do - let expectedBefore, expectedAfter = List.splitAt index elements - let struct (before, after) = ops.SplitAt index collection - - Assert.AreEqual( - expectedBefore, - ops.ToList before, - $"%s{ops.Name}.splitAt %d{index} first part of %A{elements}" - ) - - Assert.AreEqual( - expectedAfter, - ops.ToList after, - $"%s{ops.Name}.splitAt %d{index} second part of %A{elements}" - ) + [.InputsAndCountsWithin)>] + member this.``splitAt splits the elements at the index`` (input : int array, index : int) = + let struct (collection, elements) = this.Create input + let expectedBefore, expectedAfter = Array.splitAt index elements + let struct (before, after) = ops.SplitAt index collection + + CollectionAssert.AreEqual ( + expectedBefore, + ops.ToArray before, + $"%s{ops.Name}.splitAt must return the elements before the index first" ) - [] - member this.``splitAt throws for a negative index or one beyond the length as FSharp.Core does`` () = - for index in [ -1; 9 ] do - this.ThrowsLikeFSharpCore ( - (fun () -> Array.splitAt index (List.toArray Inputs.several)), - (fun () -> List.splitAt index Inputs.several), - (fun () -> ops.SplitAt index (ops.OfSeq Inputs.several)), - $"splitAt %d{index} must throw" - ) + CollectionAssert.AreEqual ( + expectedAfter, + ops.ToArray after, + $"%s{ops.Name}.splitAt must return the elements from the index second" + ) - [] - member this.``splitInto splits the elements into parts of nearly equal length`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - for count in [ 1; 3; 8; 9 ] do - Assert.AreEqual( - List.splitInto count elements, - ops.SplitInto count collection, - $"%s{ops.Name}.splitInto %d{count} of %A{elements}" - ) + [] + member this.``splitAt throws for a negative index or one beyond the length as FSharp.Core does`` (index : int) = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.splitAt index Inputs.Several), + (fun () -> List.splitAt index (List.ofArray Inputs.Several)), + (fun () -> ops.SplitAt index (ops.OfSeq Inputs.Several)), + "splitAt at a negative index or beyond the length must throw" + ) + + [.InputsAndPartCounts)>] + member this.``splitInto splits the elements into parts of nearly equal length`` (input : int array, count : int) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.splitInto count elements, + ops.SplitInto count collection, + $"%s{ops.Name}.splitInto must split the elements into parts of nearly equal length" ) [] member this.``splitInto throws for a count that is not positive as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( - (fun () -> Array.splitInto 0 (List.toArray Inputs.several)), - (fun () -> List.splitInto 0 Inputs.several), - (fun () -> ops.SplitInto 0 (ops.OfSeq Inputs.several)), + (fun () -> Array.splitInto 0 Inputs.Several), + (fun () -> List.splitInto 0 (List.ofArray Inputs.Several)), + (fun () -> ops.SplitInto 0 (ops.OfSeq Inputs.Several)), "splitInto 0 must throw" ) - [] - member this.``take returns the first elements`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - for count in List.distinct [ 0; min 3 elements.Length; elements.Length ] do - Assert.AreEqual( - List.take count elements, - ops.ToList (ops.Take count collection), - $"%s{ops.Name}.take %d{count} of %A{elements}" - ) + [.InputsAndCountsWithin)>] + member this.``take returns the first elements`` (input : int array, count : int) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.take count elements, + ops.ToArray (ops.Take count collection), + $"%s{ops.Name}.take must return the first elements" ) - [] - member this.``take throws for a negative count or one beyond the length as FSharp.Core does`` () = - for count in [ -1; 9 ] do - this.ThrowsLikeFSharpCore ( - (fun () -> Array.take count (List.toArray Inputs.several)), - (fun () -> List.take count Inputs.several), - (fun () -> ops.Take count (ops.OfSeq Inputs.several)), - $"take %d{count} must throw" - ) + [] + member this.``take throws for a negative count or one beyond the length as FSharp.Core does`` (count : int) = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.take count Inputs.Several), + (fun () -> List.take count (List.ofArray Inputs.Several)), + (fun () -> ops.Take count (ops.OfSeq Inputs.Several)), + "take of a negative count or beyond the length must throw" + ) - [] - member this.``takeLast returns the last elements`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - for count in List.distinct [ 0; min 3 elements.Length; elements.Length ] do - Assert.AreEqual( - List.skip (elements.Length - count) elements, - ops.ToList (ops.TakeLast count collection), - $"%s{ops.Name}.takeLast %d{count} of %A{elements}" - ) + [.InputsAndCountsWithin)>] + member this.``takeLast returns the last elements`` (input : int array, count : int) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.skip (elements.Length - count) elements, + ops.ToArray (ops.TakeLast count collection), + $"%s{ops.Name}.takeLast must return the last elements" ) - [] - member this.``takeLast throws for a negative count or one beyond the length as take does`` () = - for count in [ -1; 9 ] do - this.ThrowsLikeFSharpCore ( - (fun () -> Array.take count (List.toArray Inputs.several)), - (fun () -> List.take count Inputs.several), - (fun () -> ops.TakeLast count (ops.OfSeq Inputs.several)), - $"takeLast %d{count} must throw" - ) + [] + member this.``takeLast throws for a negative count or one beyond the length as take does`` (count : int) = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.take count Inputs.Several), + (fun () -> List.take count (List.ofArray Inputs.Several)), + (fun () -> ops.TakeLast count (ops.OfSeq Inputs.Several)), + "takeLast of a negative count or beyond the length must throw" + ) - [] - member this.``takeWhile returns the first elements that satisfy the predicate`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - for limit in [ -10; 6; 100 ] do - Assert.AreEqual( - List.takeWhile (fun item -> item < limit) elements, - ops.ToList (ops.TakeWhile (fun item -> item < limit) collection), - $"%s{ops.Name}.takeWhile below %d{limit} of %A{elements}" - ) + [.InputsAndLimits)>] + member this.``takeWhile returns the first elements that satisfy the predicate`` (input : int array, limit : int) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.takeWhile (fun item -> item < limit) elements, + ops.ToArray (ops.TakeWhile (fun item -> item < limit) collection), + $"%s{ops.Name}.takeWhile must return the first elements that satisfy the predicate" ) - [] - member this.``truncate returns at most the given number of first elements`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - for count in countsFor elements.Length @ [ elements.Length + 5 ] do - Assert.AreEqual( - List.truncate count elements, - ops.ToList (ops.Truncate count collection), - $"%s{ops.Name}.truncate %d{count} of %A{elements}" - ) + [.InputsAndCountsBeyond)>] + member this.``truncate returns at most the given number of first elements`` (input : int array, count : int) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.truncate count elements, + ops.ToArray (ops.Truncate count collection), + $"%s{ops.Name}.truncate must return at most the given number of first elements" ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeRandomContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeRandomContract.fs index b5a26695..e1023947 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeRandomContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeRandomContract.fs @@ -20,70 +20,69 @@ type SeqLikeRandomContract<'C> (ops : SeqOps<'C>) = let random = Random seed fun () -> random.NextDouble () - /// The elements of a result as a sorted list, for the comparison of multisets. - static let sorted (items : int list) = List.sort items + /// The elements of a result, sorted, for the comparison of multisets. + static let sorted (items : int array) = Array.sort items - [] - member this.``randomChoice returns an element`` () = - this.ForEach ( - Inputs.nonEmpty, - fun collection elements -> - let chosen = ops.RandomChoice collection - Assert.Contains (chosen, elements, $"%s{ops.Name}.randomChoice must return an element of %A{elements}") - ) + /// The counts and the inputs that randomChoices rejects: a negative count, and a count of an empty input. + static member ChoicesFailures = seq { + struct (-1, Inputs.Several) + struct (3, Inputs.Empty) + } + + [)>] + member this.``randomChoice returns an element`` (input : int array) = + let struct (collection, elements) = this.Create input + let chosen = ops.RandomChoice collection + Assert.Contains (chosen, elements, $"%s{ops.Name}.randomChoice must return an element of the collection") [] member this.``randomChoice throws for an empty collection as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( (fun () -> Array.randomChoice Array.empty), (fun () -> List.randomChoice List.empty), - (fun () -> ops.RandomChoice (ops.OfSeq Inputs.empty)), + (fun () -> ops.RandomChoice (ops.OfSeq Inputs.Empty)), "randomChoice of an empty collection must throw" ) - [] - member this.``randomChoiceBy chooses as FSharp.Core does with the same randomizer`` () = - this.ForEach ( - Inputs.nonEmpty, - fun collection elements -> - let expected = - this.Mirrored ( - (fun () -> Array.randomChoiceBy (randomizer ()) (List.toArray elements)), - fun () -> List.randomChoiceBy (randomizer ()) elements - ) - - Assert.AreEqual( - expected, - ops.RandomChoiceBy (randomizer ()) collection, - $"%s{ops.Name}.randomChoiceBy of %A{elements}" - ) + [)>] + member this.``randomChoiceBy chooses as FSharp.Core does with the same randomizer`` (input : int array) = + let struct (collection, elements) = this.Create input + + let expected = + this.Mirrored ( + (fun () -> Array.randomChoiceBy (randomizer ()) elements), + fun () -> List.randomChoiceBy (randomizer ()) (List.ofArray elements) + ) + + Assert.AreEqual( + expected, + ops.RandomChoiceBy (randomizer ()) collection, + $"%s{ops.Name}.randomChoiceBy must choose as FSharp.Core does with the same randomizer" ) [] member this.``randomChoiceBy throws for a randomizer outside the range as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( - (fun () -> Array.randomChoiceBy (fun () -> 1.0) (List.toArray Inputs.several)), - (fun () -> List.randomChoiceBy (fun () -> 1.0) Inputs.several), - (fun () -> ops.RandomChoiceBy (fun () -> 1.0) (ops.OfSeq Inputs.several)), + (fun () -> Array.randomChoiceBy (fun () -> 1.0) Inputs.Several), + (fun () -> List.randomChoiceBy (fun () -> 1.0) (List.ofArray Inputs.Several)), + (fun () -> ops.RandomChoiceBy (fun () -> 1.0) (ops.OfSeq Inputs.Several)), "randomChoiceBy with a randomizer that returns 1.0 must throw" ) - [] - member this.``randomChoiceWith chooses as FSharp.Core does with the same generator`` () = - this.ForEach ( - Inputs.nonEmpty, - fun collection elements -> - let expected = - this.Mirrored ( - (fun () -> Array.randomChoiceWith (Random seed) (List.toArray elements)), - fun () -> List.randomChoiceWith (Random seed) elements - ) - - Assert.AreEqual( - expected, - ops.RandomChoiceWith (Random seed) collection, - $"%s{ops.Name}.randomChoiceWith of %A{elements}" - ) + [)>] + member this.``randomChoiceWith chooses as FSharp.Core does with the same generator`` (input : int array) = + let struct (collection, elements) = this.Create input + + let expected = + this.Mirrored ( + (fun () -> Array.randomChoiceWith (Random seed) elements), + fun () -> List.randomChoiceWith (Random seed) (List.ofArray elements) + ) + + Assert.AreEqual( + expected, + ops.RandomChoiceWith (Random seed) collection, + $"%s{ops.Name}.randomChoiceWith must choose as FSharp.Core does with the same generator" ) [] @@ -91,201 +90,173 @@ type SeqLikeRandomContract<'C> (ops : SeqOps<'C>) = let random = Unchecked.defaultof this.ThrowsLikeFSharpCore ( - (fun () -> Array.randomChoiceWith random (List.toArray Inputs.several)), - (fun () -> List.randomChoiceWith random Inputs.several), - (fun () -> ops.RandomChoiceWith random (ops.OfSeq Inputs.several)), + (fun () -> Array.randomChoiceWith random Inputs.Several), + (fun () -> List.randomChoiceWith random (List.ofArray Inputs.Several)), + (fun () -> ops.RandomChoiceWith random (ops.OfSeq Inputs.Several)), "randomChoiceWith of a null generator must throw" ) - [] - member this.``randomChoices returns the given number of elements`` () = - this.ForEach ( - Inputs.nonEmpty, - fun collection elements -> - let chosen = ops.ToList (ops.RandomChoices 20 collection) - Assert.HasCount (20, chosen, $"%s{ops.Name}.randomChoices 20 must return 20 elements") - - for item in chosen do - Assert.Contains (item, elements, $"%s{ops.Name}.randomChoices must return elements of %A{elements}") + [)>] + member this.``randomChoices returns the given number of elements`` (input : int array) = + let struct (collection, elements) = this.Create input + let chosen = ops.ToArray (ops.RandomChoices 20 collection) + Assert.HasCount (20, chosen, $"%s{ops.Name}.randomChoices 20 must return 20 elements") + + for item in chosen do + Assert.Contains (item, elements, $"%s{ops.Name}.randomChoices must return elements of the collection") + + [.ChoicesFailures)>] + member this.``randomChoices throws for a negative count or an empty collection as FSharp.Core does`` + (count : int, input : int array) + = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.randomChoices count input), + (fun () -> List.randomChoices count (List.ofArray input)), + (fun () -> ops.RandomChoices count (ops.OfSeq input)), + "randomChoices of a negative count or of an empty collection must throw" ) - [] - member this.``randomChoices throws for a negative count or an empty collection as FSharp.Core does`` () = - for struct (count, input) in [ struct (-1, Inputs.several); struct (3, Inputs.empty) ] do - this.ThrowsLikeFSharpCore ( - (fun () -> Array.randomChoices count (List.toArray input)), - (fun () -> List.randomChoices count input), - (fun () -> ops.RandomChoices count (ops.OfSeq input)), - $"randomChoices %d{count} of %A{input} must throw" + [)>] + member this.``randomChoicesBy chooses as FSharp.Core does with the same randomizer`` (input : int array) = + let struct (collection, elements) = this.Create input + + let expected = + this.Mirrored ( + (fun () -> Array.randomChoicesBy (randomizer ()) 5 elements), + fun () -> + List.randomChoicesBy (randomizer ()) 5 (List.ofArray elements) + |> List.toArray ) - [] - member this.``randomChoicesBy chooses as FSharp.Core does with the same randomizer`` () = - this.ForEach ( - Inputs.nonEmpty, - fun collection elements -> - let expected = - this.Mirrored ( - (fun () -> - Array.randomChoicesBy (randomizer ()) 5 (List.toArray elements) - |> List.ofArray - ), - fun () -> List.randomChoicesBy (randomizer ()) 5 elements - ) - - Assert.AreEqual( - expected, - ops.ToList (ops.RandomChoicesBy (randomizer ()) 5 collection), - $"%s{ops.Name}.randomChoicesBy of %A{elements}" - ) + CollectionAssert.AreEqual ( + expected, + ops.ToArray (ops.RandomChoicesBy (randomizer ()) 5 collection), + $"%s{ops.Name}.randomChoicesBy must choose as FSharp.Core does with the same randomizer" ) - [] - member this.``randomChoicesWith chooses as FSharp.Core does with the same generator`` () = - this.ForEach ( - Inputs.nonEmpty, - fun collection elements -> - let expected = - this.Mirrored ( - (fun () -> - Array.randomChoicesWith (Random seed) 5 (List.toArray elements) - |> List.ofArray - ), - fun () -> List.randomChoicesWith (Random seed) 5 elements - ) - - Assert.AreEqual( - expected, - ops.ToList (ops.RandomChoicesWith (Random seed) 5 collection), - $"%s{ops.Name}.randomChoicesWith of %A{elements}" - ) + [)>] + member this.``randomChoicesWith chooses as FSharp.Core does with the same generator`` (input : int array) = + let struct (collection, elements) = this.Create input + + let expected = + this.Mirrored ( + (fun () -> Array.randomChoicesWith (Random seed) 5 elements), + fun () -> + List.randomChoicesWith (Random seed) 5 (List.ofArray elements) + |> List.toArray + ) + + CollectionAssert.AreEqual ( + expected, + ops.ToArray (ops.RandomChoicesWith (Random seed) 5 collection), + $"%s{ops.Name}.randomChoicesWith must choose as FSharp.Core does with the same generator" ) - [] - member this.``randomSample returns elements at distinct indices`` () = - this.ForEach ( - Inputs.nonEmpty, - fun collection elements -> - let count = min 3 elements.Length - let sample = ops.ToList (ops.RandomSample count collection) - Assert.HasCount (count, sample, $"%s{ops.Name}.randomSample %d{count} must return %d{count} elements") - - // Every sampled element must be available in the input as often as it is sampled - let remaining = ResizeArray elements - - for item in sample do - Assert.IsTrue ( - remaining.Remove item, - $"%s{ops.Name}.randomSample must take each element of %A{elements} once at most" - ) + [)>] + member this.``randomSample returns elements at distinct indices`` (input : int array) = + let struct (collection, elements) = this.Create input + let count = min 3 elements.Length + let sample = ops.ToArray (ops.RandomSample count collection) + Assert.HasCount (count, sample, $"%s{ops.Name}.randomSample must return the given number of elements") + + // Every sampled element must be available in the input as often as it is sampled + let remaining = ResizeArray elements + + for item in sample do + Assert.IsTrue ( + remaining.Remove item, + $"%s{ops.Name}.randomSample must take each element of the collection once at most" + ) + + [] + member this.``randomSample throws for a negative count or one beyond the length as FSharp.Core does`` (count : int) = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.randomSample count Inputs.Several), + (fun () -> List.randomSample count (List.ofArray Inputs.Several)), + (fun () -> ops.RandomSample count (ops.OfSeq Inputs.Several)), + "randomSample of a negative count or beyond the length must throw" ) - [] - member this.``randomSample throws for a negative count or one beyond the length as FSharp.Core does`` () = - for count in [ -1; 9 ] do - this.ThrowsLikeFSharpCore ( - (fun () -> Array.randomSample count (List.toArray Inputs.several)), - (fun () -> List.randomSample count Inputs.several), - (fun () -> ops.RandomSample count (ops.OfSeq Inputs.several)), - $"randomSample %d{count} must throw" + [)>] + member this.``randomSampleBy samples as FSharp.Core does with the same randomizer`` (input : int array) = + let struct (collection, elements) = this.Create input + let count = min 3 elements.Length + + let expected = + this.Mirrored ( + (fun () -> Array.randomSampleBy (randomizer ()) count elements), + fun () -> + List.randomSampleBy (randomizer ()) count (List.ofArray elements) + |> List.toArray ) - [] - member this.``randomSampleBy samples as FSharp.Core does with the same randomizer`` () = - this.ForEach ( - Inputs.nonEmpty, - fun collection elements -> - let count = min 3 elements.Length - - let expected = - this.Mirrored ( - (fun () -> - Array.randomSampleBy (randomizer ()) count (List.toArray elements) - |> List.ofArray - ), - fun () -> List.randomSampleBy (randomizer ()) count elements - ) - - Assert.AreEqual( - expected, - ops.ToList (ops.RandomSampleBy (randomizer ()) count collection), - $"%s{ops.Name}.randomSampleBy of %A{elements}" - ) + CollectionAssert.AreEqual ( + expected, + ops.ToArray (ops.RandomSampleBy (randomizer ()) count collection), + $"%s{ops.Name}.randomSampleBy must sample as FSharp.Core does with the same randomizer" ) - [] - member this.``randomSampleWith samples as FSharp.Core does with the same generator`` () = - this.ForEach ( - Inputs.nonEmpty, - fun collection elements -> - let count = min 3 elements.Length - - let expected = - this.Mirrored ( - (fun () -> - Array.randomSampleWith (Random seed) count (List.toArray elements) - |> List.ofArray - ), - fun () -> List.randomSampleWith (Random seed) count elements - ) - - Assert.AreEqual( - expected, - ops.ToList (ops.RandomSampleWith (Random seed) count collection), - $"%s{ops.Name}.randomSampleWith of %A{elements}" - ) + [)>] + member this.``randomSampleWith samples as FSharp.Core does with the same generator`` (input : int array) = + let struct (collection, elements) = this.Create input + let count = min 3 elements.Length + + let expected = + this.Mirrored ( + (fun () -> Array.randomSampleWith (Random seed) count elements), + fun () -> + List.randomSampleWith (Random seed) count (List.ofArray elements) + |> List.toArray + ) + + CollectionAssert.AreEqual ( + expected, + ops.ToArray (ops.RandomSampleWith (Random seed) count collection), + $"%s{ops.Name}.randomSampleWith must sample as FSharp.Core does with the same generator" ) - [] - member this.``randomShuffle returns a permutation of the elements`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - sorted elements, - sorted (ops.ToList (ops.RandomShuffle collection)), - $"%s{ops.Name}.randomShuffle must keep the elements of %A{elements}" - ) + [)>] + member this.``randomShuffle returns a permutation of the elements`` (input : int array) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + sorted elements, + sorted (ops.ToArray (ops.RandomShuffle collection)), + $"%s{ops.Name}.randomShuffle must keep the elements" ) - [] - member this.``randomShuffleBy shuffles as FSharp.Core does with the same randomizer`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - let expected = - this.Mirrored ( - (fun () -> - Array.randomShuffleBy (randomizer ()) (List.toArray elements) - |> List.ofArray - ), - fun () -> List.randomShuffleBy (randomizer ()) elements - ) - - Assert.AreEqual( - expected, - ops.ToList (ops.RandomShuffleBy (randomizer ()) collection), - $"%s{ops.Name}.randomShuffleBy of %A{elements}" - ) + [)>] + member this.``randomShuffleBy shuffles as FSharp.Core does with the same randomizer`` (input : int array) = + let struct (collection, elements) = this.Create input + + let expected = + this.Mirrored ( + (fun () -> Array.randomShuffleBy (randomizer ()) elements), + fun () -> + List.randomShuffleBy (randomizer ()) (List.ofArray elements) + |> List.toArray + ) + + CollectionAssert.AreEqual ( + expected, + ops.ToArray (ops.RandomShuffleBy (randomizer ()) collection), + $"%s{ops.Name}.randomShuffleBy must shuffle as FSharp.Core does with the same randomizer" ) - [] - member this.``randomShuffleWith shuffles as FSharp.Core does with the same generator`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - let expected = - this.Mirrored ( - (fun () -> - Array.randomShuffleWith (Random seed) (List.toArray elements) - |> List.ofArray - ), - fun () -> List.randomShuffleWith (Random seed) elements - ) - - Assert.AreEqual( - expected, - ops.ToList (ops.RandomShuffleWith (Random seed) collection), - $"%s{ops.Name}.randomShuffleWith of %A{elements}" - ) + [)>] + member this.``randomShuffleWith shuffles as FSharp.Core does with the same generator`` (input : int array) = + let struct (collection, elements) = this.Create input + + let expected = + this.Mirrored ( + (fun () -> Array.randomShuffleWith (Random seed) elements), + fun () -> + List.randomShuffleWith (Random seed) (List.ofArray elements) + |> List.toArray + ) + + CollectionAssert.AreEqual ( + expected, + ops.ToArray (ops.RandomShuffleWith (Random seed) collection), + $"%s{ops.Name}.randomShuffleWith must shuffle as FSharp.Core does with the same generator" ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSearchContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSearchContract.fs index e3960c26..03c2b019 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSearchContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSearchContract.fs @@ -3,13 +3,62 @@ namespace FSharp.Collections.Immutable.Tests open FSharp.Collections.Immutable open Microsoft.VisualStudio.TestTools.UnitTesting +/// The predicates of the search tests over elements, as the cases of their rows. +/// +/// MSTest names a row after its values, and a function has no readable name, so a row holds a case and the test takes +/// the function of the case. +/// +[] +type ElementPredicate = + /// The even elements. + | IsEven + /// The negative elements. + | IsNegative + /// The elements below a hundred, which are all the elements of every input. + | IsBelowHundred + /// No element. + | Never + + /// Tells whether an element satisfies the predicate. + member this.Matches (item : int) = + match this with + | ElementPredicate.IsEven -> item % 2 = 0 + | ElementPredicate.IsNegative -> item < 0 + | ElementPredicate.IsBelowHundred -> item < 100 + | ElementPredicate.Never -> false + +/// The predicates of the search tests over the pairs of elements at the same index, as the cases of their rows. +/// +/// MSTest names a row after its values, and a function has no readable name, so a row holds a case and the test takes +/// the function of the case. +/// +[] +type PairPredicate = + /// The pairs whose sum is six. + | SumIsSix + /// The pairs whose first element is greater than the second. + | FirstIsGreater + /// The pairs whose first element is smaller than the second. + | FirstIsSmaller + /// The pairs of different elements. + | AreDifferent + /// No pair. + | Never + + /// Tells whether a pair satisfies the predicate. + member this.Matches (first : int) (second : int) = + match this with + | PairPredicate.SumIsSix -> first + second = 6 + | PairPredicate.FirstIsGreater -> first > second + | PairPredicate.FirstIsSmaller -> first < second + | PairPredicate.AreDifferent -> first <> second + | PairPredicate.Never -> false + /// The contract of the search functions of a sequence-like module. [] type SeqLikeSearchContract<'C> (ops : SeqOps<'C>) = inherit SeqLikeAccessContract<'C> (ops) - static let isEven (item : int) = item % 2 = 0 - static let isNegative (item : int) = item < 0 static let never (_ : int) = false /// A chooser that returns a value for the negative elements only. @@ -18,262 +67,275 @@ type SeqLikeSearchContract<'C> (ops : SeqOps<'C>) = /// The same chooser as `negatedIfNegative`, for the FSharp.Core oracle. static let negatedIfNegativeOption (item : int) = if item < 0 then Some (-item) else None - [] - member this.``contains finds an element equal to the value`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - for value in [ 42; 5; -3; 1000 ] do - Assert.AreEqual( - List.contains value elements, - ops.Contains value collection, - $"%s{ops.Name}.contains %d{value} in %A{elements}" - ) + /// Every input with each value to look for: the element of the singleton, two elements of the input with several, + /// both repeated, and a value that no input has. + static member InputsAndValues = seq { + for input in Inputs.All do + for value in [| 42; 5; -3; 1000 |] do + struct (input, value) + } + + /// Every input with each predicate of exists and of the searches that may find nothing. + static member InputsAndPredicates = seq { + for input in Inputs.All do + for predicate in [| ElementPredicate.IsEven; ElementPredicate.IsNegative; ElementPredicate.Never |] do + struct (input, predicate) + } + + /// Every input with each predicate of forall: one that some elements satisfy, one that every element satisfies and + /// one that no element satisfies. + static member InputsAndForallPredicates = seq { + for input in Inputs.All do + for predicate in [| ElementPredicate.IsEven; ElementPredicate.IsBelowHundred; ElementPredicate.Never |] do + struct (input, predicate) + } + + /// The predicates of find and its variants, which some elements of the input with several elements satisfy. + static member FindPredicates = seq { + ElementPredicate.IsEven + ElementPredicate.IsNegative + } + + /// The predicates of exists2. + static member Exists2Predicates = seq { + PairPredicate.SumIsSix + PairPredicate.FirstIsGreater + PairPredicate.Never + } + + /// The predicates of forall2. + static member Forall2Predicates = seq { + PairPredicate.FirstIsSmaller + PairPredicate.AreDifferent + PairPredicate.Never + } + + [.InputsAndValues)>] + member this.``contains finds an element equal to the value`` (input : int array, value : int) = + let struct (collection, elements) = this.Create input + + Assert.AreEqual( + Array.contains value elements, + ops.Contains value collection, + $"%s{ops.Name}.contains must find an element equal to the value" ) - [] - member this.``exists finds an element that satisfies the predicate`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - for predicate in [ isEven; isNegative; never ] do - Assert.AreEqual( - List.exists predicate elements, - ops.Exists predicate collection, - $"%s{ops.Name}.exists over %A{elements}" - ) + [.InputsAndPredicates)>] + member this.``exists finds an element that satisfies the predicate`` (input : int array, predicate : ElementPredicate) = + let struct (collection, elements) = this.Create input + + Assert.AreEqual( + Array.exists predicate.Matches elements, + ops.Exists predicate.Matches collection, + $"%s{ops.Name}.exists must find an element that satisfies the predicate" ) - [] - member this.``exists2 tests the pairs at the same index`` () = - let collection = ops.OfSeq Inputs.several - let other = ops.OfSeq Inputs.other - let elements = ops.ToList collection - let otherElements = ops.ToList other - - for predicate in [ (fun a b -> a + b = 6); (fun a b -> a > b); (fun _ _ -> false) ] do - Assert.AreEqual( - List.exists2 predicate elements otherElements, - ops.Exists2 predicate collection other, - $"%s{ops.Name}.exists2 over %A{elements} and %A{otherElements}" - ) + [.Exists2Predicates)>] + member this.``exists2 tests the pairs at the same index`` (predicate : PairPredicate) = + let struct (collection, elements) = this.Create Inputs.Several + let struct (other, otherElements) = this.Create Inputs.Other + + Assert.AreEqual( + Array.exists2 predicate.Matches elements otherElements, + ops.Exists2 predicate.Matches collection other, + $"%s{ops.Name}.exists2 must test the pairs at the same index" + ) [] member this.``exists2 throws for different lengths as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( - (fun () -> Array.exists2 (fun _ _ -> false) (List.toArray Inputs.singleton) (List.toArray Inputs.several)), - (fun () -> List.exists2 (fun _ _ -> false) Inputs.singleton Inputs.several), - (fun () -> ops.Exists2 (fun _ _ -> false) (ops.OfSeq Inputs.singleton) (ops.OfSeq Inputs.several)), + (fun () -> Array.exists2 (fun _ _ -> false) Inputs.Singleton Inputs.Several), + (fun () -> List.exists2 (fun _ _ -> false) (List.ofArray Inputs.Singleton) (List.ofArray Inputs.Several)), + (fun () -> ops.Exists2 (fun _ _ -> false) (ops.OfSeq Inputs.Singleton) (ops.OfSeq Inputs.Several)), "exists2 of collections of different lengths must throw" ) - [] - member this.``find returns the first element that satisfies the predicate`` () = - this.ForEach ( - [ Inputs.several ], - fun collection elements -> - for predicate in [ isEven; isNegative ] do - Assert.AreEqual( - List.find predicate elements, - ops.Find predicate collection, - $"%s{ops.Name}.find in %A{elements}" - ) + [.FindPredicates)>] + member this.``find returns the first element that satisfies the predicate`` (predicate : ElementPredicate) = + let struct (collection, elements) = this.Create Inputs.Several + + Assert.AreEqual( + Array.find predicate.Matches elements, + ops.Find predicate.Matches collection, + $"%s{ops.Name}.find must return the first element that satisfies the predicate" ) [] member this.``find throws when no element satisfies the predicate as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( - (fun () -> Array.find never (List.toArray Inputs.several)), - (fun () -> List.find never Inputs.several), - (fun () -> ops.Find never (ops.OfSeq Inputs.several)), + (fun () -> Array.find never Inputs.Several), + (fun () -> List.find never (List.ofArray Inputs.Several)), + (fun () -> ops.Find never (ops.OfSeq Inputs.Several)), "find without a match must throw" ) - [] - member this.``findBack returns the last element that satisfies the predicate`` () = - this.ForEach ( - [ Inputs.several ], - fun collection elements -> - for predicate in [ isEven; isNegative ] do - Assert.AreEqual( - List.findBack predicate elements, - ops.FindBack predicate collection, - $"%s{ops.Name}.findBack in %A{elements}" - ) + [.FindPredicates)>] + member this.``findBack returns the last element that satisfies the predicate`` (predicate : ElementPredicate) = + let struct (collection, elements) = this.Create Inputs.Several + + Assert.AreEqual( + Array.findBack predicate.Matches elements, + ops.FindBack predicate.Matches collection, + $"%s{ops.Name}.findBack must return the last element that satisfies the predicate" ) [] member this.``findBack throws when no element satisfies the predicate as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( - (fun () -> Array.findBack never (List.toArray Inputs.several)), - (fun () -> List.findBack never Inputs.several), - (fun () -> ops.FindBack never (ops.OfSeq Inputs.several)), + (fun () -> Array.findBack never Inputs.Several), + (fun () -> List.findBack never (List.ofArray Inputs.Several)), + (fun () -> ops.FindBack never (ops.OfSeq Inputs.Several)), "findBack without a match must throw" ) - [] - member this.``findIndex returns the index of the first element that satisfies the predicate`` () = - this.ForEach ( - [ Inputs.several ], - fun collection elements -> - for predicate in [ isEven; isNegative ] do - Assert.AreEqual( - List.findIndex predicate elements, - ops.FindIndex predicate collection, - $"%s{ops.Name}.findIndex in %A{elements}" - ) + [.FindPredicates)>] + member this.``findIndex returns the index of the first element that satisfies the predicate`` (predicate : ElementPredicate) = + let struct (collection, elements) = this.Create Inputs.Several + + Assert.AreEqual( + Array.findIndex predicate.Matches elements, + ops.FindIndex predicate.Matches collection, + $"%s{ops.Name}.findIndex must return the index of the first element that satisfies the predicate" ) [] member this.``findIndex throws when no element satisfies the predicate as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( - (fun () -> Array.findIndex never (List.toArray Inputs.several)), - (fun () -> List.findIndex never Inputs.several), - (fun () -> ops.FindIndex never (ops.OfSeq Inputs.several)), + (fun () -> Array.findIndex never Inputs.Several), + (fun () -> List.findIndex never (List.ofArray Inputs.Several)), + (fun () -> ops.FindIndex never (ops.OfSeq Inputs.Several)), "findIndex without a match must throw" ) - [] - member this.``findIndexBack returns the index of the last element that satisfies the predicate`` () = - this.ForEach ( - [ Inputs.several ], - fun collection elements -> - for predicate in [ isEven; isNegative ] do - Assert.AreEqual( - List.findIndexBack predicate elements, - ops.FindIndexBack predicate collection, - $"%s{ops.Name}.findIndexBack in %A{elements}" - ) + [.FindPredicates)>] + member this.``findIndexBack returns the index of the last element that satisfies the predicate`` + (predicate : ElementPredicate) + = + let struct (collection, elements) = this.Create Inputs.Several + + Assert.AreEqual( + Array.findIndexBack predicate.Matches elements, + ops.FindIndexBack predicate.Matches collection, + $"%s{ops.Name}.findIndexBack must return the index of the last element that satisfies the predicate" ) [] member this.``findIndexBack throws when no element satisfies the predicate as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( - (fun () -> Array.findIndexBack never (List.toArray Inputs.several)), - (fun () -> List.findIndexBack never Inputs.several), - (fun () -> ops.FindIndexBack never (ops.OfSeq Inputs.several)), + (fun () -> Array.findIndexBack never Inputs.Several), + (fun () -> List.findIndexBack never (List.ofArray Inputs.Several)), + (fun () -> ops.FindIndexBack never (ops.OfSeq Inputs.Several)), "findIndexBack without a match must throw" ) - [] - member this.``forall checks that every element satisfies the predicate`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - for predicate in [ isEven; (fun item -> item < 100); never ] do - Assert.AreEqual( - List.forall predicate elements, - ops.Forall predicate collection, - $"%s{ops.Name}.forall over %A{elements}" - ) + [.InputsAndForallPredicates)>] + member this.``forall checks that every element satisfies the predicate`` (input : int array, predicate : ElementPredicate) = + let struct (collection, elements) = this.Create input + + Assert.AreEqual( + Array.forall predicate.Matches elements, + ops.Forall predicate.Matches collection, + $"%s{ops.Name}.forall must check that every element satisfies the predicate" ) - [] - member this.``forall2 tests every pair at the same index`` () = - let collection = ops.OfSeq Inputs.several - let other = ops.OfSeq Inputs.other - let elements = ops.ToList collection - let otherElements = ops.ToList other - - for predicate in [ (fun a b -> a < b); (fun a b -> a <> b); (fun _ _ -> false) ] do - Assert.AreEqual( - List.forall2 predicate elements otherElements, - ops.Forall2 predicate collection other, - $"%s{ops.Name}.forall2 over %A{elements} and %A{otherElements}" - ) + [.Forall2Predicates)>] + member this.``forall2 tests every pair at the same index`` (predicate : PairPredicate) = + let struct (collection, elements) = this.Create Inputs.Several + let struct (other, otherElements) = this.Create Inputs.Other + + Assert.AreEqual( + Array.forall2 predicate.Matches elements otherElements, + ops.Forall2 predicate.Matches collection other, + $"%s{ops.Name}.forall2 must test every pair at the same index" + ) [] member this.``forall2 throws for different lengths as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( - (fun () -> Array.forall2 (fun _ _ -> true) (List.toArray Inputs.singleton) (List.toArray Inputs.several)), - (fun () -> List.forall2 (fun _ _ -> true) Inputs.singleton Inputs.several), - (fun () -> ops.Forall2 (fun _ _ -> true) (ops.OfSeq Inputs.singleton) (ops.OfSeq Inputs.several)), + (fun () -> Array.forall2 (fun _ _ -> true) Inputs.Singleton Inputs.Several), + (fun () -> List.forall2 (fun _ _ -> true) (List.ofArray Inputs.Singleton) (List.ofArray Inputs.Several)), + (fun () -> ops.Forall2 (fun _ _ -> true) (ops.OfSeq Inputs.Singleton) (ops.OfSeq Inputs.Several)), "forall2 of collections of different lengths must throw" ) [] member this.``pick returns the first chosen value`` () = - this.ForEach ( - [ Inputs.several ], - fun collection elements -> - Assert.AreEqual( - List.pick negatedIfNegativeOption elements, - ops.Pick negatedIfNegative collection, - $"%s{ops.Name}.pick in %A{elements}" - ) + let struct (collection, elements) = this.Create Inputs.Several + + Assert.AreEqual( + Array.pick negatedIfNegativeOption elements, + ops.Pick negatedIfNegative collection, + $"%s{ops.Name}.pick must return the first chosen value" ) [] member this.``pick throws when the chooser returns nothing as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( - (fun () -> Array.pick (fun _ -> None) (List.toArray Inputs.several) : int), - (fun () -> List.pick (fun _ -> None) Inputs.several : int), - (fun () -> ops.Pick (fun _ -> ValueNone) (ops.OfSeq Inputs.several)), + (fun () -> Array.pick (fun _ -> None) Inputs.Several : int), + (fun () -> List.pick (fun _ -> None) (List.ofArray Inputs.Several) : int), + (fun () -> ops.Pick (fun _ -> ValueNone) (ops.OfSeq Inputs.Several)), "pick without a choice must throw" ) - [] - member this.``tryFind returns the first element that satisfies the predicate or nothing`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - for predicate in [ isEven; isNegative; never ] do - Assert.AreEqual( - List.tryFind predicate elements |> ValueOption.ofOption, - ops.TryFind predicate collection, - $"%s{ops.Name}.tryFind in %A{elements}" - ) + [.InputsAndPredicates)>] + member this.``tryFind returns the first element that satisfies the predicate or nothing`` + (input : int array, predicate : ElementPredicate) + = + let struct (collection, elements) = this.Create input + + Assert.AreEqual( + Array.tryFind predicate.Matches elements + |> ValueOption.ofOption, + ops.TryFind predicate.Matches collection, + $"%s{ops.Name}.tryFind must return the first element that satisfies the predicate or nothing" ) - [] - member this.``tryFindBack returns the last element that satisfies the predicate or nothing`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - for predicate in [ isEven; isNegative; never ] do - Assert.AreEqual( - List.tryFindBack predicate elements |> ValueOption.ofOption, - ops.TryFindBack predicate collection, - $"%s{ops.Name}.tryFindBack in %A{elements}" - ) + [.InputsAndPredicates)>] + member this.``tryFindBack returns the last element that satisfies the predicate or nothing`` + (input : int array, predicate : ElementPredicate) + = + let struct (collection, elements) = this.Create input + + Assert.AreEqual( + Array.tryFindBack predicate.Matches elements + |> ValueOption.ofOption, + ops.TryFindBack predicate.Matches collection, + $"%s{ops.Name}.tryFindBack must return the last element that satisfies the predicate or nothing" ) - [] - member this.``tryFindIndex returns the index of the first element that satisfies the predicate or nothing`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - for predicate in [ isEven; isNegative; never ] do - Assert.AreEqual( - List.tryFindIndex predicate elements |> ValueOption.ofOption, - ops.TryFindIndex predicate collection, - $"%s{ops.Name}.tryFindIndex in %A{elements}" - ) + [.InputsAndPredicates)>] + member this.``tryFindIndex returns the index of the first element that satisfies the predicate or nothing`` + (input : int array, predicate : ElementPredicate) + = + let struct (collection, elements) = this.Create input + + Assert.AreEqual( + Array.tryFindIndex predicate.Matches elements + |> ValueOption.ofOption, + ops.TryFindIndex predicate.Matches collection, + $"%s{ops.Name}.tryFindIndex must return the index of the first element that satisfies the predicate or nothing" ) - [] - member this.``tryFindIndexBack returns the index of the last element that satisfies the predicate or nothing`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - for predicate in [ isEven; isNegative; never ] do - Assert.AreEqual( - List.tryFindIndexBack predicate elements - |> ValueOption.ofOption, - ops.TryFindIndexBack predicate collection, - $"%s{ops.Name}.tryFindIndexBack in %A{elements}" - ) + [.InputsAndPredicates)>] + member this.``tryFindIndexBack returns the index of the last element that satisfies the predicate or nothing`` + (input : int array, predicate : ElementPredicate) + = + let struct (collection, elements) = this.Create input + + Assert.AreEqual( + Array.tryFindIndexBack predicate.Matches elements + |> ValueOption.ofOption, + ops.TryFindIndexBack predicate.Matches collection, + $"%s{ops.Name}.tryFindIndexBack must return the index of the last element that satisfies the predicate or nothing" ) - [] - member this.``tryPick returns the first chosen value or nothing`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.tryPick negatedIfNegativeOption elements - |> ValueOption.ofOption, - ops.TryPick negatedIfNegative collection, - $"%s{ops.Name}.tryPick in %A{elements}" - ) + [)>] + member this.``tryPick returns the first chosen value or nothing`` (input : int array) = + let struct (collection, elements) = this.Create input + + Assert.AreEqual( + Array.tryPick negatedIfNegativeOption elements + |> ValueOption.ofOption, + ops.TryPick negatedIfNegative collection, + $"%s{ops.Name}.tryPick must return the first chosen value or nothing" ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSortingContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSortingContract.fs index 683bd1fb..dd13e589 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSortingContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSortingContract.fs @@ -13,78 +13,64 @@ open Microsoft.VisualStudio.TestTools.UnitTesting type SeqLikeSortingContract<'C> (ops : SeqOps<'C>) = inherit SeqLikePartitioningContract<'C> (ops) - [] - member this.``sort orders the elements ascending`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.sort elements, - ops.ToList (ops.Sort collection), - $"%s{ops.Name}.sort of %A{elements}" - ) + [)>] + member this.``sort orders the elements ascending`` (input : int array) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.sort elements, + ops.ToArray (ops.Sort collection), + $"%s{ops.Name}.sort must order the elements ascending" ) - [] - member this.``sortBy orders the elements by the projection`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.sortBy abs elements, - ops.ToList (ops.SortBy abs collection), - $"%s{ops.Name}.sortBy of %A{elements}" - ) + [)>] + member this.``sortBy orders the elements by the projection`` (input : int array) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.sortBy abs elements, + ops.ToArray (ops.SortBy abs collection), + $"%s{ops.Name}.sortBy must order the elements by the projection" ) - [] - member this.``sortByDescending orders the elements by the projection descending`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.sortByDescending abs elements, - ops.ToList (ops.SortByDescending abs collection), - $"%s{ops.Name}.sortByDescending of %A{elements}" - ) + [)>] + member this.``sortByDescending orders the elements by the projection descending`` (input : int array) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.sortByDescending abs elements, + ops.ToArray (ops.SortByDescending abs collection), + $"%s{ops.Name}.sortByDescending must order the elements by the projection descending" ) - [] - member this.``sortDescending orders the elements descending`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.sortDescending elements, - ops.ToList (ops.SortDescending collection), - $"%s{ops.Name}.sortDescending of %A{elements}" - ) + [)>] + member this.``sortDescending orders the elements descending`` (input : int array) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.sortDescending elements, + ops.ToArray (ops.SortDescending collection), + $"%s{ops.Name}.sortDescending must order the elements descending" ) - [] - member this.``sortWith orders the elements with the comparison function`` () = + [)>] + member this.``sortWith orders the elements with the comparison function`` (input : int array) = + let struct (collection, elements) = this.Create input let descending (left : int) (right : int) = compare right left - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.sortWith descending elements, - ops.ToList (ops.SortWith descending collection), - $"%s{ops.Name}.sortWith of %A{elements}" - ) + CollectionAssert.AreEqual ( + Array.sortWith descending elements, + ops.ToArray (ops.SortWith descending collection), + $"%s{ops.Name}.sortWith must order the elements with the comparison function" ) - [] - member this.``sortWithComparer orders the elements with the comparer`` () = + [)>] + member this.``sortWithComparer orders the elements with the comparer`` (input : int array) = + let struct (collection, elements) = this.Create input let descending = Comparer.Create (fun left right -> compare right left) - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.sortDescending elements, - ops.ToList (ops.SortWithComparer descending collection), - $"%s{ops.Name}.sortWithComparer of %A{elements}" - ) + CollectionAssert.AreEqual ( + Array.sortDescending elements, + ops.ToArray (ops.SortWithComparer descending collection), + $"%s{ops.Name}.sortWithComparer must order the elements with the comparer" ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeTransformationContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeTransformationContract.fs index 2b3de026..d970ba11 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeTransformationContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeTransformationContract.fs @@ -8,379 +8,366 @@ open Microsoft.VisualStudio.TestTools.UnitTesting type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = inherit SeqLikeSearchContract<'C> (ops) - static let toStructPairs (pairs : ('T1 * 'T2) list) = + static let toStructPairs (pairs : ('T1 * 'T2) array) = pairs - |> List.map (fun (first, second) -> struct (first, second)) + |> Array.map (fun (first, second) -> struct (first, second)) - static let toStructTriples (triples : ('T1 * 'T2 * 'T3) list) = + static let toStructTriples (triples : ('T1 * 'T2 * 'T3) array) = triples - |> List.map (fun (first, second, third) -> struct (first, second, third)) + |> Array.map (fun (first, second, third) -> struct (first, second, third)) + + /// Every input with every input, for the functions of two collections of any lengths. + static member InputPairs = seq { + for first in Inputs.All do + for second in Inputs.All do + struct (first, second) + } + + /// Every input with each size of a chunk or a window: one, inside the length, the length and beyond it. + static member InputsAndSizes = seq { + for input in Inputs.All do + for size in [| 1; 3; 8; 9 |] do + struct (input, size) + } + + [)>] + member this.``allPairs pairs every element with every element of the other collection`` (input : int array) = + let struct (collection, elements) = this.Create input + let struct (other, otherElements) = this.Create [| 1; 2; 3 |] + + CollectionAssert.AreEqual ( + Array.allPairs elements otherElements |> toStructPairs, + ops.AllPairs collection other, + $"%s{ops.Name}.allPairs must pair every element with every element of the other collection" + ) - [] - member this.``allPairs pairs every element with every element of the other collection`` () = - let other = ops.OfSeq [ 1; 2; 3 ] - let otherElements = ops.ToList other - - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.allPairs elements otherElements |> toStructPairs, - ops.AllPairs collection other, - $"%s{ops.Name}.allPairs of %A{elements} and %A{otherElements}" - ) + [.InputPairs)>] + member this.``append concatenates two collections`` (first : int array, second : int array) = + let struct (collection, elements) = this.Create first + let struct (other, otherElements) = this.Create second + + CollectionAssert.AreEqual ( + Array.append elements otherElements, + ops.ToArray (ops.Append collection other), + $"%s{ops.Name}.append must concatenate two collections" ) - [] - member this.``append concatenates two collections`` () = - for first in Inputs.all do - for second in Inputs.all do - let collection = ops.OfSeq first - let other = ops.OfSeq second - - Assert.AreEqual( - ops.ToList collection @ ops.ToList other, - ops.ToList (ops.Append collection other), - $"%s{ops.Name}.append of %A{first} and %A{second}" - ) + [)>] + member this.``choose keeps the chosen values in order`` (input : int array) = + let struct (collection, elements) = this.Create input + let chooser (item : int) = if item > 0 then ValueSome (item * 10) else ValueNone + let chooserOption (item : int) = if item > 0 then Some (item * 10) else None - [] - member this.``choose keeps the chosen values in order`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - let chooser (item : int) = if item > 0 then ValueSome (item * 10) else ValueNone - let chooserOption (item : int) = if item > 0 then Some (item * 10) else None - - Assert.AreEqual( - List.choose chooserOption elements, - ops.ToList (ops.Choose chooser collection), - $"%s{ops.Name}.choose of %A{elements}" - ) + CollectionAssert.AreEqual ( + Array.choose chooserOption elements, + ops.ToArray (ops.Choose chooser collection), + $"%s{ops.Name}.choose must keep the chosen values in order" ) - [] - member this.``chunkBySize divides the elements into chunks`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - for size in [ 1; 3; 8; 9 ] do - Assert.AreEqual( - List.chunkBySize size elements, - ops.ChunkBySize size collection, - $"%s{ops.Name}.chunkBySize %d{size} of %A{elements}" - ) + [.InputsAndSizes)>] + member this.``chunkBySize divides the elements into chunks`` (input : int array, size : int) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.chunkBySize size elements, + ops.ChunkBySize size collection, + $"%s{ops.Name}.chunkBySize must divide the elements into chunks" ) - [] - member this.``chunkBySize throws for a size that is not positive as FSharp.Core does`` () = - for size in [ 0; -1 ] do - this.ThrowsLikeFSharpCore ( - (fun () -> Array.chunkBySize size (List.toArray Inputs.several)), - (fun () -> List.chunkBySize size Inputs.several), - (fun () -> ops.ChunkBySize size (ops.OfSeq Inputs.several)), - $"chunkBySize %d{size} must throw" - ) + [] + member this.``chunkBySize throws for a size that is not positive as FSharp.Core does`` (size : int) = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.chunkBySize size Inputs.Several), + (fun () -> List.chunkBySize size (List.ofArray Inputs.Several)), + (fun () -> ops.ChunkBySize size (ops.OfSeq Inputs.Several)), + "chunkBySize of a size that is not positive must throw" + ) - [] - member this.``collect concatenates the collections of the elements`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.collect (fun item -> [ item; item * 10 ]) elements, - ops.ToList (ops.Collect (fun item -> ops.OfSeq [ item; item * 10 ]) collection), - $"%s{ops.Name}.collect of %A{elements}" - ) + [)>] + member this.``collect concatenates the collections of the elements`` (input : int array) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.collect (fun item -> [| item; item * 10 |]) elements, + ops.ToArray (ops.Collect (fun item -> ops.OfSeq [| item; item * 10 |]) collection), + $"%s{ops.Name}.collect must concatenate the collections of the elements" ) [] - member this.``concat concatenates a sequence of collections`` () = - let collections = Inputs.all |> List.map (fun input -> ops.OfSeq input) - let elements = collections |> List.map ops.ToList - - Assert.AreEqual( - List.concat elements, - ops.ToList (ops.Concat collections), - $"%s{ops.Name}.concat of %A{elements}" + member _.``concat concatenates a sequence of collections`` () = + // A call converts an array to the sequence that OfSeq takes; the function value itself does not take an array + let collections = + Inputs.All + |> Seq.map (fun input -> ops.OfSeq input) + |> Seq.toArray + let elements = collections |> Array.map ops.ToArray + + CollectionAssert.AreEqual ( + Array.concat elements, + ops.ToArray (ops.Concat collections), + $"%s{ops.Name}.concat must concatenate the collections in order" ) - Assert.AreEqual([], ops.ToList (ops.Concat []), $"%s{ops.Name}.concat of no collections") - [] - member this.``filter keeps the elements that satisfy the predicate`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.filter (fun item -> item > 0) elements, - ops.ToList (ops.Filter (fun item -> item > 0) collection), - $"%s{ops.Name}.filter of %A{elements}" - ) + Assert.IsEmpty (ops.ToArray (ops.Concat Array.empty), $"%s{ops.Name}.concat of no collections must be empty") + + [)>] + member this.``filter keeps the elements that satisfy the predicate`` (input : int array) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.filter (fun item -> item > 0) elements, + ops.ToArray (ops.Filter (fun item -> item > 0) collection), + $"%s{ops.Name}.filter must keep the elements that satisfy the predicate" ) - [] - member this.``indexed pairs every element with its index`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.indexed elements |> toStructPairs, - ops.Indexed collection, - $"%s{ops.Name}.indexed of %A{elements}" - ) + [)>] + member this.``indexed pairs every element with its index`` (input : int array) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.indexed elements |> toStructPairs, + ops.Indexed collection, + $"%s{ops.Name}.indexed must pair every element with its index" ) - [] - member this.``map transforms every element`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.map (fun item -> item * 3) elements, - ops.ToList (ops.Map (fun item -> item * 3) collection), - $"%s{ops.Name}.map of %A{elements}" - ) + [)>] + member this.``map transforms every element`` (input : int array) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.map (fun item -> item * 3) elements, + ops.ToArray (ops.Map (fun item -> item * 3) collection), + $"%s{ops.Name}.map must transform every element" ) [] member this.``map2 transforms the pairs at the same index`` () = - let collection = ops.OfSeq Inputs.several - let other = ops.OfSeq Inputs.other + let struct (collection, elements) = this.Create Inputs.Several + let struct (other, otherElements) = this.Create Inputs.Other - Assert.AreEqual( - List.map2 (fun a b -> a * 100 + b) (ops.ToList collection) (ops.ToList other), - ops.ToList (ops.Map2 (fun a b -> a * 100 + b) collection other), - $"%s{ops.Name}.map2 of two collections" + CollectionAssert.AreEqual ( + Array.map2 (fun a b -> a * 100 + b) elements otherElements, + ops.ToArray (ops.Map2 (fun a b -> a * 100 + b) collection other), + $"%s{ops.Name}.map2 must transform the pairs at the same index" ) [] member this.``map2 throws for different lengths as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( - (fun () -> Array.map2 (+) (List.toArray Inputs.singleton) (List.toArray Inputs.several)), - (fun () -> List.map2 (+) Inputs.singleton Inputs.several), - (fun () -> ops.Map2 (+) (ops.OfSeq Inputs.singleton) (ops.OfSeq Inputs.several)), + (fun () -> Array.map2 (+) Inputs.Singleton Inputs.Several), + (fun () -> List.map2 (+) (List.ofArray Inputs.Singleton) (List.ofArray Inputs.Several)), + (fun () -> ops.Map2 (+) (ops.OfSeq Inputs.Singleton) (ops.OfSeq Inputs.Several)), "map2 of collections of different lengths must throw" ) [] member this.``map3 transforms the triples at the same index`` () = - let collection = ops.OfSeq Inputs.several - let other = ops.OfSeq Inputs.other - let third = ops.OfSeq Inputs.third - - Assert.AreEqual( - List.map3 (fun a b c -> a * 10000 + b * 100 + c) (ops.ToList collection) (ops.ToList other) (ops.ToList third), - ops.ToList (ops.Map3 (fun a b c -> a * 10000 + b * 100 + c) collection other third), - $"%s{ops.Name}.map3 of three collections" + let struct (collection, elements) = this.Create Inputs.Several + let struct (other, otherElements) = this.Create Inputs.Other + let struct (third, thirdElements) = this.Create Inputs.Third + + CollectionAssert.AreEqual ( + Array.map3 (fun a b c -> a * 10000 + b * 100 + c) elements otherElements thirdElements, + ops.ToArray (ops.Map3 (fun a b c -> a * 10000 + b * 100 + c) collection other third), + $"%s{ops.Name}.map3 must transform the triples at the same index" ) [] member this.``map3 throws for different lengths as FSharp.Core does`` () = - let shortArray = List.toArray Inputs.singleton - let longArray = List.toArray Inputs.several - this.ThrowsLikeFSharpCore ( - (fun () -> Array.map3 (fun a b c -> a + b + c) longArray shortArray longArray), - (fun () -> List.map3 (fun a b c -> a + b + c) Inputs.several Inputs.singleton Inputs.several), + (fun () -> Array.map3 (fun a b c -> a + b + c) Inputs.Several Inputs.Singleton Inputs.Several), + (fun () -> + List.map3 + (fun a b c -> a + b + c) + (List.ofArray Inputs.Several) + (List.ofArray Inputs.Singleton) + (List.ofArray Inputs.Several) + ), (fun () -> ops.Map3 (fun a b c -> a + b + c) - (ops.OfSeq Inputs.several) - (ops.OfSeq Inputs.singleton) - (ops.OfSeq Inputs.several) + (ops.OfSeq Inputs.Several) + (ops.OfSeq Inputs.Singleton) + (ops.OfSeq Inputs.Several) ), "map3 of collections of different lengths must throw" ) - [] - member this.``mapi transforms every element with its index`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.mapi (fun index item -> index * 1000 + item) elements, - ops.ToList (ops.Mapi (fun index item -> index * 1000 + item) collection), - $"%s{ops.Name}.mapi of %A{elements}" - ) + [)>] + member this.``mapi transforms every element with its index`` (input : int array) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.mapi (fun index item -> index * 1000 + item) elements, + ops.ToArray (ops.Mapi (fun index item -> index * 1000 + item) collection), + $"%s{ops.Name}.mapi must transform every element with its index" ) [] member this.``mapi2 transforms the pairs at the same index with the index`` () = - let collection = ops.OfSeq Inputs.several - let other = ops.OfSeq Inputs.other + let struct (collection, elements) = this.Create Inputs.Several + let struct (other, otherElements) = this.Create Inputs.Other - Assert.AreEqual( - List.mapi2 (fun index a b -> index * 10000 + a * 100 + b) (ops.ToList collection) (ops.ToList other), - ops.ToList (ops.Mapi2 (fun index a b -> index * 10000 + a * 100 + b) collection other), - $"%s{ops.Name}.mapi2 of two collections" + CollectionAssert.AreEqual ( + Array.mapi2 (fun index a b -> index * 10000 + a * 100 + b) elements otherElements, + ops.ToArray (ops.Mapi2 (fun index a b -> index * 10000 + a * 100 + b) collection other), + $"%s{ops.Name}.mapi2 must transform the pairs at the same index with the index" ) [] member this.``mapi2 throws for different lengths as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( - (fun () -> Array.mapi2 (fun _ a b -> a + b) (List.toArray Inputs.singleton) (List.toArray Inputs.several)), - (fun () -> List.mapi2 (fun _ a b -> a + b) Inputs.singleton Inputs.several), - (fun () -> ops.Mapi2 (fun _ a b -> a + b) (ops.OfSeq Inputs.singleton) (ops.OfSeq Inputs.several)), + (fun () -> Array.mapi2 (fun _ a b -> a + b) Inputs.Singleton Inputs.Several), + (fun () -> List.mapi2 (fun _ a b -> a + b) (List.ofArray Inputs.Singleton) (List.ofArray Inputs.Several)), + (fun () -> ops.Mapi2 (fun _ a b -> a + b) (ops.OfSeq Inputs.Singleton) (ops.OfSeq Inputs.Several)), "mapi2 of collections of different lengths must throw" ) - [] - member this.``pairwise pairs every element with its successor`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.pairwise elements |> toStructPairs, - ops.Pairwise collection, - $"%s{ops.Name}.pairwise of %A{elements}" - ) + [)>] + member this.``pairwise pairs every element with its successor`` (input : int array) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.pairwise elements |> toStructPairs, + ops.Pairwise collection, + $"%s{ops.Name}.pairwise must pair every element with its successor" ) - [] - member this.``permute moves every element to its new index`` () = - this.ForEach ( - Inputs.nonEmpty, - fun collection elements -> - let length = List.length elements - let indexMap index = (index + 3) % length - - Assert.AreEqual( - List.permute indexMap elements, - ops.ToList (ops.Permute indexMap collection), - $"%s{ops.Name}.permute of %A{elements}" - ) + [)>] + member this.``permute moves every element to its new index`` (input : int array) = + let struct (collection, elements) = this.Create input + let indexMap index = (index + 3) % elements.Length + + CollectionAssert.AreEqual ( + Array.permute indexMap elements, + ops.ToArray (ops.Permute indexMap collection), + $"%s{ops.Name}.permute must move every element to its new index" ) [] member this.``permute throws for a function that is not a permutation as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( - (fun () -> Array.permute (fun _ -> 0) (List.toArray Inputs.several)), - (fun () -> List.permute (fun _ -> 0) Inputs.several), - (fun () -> ops.Permute (fun _ -> 0) (ops.OfSeq Inputs.several)), + (fun () -> Array.permute (fun _ -> 0) Inputs.Several), + (fun () -> List.permute (fun _ -> 0) (List.ofArray Inputs.Several)), + (fun () -> ops.Permute (fun _ -> 0) (ops.OfSeq Inputs.Several)), "permute with a function that is not a permutation must throw" ) - [] - member this.``rev reverses the elements`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual(List.rev elements, ops.ToList (ops.Rev collection), $"%s{ops.Name}.rev of %A{elements}") + [)>] + member this.``rev reverses the elements`` (input : int array) = + let struct (collection, elements) = this.Create input + CollectionAssert.AreEqual ( + Array.rev elements, + ops.ToArray (ops.Rev collection), + $"%s{ops.Name}.rev must reverse the elements" ) [] - member this.``transpose turns the collections into their columns`` () = + member _.``transpose turns the collections into their columns`` () = let collections = - [ Inputs.several; Inputs.other; Inputs.third ] - |> List.map (fun input -> ops.OfSeq input) - let elements = collections |> List.map ops.ToList + [| Inputs.Several; Inputs.Other; Inputs.Third |] + |> Array.map (fun input -> ops.OfSeq input) + let elements = collections |> Array.map ops.ToArray - Assert.AreEqual( - List.transpose elements, + CollectionAssert.AreEqual ( + Array.transpose elements, ops.Transpose collections, - $"%s{ops.Name}.transpose of %A{elements}" + $"%s{ops.Name}.transpose must turn the collections into their columns" ) [] member this.``transpose throws for collections of different lengths as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( - (fun () -> Array.transpose [ List.toArray Inputs.several; List.toArray Inputs.singleton ]), - (fun () -> List.transpose [ Inputs.several; Inputs.singleton ]), - (fun () -> ops.Transpose [ ops.OfSeq Inputs.several; ops.OfSeq Inputs.singleton ]), + (fun () -> Array.transpose [| Inputs.Several; Inputs.Singleton |]), + (fun () -> List.transpose [| List.ofArray Inputs.Several; List.ofArray Inputs.Singleton |]), + (fun () -> ops.Transpose [| ops.OfSeq Inputs.Several; ops.OfSeq Inputs.Singleton |]), "transpose of collections of different lengths must throw" ) [] member _.``unzip splits the pairs`` () = - let pairs = List.zip Inputs.several Inputs.other + let pairs = Array.zip Inputs.Several Inputs.Other let struct (first, second) = ops.Unzip (toStructPairs pairs) - let expectedFirst, expectedSecond = List.unzip pairs - Assert.AreEqual(expectedFirst, first, $"%s{ops.Name}.unzip must return the first components") - Assert.AreEqual(expectedSecond, second, $"%s{ops.Name}.unzip must return the second components") + let expectedFirst, expectedSecond = Array.unzip pairs + CollectionAssert.AreEqual (expectedFirst, first, $"%s{ops.Name}.unzip must return the first components") + CollectionAssert.AreEqual (expectedSecond, second, $"%s{ops.Name}.unzip must return the second components") [] member _.``unzip3 splits the triples`` () = - let triples = List.zip3 Inputs.several Inputs.other Inputs.third + let triples = Array.zip3 Inputs.Several Inputs.Other Inputs.Third let struct (first, second, third) = ops.Unzip3 (toStructTriples triples) - let expectedFirst, expectedSecond, expectedThird = List.unzip3 triples - Assert.AreEqual(expectedFirst, first, $"%s{ops.Name}.unzip3 must return the first components") - Assert.AreEqual(expectedSecond, second, $"%s{ops.Name}.unzip3 must return the second components") - Assert.AreEqual(expectedThird, third, $"%s{ops.Name}.unzip3 must return the third components") - - [] - member this.``where keeps the elements that satisfy the predicate`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - Assert.AreEqual( - List.where (fun item -> item % 2 = 0) elements, - ops.ToList (ops.Where (fun item -> item % 2 = 0) collection), - $"%s{ops.Name}.where of %A{elements}" - ) + let expectedFirst, expectedSecond, expectedThird = Array.unzip3 triples + CollectionAssert.AreEqual (expectedFirst, first, $"%s{ops.Name}.unzip3 must return the first components") + CollectionAssert.AreEqual (expectedSecond, second, $"%s{ops.Name}.unzip3 must return the second components") + CollectionAssert.AreEqual (expectedThird, third, $"%s{ops.Name}.unzip3 must return the third components") + + [)>] + member this.``where keeps the elements that satisfy the predicate`` (input : int array) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.where (fun item -> item % 2 = 0) elements, + ops.ToArray (ops.Where (fun item -> item % 2 = 0) collection), + $"%s{ops.Name}.where must keep the elements that satisfy the predicate" ) - [] - member this.``windowed returns the windows that slide over the elements`` () = - this.ForEach ( - Inputs.all, - fun collection elements -> - for size in [ 1; 3; 8; 9 ] do - Assert.AreEqual( - List.windowed size elements, - ops.Windowed size collection, - $"%s{ops.Name}.windowed %d{size} of %A{elements}" - ) + [.InputsAndSizes)>] + member this.``windowed returns the windows that slide over the elements`` (input : int array, size : int) = + let struct (collection, elements) = this.Create input + + CollectionAssert.AreEqual ( + Array.windowed size elements, + ops.Windowed size collection, + $"%s{ops.Name}.windowed must return the windows that slide over the elements" ) - [] - member this.``windowed throws for a size that is not positive as FSharp.Core does`` () = - for size in [ 0; -1 ] do - this.ThrowsLikeFSharpCore ( - (fun () -> Array.windowed size (List.toArray Inputs.several)), - (fun () -> List.windowed size Inputs.several), - (fun () -> ops.Windowed size (ops.OfSeq Inputs.several)), - $"windowed %d{size} must throw" - ) + [] + member this.``windowed throws for a size that is not positive as FSharp.Core does`` (size : int) = + this.ThrowsLikeFSharpCore ( + (fun () -> Array.windowed size Inputs.Several), + (fun () -> List.windowed size (List.ofArray Inputs.Several)), + (fun () -> ops.Windowed size (ops.OfSeq Inputs.Several)), + "windowed of a size that is not positive must throw" + ) [] - member _.``zip pairs the elements at the same index`` () = - let collection = ops.OfSeq Inputs.several - let other = ops.OfSeq Inputs.other + member this.``zip pairs the elements at the same index`` () = + let struct (collection, elements) = this.Create Inputs.Several + let struct (other, otherElements) = this.Create Inputs.Other - Assert.AreEqual( - List.zip (ops.ToList collection) (ops.ToList other) - |> toStructPairs, + CollectionAssert.AreEqual ( + Array.zip elements otherElements |> toStructPairs, ops.Zip collection other, - $"%s{ops.Name}.zip of two collections" + $"%s{ops.Name}.zip must pair the elements at the same index" ) [] member this.``zip throws for different lengths as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( - (fun () -> Array.zip (List.toArray Inputs.singleton) (List.toArray Inputs.several)), - (fun () -> List.zip Inputs.singleton Inputs.several), - (fun () -> ops.Zip (ops.OfSeq Inputs.singleton) (ops.OfSeq Inputs.several)), + (fun () -> Array.zip Inputs.Singleton Inputs.Several), + (fun () -> List.zip (List.ofArray Inputs.Singleton) (List.ofArray Inputs.Several)), + (fun () -> ops.Zip (ops.OfSeq Inputs.Singleton) (ops.OfSeq Inputs.Several)), "zip of collections of different lengths must throw" ) [] - member _.``zip3 combines the elements at the same index`` () = - let collection = ops.OfSeq Inputs.several - let other = ops.OfSeq Inputs.other - let third = ops.OfSeq Inputs.third + member this.``zip3 combines the elements at the same index`` () = + let struct (collection, elements) = this.Create Inputs.Several + let struct (other, otherElements) = this.Create Inputs.Other + let struct (third, thirdElements) = this.Create Inputs.Third - Assert.AreEqual( - List.zip3 (ops.ToList collection) (ops.ToList other) (ops.ToList third) + CollectionAssert.AreEqual ( + Array.zip3 elements otherElements thirdElements |> toStructTriples, ops.Zip3 collection other third, - $"%s{ops.Name}.zip3 of three collections" + $"%s{ops.Name}.zip3 must combine the elements at the same index" ) [] member this.``zip3 throws for different lengths as FSharp.Core does`` () = this.ThrowsLikeFSharpCore ( - (fun () -> Array.zip3 (List.toArray Inputs.several) (List.toArray Inputs.singleton) (List.toArray Inputs.several)), - (fun () -> List.zip3 Inputs.several Inputs.singleton Inputs.several), - (fun () -> ops.Zip3 (ops.OfSeq Inputs.several) (ops.OfSeq Inputs.singleton) (ops.OfSeq Inputs.several)), + (fun () -> Array.zip3 Inputs.Several Inputs.Singleton Inputs.Several), + (fun () -> List.zip3 (List.ofArray Inputs.Several) (List.ofArray Inputs.Singleton) (List.ofArray Inputs.Several)), + (fun () -> ops.Zip3 (ops.OfSeq Inputs.Several) (ops.OfSeq Inputs.Singleton) (ops.OfSeq Inputs.Several)), "zip3 of collections of different lengths must throw" ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqOps.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqOps.fs index af0e7d36..0502367e 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqOps.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqOps.fs @@ -10,8 +10,8 @@ open System.Collections.Generic /// fields follow the order of the signature files. /// /// -/// Results of another element type are lists: the instance of a collection converts its results with its own -/// toList, so that one record type serves every collection. +/// Results of another element type are arrays: the instance of a collection converts its results with its own +/// toArray, so that one record type serves every collection. /// /// type SeqOps<'C> = { @@ -23,8 +23,9 @@ type SeqOps<'C> = { ParamName : string -> string /// Creates a collection that enumerates the given elements in their order. OfSeq : seq -> 'C - /// The elements in the order in which the collection enumerates them. - ToList : 'C -> int list + /// The elements in the order in which the collection enumerates them: the `toArray` of the module, which the + /// conversion tests check as well. + ToArray : 'C -> int array IsEmpty : 'C -> bool Length : 'C -> int @@ -54,69 +55,69 @@ type SeqOps<'C> = { TryFindIndexBack : (int -> bool) -> 'C -> int voption TryPick : (int -> int voption) -> 'C -> int voption - AllPairs : 'C -> 'C -> struct (int * int) list + AllPairs : 'C -> 'C -> struct (int * int) array Append : 'C -> 'C -> 'C Choose : (int -> int voption) -> 'C -> 'C - ChunkBySize : int -> 'C -> int list list + ChunkBySize : int -> 'C -> int array array Collect : (int -> 'C) -> 'C -> 'C - Concat : 'C list -> 'C + Concat : seq<'C> -> 'C Filter : (int -> bool) -> 'C -> 'C - Indexed : 'C -> struct (int * int) list + Indexed : 'C -> struct (int * int) array Map : (int -> int) -> 'C -> 'C Map2 : (int -> int -> int) -> 'C -> 'C -> 'C Map3 : (int -> int -> int -> int) -> 'C -> 'C -> 'C -> 'C Mapi : (int -> int -> int) -> 'C -> 'C Mapi2 : (int -> int -> int -> int) -> 'C -> 'C -> 'C - Pairwise : 'C -> struct (int * int) list + Pairwise : 'C -> struct (int * int) array Permute : (int -> int) -> 'C -> 'C Rev : 'C -> 'C - Transpose : 'C list -> int list list + Transpose : seq<'C> -> int array array /// Unzips a collection of the given pairs. - Unzip : struct (int * int) list -> struct (int list * int list) + Unzip : struct (int * int) array -> struct (int array * int array) /// Unzips a collection of the given triples. - Unzip3 : struct (int * int * int) list -> struct (int list * int list * int list) + Unzip3 : struct (int * int * int) array -> struct (int array * int array * int array) Where : (int -> bool) -> 'C -> 'C - Windowed : int -> 'C -> int list list - Zip : 'C -> 'C -> struct (int * int) list - Zip3 : 'C -> 'C -> 'C -> struct (int * int * int) list + Windowed : int -> 'C -> int array array + Zip : 'C -> 'C -> struct (int * int) array + Zip3 : 'C -> 'C -> 'C -> struct (int * int * int) array /// Averages a collection of the given floats. - Average : float list -> float + Average : float array -> float AverageBy : (int -> float) -> 'C -> float CompareWith : (int -> int -> int) -> 'C -> 'C -> int - Fold : (int list -> int -> int list) -> int list -> 'C -> int list - Fold2 : (int list -> int -> int -> int list) -> int list -> 'C -> 'C -> int list - FoldBack : (int -> int list -> int list) -> 'C -> int list -> int list - FoldBack2 : (int -> int -> int list -> int list) -> 'C -> 'C -> int list -> int list + Fold : (int array -> int -> int array) -> int array -> 'C -> int array + Fold2 : (int array -> int -> int -> int array) -> int array -> 'C -> 'C -> int array + FoldBack : (int -> int array -> int array) -> 'C -> int array -> int array + FoldBack2 : (int -> int -> int array -> int array) -> 'C -> 'C -> int array -> int array Iter : (int -> unit) -> 'C -> unit Iter2 : (int -> int -> unit) -> 'C -> 'C -> unit Iteri : (int -> int -> unit) -> 'C -> unit Iteri2 : (int -> int -> int -> unit) -> 'C -> 'C -> unit - MapFold : (int -> int -> struct (int * int)) -> int -> 'C -> struct (int list * int) - MapFoldBack : (int -> int -> struct (int * int)) -> 'C -> int -> struct (int list * int) + MapFold : (int -> int -> struct (int * int)) -> int -> 'C -> struct (int array * int) + MapFoldBack : (int -> int -> struct (int * int)) -> 'C -> int -> struct (int array * int) Max : 'C -> int MaxBy : (int -> int) -> 'C -> int Min : 'C -> int MinBy : (int -> int) -> 'C -> int Reduce : (int -> int -> int) -> 'C -> int ReduceBack : (int -> int -> int) -> 'C -> int - Scan : (int list -> int -> int list) -> int list -> 'C -> int list list - ScanBack : (int -> int list -> int list) -> 'C -> int list -> int list list + Scan : (int array -> int -> int array) -> int array -> 'C -> int array array + ScanBack : (int -> int array -> int array) -> 'C -> int array -> int array array Sum : 'C -> int SumBy : (int -> int) -> 'C -> int - CountBy : (int -> int) -> 'C -> struct (int * int) list + CountBy : (int -> int) -> 'C -> struct (int * int) array Distinct : 'C -> 'C DistinctBy : (int -> int) -> 'C -> 'C Except : seq -> 'C -> 'C - GroupBy : (int -> int) -> 'C -> struct (int * int list) list + GroupBy : (int -> int) -> 'C -> struct (int * int array) array Partition : (int -> bool) -> 'C -> struct ('C * 'C) - PartitionWith : (int -> Choice) -> 'C -> struct (int list * string list) + PartitionWith : (int -> Choice) -> 'C -> struct (int array * string array) Skip : int -> 'C -> 'C SkipLast : int -> 'C -> 'C SkipWhile : (int -> bool) -> 'C -> 'C SplitAt : int -> 'C -> struct ('C * 'C) - SplitInto : int -> 'C -> int list list + SplitInto : int -> 'C -> int array array Take : int -> 'C -> 'C TakeLast : int -> 'C -> 'C TakeWhile : (int -> bool) -> 'C -> 'C @@ -142,7 +143,7 @@ type SeqOps<'C> = { RandomShuffleBy : (unit -> float) -> 'C -> 'C RandomShuffleWith : Random -> 'C -> 'C - ToArray : 'C -> int array + ToList : 'C -> int list ToSeq : 'C -> seq } @@ -156,7 +157,7 @@ type SeqCreationOps<'C> = { /// Maps the name of a parameter of the FSharp.Core oracle to the name of the parameter of the module. ParamName : string -> string /// The elements in the order in which the collection enumerates them. - ToList : 'C -> int list + ToArray : 'C -> int array Empty : unit -> 'C Init : int -> (int -> int) -> 'C @@ -181,7 +182,7 @@ type IndexedOps<'C> = { /// Creates a collection that enumerates the given elements in their order. OfSeq : seq -> 'C /// The elements in the order in which the collection enumerates them. - ToList : 'C -> int list + ToArray : 'C -> int array Item : int -> 'C -> int TryItem : int -> 'C -> int voption diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListBuilderTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListBuilderTests.fs index 32ad942c..298f4c47 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListBuilderTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListBuilderTests.fs @@ -12,13 +12,13 @@ type FlatListBuilderTests () = /// A builder that holds the elements of the input with several elements. static let filled () = let builder = FlatList.builder() - builder.AddRange Inputs.several + builder.AddRange Inputs.Several builder [] member _.``build returns the elements that the action adds`` () = - let array = FlatList.build (fun builder -> builder.AddRange Inputs.several) - Assert.AreEqual(Inputs.several, FlatList.toList array, "FlatList.build must return the added elements") + let array = FlatList.build (fun builder -> builder.AddRange Inputs.Several) + CollectionAssert.AreEqual (Inputs.Several, FlatList.toArray array, "FlatList.build must return the added elements") [] member _.``builder starts empty`` () = @@ -42,9 +42,9 @@ type FlatListBuilderTests () = [] member _.``moveFromBuilder takes the contents of a full builder`` () = let builder = FlatList.builderWith 3 - builder.AddRange [ 1; 2; 3 ] + builder.AddRange [| 1; 2; 3 |] let array = FlatList.moveFromBuilder builder - Assert.AreEqual([ 1; 2; 3 ], FlatList.toList array, "FlatList.moveFromBuilder must return the elements") + CollectionAssert.AreEqual ([| 1; 2; 3 |], FlatList.toArray array, "FlatList.moveFromBuilder must return the elements") Assert.AreEqual(0, builder.Count, "FlatList.moveFromBuilder must empty the builder") [] @@ -63,8 +63,8 @@ type FlatListBuilderTests () = let builder = filled () let array = FlatList.ofBuilder builder builder.Add 99 - Assert.AreEqual(Inputs.several, FlatList.toList array, "FlatList.ofBuilder must copy the elements") - Assert.AreEqual(Inputs.several.Length + 1, builder.Count, "FlatList.ofBuilder must keep the builder usable") + CollectionAssert.AreEqual (Inputs.Several, FlatList.toArray array, "FlatList.ofBuilder must copy the elements") + Assert.AreEqual(Inputs.Several.Length + 1, builder.Count, "FlatList.ofBuilder must keep the builder usable") [] member _.``ofBuilder throws for a null builder`` () = @@ -81,18 +81,24 @@ type FlatListBuilderTests () = [] member _.``toBuilder copies the elements into a new builder`` () = - let array = FlatList.ofList Inputs.several + let array = FlatList.ofArray Inputs.Several let builder = FlatList.toBuilder array builder.Add 99 - Assert.AreEqual(Inputs.several @ [ 99 ], List.ofSeq builder, "FlatList.toBuilder must hold the elements") - Assert.AreEqual(Inputs.several, FlatList.toList array, "FlatList.toBuilder must not change the flat list") + + CollectionAssert.AreEqual ( + Array.append Inputs.Several [| 99 |], + Seq.toArray builder, + "FlatList.toBuilder must hold the elements" + ) + + CollectionAssert.AreEqual (Inputs.Several, FlatList.toArray array, "FlatList.toBuilder must not change the flat list") [] member _.``update applies the action to a copy`` () = - let array = FlatList.ofList Inputs.several + let array = FlatList.ofArray Inputs.Several let updated = FlatList.update (fun builder -> builder.RemoveAt 0) array - Assert.AreEqual(List.tail Inputs.several, FlatList.toList updated, "FlatList.update must apply the action") - Assert.AreEqual(Inputs.several, FlatList.toList array, "FlatList.update must not change the input") + CollectionAssert.AreEqual (Array.tail Inputs.Several, FlatList.toArray updated, "FlatList.update must apply the action") + CollectionAssert.AreEqual (Inputs.Several, FlatList.toArray array, "FlatList.update must not change the input") [] member _.``add, addRange and insertAt return the same builder`` () = @@ -103,7 +109,7 @@ type FlatListBuilderTests () = |> FlatListBuilder.addRange [ 2; 3 ] |> FlatListBuilder.insertAt 0 0 Assert.AreSame (builder, result, "The functions that change a builder must return it") - Assert.AreEqual([ 0; 1; 2; 3 ], List.ofSeq builder, "add, addRange and insertAt must change the builder") + CollectionAssert.AreEqual ([| 0; 1; 2; 3 |], Seq.toArray builder, "add, addRange and insertAt must change the builder") [] member _.``capacity, setCapacity and length report the builder`` () = @@ -131,11 +137,13 @@ type FlatListBuilderTests () = member _.``drainToFlatList returns the elements and empties the builder`` () = let builder = filled () let array = FlatListBuilder.drainToFlatList builder - Assert.AreEqual( - Inputs.several, - FlatList.toList array, + + CollectionAssert.AreEqual ( + Inputs.Several, + FlatList.toArray array, "FlatListBuilder.drainToFlatList must return the elements" ) + Assert.AreEqual(0, FlatListBuilder.length builder, "FlatListBuilder.drainToFlatList must empty the builder") [] @@ -149,33 +157,33 @@ type FlatListBuilderTests () = Assert.IsTrue (FlatListBuilder.remove -3 builder, "FlatListBuilder.remove must report a removed element") Assert.IsFalse (FlatListBuilder.remove 1000 builder, "FlatListBuilder.remove must report a missing element") FlatListBuilder.removeAt 0 builder |> ignore - Assert.AreEqual([ 8; 5; 0; -3; 12; 7 ], List.ofSeq builder, "remove and removeAt must remove the elements") + CollectionAssert.AreEqual ([| 8; 5; 0; -3; 12; 7 |], Seq.toArray builder, "remove and removeAt must remove the elements") [] member _.``rev reverses the builder`` () = let builder = filled () |> FlatListBuilder.rev - Assert.AreEqual(List.rev Inputs.several, List.ofSeq builder, "FlatListBuilder.rev must reverse the elements") + CollectionAssert.AreEqual (Array.rev Inputs.Several, Seq.toArray builder, "FlatListBuilder.rev must reverse the elements") [] member _.``sort, sortWith and sortWithComparer order the builder`` () = - Assert.AreEqual( - List.sort Inputs.several, - List.ofSeq (filled () |> FlatListBuilder.sort), + CollectionAssert.AreEqual ( + Array.sort Inputs.Several, + Seq.toArray (filled () |> FlatListBuilder.sort), "FlatListBuilder.sort must sort ascending" ) - Assert.AreEqual( - List.sortDescending Inputs.several, - List.ofSeq ( + CollectionAssert.AreEqual ( + Array.sortDescending Inputs.Several, + Seq.toArray ( filled () |> FlatListBuilder.sortWith (fun left right -> compare right left) ), "FlatListBuilder.sortWith must use the comparison function" ) - Assert.AreEqual( - List.sortDescending Inputs.several, - List.ofSeq ( + CollectionAssert.AreEqual ( + Array.sortDescending Inputs.Several, + Seq.toArray ( filled () |> FlatListBuilder.sortWithComparer (Comparer.Create (fun left right -> compare right left)) ), @@ -186,7 +194,7 @@ type FlatListBuilderTests () = member _.``toArray copies the elements`` () = let builder = filled () CollectionAssert.AreEqual ( - List.toArray Inputs.several, + Inputs.Several, FlatListBuilder.toArray builder, "FlatListBuilder.toArray must copy the elements" ) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs index f2eea4f5..48496fe1 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs @@ -21,7 +21,7 @@ type FlatListComputationExpressionTests () = yield 3 } - Assert.AreEqual([ 1; 2; 3 ], FlatList.toList array, "flatList must keep the order of yield") + CollectionAssert.AreEqual ([| 1; 2; 3 |], FlatList.toArray array, "flatList must keep the order of yield") [] member _.``implicit yields add the elements in order`` () = @@ -31,7 +31,7 @@ type FlatListComputationExpressionTests () = 3 } - Assert.AreEqual([ 1; 2; 3 ], FlatList.toList array, "flatList must keep the order of implicit yields") + CollectionAssert.AreEqual ([| 1; 2; 3 |], FlatList.toArray array, "flatList must keep the order of implicit yields") [] member _.``yield! adds a sequence, an array, a flat list and another expression`` () = @@ -40,11 +40,11 @@ type FlatListComputationExpressionTests () = let array = flatList { yield! [ 1; 2 ] yield! [| 3; 4 |] - yield! FlatList.ofList [ 5; 6 ] + yield! FlatList.ofArray [| 5; 6 |] yield! inner } - Assert.AreEqual([ 1; 2; 3; 4; 5; 6; 7 ], FlatList.toList array, "flatList must add the elements of yield!") + CollectionAssert.AreEqual ([| 1; 2; 3; 4; 5; 6; 7 |], FlatList.toArray array, "flatList must add the elements of yield!") [] member _.``for adds the elements of the body for each item`` () = @@ -53,7 +53,7 @@ type FlatListComputationExpressionTests () = yield item * 10 } - Assert.AreEqual([ 10; 20; 30 ], FlatList.toList array, "flatList must run the body of for for each item") + CollectionAssert.AreEqual ([| 10; 20; 30 |], FlatList.toArray array, "flatList must run the body of for for each item") [] member _.``while adds the elements while the guard holds`` () = @@ -65,7 +65,11 @@ type FlatListComputationExpressionTests () = yield count } - Assert.AreEqual([ 1; 2; 3 ], FlatList.toList array, "flatList must run the body of while while the guard holds") + CollectionAssert.AreEqual ( + [| 1; 2; 3 |], + FlatList.toArray array, + "flatList must run the body of while while the guard holds" + ) [] member _.``if without else and following elements combine`` () = @@ -79,7 +83,7 @@ type FlatListComputationExpressionTests () = yield 2 } - Assert.AreEqual([ 1; 2 ], FlatList.toList array, "flatList must combine conditional and plain elements") + CollectionAssert.AreEqual ([| 1; 2 |], FlatList.toArray array, "flatList must combine conditional and plain elements") [] member _.``try with adds the elements of the handler after an exception`` () = @@ -91,9 +95,9 @@ type FlatListComputationExpressionTests () = yield 2 } - Assert.AreEqual( - [ 1; 2 ], - FlatList.toList array, + CollectionAssert.AreEqual ( + [| 1; 2 |], + FlatList.toArray array, "flatList must keep the elements before the exception and add the handler's" ) @@ -109,7 +113,7 @@ type FlatListComputationExpressionTests () = } Assert.IsTrue (compensated, "flatList must run the compensation of try finally") - Assert.AreEqual([ 1 ], FlatList.toList array, "flatList must keep the elements of the body of try finally") + CollectionAssert.AreEqual ([| 1 |], FlatList.toArray array, "flatList must keep the elements of the body of try finally") [] member _.``use disposes the resource after the body`` () = @@ -125,4 +129,4 @@ type FlatListComputationExpressionTests () = } Assert.IsTrue (disposed, "flatList must dispose the resource of use") - Assert.AreEqual([ 1 ], FlatList.toList array, "flatList must keep the elements of the body of use") + CollectionAssert.AreEqual ([| 1 |], FlatList.toArray array, "flatList must keep the elements of the body of use") diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOps.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOps.fs index 012e4b0f..b44a2853 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOps.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOps.fs @@ -5,10 +5,10 @@ module FSharp.Collections.Immutable.Tests.FlatListOps open System open FSharp.Collections.Immutable -let private toLists (lists : FlatList>) = lists |> FlatList.toList |> List.map FlatList.toList +let private toArrays (arrays : FlatList>) = arrays |> FlatList.toArray |> Array.map FlatList.toArray -let private toListPair (struct (first : FlatList<'T1>, second : FlatList<'T2>)) = - struct (FlatList.toList first, FlatList.toList second) +let private toArrayPair (struct (first : FlatList<'T1>, second : FlatList<'T2>)) = + struct (FlatList.toArray first, FlatList.toArray second) /// The functions of the sequence-like facet. `FlatList` names its parameters as the `Array` module does. let seqLike : SeqOps> = { @@ -16,7 +16,7 @@ let seqLike : SeqOps> = { Mirror = ArrayMirror ParamName = id OfSeq = FlatList.ofSeq - ToList = FlatList.toList + ToArray = FlatList.toArray IsEmpty = FlatList.isEmpty Length = FlatList.length @@ -46,34 +46,34 @@ let seqLike : SeqOps> = { TryFindIndexBack = FlatList.tryFindIndexBack TryPick = FlatList.tryPick - AllPairs = fun array1 array2 -> FlatList.allPairs array1 array2 |> FlatList.toList + AllPairs = fun array1 array2 -> FlatList.allPairs array1 array2 |> FlatList.toArray Append = FlatList.append Choose = FlatList.choose - ChunkBySize = fun chunkSize array -> FlatList.chunkBySize chunkSize array |> toLists + ChunkBySize = fun chunkSize array -> FlatList.chunkBySize chunkSize array |> toArrays Collect = FlatList.collect Concat = FlatList.concat Filter = FlatList.filter - Indexed = FlatList.indexed >> FlatList.toList + Indexed = FlatList.indexed >> FlatList.toArray Map = FlatList.map Map2 = FlatList.map2 Map3 = FlatList.map3 Mapi = FlatList.mapi Mapi2 = FlatList.mapi2 - Pairwise = FlatList.pairwise >> FlatList.toList + Pairwise = FlatList.pairwise >> FlatList.toArray Permute = FlatList.permute Rev = FlatList.rev - Transpose = fun arrays -> FlatList.transpose arrays |> toLists - Unzip = fun pairs -> FlatList.unzip (FlatList.ofList pairs) |> toListPair + Transpose = fun arrays -> FlatList.transpose arrays |> toArrays + Unzip = fun pairs -> FlatList.unzip (FlatList.ofArray pairs) |> toArrayPair Unzip3 = fun triples -> - let struct (first, second, third) = FlatList.unzip3 (FlatList.ofList triples) - struct (FlatList.toList first, FlatList.toList second, FlatList.toList third) + let struct (first, second, third) = FlatList.unzip3 (FlatList.ofArray triples) + struct (FlatList.toArray first, FlatList.toArray second, FlatList.toArray third) Where = FlatList.where - Windowed = fun windowSize array -> FlatList.windowed windowSize array |> toLists - Zip = fun array1 array2 -> FlatList.zip array1 array2 |> FlatList.toList - Zip3 = fun array1 array2 array3 -> FlatList.zip3 array1 array2 array3 |> FlatList.toList + Windowed = fun windowSize array -> FlatList.windowed windowSize array |> toArrays + Zip = fun array1 array2 -> FlatList.zip array1 array2 |> FlatList.toArray + Zip3 = fun array1 array2 array3 -> FlatList.zip3 array1 array2 array3 |> FlatList.toArray - Average = fun items -> FlatList.average (FlatList.ofList items) + Average = fun items -> FlatList.average (FlatList.ofArray items) AverageBy = FlatList.averageBy CompareWith = FlatList.compareWith Fold = FlatList.fold @@ -87,38 +87,38 @@ let seqLike : SeqOps> = { MapFold = fun mapping state array -> let struct (results, final) = FlatList.mapFold mapping state array - struct (FlatList.toList results, final) + struct (FlatList.toArray results, final) MapFoldBack = fun mapping array state -> let struct (results, final) = FlatList.mapFoldBack mapping array state - struct (FlatList.toList results, final) + struct (FlatList.toArray results, final) Max = FlatList.max MaxBy = FlatList.maxBy Min = FlatList.min MinBy = FlatList.minBy Reduce = FlatList.reduce ReduceBack = FlatList.reduceBack - Scan = fun folder state array -> FlatList.scan folder state array |> FlatList.toList - ScanBack = fun folder array state -> FlatList.scanBack folder array state |> FlatList.toList + Scan = fun folder state array -> FlatList.scan folder state array |> FlatList.toArray + ScanBack = fun folder array state -> FlatList.scanBack folder array state |> FlatList.toArray Sum = FlatList.sum SumBy = FlatList.sumBy - CountBy = fun projection array -> FlatList.countBy projection array |> FlatList.toList + CountBy = fun projection array -> FlatList.countBy projection array |> FlatList.toArray Distinct = FlatList.distinct DistinctBy = FlatList.distinctBy Except = FlatList.except GroupBy = fun projection array -> FlatList.groupBy projection array - |> FlatList.toList - |> List.map (fun struct (key, items) -> struct (key, FlatList.toList items)) + |> FlatList.toArray + |> Array.map (fun struct (key, items) -> struct (key, FlatList.toArray items)) Partition = FlatList.partition - PartitionWith = fun partitioner array -> FlatList.partitionWith partitioner array |> toListPair + PartitionWith = fun partitioner array -> FlatList.partitionWith partitioner array |> toArrayPair Skip = FlatList.skip SkipLast = FlatList.skipLast SkipWhile = FlatList.skipWhile SplitAt = FlatList.splitAt - SplitInto = fun count array -> FlatList.splitInto count array |> toLists + SplitInto = fun count array -> FlatList.splitInto count array |> toArrays Take = FlatList.take TakeLast = FlatList.takeLast TakeWhile = FlatList.takeWhile @@ -144,7 +144,7 @@ let seqLike : SeqOps> = { RandomShuffleBy = FlatList.randomShuffleBy RandomShuffleWith = FlatList.randomShuffleWith - ToArray = FlatList.toArray + ToList = FlatList.toList ToSeq = FlatList.toSeq } @@ -153,7 +153,7 @@ let creation : SeqCreationOps> = { Name = nameof FlatList Mirror = ArrayMirror ParamName = id - ToList = FlatList.toList + ToArray = FlatList.toArray Empty = fun () -> FlatList.empty Init = FlatList.init @@ -173,7 +173,7 @@ let indexed : IndexedOps> = { Mirror = ArrayMirror ParamName = id OfSeq = FlatList.ofSeq - ToList = FlatList.toList + ToArray = FlatList.toArray Item = FlatList.item TryItem = FlatList.tryItem diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOptionTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOptionTests.fs index c7779f50..be75382a 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOptionTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOptionTests.fs @@ -12,7 +12,7 @@ open Microsoft.VisualStudio.TestTools.UnitTesting [] type FlatListOptionTests () = - static let array = FlatList.ofList Inputs.several + static let array = FlatList.ofArray Inputs.Several static let negatedIfNegative (item : int) = if item < 0 then Some (-item) else None @@ -20,10 +20,11 @@ type FlatListOptionTests () = member _.``unfold takes a generator of options of reference tuples`` () = let generator (state : int) = if state > 0 then Some (state, state - 1) else None let generated : FlatList = FlatList.unfold generator 3 - Assert.AreEqual( - List.unfold generator 3, - FlatList.toList generated, - "Option.FlatList.unfold must generate as List.unfold does" + + CollectionAssert.AreEqual ( + Array.unfold generator 3, + FlatList.toArray generated, + "Option.FlatList.unfold must generate as Array.unfold does" ) [] @@ -43,15 +44,14 @@ type FlatListOptionTests () = found "Option.FlatList.tryHead must match the default" - [] - member _.``tryItem returns an option`` () = - for index in [ -1; 2; 8 ] do - let found : int option = FlatList.tryItem index array + [] + member _.``tryItem returns an option`` (index : int) = + let found : int option = FlatList.tryItem index array - OverrideAssert.optionMatches - (FSharp.Collections.Immutable.FlatList.tryItem index array) - found - $"Option.FlatList.tryItem %d{index} must match the default" + OverrideAssert.optionMatches + (FSharp.Collections.Immutable.FlatList.tryItem index array) + found + "Option.FlatList.tryItem must match the default" [] member _.``tryLast returns an option`` () = @@ -63,23 +63,27 @@ type FlatListOptionTests () = [] member _.``tryTail returns an option`` () = + let expected = FSharp.Collections.Immutable.FlatList.tryTail array let found : FlatList option = FlatList.tryTail array - - Assert.AreEqual( - FSharp.Collections.Immutable.FlatList.tryTail array - |> ValueOption.map FlatList.toList - |> ValueOption.toOption, - found |> Option.map FlatList.toList, - "Option.FlatList.tryTail must match the default" - ) + Assert.AreEqual(expected.IsSome, found.IsSome, "Option.FlatList.tryTail must return a tail when the default does") + + // The tails are compared element by element + match expected, found with + | ValueSome expected, Some found -> + CollectionAssert.AreEqual ( + FlatList.toArray expected, + FlatList.toArray found, + "Option.FlatList.tryTail must return the tail of the default" + ) + | _ -> () [] member _.``pick takes a chooser of options`` () = let picked : int = FlatList.pick negatedIfNegative array Assert.AreEqual( - List.pick negatedIfNegative Inputs.several, + Array.pick negatedIfNegative Inputs.Several, picked, - "Option.FlatList.pick must pick as List.pick does" + "Option.FlatList.pick must pick as Array.pick does" ) [] @@ -122,32 +126,32 @@ type FlatListOptionTests () = member _.``tryPick takes a chooser of options and returns an option`` () = let found : int option = FlatList.tryPick negatedIfNegative array Assert.AreEqual( - List.tryPick negatedIfNegative Inputs.several, + Array.tryPick negatedIfNegative Inputs.Several, found, - "Option.FlatList.tryPick must pick as List.tryPick does" + "Option.FlatList.tryPick must pick as Array.tryPick does" ) [] member _.``choose takes a chooser of options`` () = let chosen : FlatList = FlatList.choose negatedIfNegative array - Assert.AreEqual( - List.choose negatedIfNegative Inputs.several, - FlatList.toList chosen, - "Option.FlatList.choose must choose as List.choose does" + CollectionAssert.AreEqual ( + Array.choose negatedIfNegative Inputs.Several, + FlatList.toArray chosen, + "Option.FlatList.choose must choose as Array.choose does" ) [] member _.``the functions without an option keep resolving to the default module`` () = let mapped : FlatList = FlatList.map (fun item -> item * 2) array let zipped : FlatList = FlatList.zip array array - Assert.AreEqual( - List.map (fun item -> item * 2) Inputs.several, - FlatList.toList mapped, + CollectionAssert.AreEqual ( + Array.map (fun item -> item * 2) Inputs.Several, + FlatList.toArray mapped, "FlatList.map must resolve to the default module" ) - Assert.AreEqual<(int * int) list>( - List.zip Inputs.several Inputs.several, - FlatList.toList zipped, + CollectionAssert.AreEqual ( + Array.zip Inputs.Several Inputs.Several, + FlatList.toArray zipped, "FlatList.zip must resolve to the Tuple module" ) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs index 5b32b397..5dabb7f6 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs @@ -17,33 +17,10 @@ type FlatListTests () = /// The array under a flat list, to tell whether two flat lists share their storage. static let underlying (array : FlatList) = ImmutableCollectionsMarshal.AsArray array - [] - member _.``create repeats the value`` () = - Assert.AreEqual([ 7; 7; 7 ], FlatList.toList (FlatList.create 3 7), "FlatList.create must repeat the value") - Assert.IsEmpty (FlatList.create 0 7, "FlatList.create 0 must be empty") - - [] - member _.``create throws for a negative count as Array does`` () = - ExceptionAssert.same - id - (fun () -> Array.create -1 7 |> ignore) - (fun () -> FlatList.create -1 7 |> ignore) - "FlatList.create -1 must throw as Array.create does" - - [] - member _.``isDefault and isDefaultOrEmpty tell a default value from an empty one`` () = - Assert.IsTrue (FlatList.isDefault defaultArray, "isDefault must be true for a default value") - Assert.IsFalse (FlatList.isDefault FlatList.empty, "isDefault must be false for an empty flat list") - Assert.IsTrue (FlatList.isDefaultOrEmpty defaultArray, "isDefaultOrEmpty must be true for a default value") - Assert.IsTrue (FlatList.isDefaultOrEmpty FlatList.empty, "isDefaultOrEmpty must be true for an empty flat list") - Assert.IsFalse ( - FlatList.isDefaultOrEmpty (FlatList.singleton 1), - "isDefaultOrEmpty must be false for a non-empty flat list" - ) - - [] - member _.``functions reject a default flat list as Array rejects a null array`` () = - let cases : struct (string * (unit -> unit) * (unit -> unit)) list = [ + /// The functions that mirror an Array function, by name, with that function on a null array and the function on a + /// default flat list. The rows of the test are the names: a function has no readable name in a row. + static let arrayRejections = + [| struct (nameof FlatList.allPairs, (fun () -> Array.allPairs nullArray [| 1 |] |> ignore), (fun () -> @@ -177,14 +154,14 @@ type FlatListTests () = struct (nameof FlatList.zip, (fun () -> Array.zip [| 1 |] nullArray |> ignore), (fun () -> FlatList.zip (FlatList.singleton 1) defaultArray |> ignore)) - ] + |] + |> Array.map (fun struct (name, oracle, actual) -> name, struct (oracle, actual)) + |> Map.ofArray - for struct (name, oracle, actual) in cases do - ExceptionAssert.same id oracle actual $"FlatList.%s{name} must reject a default flat list" - - [] - member _.``functions without an Array counterpart reject a default flat list`` () = - let cases : struct (string * string * (unit -> unit)) list = [ + /// The functions without an Array counterpart, by name, with the parameter that names the default flat list that they + /// reject and the function on a default flat list. + static let otherRejections = + [| struct (nameof FlatList.add, "array", (fun () -> FlatList.add 1 defaultArray |> ignore)) struct (nameof FlatList.asMemory, "array", (fun () -> FlatList.asMemory defaultArray |> ignore)) struct (nameof FlatList.binarySearch, "array", (fun () -> FlatList.binarySearch 1 defaultArray |> ignore)) @@ -228,47 +205,127 @@ type FlatListTests () = struct (nameof FlatList.toSeq, "array", (fun () -> FlatList.toSeq defaultArray |> ignore)) struct (nameof FlatList.tryTail, "array", (fun () -> FlatList.tryTail defaultArray |> ignore)) struct (nameof FlatList.update, "array", (fun () -> FlatList.update ignore defaultArray |> ignore)) - ] + |] + |> Array.map (fun struct (name, paramName, actual) -> name, struct (paramName, actual)) + |> Map.ofArray + + /// The functions that take a range, by name, on the whole range of the input with several elements. + static let wholeRanges : Map -> FlatList> = + Map.ofArray [| + nameof FlatList.take, FlatList.take 8 + nameof FlatList.takeLast, FlatList.takeLast 8 + nameof FlatList.takeWhile, FlatList.takeWhile (fun _ -> true) + nameof FlatList.truncate, FlatList.truncate 100 + nameof FlatList.skip, FlatList.skip -1 + nameof FlatList.skipLast, FlatList.skipLast 0 + nameof FlatList.skipWhile, FlatList.skipWhile (fun _ -> false) + |] + + /// The functions that take a range, by name, on a partial range of the input with several elements. + static let partialRanges : Map -> FlatList> = + Map.ofArray [| + nameof FlatList.take, FlatList.take 3 + nameof FlatList.skip, FlatList.skip 3 + ($"%s{nameof FlatList.splitAt} (first part)", + fun array -> + let struct (before, _) = FlatList.splitAt 3 array + before) + ($"%s{nameof FlatList.splitAt} (second part)", + fun array -> + let struct (_, after) = FlatList.splitAt 3 array + after) + |] - for struct (name, paramName, actual) in cases do - let ex = - Assert.ThrowsExactly(Action actual, $"FlatList.%s{name} must reject a default flat list") - Assert.AreEqual( - paramName, - ex.ParamName, - $"FlatList.%s{name} must name the parameter of the default flat list" - ) + /// Strings whose ordinal order differs from their order in the current culture. + static let strings () = [| "b"; "A"; "a"; "B" |] + + /// The names of the functions that mirror an Array function, as the rows of their test. + static member ArrayRejections = arrayRejections.Keys + + /// The names of the functions without an Array counterpart, as the rows of their test. + static member OtherRejections = otherRejections.Keys + + /// The names of the functions that take a range, as the rows of the test of the whole range. + static member WholeRanges = wholeRanges.Keys + + /// The names of the functions that take a range, as the rows of the test of a partial range. + static member PartialRanges = partialRanges.Keys + + /// Every index of the strings that binarySearch searches. + static member StringIndices = seq { 0 .. (strings ()).Length - 1 } + + [] + member _.``create repeats the value`` () = + CollectionAssert.AreEqual ([| 7; 7; 7 |], FlatList.toArray (FlatList.create 3 7), "FlatList.create must repeat the value") + Assert.IsEmpty (FlatList.create 0 7, "FlatList.create 0 must be empty") + + [] + member _.``create throws for a negative count as Array does`` () = + ExceptionAssert.same + id + (fun () -> Array.create -1 7 |> ignore) + (fun () -> FlatList.create -1 7 |> ignore) + "FlatList.create -1 must throw as Array.create does" + + [] + member _.``isDefault and isDefaultOrEmpty tell a default value from an empty one`` () = + Assert.IsTrue (FlatList.isDefault defaultArray, "isDefault must be true for a default value") + Assert.IsFalse (FlatList.isDefault FlatList.empty, "isDefault must be false for an empty flat list") + Assert.IsTrue (FlatList.isDefaultOrEmpty defaultArray, "isDefaultOrEmpty must be true for a default value") + Assert.IsTrue (FlatList.isDefaultOrEmpty FlatList.empty, "isDefaultOrEmpty must be true for an empty flat list") + Assert.IsFalse ( + FlatList.isDefaultOrEmpty (FlatList.singleton 1), + "isDefaultOrEmpty must be false for a non-empty flat list" + ) + + [] + member _.``functions reject a default flat list as Array rejects a null array`` (name : string) = + let struct (oracle, actual) = arrayRejections[name] + ExceptionAssert.same id oracle actual $"FlatList.%s{name} must reject a default flat list" + + [] + member _.``functions without an Array counterpart reject a default flat list`` (name : string) = + let struct (paramName, actual) = otherRejections[name] + + let ex = + Assert.ThrowsExactly(Action actual, $"FlatList.%s{name} must reject a default flat list") + + Assert.AreEqual( + paramName, + ex.ParamName, + $"FlatList.%s{name} must name the parameter of the default flat list" + ) [] member _.``countBy counts null keys like any other key`` () = - let array = FlatList.ofList Inputs.several + let array = FlatList.ofArray Inputs.Several let keyOf (item : int) = if item > 0 then Some (item % 2) else None - Assert.AreEqual( - List.countBy keyOf Inputs.several - |> List.map (fun (key, count) -> struct (key, count)), - FlatList.countBy keyOf array |> FlatList.toList, - "FlatList.countBy must count the key None, which is null, as List.countBy does" + CollectionAssert.AreEqual ( + Array.countBy keyOf Inputs.Several + |> Array.map (fun (key, count) -> struct (key, count)), + FlatList.countBy keyOf array |> FlatList.toArray, + "FlatList.countBy must count the key None, which is null, as Array.countBy does" ) [] member _.``asSpan views the elements`` () = - let array = FlatList.ofList Inputs.several + let array = FlatList.ofArray Inputs.Several let span = FlatList.asSpan array - CollectionAssert.AreEqual (List.toArray Inputs.several, span.ToArray (), "FlatList.asSpan must view the elements") + CollectionAssert.AreEqual (Inputs.Several, span.ToArray (), "FlatList.asSpan must view the elements") [] member _.``asMemory views the underlying array without a copy`` () = - let array = FlatList.ofList Inputs.several + let array = FlatList.ofArray Inputs.Several let memory = FlatList.asMemory array let isArray, segment = MemoryMarshal.TryGetArray memory Assert.IsTrue (isArray, "FlatList.asMemory must view an array") Assert.AreSame (underlying array, segment.Array, "FlatList.asMemory must view the underlying array") - Assert.HasCount (Inputs.several.Length, segment, "FlatList.asMemory must view every element") + Assert.HasCount (Inputs.Several.Length, segment, "FlatList.asMemory must view every element") [] member _.``binarySearch finds a value or the complement of its insertion point`` () = - let array = FlatList.ofList [ 1; 3; 5; 7 ] + let array = FlatList.ofArray [| 1; 3; 5; 7 |] Assert.AreEqual(2, FlatList.binarySearch 5 array, "FlatList.binarySearch must find 5 at index 2") Assert.AreEqual( ~~~2, @@ -281,27 +338,26 @@ type FlatListTests () = "FlatList.binarySearch must return the complement of the length for 9" ) - [] - member _.``binarySearch uses the order of sort, which is ordinal for strings`` () = - let array = FlatList.ofList [ "b"; "A"; "a"; "B" ] |> FlatList.sort - Assert.AreEqual([ "A"; "B"; "a"; "b" ], FlatList.toList array, "FlatList.sort must order strings ordinally") + [] + member _.``binarySearch uses the order of sort, which is ordinal for strings`` (index : int) = + let array = FlatList.ofArray (strings ()) |> FlatList.sort + CollectionAssert.AreEqual ([| "A"; "B"; "a"; "b" |], FlatList.toArray array, "FlatList.sort must order strings ordinally") - for index in 0 .. array.Length - 1 do - Assert.AreEqual( - index, - FlatList.binarySearch array[index] array, - $"FlatList.binarySearch must find %s{array[index]}" - ) + Assert.AreEqual( + index, + FlatList.binarySearch array[index] array, + "FlatList.binarySearch must find every element of a sorted flat list at its index" + ) [] member _.``blit copies a range into an array`` () = let target = Array.zeroCreate 5 - FlatList.blit (FlatList.ofList Inputs.several) 2 target 1 3 + FlatList.blit (FlatList.ofArray Inputs.Several) 2 target 1 3 CollectionAssert.AreEqual ([| 0; 8; 5; 0; 0 |], target, "FlatList.blit must copy three elements from index 2 to index 1") [] member _.``blit throws for a range out of range as Array does`` () = - let source = List.toArray Inputs.several + let source = Inputs.Several ExceptionAssert.same id @@ -311,43 +367,32 @@ type FlatListTests () = [] member _.``ofSeq of a flat list shares its storage`` () = - let array = FlatList.ofList Inputs.several + let array = FlatList.ofArray Inputs.Several Assert.AreSame (underlying array, underlying (FlatList.ofSeq array), "FlatList.ofSeq of a flat list must not copy it") - [] - member _.``range functions return the input itself for the whole range`` () = - let array = FlatList.ofList Inputs.several - - let results = [ - nameof FlatList.take, FlatList.take 8 array - nameof FlatList.takeLast, FlatList.takeLast 8 array - nameof FlatList.takeWhile, FlatList.takeWhile (fun _ -> true) array - nameof FlatList.truncate, FlatList.truncate 100 array - nameof FlatList.skip, FlatList.skip -1 array - nameof FlatList.skipLast, FlatList.skipLast 0 array - nameof FlatList.skipWhile, FlatList.skipWhile (fun _ -> false) array - ] - - for name, result in results do - Assert.AreSame (underlying array, underlying result, $"FlatList.%s{name} of the whole range must return the input") + [] + member _.``range functions return the input itself for the whole range`` (name : string) = + let array = FlatList.ofArray Inputs.Several - [] - member _.``range functions copy a partial range`` () = - let array = FlatList.ofList Inputs.several - let struct (before, after) = FlatList.splitAt 3 array - - for name, result in - [ - nameof FlatList.take, FlatList.take 3 array - nameof FlatList.skip, FlatList.skip 3 array - nameof FlatList.splitAt, before - nameof FlatList.splitAt, after - ] do - Assert.AreNotSame (underlying array, underlying result, $"FlatList.%s{name} of a partial range must copy it") + Assert.AreSame ( + underlying array, + underlying (wholeRanges[name] array), + $"FlatList.%s{name} of the whole range must return the input" + ) + + [] + member _.``range functions copy a partial range`` (name : string) = + let array = FlatList.ofArray Inputs.Several + + Assert.AreNotSame ( + underlying array, + underlying (partialRanges[name] array), + $"FlatList.%s{name} of a partial range must copy it" + ) [] member _.``append returns the other flat list when one is empty`` () = - let array = FlatList.ofList Inputs.several + let array = FlatList.ofArray Inputs.Several Assert.AreSame ( underlying array, underlying (FlatList.append array FlatList.empty), @@ -361,7 +406,7 @@ type FlatListTests () = [] member _.``sortWithComparer returns a sorted input itself`` () = - let array = FlatList.ofList [ 1; 2; 3 ] + let array = FlatList.ofArray [| 1; 2; 3 |] Assert.AreSame ( underlying array, underlying (FlatList.sortWithComparer Comparer.Default array), diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTupleTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTupleTests.fs index 177b13a5..19496141 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTupleTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTupleTests.fs @@ -10,11 +10,11 @@ open Microsoft.VisualStudio.TestTools.UnitTesting [] type FlatListTupleTests () = - static let array = FlatList.ofList Inputs.several + static let array = FlatList.ofArray Inputs.Several - static let other = FlatList.ofList Inputs.other + static let other = FlatList.ofArray Inputs.Other - static let third = FlatList.ofList Inputs.third + static let third = FlatList.ofArray Inputs.Third [] member _.``unfold takes a generator of value options of reference tuples`` () = @@ -24,58 +24,74 @@ type FlatListTupleTests () = else ValueNone let generated : FlatList = FlatList.unfold generator 3 - Assert.AreEqual([ 3; 2; 1 ], FlatList.toList generated, "Tuple.FlatList.unfold must generate the elements") + CollectionAssert.AreEqual ([| 3; 2; 1 |], FlatList.toArray generated, "Tuple.FlatList.unfold must generate the elements") [] member _.``allPairs returns reference tuples`` () = let pairs : FlatList = FlatList.allPairs array other - Assert.AreEqual<(int * int) list>( - List.allPairs Inputs.several Inputs.other, - FlatList.toList pairs, - "Tuple.FlatList.allPairs must pair as List.allPairs does" + CollectionAssert.AreEqual ( + Array.allPairs Inputs.Several Inputs.Other, + FlatList.toArray pairs, + "Tuple.FlatList.allPairs must pair as Array.allPairs does" ) [] member _.``indexed returns reference tuples`` () = let pairs : FlatList = FlatList.indexed array - Assert.AreEqual<(int * int) list>( - List.indexed Inputs.several, - FlatList.toList pairs, - "Tuple.FlatList.indexed must index as List.indexed does" + CollectionAssert.AreEqual ( + Array.indexed Inputs.Several, + FlatList.toArray pairs, + "Tuple.FlatList.indexed must index as Array.indexed does" ) [] member _.``pairwise returns reference tuples`` () = let pairs : FlatList = FlatList.pairwise array - Assert.AreEqual<(int * int) list>( - List.pairwise Inputs.several, - FlatList.toList pairs, - "Tuple.FlatList.pairwise must pair as List.pairwise does" + CollectionAssert.AreEqual ( + Array.pairwise Inputs.Several, + FlatList.toArray pairs, + "Tuple.FlatList.pairwise must pair as Array.pairwise does" ) [] member _.``unzip takes and returns reference tuples`` () = let (first : FlatList), (second : FlatList) = FlatList.unzip (FlatList.zip array other) - Assert.AreEqual(Inputs.several, FlatList.toList first, "Tuple.FlatList.unzip must return the first components") - Assert.AreEqual(Inputs.other, FlatList.toList second, "Tuple.FlatList.unzip must return the second components") + CollectionAssert.AreEqual ( + Inputs.Several, + FlatList.toArray first, + "Tuple.FlatList.unzip must return the first components" + ) + CollectionAssert.AreEqual ( + Inputs.Other, + FlatList.toArray second, + "Tuple.FlatList.unzip must return the second components" + ) [] member _.``unzip3 takes and returns reference tuples`` () = let (first : FlatList), (second : FlatList), (last : FlatList) = FlatList.unzip3 (FlatList.zip3 array other third) - Assert.AreEqual(Inputs.several, FlatList.toList first, "Tuple.FlatList.unzip3 must return the first components") - Assert.AreEqual(Inputs.other, FlatList.toList second, "Tuple.FlatList.unzip3 must return the second components") - Assert.AreEqual(Inputs.third, FlatList.toList last, "Tuple.FlatList.unzip3 must return the third components") + CollectionAssert.AreEqual ( + Inputs.Several, + FlatList.toArray first, + "Tuple.FlatList.unzip3 must return the first components" + ) + CollectionAssert.AreEqual ( + Inputs.Other, + FlatList.toArray second, + "Tuple.FlatList.unzip3 must return the second components" + ) + CollectionAssert.AreEqual (Inputs.Third, FlatList.toArray last, "Tuple.FlatList.unzip3 must return the third components") [] member _.``zip returns reference tuples`` () = let pairs : FlatList = FlatList.zip array other - Assert.AreEqual<(int * int) list>( - List.zip Inputs.several Inputs.other, - FlatList.toList pairs, - "Tuple.FlatList.zip must zip as List.zip does" + CollectionAssert.AreEqual ( + Array.zip Inputs.Several Inputs.Other, + FlatList.toArray pairs, + "Tuple.FlatList.zip must zip as Array.zip does" ) [] @@ -93,10 +109,10 @@ type FlatListTupleTests () = member _.``zip3 returns reference tuples`` () = let triples : FlatList = FlatList.zip3 array other third - Assert.AreEqual<(int * int * int) list>( - List.zip3 Inputs.several Inputs.other Inputs.third, - FlatList.toList triples, - "Tuple.FlatList.zip3 must zip as List.zip3 does" + CollectionAssert.AreEqual ( + Array.zip3 Inputs.Several Inputs.Other Inputs.Third, + FlatList.toArray triples, + "Tuple.FlatList.zip3 must zip as Array.zip3 does" ) [] @@ -104,62 +120,71 @@ type FlatListTupleTests () = let (results : FlatList), (state : int) = FlatList.mapFold (fun state item -> item * state, state + 1) 1 array let expectedResults, expectedState = - List.mapFold (fun state item -> item * state, state + 1) 1 Inputs.several - Assert.AreEqual( + Array.mapFold (fun state item -> item * state, state + 1) 1 Inputs.Several + CollectionAssert.AreEqual ( expectedResults, - FlatList.toList results, - "Tuple.FlatList.mapFold must map as List.mapFold does" + FlatList.toArray results, + "Tuple.FlatList.mapFold must map as Array.mapFold does" ) - Assert.AreEqual(expectedState, state, "Tuple.FlatList.mapFold must thread the state as List.mapFold does") + Assert.AreEqual(expectedState, state, "Tuple.FlatList.mapFold must thread the state as Array.mapFold does") [] member _.``mapFoldBack takes and returns reference tuples`` () = let (results : FlatList), (state : int) = FlatList.mapFoldBack (fun item state -> item * state, state + 1) array 1 let expectedResults, expectedState = - List.mapFoldBack (fun item state -> item * state, state + 1) Inputs.several 1 - Assert.AreEqual( + Array.mapFoldBack (fun item state -> item * state, state + 1) Inputs.Several 1 + CollectionAssert.AreEqual ( expectedResults, - FlatList.toList results, - "Tuple.FlatList.mapFoldBack must map as List.mapFoldBack does" + FlatList.toArray results, + "Tuple.FlatList.mapFoldBack must map as Array.mapFoldBack does" ) - Assert.AreEqual(expectedState, state, "Tuple.FlatList.mapFoldBack must thread the state as List.mapFoldBack does") + Assert.AreEqual(expectedState, state, "Tuple.FlatList.mapFoldBack must thread the state as Array.mapFoldBack does") [] member _.``countBy returns reference tuples`` () = let counts : FlatList = FlatList.countBy (fun item -> item % 3) array - Assert.AreEqual<(int * int) list>( - List.countBy (fun item -> item % 3) Inputs.several, - FlatList.toList counts, - "Tuple.FlatList.countBy must count as List.countBy does" + CollectionAssert.AreEqual ( + Array.countBy (fun item -> item % 3) Inputs.Several, + FlatList.toArray counts, + "Tuple.FlatList.countBy must count as Array.countBy does" ) [] member _.``groupBy returns reference tuples`` () = let groups : FlatList> = FlatList.groupBy (fun item -> item % 3) array + let expected = Array.groupBy (fun item -> item % 3) Inputs.Several + + // A group pairs its key with its elements, which a pair compares by reference, so the keys and the elements are + // compared apart + CollectionAssert.AreEqual ( + Array.map fst expected, + groups |> FlatList.toArray |> Array.map fst, + "Tuple.FlatList.groupBy must return the keys as Array.groupBy does" + ) - Assert.AreEqual<(int * int list) list>( - List.groupBy (fun item -> item % 3) Inputs.several, + CollectionAssert.AreEqual ( + Array.map snd expected, groups - |> FlatList.toList - |> List.map (fun (key, items) -> key, FlatList.toList items), - "Tuple.FlatList.groupBy must group as List.groupBy does" + |> FlatList.toArray + |> Array.map (fun (_, items) -> FlatList.toArray items), + "Tuple.FlatList.groupBy must group the elements as Array.groupBy does" ) [] member _.``partition returns a reference tuple`` () = let (accepted : FlatList), (rejected : FlatList) = FlatList.partition (fun item -> item > 0) array - let expectedAccepted, expectedRejected = List.partition (fun item -> item > 0) Inputs.several - Assert.AreEqual( + let expectedAccepted, expectedRejected = Array.partition (fun item -> item > 0) Inputs.Several + CollectionAssert.AreEqual ( expectedAccepted, - FlatList.toList accepted, + FlatList.toArray accepted, "Tuple.FlatList.partition must return the accepted elements" ) - Assert.AreEqual( + CollectionAssert.AreEqual ( expectedRejected, - FlatList.toList rejected, + FlatList.toArray rejected, "Tuple.FlatList.partition must return the rejected elements" ) @@ -171,24 +196,24 @@ type FlatListTupleTests () = else Choice2Of2 (string item) let (first : FlatList), (second : FlatList) = FlatList.partitionWith partitioner array - let expectedFirst, expectedSecond = List.partitionWith partitioner Inputs.several - Assert.AreEqual( + let expectedFirst, expectedSecond = Array.partitionWith partitioner Inputs.Several + CollectionAssert.AreEqual ( expectedFirst, - FlatList.toList first, + FlatList.toArray first, "Tuple.FlatList.partitionWith must return the first choices" ) - Assert.AreEqual( + CollectionAssert.AreEqual ( expectedSecond, - FlatList.toList second, + FlatList.toArray second, "Tuple.FlatList.partitionWith must return the second choices" ) [] member _.``splitAt returns a reference tuple`` () = let (before : FlatList), (after : FlatList) = FlatList.splitAt 3 array - let expectedBefore, expectedAfter = List.splitAt 3 Inputs.several - Assert.AreEqual(expectedBefore, FlatList.toList before, "Tuple.FlatList.splitAt must return the first part") - Assert.AreEqual(expectedAfter, FlatList.toList after, "Tuple.FlatList.splitAt must return the second part") + let expectedBefore, expectedAfter = Array.splitAt 3 Inputs.Several + CollectionAssert.AreEqual (expectedBefore, FlatList.toArray before, "Tuple.FlatList.splitAt must return the first part") + CollectionAssert.AreEqual (expectedAfter, FlatList.toArray after, "Tuple.FlatList.splitAt must return the second part") [] member _.``the functions without a tuple keep resolving to the default module`` () = From cf8ef72a6b6958112be81fc863307e3a561150b9 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 17:21:59 +0200 Subject: [PATCH 10/27] fixup! feat: `FlatList` module with the vocabulary of the FSharp.Core `Array` module A `` and a `` stay on one line each only when both are short (up to 60 characters of text); otherwise both span several lines. Co-Authored-By: Claude Opus 5.5 --- .../ComputationExpressions.fs | 4 +- .../FlatList/FlatList.fsi | 752 +++++++++++++----- .../FlatList/FlatListBuilder.fs | 28 +- .../FlatList/FlatListCE.fs | 12 +- .../FlatList/FlatListOption.fsi | 104 ++- .../FlatList/FlatListTuple.fsi | 48 +- .../FlatList/FlatListTypes.fs | 8 +- src/FSharp.Collections.Immutable/Helper.fs | 28 +- 8 files changed, 738 insertions(+), 246 deletions(-) diff --git a/src/FSharp.Collections.Immutable/ComputationExpressions.fs b/src/FSharp.Collections.Immutable/ComputationExpressions.fs index 327945a0..2d3def91 100644 --- a/src/FSharp.Collections.Immutable/ComputationExpressions.fs +++ b/src/FSharp.Collections.Immutable/ComputationExpressions.fs @@ -1,4 +1,6 @@ -/// The computation expressions of the collections (ADR 0001, D7). +/// +/// The computation expressions of the collections (ADR 0001, D7). +/// [] module FSharp.Collections.Immutable.ComputationExpressions diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi b/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi index 19f9f4da..88459c35 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi +++ b/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi @@ -1,4 +1,6 @@ -/// Functions for , the immutable array. +/// +/// Functions for , the immutable array. +/// /// /// /// The module mirrors : each function has the semantics, the @@ -38,8 +40,12 @@ open System.Collections.Immutable [] val create : count : int -> value : 'T -> FlatList<'T> -/// The empty flat list. -/// O(1). The value is . +/// +/// The empty flat list. +/// +/// +/// O(1). The value is . +/// [] [] val empty<'T> : FlatList<'T> @@ -53,11 +59,15 @@ val empty<'T> : FlatList<'T> [] val init : count : int -> initializer : (int -> 'T) -> FlatList<'T> -/// Creates a flat list from a copy of an array. +/// +/// Creates a flat list from a copy of an array. +/// /// The input array. /// The flat list of the elements of the array. /// Thrown when is . -/// O(n). Later changes to the array do not change the flat list. +/// +/// O(n). Later changes to the array do not change the flat list. +/// [] val ofArray : array : 'T array -> FlatList<'T> @@ -68,10 +78,14 @@ val ofArray : array : 'T array -> FlatList<'T> [] val ofList : list : 'T list -> FlatList<'T> -/// Creates a flat list from a copy of a read-only span. +/// +/// Creates a flat list from a copy of a read-only span. +/// /// The input span. /// The flat list of the elements of the span. -/// O(n). The function wraps . +/// +/// O(n). The function wraps . +/// [] val ofReadOnlySpan : span : ReadOnlySpan<'T> -> FlatList<'T> @@ -83,10 +97,14 @@ val ofReadOnlySpan : span : ReadOnlySpan<'T> -> FlatList<'T> [] val ofSeq : source : seq<'T> -> FlatList<'T> -/// Creates a flat list from a copy of a span. +/// +/// Creates a flat list from a copy of a span. +/// /// The input span. /// The flat list of the elements of the span. -/// O(n). The function wraps . +/// +/// O(n). The function wraps . +/// [] val ofSpan : span : Span<'T> -> FlatList<'T> @@ -106,11 +124,15 @@ val replicate : count : int -> initial : 'T -> FlatList<'T> [] val singleton : value : 'T -> FlatList<'T> -/// Creates a flat list from a generator, which returns the next element and the next state. +/// +/// Creates a flat list from a generator, which returns the next element and the next state. +/// /// The function that returns the next element and state, or ValueNone to stop. /// The initial state. /// The generated flat list. -/// O(n). +/// +/// O(n). +/// [] val unfold : generator : ('State -> struct ('T * 'State) voption) -> state : 'State -> FlatList<'T> @@ -123,49 +145,73 @@ val unfold : generator : ('State -> struct ('T * 'State) voption) -> state : 'St [] val build : action : (FlatList<'T>.Builder -> unit) -> FlatList<'T> -/// Creates an empty builder. +/// +/// Creates an empty builder. +/// /// A builder of capacity 8. -/// O(1). The function wraps . +/// +/// O(1). The function wraps . +/// [] val builder : unit -> FlatList<'T>.Builder -/// Creates an empty builder of a given capacity. +/// +/// Creates an empty builder of a given capacity. +/// /// The initial capacity. /// The builder. /// Thrown when is negative. -/// O(capacity). The function wraps . +/// +/// O(capacity). The function wraps . +/// [] val builderWith : capacity : int -> FlatList<'T>.Builder -/// Returns the contents of a full builder as a flat list without a copy and empties the builder. +/// +/// Returns the contents of a full builder as a flat list without a copy and empties the builder. +/// /// The builder, whose count must equal its capacity. /// The flat list of the elements of the builder. /// Thrown when is . /// Thrown when the count of the builder differs from its capacity. -/// O(1). The function wraps . +/// +/// O(1). The function wraps . +/// [] val moveFromBuilder : builder : FlatList<'T>.Builder -> FlatList<'T> -/// Returns a copy of the contents of a builder as a flat list. +/// +/// Returns a copy of the contents of a builder as a flat list. +/// /// The builder, which stays unchanged. /// The flat list of the elements of the builder. /// Thrown when is . -/// O(n). The function wraps . +/// +/// O(n). The function wraps . +/// [] val ofBuilder : builder : FlatList<'T>.Builder -> FlatList<'T> -/// Creates a builder that contains a copy of the elements of a flat list. +/// +/// Creates a builder that contains a copy of the elements of a flat list. +/// /// The input flat list. /// The builder. -/// O(n). The function wraps . +/// +/// O(n). The function wraps . +/// [] val toBuilder : array : FlatList<'T> -> FlatList<'T>.Builder -/// Creates a flat list from a builder over the elements of a flat list, after an action changes the builder. +/// +/// Creates a flat list from a builder over the elements of a flat list, after an action changes the builder. +/// /// The action that changes the builder. /// The input flat list, which stays unchanged. /// The flat list of the elements of the changed builder. -/// O(n). +/// +/// O(n). +/// [] val update : action : (FlatList<'T>.Builder -> unit) -> array : FlatList<'T> -> FlatList<'T> @@ -228,45 +274,67 @@ val last : array : FlatList<'T> -> 'T [] val tail : array : FlatList<'T> -> FlatList<'T> -/// Returns the only element of the flat list, or ValueNone when it does not have exactly one element. +/// +/// Returns the only element of the flat list, or ValueNone when it does not have exactly one element. +/// /// The input flat list. /// The only element, or ValueNone. -/// O(1). +/// +/// O(1). +/// [] val tryExactlyOne : array : FlatList<'T> -> 'T voption -/// Returns the first element of the flat list, or ValueNone when it is empty. +/// +/// Returns the first element of the flat list, or ValueNone when it is empty. +/// /// The input flat list. /// The first element, or ValueNone. -/// O(1). +/// +/// O(1). +/// [] val tryHead : array : FlatList<'T> -> 'T voption -/// Returns the element at an index, or ValueNone when the index is out of range. +/// +/// Returns the element at an index, or ValueNone when the index is out of range. +/// /// The index of the element. /// The input flat list. /// The element at the index, or ValueNone. -/// O(1). +/// +/// O(1). +/// [] val tryItem : index : int -> array : FlatList<'T> -> 'T voption -/// Returns the last element of the flat list, or ValueNone when it is empty. +/// +/// Returns the last element of the flat list, or ValueNone when it is empty. +/// /// The input flat list. /// The last element, or ValueNone. -/// O(1). +/// +/// O(1). +/// [] val tryLast : array : FlatList<'T> -> 'T voption -/// Returns the flat list without its first element, or ValueNone when it is empty. +/// +/// Returns the flat list without its first element, or ValueNone when it is empty. +/// /// The input flat list. /// A copy of the elements after the first one, or ValueNone. -/// O(n). +/// +/// O(n). +/// [] val tryTail : array : FlatList<'T> -> FlatList<'T> voption // ---- Search ---- -/// Searches a sorted flat list for a value with the binary search algorithm. +/// +/// Searches a sorted flat list for a value with the binary search algorithm. +/// /// The value to search for. /// The input flat list, sorted in the order of F# generic comparison. /// @@ -280,11 +348,15 @@ val tryTail : array : FlatList<'T> -> FlatList<'T> voption [] val binarySearch : value : 'T -> array : FlatList<'T> -> int when 'T : comparison -/// Returns whether the flat list contains an element equal to a value, in F# structural equality. +/// +/// Returns whether the flat list contains an element equal to a value, in F# structural equality. +/// /// The value to find. /// The input flat list. /// when an element equals the value. -/// O(n). +/// +/// O(n). +/// [] val contains : value : 'T -> array : FlatList<'T> -> bool when 'T : equality @@ -296,13 +368,17 @@ val contains : value : 'T -> array : FlatList<'T> -> bool when 'T : equality [] val exists : predicate : ('T -> bool) -> array : FlatList<'T> -> bool -/// Returns whether any pair of elements at the same index satisfies the predicate. +/// +/// Returns whether any pair of elements at the same index satisfies the predicate. +/// /// The function to test the pairs. /// The first flat list. /// The second flat list. /// when a pair satisfies the predicate. /// Thrown when the flat lists have different lengths. -/// O(n). +/// +/// O(n). +/// [] val exists2 : predicate : ('T1 -> 'T2 -> bool) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> bool @@ -324,21 +400,29 @@ val find : predicate : ('T -> bool) -> array : FlatList<'T> -> 'T [] val findBack : predicate : ('T -> bool) -> array : FlatList<'T> -> 'T -/// Returns the index of the first element that satisfies the predicate. +/// +/// Returns the index of the first element that satisfies the predicate. +/// /// The function to test the elements. /// The input flat list. /// The index of the first element that satisfies the predicate. /// Thrown when no element satisfies the predicate. -/// O(n). +/// +/// O(n). +/// [] val findIndex : predicate : ('T -> bool) -> array : FlatList<'T> -> int -/// Returns the index of the last element that satisfies the predicate. +/// +/// Returns the index of the last element that satisfies the predicate. +/// /// The function to test the elements. /// The input flat list. /// The index of the last element that satisfies the predicate. /// Thrown when no element satisfies the predicate. -/// O(n). +/// +/// O(n). +/// [] val findIndexBack : predicate : ('T -> bool) -> array : FlatList<'T> -> int @@ -350,25 +434,35 @@ val findIndexBack : predicate : ('T -> bool) -> array : FlatList<'T> -> int [] val forall : predicate : ('T -> bool) -> array : FlatList<'T> -> bool -/// Returns whether all pairs of elements at the same index satisfy the predicate. +/// +/// Returns whether all pairs of elements at the same index satisfy the predicate. +/// /// The function to test the pairs. /// The first flat list. /// The second flat list. /// when every pair satisfies the predicate. /// Thrown when the flat lists have different lengths. -/// O(n). +/// +/// O(n). +/// [] val forall2 : predicate : ('T1 -> 'T2 -> bool) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> bool -/// Returns the index of the first element equal to a value, in . +/// +/// Returns the index of the first element equal to a value, in . +/// /// The value to find. /// The input flat list. /// The index of the first equal element, or -1. -/// O(n). The function wraps . +/// +/// O(n). The function wraps . +/// [] val indexOf : value : 'T -> array : FlatList<'T> -> int -/// Returns the index of the first element equal to a value in a given equality comparer. +/// +/// Returns the index of the first element equal to a value in a given equality comparer. +/// /// The equality comparer. /// The value to find. /// The input flat list. @@ -380,15 +474,21 @@ val indexOf : value : 'T -> array : FlatList<'T> -> int [] val indexOfWith : comparer : IEqualityComparer<'T> -> value : 'T -> array : FlatList<'T> -> int -/// Returns the index of the last element equal to a value, in . +/// +/// Returns the index of the last element equal to a value, in . +/// /// The value to find. /// The input flat list. /// The index of the last equal element, or -1. -/// O(n). The function wraps . +/// +/// O(n). The function wraps . +/// [] val lastIndexOf : value : 'T -> array : FlatList<'T> -> int -/// Returns the index of the last element equal to a value in a given equality comparer. +/// +/// Returns the index of the last element equal to a value in a given equality comparer. +/// /// The equality comparer. /// The value to find. /// The input flat list. @@ -400,86 +500,126 @@ val lastIndexOf : value : 'T -> array : FlatList<'T> -> int [] val lastIndexOfWith : comparer : IEqualityComparer<'T> -> value : 'T -> array : FlatList<'T> -> int -/// Returns the first value that the chooser returns for the elements. +/// +/// Returns the first value that the chooser returns for the elements. +/// /// The function that returns a value, or ValueNone to go on. /// The input flat list. /// The first chosen value. /// Thrown when the chooser returns ValueNone for every element. -/// O(n). +/// +/// O(n). +/// [] val pick : chooser : ('T -> 'U voption) -> array : FlatList<'T> -> 'U -/// Returns the first element that satisfies the predicate, or ValueNone. +/// +/// Returns the first element that satisfies the predicate, or ValueNone. +/// /// The function to test the elements. /// The input flat list. /// The first element that satisfies the predicate, or ValueNone. -/// O(n). +/// +/// O(n). +/// [] val tryFind : predicate : ('T -> bool) -> array : FlatList<'T> -> 'T voption -/// Returns the last element that satisfies the predicate, or ValueNone. +/// +/// Returns the last element that satisfies the predicate, or ValueNone. +/// /// The function to test the elements. /// The input flat list. /// The last element that satisfies the predicate, or ValueNone. -/// O(n). +/// +/// O(n). +/// [] val tryFindBack : predicate : ('T -> bool) -> array : FlatList<'T> -> 'T voption -/// Returns the index of the first element that satisfies the predicate, or ValueNone. +/// +/// Returns the index of the first element that satisfies the predicate, or ValueNone. +/// /// The function to test the elements. /// The input flat list. /// The index of the first element that satisfies the predicate, or ValueNone. -/// O(n). +/// +/// O(n). +/// [] val tryFindIndex : predicate : ('T -> bool) -> array : FlatList<'T> -> int voption -/// Returns the index of the last element that satisfies the predicate, or ValueNone. +/// +/// Returns the index of the last element that satisfies the predicate, or ValueNone. +/// /// The function to test the elements. /// The input flat list. /// The index of the last element that satisfies the predicate, or ValueNone. -/// O(n). +/// +/// O(n). +/// [] val tryFindIndexBack : predicate : ('T -> bool) -> array : FlatList<'T> -> int voption -/// Returns the first value that the chooser returns for the elements, or ValueNone. +/// +/// Returns the first value that the chooser returns for the elements, or ValueNone. +/// /// The function that returns a value, or ValueNone to go on. /// The input flat list. /// The first chosen value, or ValueNone. -/// O(n). +/// +/// O(n). +/// [] val tryPick : chooser : ('T -> 'U voption) -> array : FlatList<'T> -> 'U voption // ---- Transformation ---- -/// Returns a flat list with a value added after the last element. +/// +/// Returns a flat list with a value added after the last element. +/// /// The value to add. /// The input flat list. /// A flat list of the elements and the value. -/// O(n). The function wraps . +/// +/// O(n). The function wraps . +/// [] val add : value : 'T -> array : FlatList<'T> -> FlatList<'T> -/// Returns every element of the first flat list paired with every element of the second one. +/// +/// Returns every element of the first flat list paired with every element of the second one. +/// /// The first flat list. /// The second flat list. /// The pairs, in the order of the first flat list and then of the second one. -/// O(n × m). +/// +/// O(n × m). +/// [] val allPairs : array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> FlatList -/// Returns a flat list of the elements of the first flat list followed by the elements of the second one. +/// +/// Returns a flat list of the elements of the first flat list followed by the elements of the second one. +/// /// The first flat list. /// The second flat list. /// The concatenation. -/// O(n + m). When one of the flat lists is empty, the function returns the other one. +/// +/// O(n + m). When one of the flat lists is empty, the function returns the other one. +/// [] val append : array1 : FlatList<'T> -> array2 : FlatList<'T> -> FlatList<'T> -/// Returns the values that the chooser returns for the elements. +/// +/// Returns the values that the chooser returns for the elements. +/// /// The function that returns a value, or ValueNone to skip the element. /// The input flat list. /// The chosen values. -/// O(n). +/// +/// O(n). +/// [] val choose : chooser : ('T -> 'U voption) -> array : FlatList<'T> -> FlatList<'U> @@ -492,12 +632,16 @@ val choose : chooser : ('T -> 'U voption) -> array : FlatList<'T> -> FlatList<'U [] val chunkBySize : chunkSize : int -> array : FlatList<'T> -> FlatList> -/// Concatenates the flat lists that a function returns for the elements. +/// +/// Concatenates the flat lists that a function returns for the elements. +/// /// The function that returns a flat list for each element. /// The input flat list. /// The concatenation of the returned flat lists. /// Thrown when the mapping returns a default flat list. -/// O(n + the total length of the returned flat lists). +/// +/// O(n + the total length of the returned flat lists). +/// [] val collect : mapping : ('T -> FlatList<'U>) -> array : FlatList<'T> -> FlatList<'U> @@ -552,24 +696,32 @@ val insertManyAt : index : int -> values : seq<'T> -> source : FlatList<'T> -> F [] val map : mapping : ('T -> 'U) -> array : FlatList<'T> -> FlatList<'U> -/// Returns the results of a function applied to the pairs of elements at the same index. +/// +/// Returns the results of a function applied to the pairs of elements at the same index. +/// /// The function to transform the pairs. /// The first flat list. /// The second flat list. /// The results. /// Thrown when the flat lists have different lengths. -/// O(n). +/// +/// O(n). +/// [] val map2 : mapping : ('T1 -> 'T2 -> 'U) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> FlatList<'U> -/// Returns the results of a function applied to the triples of elements at the same index. +/// +/// Returns the results of a function applied to the triples of elements at the same index. +/// /// The function to transform the triples. /// The first flat list. /// The second flat list. /// The third flat list. /// The results. /// Thrown when the flat lists have different lengths. -/// O(n). +/// +/// O(n). +/// [] val map3 : mapping : ('T1 -> 'T2 -> 'T3 -> 'U) -> @@ -578,21 +730,29 @@ val map3 : array3 : FlatList<'T3> -> FlatList<'U> -/// Returns the results of a function applied to each element and its index. +/// +/// Returns the results of a function applied to each element and its index. +/// /// The function to transform the elements. /// The input flat list. /// The results. -/// O(n). +/// +/// O(n). +/// [] val mapi : mapping : (int -> 'T -> 'U) -> array : FlatList<'T> -> FlatList<'U> -/// Returns the results of a function applied to the index and the pair of elements at that index. +/// +/// Returns the results of a function applied to the index and the pair of elements at that index. +/// /// The function to transform the pairs. /// The first flat list. /// The second flat list. /// The results. /// Thrown when the flat lists have different lengths. -/// O(n). +/// +/// O(n). +/// [] val mapi2 : mapping : (int -> 'T1 -> 'T2 -> 'U) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> FlatList<'U> @@ -603,12 +763,16 @@ val mapi2 : mapping : (int -> 'T1 -> 'T2 -> 'U) -> array1 : FlatList<'T1> -> arr [] val pairwise : array : FlatList<'T> -> FlatList -/// Returns the elements moved to the indices that a function computes. +/// +/// Returns the elements moved to the indices that a function computes. +/// /// The function that maps the index of each element to its new index. /// The input flat list. /// The permuted flat list. /// Thrown when the function does not compute a permutation. -/// O(n). +/// +/// O(n). +/// [] val permute : indexMap : (int -> int) -> array : FlatList<'T> -> FlatList<'T> @@ -638,11 +802,15 @@ val removeManyAt : index : int -> count : int -> source : FlatList<'T> -> FlatLi [] val rev : array : FlatList<'T> -> FlatList<'T> -/// Returns the transpose of a sequence of flat lists of the same length. +/// +/// Returns the transpose of a sequence of flat lists of the same length. +/// /// The flat lists. /// A flat list whose element i holds the element i of every input flat list. /// Thrown when the flat lists have different lengths. -/// O(the total length). +/// +/// O(the total length). +/// [] val transpose : arrays : seq> -> FlatList> @@ -660,30 +828,42 @@ val unzip : array : FlatList -> struct (FlatList<'T1> * Flat [] val unzip3 : array : FlatList -> struct (FlatList<'T1> * FlatList<'T2> * FlatList<'T3>) -/// Returns a flat list with the element at an index replaced by a value. +/// +/// Returns a flat list with the element at an index replaced by a value. +/// /// The index of the replaced element. /// The new value. /// The input flat list. /// The flat list with the new value. /// Thrown when the index is out of range. -/// O(n). +/// +/// O(n). +/// [] val updateAt : index : int -> value : 'T -> source : FlatList<'T> -> FlatList<'T> -/// Returns the elements that satisfy the predicate. +/// +/// Returns the elements that satisfy the predicate. +/// /// The function to test the elements. /// The input flat list. /// The elements that satisfy the predicate, in their order. -/// O(n). A synonym of . +/// +/// O(n). A synonym of . +/// [] val where : predicate : ('T -> bool) -> array : FlatList<'T> -> FlatList<'T> -/// Returns the windows of a given size that slide over the flat list. +/// +/// Returns the windows of a given size that slide over the flat list. +/// /// The size of a window. /// The input flat list. /// The windows; empty when the flat list is shorter than a window. /// Thrown when is not positive. -/// O(n × windowSize). The windows are copies. +/// +/// O(n × windowSize). The windows are copies. +/// [] val windowed : windowSize : int -> array : FlatList<'T> -> FlatList> @@ -720,12 +900,16 @@ val inline average : and ^T : (static member DivideByInt : ^T * int -> ^T) and ^T : (static member Zero : ^T) -/// Returns the average of the results of a function applied to each element. +/// +/// Returns the average of the results of a function applied to each element. +/// /// The function to transform the elements. /// The input flat list. /// The average of the results. /// Thrown when the flat list is empty. -/// O(n). +/// +/// O(n). +/// [] val inline averageBy : projection : ('T -> ^U) -> array : FlatList<'T> -> ^U @@ -744,44 +928,60 @@ val inline averageBy : [] val compareWith : comparer : ('T -> 'T -> int) -> array1 : FlatList<'T> -> array2 : FlatList<'T> -> int -/// Applies a function to a state and each element, from the first element to the last one. +/// +/// Applies a function to a state and each element, from the first element to the last one. +/// /// The function that computes the next state. /// The initial state. /// The input flat list. /// The final state. -/// O(n). +/// +/// O(n). +/// [] val fold : folder : ('State -> 'T -> 'State) -> state : 'State -> array : FlatList<'T> -> 'State -/// Applies a function to a state and each pair of elements at the same index, from the first index to the last one. +/// +/// Applies a function to a state and each pair of elements at the same index, from the first index to the last one. +/// /// The function that computes the next state. /// The initial state. /// The first flat list. /// The second flat list. /// The final state. /// Thrown when the flat lists have different lengths. -/// O(n). +/// +/// O(n). +/// [] val fold2 : folder : ('State -> 'T1 -> 'T2 -> 'State) -> state : 'State -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> 'State -/// Applies a function to each element and a state, from the last element to the first one. +/// +/// Applies a function to each element and a state, from the last element to the first one. +/// /// The function that computes the next state. /// The input flat list. /// The initial state. /// The final state. -/// O(n). +/// +/// O(n). +/// [] val foldBack : folder : ('T -> 'State -> 'State) -> array : FlatList<'T> -> state : 'State -> 'State -/// Applies a function to each pair of elements at the same index and a state, from the last index to the first one. +/// +/// Applies a function to each pair of elements at the same index and a state, from the last index to the first one. +/// /// The function that computes the next state. /// The first flat list. /// The second flat list. /// The initial state. /// The final state. /// Thrown when the flat lists have different lengths. -/// O(n). +/// +/// O(n). +/// [] val foldBack2 : folder : ('T1 -> 'T2 -> 'State -> 'State) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> state : 'State -> 'State @@ -793,12 +993,16 @@ val foldBack2 : [] val iter : action : ('T -> unit) -> array : FlatList<'T> -> unit -/// Applies an action to each pair of elements at the same index. +/// +/// Applies an action to each pair of elements at the same index. +/// /// The action. /// The first flat list. /// The second flat list. /// Thrown when the flat lists have different lengths. -/// O(n). +/// +/// O(n). +/// [] val iter2 : action : ('T1 -> 'T2 -> unit) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> unit @@ -809,21 +1013,29 @@ val iter2 : action : ('T1 -> 'T2 -> unit) -> array1 : FlatList<'T1> -> array2 : [] val iteri : action : (int -> 'T -> unit) -> array : FlatList<'T> -> unit -/// Applies an action to the index and the pair of elements at that index. +/// +/// Applies an action to the index and the pair of elements at that index. +/// /// The action. /// The first flat list. /// The second flat list. /// Thrown when the flat lists have different lengths. -/// O(n). +/// +/// O(n). +/// [] val iteri2 : action : (int -> 'T1 -> 'T2 -> unit) -> array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> unit -/// Maps each element and threads a state through the mapping, from the first element to the last one. +/// +/// Maps each element and threads a state through the mapping, from the first element to the last one. +/// /// The function that returns the result for an element and the next state. /// The initial state. /// The input flat list. /// The results and the final state. -/// O(n). +/// +/// O(n). +/// [] val mapFold : mapping : ('State -> 'T -> struct ('Result * 'State)) -> @@ -831,12 +1043,16 @@ val mapFold : array : FlatList<'T> -> struct (FlatList<'Result> * 'State) -/// Maps each element and threads a state through the mapping, from the last element to the first one. +/// +/// Maps each element and threads a state through the mapping, from the last element to the first one. +/// /// The function that returns the result for an element and the next state. /// The input flat list. /// The initial state. /// The results, in the order of the elements, and the final state. -/// O(n). +/// +/// O(n). +/// [] val mapFoldBack : mapping : ('T -> 'State -> struct ('Result * 'State)) -> @@ -852,12 +1068,16 @@ val mapFoldBack : [] val max : array : FlatList<'T> -> 'T when 'T : comparison -/// Returns the element with the greatest result of a projection. +/// +/// Returns the element with the greatest result of a projection. +/// /// The function to transform the elements for the comparison. /// The input flat list. /// The first element with the greatest result. /// Thrown when the flat list is empty. -/// O(n). +/// +/// O(n). +/// [] val maxBy : projection : ('T -> 'U) -> array : FlatList<'T> -> 'T when 'U : comparison @@ -869,48 +1089,68 @@ val maxBy : projection : ('T -> 'U) -> array : FlatList<'T> -> 'T when 'U : comp [] val min : array : FlatList<'T> -> 'T when 'T : comparison -/// Returns the element with the smallest result of a projection. +/// +/// Returns the element with the smallest result of a projection. +/// /// The function to transform the elements for the comparison. /// The input flat list. /// The first element with the smallest result. /// Thrown when the flat list is empty. -/// O(n). +/// +/// O(n). +/// [] val minBy : projection : ('T -> 'U) -> array : FlatList<'T> -> 'T when 'U : comparison -/// Combines the elements with a function, from the first element to the last one. +/// +/// Combines the elements with a function, from the first element to the last one. +/// /// The function that combines the result so far with the next element. /// The input flat list. /// The combined value. /// Thrown when the flat list is empty. -/// O(n). +/// +/// O(n). +/// [] val reduce : reduction : ('T -> 'T -> 'T) -> array : FlatList<'T> -> 'T -/// Combines the elements with a function, from the last element to the first one. +/// +/// Combines the elements with a function, from the last element to the first one. +/// /// The function that combines an element with the result so far. /// The input flat list. /// The combined value. /// Thrown when the flat list is empty. -/// O(n). +/// +/// O(n). +/// [] val reduceBack : reduction : ('T -> 'T -> 'T) -> array : FlatList<'T> -> 'T -/// Returns every state of a fold from the first element to the last one, the initial state first. +/// +/// Returns every state of a fold from the first element to the last one, the initial state first. +/// /// The function that computes the next state. /// The initial state. /// The input flat list. /// The states; one more than the elements. -/// O(n). +/// +/// O(n). +/// [] val scan : folder : ('State -> 'T -> 'State) -> state : 'State -> array : FlatList<'T> -> FlatList<'State> -/// Returns every state of a fold from the last element to the first one, the initial state last. +/// +/// Returns every state of a fold from the last element to the first one, the initial state last. +/// /// The function that computes the next state. /// The input flat list. /// The initial state. /// The states; one more than the elements. -/// O(n). +/// +/// O(n). +/// [] val scanBack : folder : ('T -> 'State -> 'State) -> array : FlatList<'T> -> state : 'State -> FlatList<'State> @@ -921,11 +1161,15 @@ val scanBack : folder : ('T -> 'State -> 'State) -> array : FlatList<'T> -> stat [] val inline sum : array : FlatList< ^T > -> ^T when ^T : (static member (+) : ^T * ^T -> ^T) and ^T : (static member Zero : ^T) -/// Returns the sum of the results of a function applied to each element. +/// +/// Returns the sum of the results of a function applied to each element. +/// /// The function to transform the elements. /// The input flat list. /// The sum of the results; zero for an empty flat list. -/// O(n). +/// +/// O(n). +/// [] val inline sumBy : projection : ('T -> ^U) -> array : FlatList<'T> -> ^U @@ -941,27 +1185,39 @@ val inline sumBy : [] val countBy : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList when 'Key : equality -/// Returns the elements without duplicates, in F# structural equality. +/// +/// Returns the elements without duplicates, in F# structural equality. +/// /// The input flat list. /// The first occurrence of each element, in their order. -/// O(n). +/// +/// O(n). +/// [] val distinct : array : FlatList<'T> -> FlatList<'T> when 'T : equality -/// Returns the elements without duplicates of the result of a projection. +/// +/// Returns the elements without duplicates of the result of a projection. +/// /// The function that returns the key of an element. /// The input flat list. /// The first element of each key, in their order. -/// O(n). +/// +/// O(n). +/// [] val distinctBy : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList<'T> when 'Key : equality -/// Returns the distinct elements that do not equal any of the excluded items. +/// +/// Returns the distinct elements that do not equal any of the excluded items. +/// /// The items to remove. /// The input flat list. /// The distinct remaining elements, in their order. /// Thrown when is . -/// O(n + m). +/// +/// O(n + m). +/// [] val except : itemsToExclude : seq<'T> -> array : FlatList<'T> -> FlatList<'T> when 'T : equality @@ -981,124 +1237,180 @@ val groupBy : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList] val partition : predicate : ('T -> bool) -> array : FlatList<'T> -> struct (FlatList<'T> * FlatList<'T>) -/// Splits the elements by the choice that a function returns for them. +/// +/// Splits the elements by the choice that a function returns for them. +/// /// The function that returns Choice1Of2 or Choice2Of2 for an element. /// The input flat list. /// The values of the first choices and of the second choices, both in the order of the elements. -/// O(n). +/// +/// O(n). +/// [] val partitionWith : partitioner : ('T -> Choice<'T1, 'T2>) -> array : FlatList<'T> -> struct (FlatList<'T1> * FlatList<'T2>) -/// Returns the flat list without its first elements. +/// +/// Returns the flat list without its first elements. +/// /// The number of elements to skip; a negative count skips nothing. /// The input flat list. /// The remaining elements. /// Thrown when the count exceeds the length. -/// O(n). The result is a copy, unless it is empty or the whole flat list. +/// +/// O(n). The result is a copy, unless it is empty or the whole flat list. +/// [] val skip : count : int -> array : FlatList<'T> -> FlatList<'T> -/// Returns the flat list without its last elements. +/// +/// Returns the flat list without its last elements. +/// /// The number of elements to skip; a negative count skips nothing. /// The input flat list. /// The remaining elements. /// Thrown when the count exceeds the length. -/// O(n). The checks follow . The result is a copy, unless it is empty or the whole flat list. +/// +/// O(n). The checks follow . The result is a copy, unless it is empty or the whole flat list. +/// [] val skipLast : count : int -> array : FlatList<'T> -> FlatList<'T> -/// Returns the flat list without its first elements that satisfy the predicate. +/// +/// Returns the flat list without its first elements that satisfy the predicate. +/// /// The function to test the elements. /// The input flat list. /// The elements from the first one that does not satisfy the predicate. -/// O(n). The result is a copy, unless it is empty or the whole flat list. +/// +/// O(n). The result is a copy, unless it is empty or the whole flat list. +/// [] val skipWhile : predicate : ('T -> bool) -> array : FlatList<'T> -> FlatList<'T> -/// Splits the flat list at an index. +/// +/// Splits the flat list at an index. +/// /// The index of the first element of the second part. /// The input flat list. /// The elements before the index and the elements from the index. /// Thrown when the index is negative. /// Thrown when the index exceeds the length. -/// O(n). The parts are copies, unless they are empty or the whole flat list. +/// +/// O(n). The parts are copies, unless they are empty or the whole flat list. +/// [] val splitAt : index : int -> array : FlatList<'T> -> struct (FlatList<'T> * FlatList<'T>) -/// Splits the flat list into at most a given number of parts of nearly equal length. +/// +/// Splits the flat list into at most a given number of parts of nearly equal length. +/// /// The maximum number of parts. /// The input flat list. /// The parts; the first ones are one element longer when the length does not divide evenly. /// Thrown when is not positive. -/// O(n). The parts are copies. +/// +/// O(n). The parts are copies. +/// [] val splitInto : count : int -> array : FlatList<'T> -> FlatList> -/// Returns the first elements of the flat list. +/// +/// Returns the first elements of the flat list. +/// /// The number of elements to take. /// The input flat list. /// The first elements. /// Thrown when the count is negative. /// Thrown when the count exceeds the length. -/// O(n). The result is a copy, unless it is empty or the whole flat list. +/// +/// O(n). The result is a copy, unless it is empty or the whole flat list. +/// [] val take : count : int -> array : FlatList<'T> -> FlatList<'T> -/// Returns the last elements of the flat list. +/// +/// Returns the last elements of the flat list. +/// /// The number of elements to take. /// The input flat list. /// The last elements. /// Thrown when the count is negative. /// Thrown when the count exceeds the length. -/// O(n). The checks follow . The result is a copy, unless it is empty or the whole flat list. +/// +/// O(n). The checks follow . The result is a copy, unless it is empty or the whole flat list. +/// [] val takeLast : count : int -> array : FlatList<'T> -> FlatList<'T> -/// Returns the first elements of the flat list that satisfy the predicate. +/// +/// Returns the first elements of the flat list that satisfy the predicate. +/// /// The function to test the elements. /// The input flat list. /// The elements before the first one that does not satisfy the predicate. -/// O(n). The result is a copy, unless it is empty or the whole flat list. +/// +/// O(n). The result is a copy, unless it is empty or the whole flat list. +/// [] val takeWhile : predicate : ('T -> bool) -> array : FlatList<'T> -> FlatList<'T> -/// Returns at most a given number of first elements. +/// +/// Returns at most a given number of first elements. +/// /// The maximum number of elements; a count that is not positive returns an empty flat list. /// The input flat list. /// The first elements. -/// O(n). The result is a copy, unless it is empty or the whole flat list. +/// +/// O(n). The result is a copy, unless it is empty or the whole flat list. +/// [] val truncate : count : int -> array : FlatList<'T> -> FlatList<'T> // ---- Sorting ---- -/// Sorts the elements in the order of F# generic comparison. +/// +/// Sorts the elements in the order of F# generic comparison. +/// /// The input flat list. /// The sorted flat list. -/// O(n log n). This is not a stable sort, as . +/// +/// O(n log n). This is not a stable sort, as . +/// [] val sort : array : FlatList<'T> -> FlatList<'T> when 'T : comparison -/// Sorts the elements by the result of a projection, in the order of F# generic comparison. +/// +/// Sorts the elements by the result of a projection, in the order of F# generic comparison. +/// /// The function that returns the key of an element. /// The input flat list. /// The sorted flat list. -/// O(n log n). This is not a stable sort. +/// +/// O(n log n). This is not a stable sort. +/// [] val sortBy : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList<'T> when 'Key : comparison -/// Sorts the elements by the result of a projection, in descending order. +/// +/// Sorts the elements by the result of a projection, in descending order. +/// /// The function that returns the key of an element. /// The input flat list. /// The sorted flat list. -/// O(n log n). This is not a stable sort. +/// +/// O(n log n). This is not a stable sort. +/// [] val sortByDescending : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList<'T> when 'Key : comparison -/// Sorts the elements in descending order of F# generic comparison. +/// +/// Sorts the elements in descending order of F# generic comparison. +/// /// The input flat list. /// The sorted flat list. -/// O(n log n). This is not a stable sort. +/// +/// O(n log n). This is not a stable sort. +/// [] val sortDescending : array : FlatList<'T> -> FlatList<'T> when 'T : comparison @@ -1110,7 +1422,9 @@ val sortDescending : array : FlatList<'T> -> FlatList<'T> when 'T : comparison [] val sortWith : comparer : ('T -> 'T -> int) -> array : FlatList<'T> -> FlatList<'T> -/// Sorts the elements with a comparer. +/// +/// Sorts the elements with a comparer. +/// /// The comparer. /// The input flat list. /// The sorted flat list; the input itself when it is already sorted. @@ -1123,131 +1437,185 @@ val sortWithComparer : comparer : IComparer<'T> -> array : FlatList<'T> -> FlatL // ---- Random ---- -/// Returns a random element, chosen with . +/// +/// Returns a random element, chosen with . +/// /// The input flat list. /// A random element. /// Thrown when the flat list is empty. -/// O(1). +/// +/// O(1). +/// [] val randomChoice : source : FlatList<'T> -> 'T -/// Returns a random element, chosen with a function that returns numbers in [0, 1). +/// +/// Returns a random element, chosen with a function that returns numbers in [0, 1). +/// /// The function that returns a random number in [0, 1). /// The input flat list. /// A random element. /// Thrown when the flat list is empty. /// Thrown when the randomizer returns a number outside [0, 1). -/// O(1). +/// +/// O(1). +/// [] val randomChoiceBy : randomizer : (unit -> float) -> source : FlatList<'T> -> 'T -/// Returns a random element, chosen with a given random number generator. +/// +/// Returns a random element, chosen with a given random number generator. +/// /// The random number generator. /// The input flat list. /// A random element. /// Thrown when is . /// Thrown when the flat list is empty. -/// O(1). +/// +/// O(1). +/// [] val randomChoiceWith : random : Random -> source : FlatList<'T> -> 'T -/// Returns random elements, chosen with ; an element can repeat. +/// +/// Returns random elements, chosen with ; an element can repeat. +/// /// The number of elements to choose. /// The input flat list. /// The chosen elements. /// Thrown when the count is negative or the flat list is empty. -/// O(count). +/// +/// O(count). +/// [] val randomChoices : count : int -> source : FlatList<'T> -> FlatList<'T> -/// Returns random elements, chosen with a function that returns numbers in [0, 1); an element can repeat. +/// +/// Returns random elements, chosen with a function that returns numbers in [0, 1); an element can repeat. +/// /// The function that returns a random number in [0, 1). /// The number of elements to choose. /// The input flat list. /// The chosen elements. /// Thrown when the count is negative or the flat list is empty. /// Thrown when the randomizer returns a number outside [0, 1). -/// O(count). +/// +/// O(count). +/// [] val randomChoicesBy : randomizer : (unit -> float) -> count : int -> source : FlatList<'T> -> FlatList<'T> -/// Returns random elements, chosen with a given random number generator; an element can repeat. +/// +/// Returns random elements, chosen with a given random number generator; an element can repeat. +/// /// The random number generator. /// The number of elements to choose. /// The input flat list. /// The chosen elements. /// Thrown when is . /// Thrown when the count is negative or the flat list is empty. -/// O(count). +/// +/// O(count). +/// [] val randomChoicesWith : random : Random -> count : int -> source : FlatList<'T> -> FlatList<'T> -/// Returns random elements at distinct indices, chosen with . +/// +/// Returns random elements at distinct indices, chosen with . +/// /// The number of elements to choose. /// The input flat list. /// The chosen elements. /// Thrown when the count is negative or exceeds the length. -/// O(count). +/// +/// O(count). +/// [] val randomSample : count : int -> source : FlatList<'T> -> FlatList<'T> -/// Returns random elements at distinct indices, chosen with a function that returns numbers in [0, 1). +/// +/// Returns random elements at distinct indices, chosen with a function that returns numbers in [0, 1). +/// /// The function that returns a random number in [0, 1). /// The number of elements to choose. /// The input flat list. /// The chosen elements. /// Thrown when the count is negative or exceeds the length. /// Thrown when the randomizer returns a number outside [0, 1). -/// O(count). +/// +/// O(count). +/// [] val randomSampleBy : randomizer : (unit -> float) -> count : int -> source : FlatList<'T> -> FlatList<'T> -/// Returns random elements at distinct indices, chosen with a given random number generator. +/// +/// Returns random elements at distinct indices, chosen with a given random number generator. +/// /// The random number generator. /// The number of elements to choose. /// The input flat list. /// The chosen elements. /// Thrown when is . /// Thrown when the count is negative or exceeds the length. -/// O(count). +/// +/// O(count). +/// [] val randomSampleWith : random : Random -> count : int -> source : FlatList<'T> -> FlatList<'T> -/// Returns the elements in a random order, chosen with . +/// +/// Returns the elements in a random order, chosen with . +/// /// The input flat list. /// The shuffled flat list. -/// O(n). +/// +/// O(n). +/// [] val randomShuffle : source : FlatList<'T> -> FlatList<'T> -/// Returns the elements in a random order, chosen with a function that returns numbers in [0, 1). +/// +/// Returns the elements in a random order, chosen with a function that returns numbers in [0, 1). +/// /// The function that returns a random number in [0, 1). /// The input flat list. /// The shuffled flat list. /// Thrown when the randomizer returns a number outside [0, 1). -/// O(n). +/// +/// O(n). +/// [] val randomShuffleBy : randomizer : (unit -> float) -> source : FlatList<'T> -> FlatList<'T> -/// Returns the elements in a random order, chosen with a given random number generator. +/// +/// Returns the elements in a random order, chosen with a given random number generator. +/// /// The random number generator. /// The input flat list. /// The shuffled flat list. /// Thrown when is . -/// O(n). +/// +/// O(n). +/// [] val randomShuffleWith : random : Random -> source : FlatList<'T> -> FlatList<'T> // ---- Conversion ---- -/// Returns a read-only memory region over the elements, without a copy. +/// +/// Returns a read-only memory region over the elements, without a copy. +/// /// The input flat list. /// The memory region. -/// O(1). The function wraps . +/// +/// O(1). The function wraps . +/// [] val asMemory : array : FlatList<'T> -> ReadOnlyMemory<'T> -/// Returns a read-only span over the elements, without a copy. +/// +/// Returns a read-only span over the elements, without a copy. +/// /// The input flat list. /// The span. /// @@ -1292,16 +1660,24 @@ val toSeq : array : FlatList<'T> -> seq<'T> // ---- Type-specific ---- -/// Returns whether the immutable array is a default value without an underlying array. +/// +/// Returns whether the immutable array is a default value without an underlying array. +/// /// The input flat list, which can be a default value. /// for a default value. -/// O(1). The function wraps . +/// +/// O(1). The function wraps . +/// [] val isDefault : array : FlatList<'T> -> bool -/// Returns whether the immutable array is a default value or empty. +/// +/// Returns whether the immutable array is a default value or empty. +/// /// The input flat list, which can be a default value. /// for a default value or an empty flat list. -/// O(1). The function wraps . +/// +/// O(1). The function wraps . +/// [] val isDefaultOrEmpty : array : FlatList<'T> -> bool diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs b/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs index 50533a5d..2f72e553 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs @@ -16,7 +16,9 @@ open System.Collections.Immutable // ---- Properties ---- -/// Returns the number of elements that the builder can hold without growing. +/// +/// Returns the number of elements that the builder can hold without growing. +/// /// The builder. /// The capacity. [] @@ -40,21 +42,27 @@ let item index (builder : FlatList<'T>.Builder) = builder[index] // ---- Search ---- -/// Returns whether the builder contains a value, in . +/// +/// Returns whether the builder contains a value, in . +/// /// The value to find. /// The builder. /// when an element equals the value. [] let contains (value : 'T) (builder : FlatList<'T>.Builder) = builder.Contains value -/// Returns the index of the first element equal to a value, or -1. +/// +/// Returns the index of the first element equal to a value, or -1. +/// /// The value to find. /// The builder. /// The index, or -1. [] let indexOf (value : 'T) (builder : FlatList<'T>.Builder) = builder.IndexOf value -/// Returns the index of the last element equal to a value, or -1. +/// +/// Returns the index of the last element equal to a value, or -1. +/// /// The value to find. /// The builder. /// The index, or -1. @@ -100,7 +108,9 @@ let insertAt index (value : 'T) (builder : FlatList<'T>.Builder) = builder.Insert (index, value) builder -/// Removes the first element equal to a value, in . +/// +/// Removes the first element equal to a value, in . +/// /// The value to remove. /// The builder. /// when an element was removed. @@ -125,7 +135,9 @@ let rev (builder : FlatList<'T>.Builder) = builder.Reverse () builder -/// Sets the number of elements that the builder can hold without growing. +/// +/// Sets the number of elements that the builder can hold without growing. +/// /// The new capacity, not less than the count. /// The builder. /// The same builder. @@ -179,7 +191,9 @@ let sortWithComparer (comparer : IComparer<'T>) (builder : FlatList<'T>.Builder) // ---- Conversion ---- -/// Returns the contents of the builder as a flat list and empties the builder. +/// +/// Returns the contents of the builder as a flat list and empties the builder. +/// /// The builder. /// The flat list of the elements; without a copy when the count equals the capacity. [] diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fs b/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fs index f95c4a13..cd15439d 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fs +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fs @@ -3,10 +3,14 @@ namespace FSharp.Collections.Immutable open System open System.Collections.Immutable -/// The body of a flatList { } expression: a function that adds the elements to a builder. +/// +/// The body of a flatList { } expression: a function that adds the elements to a builder. +/// type FlatListBuffer<'T> = FlatList<'T>.Builder -> unit -/// The builder of the flatList { } computation expression. +/// +/// The builder of the flatList { } computation expression. +/// /// /// The expression collects its elements in an and /// returns them without a copy when the builder is full (ADR 0001, D7). @@ -38,7 +42,9 @@ type FlatListCE () = first builder second builder - /// Runs the body of the expression each time the elements are collected. + /// + /// Runs the body of the expression each time the elements are collected. + /// member inline _.Delay ([] delayed : unit -> FlatListBuffer<'T>) : FlatListBuffer<'T> = fun builder -> delayed () builder diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListOption.fsi b/src/FSharp.Collections.Immutable/FlatList/FlatListOption.fsi index bfb30ffe..33a1a000 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListOption.fsi +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListOption.fsi @@ -15,109 +15,161 @@ open FSharp.Collections.Immutable // ---- Creation ---- -/// Creates a flat list from a generator, which returns the next element and the next state. +/// +/// Creates a flat list from a generator, which returns the next element and the next state. +/// /// The function that returns the next element and state, or None to stop. /// The initial state. /// The generated flat list. -/// O(n). +/// +/// O(n). +/// [] val unfold : generator : ('State -> ('T * 'State) option) -> state : 'State -> FlatList<'T> // ---- Access ---- -/// Returns the only element of the flat list, or None when it does not have exactly one element. +/// +/// Returns the only element of the flat list, or None when it does not have exactly one element. +/// /// The input flat list. /// The only element, or None. -/// O(1). +/// +/// O(1). +/// [] val tryExactlyOne : array : FlatList<'T> -> 'T option -/// Returns the first element of the flat list, or None when it is empty. +/// +/// Returns the first element of the flat list, or None when it is empty. +/// /// The input flat list. /// The first element, or None. -/// O(1). +/// +/// O(1). +/// [] val tryHead : array : FlatList<'T> -> 'T option -/// Returns the element at an index, or None when the index is out of range. +/// +/// Returns the element at an index, or None when the index is out of range. +/// /// The index of the element. /// The input flat list. /// The element at the index, or None. -/// O(1). +/// +/// O(1). +/// [] val tryItem : index : int -> array : FlatList<'T> -> 'T option -/// Returns the last element of the flat list, or None when it is empty. +/// +/// Returns the last element of the flat list, or None when it is empty. +/// /// The input flat list. /// The last element, or None. -/// O(1). +/// +/// O(1). +/// [] val tryLast : array : FlatList<'T> -> 'T option -/// Returns the flat list without its first element, or None when it is empty. +/// +/// Returns the flat list without its first element, or None when it is empty. +/// /// The input flat list. /// A copy of the elements after the first one, or None. -/// O(n). +/// +/// O(n). +/// [] val tryTail : array : FlatList<'T> -> FlatList<'T> option // ---- Search ---- -/// Returns the first value that the chooser returns for the elements. +/// +/// Returns the first value that the chooser returns for the elements. +/// /// The function that returns a value, or None to go on. /// The input flat list. /// The first chosen value. /// Thrown when the chooser returns None for every element. -/// O(n). +/// +/// O(n). +/// [] val pick : chooser : ('T -> 'U option) -> array : FlatList<'T> -> 'U -/// Returns the first element that satisfies the predicate, or None. +/// +/// Returns the first element that satisfies the predicate, or None. +/// /// The function to test the elements. /// The input flat list. /// The first element that satisfies the predicate, or None. -/// O(n). +/// +/// O(n). +/// [] val tryFind : predicate : ('T -> bool) -> array : FlatList<'T> -> 'T option -/// Returns the last element that satisfies the predicate, or None. +/// +/// Returns the last element that satisfies the predicate, or None. +/// /// The function to test the elements. /// The input flat list. /// The last element that satisfies the predicate, or None. -/// O(n). +/// +/// O(n). +/// [] val tryFindBack : predicate : ('T -> bool) -> array : FlatList<'T> -> 'T option -/// Returns the index of the first element that satisfies the predicate, or None. +/// +/// Returns the index of the first element that satisfies the predicate, or None. +/// /// The function to test the elements. /// The input flat list. /// The index of the first element that satisfies the predicate, or None. -/// O(n). +/// +/// O(n). +/// [] val tryFindIndex : predicate : ('T -> bool) -> array : FlatList<'T> -> int option -/// Returns the index of the last element that satisfies the predicate, or None. +/// +/// Returns the index of the last element that satisfies the predicate, or None. +/// /// The function to test the elements. /// The input flat list. /// The index of the last element that satisfies the predicate, or None. -/// O(n). +/// +/// O(n). +/// [] val tryFindIndexBack : predicate : ('T -> bool) -> array : FlatList<'T> -> int option -/// Returns the first value that the chooser returns for the elements, or None. +/// +/// Returns the first value that the chooser returns for the elements, or None. +/// /// The function that returns a value, or None to go on. /// The input flat list. /// The first chosen value, or None. -/// O(n). +/// +/// O(n). +/// [] val tryPick : chooser : ('T -> 'U option) -> array : FlatList<'T> -> 'U option // ---- Transformation ---- -/// Returns the values that the chooser returns for the elements. +/// +/// Returns the values that the chooser returns for the elements. +/// /// The function that returns a value, or None to skip the element. /// The input flat list. /// The chosen values. -/// O(n). +/// +/// O(n). +/// [] val choose : chooser : ('T -> 'U option) -> array : FlatList<'T> -> FlatList<'U> diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fsi b/src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fsi index cd04092a..6000cc94 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fsi +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListTuple.fsi @@ -16,21 +16,29 @@ open FSharp.Collections.Immutable // ---- Creation ---- -/// Creates a flat list from a generator, which returns the next element and the next state. +/// +/// Creates a flat list from a generator, which returns the next element and the next state. +/// /// The function that returns the next element and state, or ValueNone to stop. /// The initial state. /// The generated flat list. -/// O(n). +/// +/// O(n). +/// [] val unfold : generator : ('State -> ('T * 'State) voption) -> state : 'State -> FlatList<'T> // ---- Transformation ---- -/// Returns every element of the first flat list paired with every element of the second one. +/// +/// Returns every element of the first flat list paired with every element of the second one. +/// /// The first flat list. /// The second flat list. /// The pairs, in the order of the first flat list and then of the second one. -/// O(n × m). +/// +/// O(n × m). +/// [] val allPairs : array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> FlatList<'T1 * 'T2> @@ -83,21 +91,29 @@ val zip3 : array1 : FlatList<'T1> -> array2 : FlatList<'T2> -> array3 : FlatList // ---- Folding ---- -/// Maps each element and threads a state through the mapping, from the first element to the last one. +/// +/// Maps each element and threads a state through the mapping, from the first element to the last one. +/// /// The function that returns the result for an element and the next state. /// The initial state. /// The input flat list. /// The results and the final state. -/// O(n). +/// +/// O(n). +/// [] val mapFold : mapping : ('State -> 'T -> 'Result * 'State) -> state : 'State -> array : FlatList<'T> -> FlatList<'Result> * 'State -/// Maps each element and threads a state through the mapping, from the last element to the first one. +/// +/// Maps each element and threads a state through the mapping, from the last element to the first one. +/// /// The function that returns the result for an element and the next state. /// The input flat list. /// The initial state. /// The results, in the order of the elements, and the final state. -/// O(n). +/// +/// O(n). +/// [] val mapFoldBack : mapping : ('T -> 'State -> 'Result * 'State) -> array : FlatList<'T> -> state : 'State -> FlatList<'Result> * 'State @@ -128,20 +144,28 @@ val groupBy : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList<'Key [] val partition : predicate : ('T -> bool) -> array : FlatList<'T> -> FlatList<'T> * FlatList<'T> -/// Splits the elements by the choice that a function returns for them. +/// +/// Splits the elements by the choice that a function returns for them. +/// /// The function that returns Choice1Of2 or Choice2Of2 for an element. /// The input flat list. /// The values of the first choices and of the second choices, both in the order of the elements. -/// O(n). +/// +/// O(n). +/// [] val partitionWith : partitioner : ('T -> Choice<'T1, 'T2>) -> array : FlatList<'T> -> FlatList<'T1> * FlatList<'T2> -/// Splits the flat list at an index. +/// +/// Splits the flat list at an index. +/// /// The index of the first element of the second part. /// The input flat list. /// The elements before the index and the elements from the index. /// Thrown when the index is negative. /// Thrown when the index exceeds the length. -/// O(n). The parts are copies, unless they are empty or the whole flat list. +/// +/// O(n). The parts are copies, unless they are empty or the whole flat list. +/// [] val splitAt : index : int -> array : FlatList<'T> -> FlatList<'T> * FlatList<'T> diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListTypes.fs b/src/FSharp.Collections.Immutable/FlatList/FlatListTypes.fs index 3315cc3f..ae31a470 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListTypes.fs +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListTypes.fs @@ -5,6 +5,10 @@ open System.Collections.Immutable // The abbreviation has its own file because the module of the folder is a root module, which cannot declare a type of // the namespace; a file of abbreviations needs no signature file (ADR 0001, D2). -/// An immutable array, . -/// The name comes from a similar construct in the sources of the F# compiler. +/// +/// An immutable array, . +/// +/// +/// The name comes from a similar construct in the sources of the F# compiler. +/// type FlatList<'T> = ImmutableArray<'T> diff --git a/src/FSharp.Collections.Immutable/Helper.fs b/src/FSharp.Collections.Immutable/Helper.fs index cf658ced..8c1e6804 100644 --- a/src/FSharp.Collections.Immutable/Helper.fs +++ b/src/FSharp.Collections.Immutable/Helper.fs @@ -32,7 +32,9 @@ module internal ImmutableCollectionUtil = if array.IsDefault then nullArg name - /// The array under an immutable array, without a copy (ADR 0001, D5). + /// + /// The array under an immutable array, without a copy (ADR 0001, D5). + /// /// /// The caller reads the array and never writes to it: the immutable array and every value that shares it see the same /// memory. A default immutable array throws for . @@ -43,8 +45,12 @@ module internal ImmutableCollectionUtil = ImmutableCollectionsMarshal.AsArray array |> Unchecked.nonNull - /// An immutable array over an array, without a copy (ADR 0001, D5). - /// Only an array that nobody can write to any more may be wrapped: a fresh result of a computation. + /// + /// An immutable array over an array, without a copy (ADR 0001, D5). + /// + /// + /// Only an array that nobody can write to any more may be wrapped: a fresh result of a computation. + /// let inline wrapArray (array : 'T array) : ImmutableArray<'T> = ImmutableCollectionsMarshal.AsImmutableArray array /// The constructor of a struct pair, for the algorithms shared by the default and the `Tuple` modules. @@ -87,7 +93,9 @@ module internal Comparers = /// The comparer of F# generic comparison for the type. static member Instance : IComparer<'T> = comparer -/// The exception messages of FSharp.Core (FSCore.resx), in English. +/// +/// The exception messages of FSharp.Core (FSCore.resx), in English. +/// /// /// The modules throw what the FSharp.Core function that they mirror throws (ADR 0001, D6). FSharp.Core keeps its resources /// internal, so the texts are copied. @@ -143,7 +151,9 @@ module internal ErrorStrings = /// /// The exceptions of the DetailedExceptions module of FSharp.Core, with the same types, messages and parameter names. /// -/// The callers pass the names of their own parameters (ADR 0001, D6). +/// +/// The callers pass the names of their own parameters (ADR 0001, D6). +/// module internal Raise = let inline private invalidArgFormat (arg : string) (format : string) (paramArray : obj array) = @@ -271,8 +281,12 @@ module internal Arrays = member _.GetHashCode key = structural.GetHashCode key.Key } - /// The keys of the projection in the order of their first occurrence, each with the number of its elements. - /// The counting keeps one counter per key, not the elements, so it needs memory for the keys only. + /// + /// The keys of the projection in the order of their first occurrence, each with the number of its elements. + /// + /// + /// The counting keeps one counter per key, not the elements, so it needs memory for the keys only. + /// let inline countBy ([] tuple : 'Key -> int -> 'R) (projection : 'T -> 'Key) From 554890300de773fdc5cefa40729f0efb73cb250a Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 17:21:59 +0200 Subject: [PATCH 11/27] fixup! test: shared contracts of the sequence-like modules, run for `FlatList` A `` and a `` stay on one line each only when both are short; the results of the ops records that hold arrays of arrays are `int[][]`, so that a type has one postfix level at most. Co-Authored-By: Claude Opus 5.5 --- .../Contracts/IndexedContract.fs | 4 ++- .../SeqLike/SeqLikeAccessContract.fs | 4 ++- .../SeqLike/SeqLikeRandomContract.fs | 4 ++- .../SeqLike/SeqLikeSearchContract.fs | 8 ++++-- .../SeqLike/SeqLikeSortingContract.fs | 4 ++- .../Contracts/SeqOps.fs | 28 ++++++++++++------- 6 files changed, 36 insertions(+), 16 deletions(-) diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/IndexedContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/IndexedContract.fs index d8da95a5..46f0d8d2 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/IndexedContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/IndexedContract.fs @@ -8,7 +8,9 @@ open Microsoft.VisualStudio.TestTools.UnitTesting /// The contract of the index-based functions of FlatList, IndexedList and IIndexedList /// (ADR 0001, Appendix A). /// -/// The tests run on the input with several elements; the indices of their rows are computed from its length. +/// +/// The tests run on the input with several elements; the indices of their rows are computed from its length. +/// [] type IndexedContract<'C> (ops : IndexedOps<'C>) = inherit ContractBase (ops.Name, ops.Mirror, ops.ParamName) diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeAccessContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeAccessContract.fs index 0fff13c1..8fee7d72 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeAccessContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeAccessContract.fs @@ -3,7 +3,9 @@ namespace FSharp.Collections.Immutable.Tests open FSharp.Collections.Immutable open Microsoft.VisualStudio.TestTools.UnitTesting -/// The helpers that the contract bases of the sequence-like modules share. +/// +/// The helpers that the contract bases of the sequence-like modules share. +/// /// /// The contract tests compute their expectations with the FSharp.Core Array module over the elements in the order /// in which the collection enumerates them, so one test body serves FIFO, LIFO and indexed collections (ADR 0001, D8). diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeRandomContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeRandomContract.fs index e1023947..2e4e6da5 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeRandomContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeRandomContract.fs @@ -4,7 +4,9 @@ open System open FSharp.Collections.Immutable open Microsoft.VisualStudio.TestTools.UnitTesting -/// The contract of the random functions of a sequence-like module. +/// +/// The contract of the random functions of a sequence-like module. +/// /// /// With a seeded generator or a fixed randomizer, a function returns what the function of the mirrored FSharp.Core module /// returns for the same elements; with , the tests check the properties of the result. diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSearchContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSearchContract.fs index 03c2b019..1d648ef6 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSearchContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSearchContract.fs @@ -3,7 +3,9 @@ namespace FSharp.Collections.Immutable.Tests open FSharp.Collections.Immutable open Microsoft.VisualStudio.TestTools.UnitTesting -/// The predicates of the search tests over elements, as the cases of their rows. +/// +/// The predicates of the search tests over elements, as the cases of their rows. +/// /// /// MSTest names a row after its values, and a function has no readable name, so a row holds a case and the test takes /// the function of the case. @@ -27,7 +29,9 @@ type ElementPredicate = | ElementPredicate.IsBelowHundred -> item < 100 | ElementPredicate.Never -> false -/// The predicates of the search tests over the pairs of elements at the same index, as the cases of their rows. +/// +/// The predicates of the search tests over the pairs of elements at the same index, as the cases of their rows. +/// /// /// MSTest names a row after its values, and a function has no readable name, so a row holds a case and the test takes /// the function of the case. diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSortingContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSortingContract.fs index dd13e589..edaf2cf5 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSortingContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeSortingContract.fs @@ -4,7 +4,9 @@ open System.Collections.Generic open FSharp.Collections.Immutable open Microsoft.VisualStudio.TestTools.UnitTesting -/// The contract of the sorting functions of a sequence-like module. +/// +/// The contract of the sorting functions of a sequence-like module. +/// /// /// Equal keys belong to equal elements in these inputs, so the results do not depend on the stability of the sort; /// StableSortContract checks the stability of the modules that promise it. diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqOps.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqOps.fs index 0502367e..c27dc794 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqOps.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqOps.fs @@ -3,7 +3,9 @@ namespace FSharp.Collections.Immutable.Tests open System open System.Collections.Generic -/// The functions of a sequence-like module for the contract tests, over elements. +/// +/// The functions of a sequence-like module for the contract tests, over elements. +/// /// /// /// A record of functions checks at compile time that a module provides every function of a facet (ADR 0001, D8). The @@ -58,7 +60,7 @@ type SeqOps<'C> = { AllPairs : 'C -> 'C -> struct (int * int) array Append : 'C -> 'C -> 'C Choose : (int -> int voption) -> 'C -> 'C - ChunkBySize : int -> 'C -> int array array + ChunkBySize : int -> 'C -> int[][] Collect : (int -> 'C) -> 'C -> 'C Concat : seq<'C> -> 'C Filter : (int -> bool) -> 'C -> 'C @@ -71,13 +73,13 @@ type SeqOps<'C> = { Pairwise : 'C -> struct (int * int) array Permute : (int -> int) -> 'C -> 'C Rev : 'C -> 'C - Transpose : seq<'C> -> int array array + Transpose : seq<'C> -> int[][] /// Unzips a collection of the given pairs. Unzip : struct (int * int) array -> struct (int array * int array) /// Unzips a collection of the given triples. Unzip3 : struct (int * int * int) array -> struct (int array * int array * int array) Where : (int -> bool) -> 'C -> 'C - Windowed : int -> 'C -> int array array + Windowed : int -> 'C -> int[][] Zip : 'C -> 'C -> struct (int * int) array Zip3 : 'C -> 'C -> 'C -> struct (int * int * int) array @@ -101,8 +103,8 @@ type SeqOps<'C> = { MinBy : (int -> int) -> 'C -> int Reduce : (int -> int -> int) -> 'C -> int ReduceBack : (int -> int -> int) -> 'C -> int - Scan : (int array -> int -> int array) -> int array -> 'C -> int array array - ScanBack : (int -> int array -> int array) -> 'C -> int array -> int array array + Scan : (int array -> int -> int array) -> int array -> 'C -> int[][] + ScanBack : (int -> int array -> int array) -> 'C -> int array -> int[][] Sum : 'C -> int SumBy : (int -> int) -> 'C -> int @@ -117,7 +119,7 @@ type SeqOps<'C> = { SkipLast : int -> 'C -> 'C SkipWhile : (int -> bool) -> 'C -> 'C SplitAt : int -> 'C -> struct ('C * 'C) - SplitInto : int -> 'C -> int array array + SplitInto : int -> 'C -> int[][] Take : int -> 'C -> 'C TakeLast : int -> 'C -> 'C TakeWhile : (int -> bool) -> 'C -> 'C @@ -147,8 +149,12 @@ type SeqOps<'C> = { ToSeq : 'C -> seq } -/// The creation functions of a sequence-like module for the contract tests. -/// A function value cannot take a span, so the span functions take an array that the instance turns into a span. +/// +/// The creation functions of a sequence-like module for the contract tests. +/// +/// +/// A function value cannot take a span, so the span functions take an array that the instance turns into a span. +/// type SeqCreationOps<'C> = { /// The name of the module, for the messages of the assertions. Name : string @@ -171,7 +177,9 @@ type SeqCreationOps<'C> = { Unfold : (int -> struct (int * int) voption) -> int -> 'C } -/// The index-based functions of a sequence-like module for the contract tests. +/// +/// The index-based functions of a sequence-like module for the contract tests. +/// type IndexedOps<'C> = { /// The name of the module, for the messages of the assertions. Name : string From bbdda1be3fe68a9b2e9a5d0c28256b0ca7be2371 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 18:17:12 +0200 Subject: [PATCH 12/27] fixup! feat: `FlatList` module with the vocabulary of the FSharp.Core `Array` module - `FlatListBuilder.fsi`, `FlatListCE.fsi` and `ComputationExpressions.fsi` carry the signatures and the XML documentation of their files, compiled right before them: every public binding file has a signature file (ADR 0001, D2). `ComputationExpressions` groups its instances under the `Creation` banner, so that `check-order.fsx` checks it as well. - `flatList { yield! array }` rejects a default flat list with `ArgumentNullException`, as the functions of `FlatList` do (D6), instead of the `NullReferenceException` of the builder; a default flat list given as a sequence is rejected as `FlatList.ofSeq` rejects it, instead of the `InvalidOperationException` of its enumeration. Co-Authored-By: Claude Opus 5.5 --- .../ComputationExpressions.fs | 17 +- .../ComputationExpressions.fsi | 21 ++ .../FSharp.Collections.Immutable.fsproj | 3 + .../FlatList/FlatListBuilder.fs | 106 ---------- .../FlatList/FlatListBuilder.fsi | 182 ++++++++++++++++++ .../FlatList/FlatListCE.fs | 40 ++-- .../FlatList/FlatListCE.fsi | 71 +++++++ 7 files changed, 291 insertions(+), 149 deletions(-) create mode 100644 src/FSharp.Collections.Immutable/ComputationExpressions.fsi create mode 100644 src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fsi create mode 100644 src/FSharp.Collections.Immutable/FlatList/FlatListCE.fsi diff --git a/src/FSharp.Collections.Immutable/ComputationExpressions.fs b/src/FSharp.Collections.Immutable/ComputationExpressions.fs index 2d3def91..6db0b059 100644 --- a/src/FSharp.Collections.Immutable/ComputationExpressions.fs +++ b/src/FSharp.Collections.Immutable/ComputationExpressions.fs @@ -1,19 +1,6 @@ -/// -/// The computation expressions of the collections (ADR 0001, D7). -/// [] module FSharp.Collections.Immutable.ComputationExpressions -/// Builds a flat list. -/// -/// -/// let squares = flatList { -/// yield 0 -/// for i in 1..3 do -/// yield i * i -/// yield! [ 100; 200 ] -/// } -/// -/// Evaluates to a flat list of 0, 1, 4, 9, 100 and 200. -/// +// ---- Creation ---- + let flatList = FlatListCE () diff --git a/src/FSharp.Collections.Immutable/ComputationExpressions.fsi b/src/FSharp.Collections.Immutable/ComputationExpressions.fsi new file mode 100644 index 00000000..c0086e56 --- /dev/null +++ b/src/FSharp.Collections.Immutable/ComputationExpressions.fsi @@ -0,0 +1,21 @@ +/// +/// The computation expressions of the collections (ADR 0001, D7). +/// +[] +module FSharp.Collections.Immutable.ComputationExpressions + +// ---- Creation ---- + +/// Builds a flat list. +/// +/// +/// let squares = flatList { +/// yield 0 +/// for i in 1..3 do +/// yield i * i +/// yield! [ 100; 200 ] +/// } +/// +/// Evaluates to a flat list of 0, 1, 4, 9, 100 and 200. +/// +val flatList : FlatListCE diff --git a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj index d0aae447..8e3efc2d 100644 --- a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj +++ b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj @@ -28,7 +28,9 @@ + + @@ -36,6 +38,7 @@ + diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs b/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs index 2f72e553..1ed7161f 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs @@ -1,12 +1,3 @@ -/// -/// Functions for the builder of a flat list, . -/// -/// -/// The builder is mutable. The functions that change it return the same builder, so that the calls chain with the pipe -/// operator. creates a builder, and -/// -/// returns its contents as a flat list. -/// [] module FSharp.Collections.Immutable.FlatListBuilder @@ -16,143 +7,68 @@ open System.Collections.Immutable // ---- Properties ---- -/// -/// Returns the number of elements that the builder can hold without growing. -/// -/// The builder. -/// The capacity. [] let capacity (builder : FlatList<'T>.Builder) = builder.Capacity -/// Returns the number of elements in the builder. -/// The builder. -/// The number of elements. [] let length (builder : FlatList<'T>.Builder) = builder.Count // ---- Access ---- -/// Returns the element at an index. -/// The index of the element. -/// The builder. -/// The element. -/// Thrown when the index is out of range. [] let item index (builder : FlatList<'T>.Builder) = builder[index] // ---- Search ---- -/// -/// Returns whether the builder contains a value, in . -/// -/// The value to find. -/// The builder. -/// when an element equals the value. [] let contains (value : 'T) (builder : FlatList<'T>.Builder) = builder.Contains value -/// -/// Returns the index of the first element equal to a value, or -1. -/// -/// The value to find. -/// The builder. -/// The index, or -1. [] let indexOf (value : 'T) (builder : FlatList<'T>.Builder) = builder.IndexOf value -/// -/// Returns the index of the last element equal to a value, or -1. -/// -/// The value to find. -/// The builder. -/// The index, or -1. [] let lastIndexOf (value : 'T) (builder : FlatList<'T>.Builder) = builder.LastIndexOf value // ---- Transformation ---- -/// Adds a value after the last element. -/// The value to add. -/// The builder. -/// The same builder. [] let add (value : 'T) (builder : FlatList<'T>.Builder) = builder.Add value builder -/// Adds values after the last element. -/// The values to add. -/// The builder. -/// The same builder. [] let addRange (values : seq<'T>) (builder : FlatList<'T>.Builder) = builder.AddRange values builder -/// Removes every element. -/// The builder. -/// The same builder. [] let clear (builder : FlatList<'T>.Builder) = builder.Clear () builder -/// Inserts a value at an index. -/// The index of the inserted value, from 0 to the count. -/// The value to insert. -/// The builder. -/// The same builder. -/// Thrown when the index is out of range. [] let insertAt index (value : 'T) (builder : FlatList<'T>.Builder) = builder.Insert (index, value) builder -/// -/// Removes the first element equal to a value, in . -/// -/// The value to remove. -/// The builder. -/// when an element was removed. [] let remove (value : 'T) (builder : FlatList<'T>.Builder) = builder.Remove value -/// Removes the element at an index. -/// The index of the removed element. -/// The builder. -/// The same builder. -/// Thrown when the index is out of range. [] let removeAt index (builder : FlatList<'T>.Builder) = builder.RemoveAt index builder -/// Reverses the order of the elements. -/// The builder. -/// The same builder. [] let rev (builder : FlatList<'T>.Builder) = builder.Reverse () builder -/// -/// Sets the number of elements that the builder can hold without growing. -/// -/// The new capacity, not less than the count. -/// The builder. -/// The same builder. -/// Thrown when the capacity is less than the count. [] let setCapacity capacity (builder : FlatList<'T>.Builder) = builder.Capacity <- capacity builder -/// Replaces the element at an index. -/// The index of the replaced element. -/// The new value. -/// The builder. -/// The same builder. -/// Thrown when the index is out of range. [] let updateAt index (value : 'T) (builder : FlatList<'T>.Builder) = builder[index] <- value @@ -160,30 +76,16 @@ let updateAt index (value : 'T) (builder : FlatList<'T>.Builder) = // ---- Sorting ---- -/// Sorts the elements in the order of F# generic comparison. -/// The builder. -/// The same builder. -/// This is not a stable sort. [] let sort (builder : FlatList<'T>.Builder) = builder.Sort DefaultComparer<'T>.Instance builder -/// Sorts the elements with a comparison function. -/// The function that compares two elements. -/// The builder. -/// The same builder. -/// This is not a stable sort. [] let sortWith (comparer : 'T -> 'T -> int) (builder : FlatList<'T>.Builder) = builder.Sort (Comparison<'T> comparer) builder -/// Sorts the elements with a comparer. -/// The comparer. -/// The builder. -/// The same builder. -/// This is not a stable sort. [] let sortWithComparer (comparer : IComparer<'T>) (builder : FlatList<'T>.Builder) = builder.Sort comparer @@ -191,16 +93,8 @@ let sortWithComparer (comparer : IComparer<'T>) (builder : FlatList<'T>.Builder) // ---- Conversion ---- -/// -/// Returns the contents of the builder as a flat list and empties the builder. -/// -/// The builder. -/// The flat list of the elements; without a copy when the count equals the capacity. [] let drainToFlatList (builder : FlatList<'T>.Builder) : FlatList<'T> = builder.DrainToImmutable () -/// Returns a new array with the elements of the builder. -/// The builder. -/// A copy of the elements. [] let toArray (builder : FlatList<'T>.Builder) = builder.ToArray () diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fsi b/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fsi new file mode 100644 index 00000000..53cb4798 --- /dev/null +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fsi @@ -0,0 +1,182 @@ +/// +/// Functions for the builder of a flat list, . +/// +/// +/// The builder is mutable. The functions that change it return the same builder, so that the calls chain with the pipe +/// operator. creates a builder, and +/// +/// returns its contents as a flat list. +/// +[] +module FSharp.Collections.Immutable.FlatListBuilder + +open System.Collections.Generic + +// ---- Properties ---- + +/// +/// Returns the number of elements that the builder can hold without growing. +/// +/// The builder. +/// The capacity. +[] +val capacity : builder : FlatList<'T>.Builder -> int + +/// Returns the number of elements in the builder. +/// The builder. +/// The number of elements. +[] +val length : builder : FlatList<'T>.Builder -> int + +// ---- Access ---- + +/// Returns the element at an index. +/// The index of the element. +/// The builder. +/// The element. +/// Thrown when the index is out of range. +[] +val item : index : int -> builder : FlatList<'T>.Builder -> 'T + +// ---- Search ---- + +/// +/// Returns whether the builder contains a value, in . +/// +/// The value to find. +/// The builder. +/// when an element equals the value. +[] +val contains : value : 'T -> builder : FlatList<'T>.Builder -> bool + +/// +/// Returns the index of the first element equal to a value, or -1. +/// +/// The value to find. +/// The builder. +/// The index, or -1. +[] +val indexOf : value : 'T -> builder : FlatList<'T>.Builder -> int + +/// +/// Returns the index of the last element equal to a value, or -1. +/// +/// The value to find. +/// The builder. +/// The index, or -1. +[] +val lastIndexOf : value : 'T -> builder : FlatList<'T>.Builder -> int + +// ---- Transformation ---- + +/// Adds a value after the last element. +/// The value to add. +/// The builder. +/// The same builder. +[] +val add : value : 'T -> builder : FlatList<'T>.Builder -> FlatList<'T>.Builder + +/// Adds values after the last element. +/// The values to add. +/// The builder. +/// The same builder. +[] +val addRange : values : seq<'T> -> builder : FlatList<'T>.Builder -> FlatList<'T>.Builder + +/// Removes every element. +/// The builder. +/// The same builder. +[] +val clear : builder : FlatList<'T>.Builder -> FlatList<'T>.Builder + +/// Inserts a value at an index. +/// The index of the inserted value, from 0 to the count. +/// The value to insert. +/// The builder. +/// The same builder. +/// Thrown when the index is out of range. +[] +val insertAt : index : int -> value : 'T -> builder : FlatList<'T>.Builder -> FlatList<'T>.Builder + +/// +/// Removes the first element equal to a value, in . +/// +/// The value to remove. +/// The builder. +/// when an element was removed. +[] +val remove : value : 'T -> builder : FlatList<'T>.Builder -> bool + +/// Removes the element at an index. +/// The index of the removed element. +/// The builder. +/// The same builder. +/// Thrown when the index is out of range. +[] +val removeAt : index : int -> builder : FlatList<'T>.Builder -> FlatList<'T>.Builder + +/// Reverses the order of the elements. +/// The builder. +/// The same builder. +[] +val rev : builder : FlatList<'T>.Builder -> FlatList<'T>.Builder + +/// +/// Sets the number of elements that the builder can hold without growing. +/// +/// The new capacity, not less than the count. +/// The builder. +/// The same builder. +/// Thrown when the capacity is less than the count. +[] +val setCapacity : capacity : int -> builder : FlatList<'T>.Builder -> FlatList<'T>.Builder + +/// Replaces the element at an index. +/// The index of the replaced element. +/// The new value. +/// The builder. +/// The same builder. +/// Thrown when the index is out of range. +[] +val updateAt : index : int -> value : 'T -> builder : FlatList<'T>.Builder -> FlatList<'T>.Builder + +// ---- Sorting ---- + +/// Sorts the elements in the order of F# generic comparison. +/// The builder. +/// The same builder. +/// This is not a stable sort. +[] +val sort : builder : FlatList<'T>.Builder -> FlatList<'T>.Builder when 'T : comparison + +/// Sorts the elements with a comparison function. +/// The function that compares two elements. +/// The builder. +/// The same builder. +/// This is not a stable sort. +[] +val sortWith : comparer : ('T -> 'T -> int) -> builder : FlatList<'T>.Builder -> FlatList<'T>.Builder + +/// Sorts the elements with a comparer. +/// The comparer. +/// The builder. +/// The same builder. +/// This is not a stable sort. +[] +val sortWithComparer : comparer : IComparer<'T> -> builder : FlatList<'T>.Builder -> FlatList<'T>.Builder + +// ---- Conversion ---- + +/// +/// Returns the contents of the builder as a flat list and empties the builder. +/// +/// The builder. +/// The flat list of the elements; without a copy when the count equals the capacity. +[] +val drainToFlatList : builder : FlatList<'T>.Builder -> FlatList<'T> + +/// Returns a new array with the elements of the builder. +/// The builder. +/// A copy of the elements. +[] +val toArray : builder : FlatList<'T>.Builder -> 'T array diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fs b/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fs index cd15439d..0976ef5a 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fs +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fs @@ -3,64 +3,51 @@ namespace FSharp.Collections.Immutable open System open System.Collections.Immutable -/// -/// The body of a flatList { } expression: a function that adds the elements to a builder. -/// type FlatListBuffer<'T> = FlatList<'T>.Builder -> unit -/// -/// The builder of the flatList { } computation expression. -/// -/// -/// The expression collects its elements in an and -/// returns them without a copy when the builder is full (ADR 0001, D7). -/// [] type FlatListCE () = - /// An expression without elements. member inline _.Zero () : FlatListBuffer<'T> = ignore - /// Adds an element. member inline _.Yield (item : 'T) : FlatListBuffer<'T> = fun builder -> builder.Add item - /// Adds the elements of a sequence. - member inline _.YieldFrom (items : seq<'T>) : FlatListBuffer<'T> = fun builder -> builder.AddRange items + member inline _.YieldFrom (items : seq<'T>) : FlatListBuffer<'T> = + // A default flat list given as a sequence is still a default flat list, as in FlatList.ofSeq (ADR 0001, D6) + match items with + | :? ImmutableArray<'T> as array -> checkNotDefault (nameof items) array + | _ -> () + + fun builder -> builder.AddRange items - /// Adds the elements of an array. member inline _.YieldFrom (items : 'T array) : FlatListBuffer<'T> = fun builder -> builder.AddRange items - /// Adds the elements of a flat list. - member inline _.YieldFrom (items : FlatList<'T>) : FlatListBuffer<'T> = fun builder -> builder.AddRange items + member inline _.YieldFrom (items : FlatList<'T>) : FlatListBuffer<'T> = + // The builder would dereference the missing array of a default flat list; the expression rejects it as the + // functions of FlatList do (ADR 0001, D6) + checkNotDefault (nameof items) items + fun builder -> builder.AddRange items - /// Adds the elements of another expression body. member inline _.YieldFrom (buffer : FlatListBuffer<'T>) : FlatListBuffer<'T> = buffer - /// Adds the elements of two parts of the expression, in order. member inline _.Combine (first : FlatListBuffer<'T>, second : FlatListBuffer<'T>) : FlatListBuffer<'T> = fun builder -> first builder second builder - /// - /// Runs the body of the expression each time the elements are collected. - /// member inline _.Delay ([] delayed : unit -> FlatListBuffer<'T>) : FlatListBuffer<'T> = fun builder -> delayed () builder - /// Adds the elements of the body for each item of a sequence. member inline _.For (source : seq<'U>, [] body : 'U -> FlatListBuffer<'T>) : FlatListBuffer<'T> = fun builder -> for item in source do body item builder - /// Adds the elements of the body while the guard holds. member inline _.While ([] guard : unit -> bool, body : FlatListBuffer<'T>) : FlatListBuffer<'T> = fun builder -> while guard () do body builder - /// Handles the exceptions of the body. member inline _.TryWith (body : FlatListBuffer<'T>, [] handler : exn -> FlatListBuffer<'T>) : FlatListBuffer<'T> @@ -71,7 +58,6 @@ type FlatListCE () = with ex -> handler ex builder - /// Runs the compensation after the body. member inline _.TryFinally (body : FlatListBuffer<'T>, [] compensation : unit -> unit) : FlatListBuffer<'T> = fun builder -> try @@ -79,13 +65,11 @@ type FlatListCE () = finally compensation () - /// Disposes a resource after the body. member inline _.Using (resource : 'Resource, [] body : 'Resource -> FlatListBuffer<'T>) : FlatListBuffer<'T> = fun builder -> use resource = resource body resource builder - /// Collects the elements of the expression. member _.Run (buffer : FlatListBuffer<'T>) : FlatList<'T> = let builder = ImmutableArray.CreateBuilder<'T>() buffer builder diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fsi b/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fsi new file mode 100644 index 00000000..a09ea508 --- /dev/null +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fsi @@ -0,0 +1,71 @@ +namespace FSharp.Collections.Immutable + +open System + +/// +/// The body of a flatList { } expression: a function that adds the elements to a builder. +/// +type FlatListBuffer<'T> = FlatList<'T>.Builder -> unit + +/// +/// The builder of the flatList { } computation expression. +/// +/// +/// The expression collects its elements in an and +/// returns them without a copy when the builder is full (ADR 0001, D7). +/// +[] +type FlatListCE = + + /// Creates the builder of the expression. + new : unit -> FlatListCE + + /// An expression without elements. + member inline Zero : unit -> FlatListBuffer<'T> + + /// Adds an element. + member inline Yield : item : 'T -> FlatListBuffer<'T> + + /// Adds the elements of a sequence. + /// Thrown when the sequence is or a default flat list. + member inline YieldFrom : items : seq<'T> -> FlatListBuffer<'T> + + /// Adds the elements of an array. + /// Thrown when the array is . + member inline YieldFrom : items : 'T array -> FlatListBuffer<'T> + + /// Adds the elements of a flat list. + /// Thrown when the flat list is a default flat list. + member inline YieldFrom : items : FlatList<'T> -> FlatListBuffer<'T> + + /// Adds the elements of another expression body. + member inline YieldFrom : buffer : FlatListBuffer<'T> -> FlatListBuffer<'T> + + /// Adds the elements of two parts of the expression, in order. + member inline Combine : first : FlatListBuffer<'T> * second : FlatListBuffer<'T> -> FlatListBuffer<'T> + + /// + /// Runs the body of the expression each time the elements are collected. + /// + member inline Delay : [] delayed : (unit -> FlatListBuffer<'T>) -> FlatListBuffer<'T> + + /// Adds the elements of the body for each item of a sequence. + member inline For : source : seq<'U> * [] body : ('U -> FlatListBuffer<'T>) -> FlatListBuffer<'T> + + /// Adds the elements of the body while the guard holds. + member inline While : [] guard : (unit -> bool) * body : FlatListBuffer<'T> -> FlatListBuffer<'T> + + /// Handles the exceptions of the body. + member inline TryWith : + body : FlatListBuffer<'T> * [] handler : (exn -> FlatListBuffer<'T>) -> FlatListBuffer<'T> + + /// Runs the compensation after the body. + member inline TryFinally : body : FlatListBuffer<'T> * [] compensation : (unit -> unit) -> FlatListBuffer<'T> + + /// Disposes a resource after the body. + member inline Using : + resource : 'Resource * [] body : ('Resource -> FlatListBuffer<'T>) -> FlatListBuffer<'T> + when 'Resource :> IDisposable | null + + /// Collects the elements of the expression. + member Run : buffer : FlatListBuffer<'T> -> FlatList<'T> From 8b69d5285ed83f680138c6d64dbaf8eb04e22ce0 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 18:17:13 +0200 Subject: [PATCH 13/27] fixup! test: shared contracts of the sequence-like modules, run for `FlatList` `yield!` of a default flat list, given as a flat list or as a sequence, throws `ArgumentNullException` for the parameter `items`. Co-Authored-By: Claude Opus 5.5 --- .../FlatListComputationExpressionTests.fs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs index 48496fe1..a46a96e5 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs @@ -8,6 +8,8 @@ open Microsoft.VisualStudio.TestTools.UnitTesting [] type FlatListComputationExpressionTests () = + static let defaultArray = Unchecked.defaultof> + [] member _.``an expression without elements is empty`` () = let array : FlatList = flatList { () } @@ -46,6 +48,26 @@ type FlatListComputationExpressionTests () = CollectionAssert.AreEqual ([| 1; 2; 3; 4; 5; 6; 7 |], FlatList.toArray array, "flatList must add the elements of yield!") + [] + member _.``yield! rejects a default flat list as the functions of FlatList do`` () = + let ex = + Assert.ThrowsExactly( + Action (fun () -> flatList { yield! defaultArray } |> ignore), + "flatList must reject a default flat list in yield!" + ) + + Assert.AreEqual("items", ex.ParamName, "flatList must name the parameter of the default flat list") + + [] + member _.``yield! rejects a default flat list given as a sequence as FlatList.ofSeq does`` () = + let ex = + Assert.ThrowsExactly( + Action (fun () -> flatList { yield! (defaultArray :> seq) } |> ignore), + "flatList must reject a default flat list in yield! of a sequence" + ) + + Assert.AreEqual("items", ex.ParamName, "flatList must name the parameter of the default flat list") + [] member _.``for adds the elements of the body for each item`` () = let array = flatList { From 047ee9b5af6f083a8e71764041cff4c2fbc85777 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 20:41:34 +0200 Subject: [PATCH 14/27] fixup! test: shared contracts of the sequence-like modules, run for `FlatList` The assertion helpers of `main` are static classes with tupled arguments, so the calls of the contracts and of the `FlatList` tests use them: `ExceptionAssert.Same` and `ExceptionAssert.Mirrored`, and `OverrideAssert.OptionMatches`. Co-Authored-By: Claude Sonnet 5.5 --- .../Contracts/ContractBase.fs | 13 +++-- .../FlatList/FlatListOptionTests.fs | 56 +++++++++++-------- .../FlatList/FlatListTests.fs | 20 ++++--- .../FlatList/FlatListTupleTests.fs | 9 +-- 4 files changed, 55 insertions(+), 43 deletions(-) diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/ContractBase.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/ContractBase.fs index 9a121ef0..e00217ce 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/ContractBase.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/ContractBase.fs @@ -15,10 +15,11 @@ type ContractBase (name : string, mirror : Mirror, paramName : string -> string) /// Asserts that a function of the module throws what the same function of the mirrored FSharp.Core module throws. member _.ThrowsLikeFSharpCore (arrayOracle : unit -> 'A, listOracle : unit -> 'L, actual : unit -> 'R, message : string) = - ExceptionAssert.mirrored - mirror - paramName - (arrayOracle >> ignore) - (listOracle >> ignore) - (actual >> ignore) + ExceptionAssert.Mirrored ( + mirror, + paramName, + arrayOracle >> ignore, + listOracle >> ignore, + actual >> ignore, $"%s{name}: %s{message}" + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOptionTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOptionTests.fs index be75382a..17554b8b 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOptionTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOptionTests.fs @@ -31,35 +31,39 @@ type FlatListOptionTests () = member _.``tryExactlyOne returns an option`` () = let found : int option = FlatList.tryExactlyOne (FlatList.singleton 5) - OverrideAssert.optionMatches - (FSharp.Collections.Immutable.FlatList.tryExactlyOne (FlatList.singleton 5)) - found + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.FlatList.tryExactlyOne (FlatList.singleton 5), + found, "Option.FlatList.tryExactlyOne must match the default after conversion" + ) [] member _.``tryHead returns an option`` () = let found : int option = FlatList.tryHead array - OverrideAssert.optionMatches - (FSharp.Collections.Immutable.FlatList.tryHead array) - found + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.FlatList.tryHead array, + found, "Option.FlatList.tryHead must match the default" + ) [] member _.``tryItem returns an option`` (index : int) = let found : int option = FlatList.tryItem index array - OverrideAssert.optionMatches - (FSharp.Collections.Immutable.FlatList.tryItem index array) - found + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.FlatList.tryItem index array, + found, "Option.FlatList.tryItem must match the default" + ) [] member _.``tryLast returns an option`` () = let found : int option = FlatList.tryLast array - OverrideAssert.optionMatches - (FSharp.Collections.Immutable.FlatList.tryLast array) - found + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.FlatList.tryLast array, + found, "Option.FlatList.tryLast must match the default" + ) [] member _.``tryTail returns an option`` () = @@ -90,37 +94,41 @@ type FlatListOptionTests () = member _.``tryFind returns an option`` () = let found : int option = FlatList.tryFind (fun item -> item > 6) array - OverrideAssert.optionMatches - (FSharp.Collections.Immutable.FlatList.tryFind (fun item -> item > 6) array) - found + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.FlatList.tryFind (fun item -> item > 6) array, + found, "Option.FlatList.tryFind must match the default" + ) [] member _.``tryFindBack returns an option`` () = let found : int option = FlatList.tryFindBack (fun item -> item > 6) array - OverrideAssert.optionMatches - (FSharp.Collections.Immutable.FlatList.tryFindBack (fun item -> item > 6) array) - found + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.FlatList.tryFindBack (fun item -> item > 6) array, + found, "Option.FlatList.tryFindBack must match the default" + ) [] member _.``tryFindIndex returns an option`` () = let found : int option = FlatList.tryFindIndex (fun item -> item > 6) array - OverrideAssert.optionMatches - (FSharp.Collections.Immutable.FlatList.tryFindIndex (fun item -> item > 6) array) - found + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.FlatList.tryFindIndex (fun item -> item > 6) array, + found, "Option.FlatList.tryFindIndex must match the default" + ) [] member _.``tryFindIndexBack returns an option`` () = let found : int option = FlatList.tryFindIndexBack (fun item -> item > 6) array - OverrideAssert.optionMatches - (FSharp.Collections.Immutable.FlatList.tryFindIndexBack (fun item -> item > 6) array) - found + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.FlatList.tryFindIndexBack (fun item -> item > 6) array, + found, "Option.FlatList.tryFindIndexBack must match the default" + ) [] member _.``tryPick takes a chooser of options and returns an option`` () = diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs index 5dabb7f6..e68e89a2 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs @@ -261,11 +261,12 @@ type FlatListTests () = [] member _.``create throws for a negative count as Array does`` () = - ExceptionAssert.same - id - (fun () -> Array.create -1 7 |> ignore) - (fun () -> FlatList.create -1 7 |> ignore) + ExceptionAssert.Same ( + id, + (fun () -> Array.create -1 7 |> ignore), + (fun () -> FlatList.create -1 7 |> ignore), "FlatList.create -1 must throw as Array.create does" + ) [] member _.``isDefault and isDefaultOrEmpty tell a default value from an empty one`` () = @@ -281,7 +282,7 @@ type FlatListTests () = [] member _.``functions reject a default flat list as Array rejects a null array`` (name : string) = let struct (oracle, actual) = arrayRejections[name] - ExceptionAssert.same id oracle actual $"FlatList.%s{name} must reject a default flat list" + ExceptionAssert.Same (id, oracle, actual, $"FlatList.%s{name} must reject a default flat list") [] member _.``functions without an Array counterpart reject a default flat list`` (name : string) = @@ -359,11 +360,12 @@ type FlatListTests () = member _.``blit throws for a range out of range as Array does`` () = let source = Inputs.Several - ExceptionAssert.same - id - (fun () -> Array.blit source 6 (Array.zeroCreate 5) 0 3) - (fun () -> FlatList.blit (FlatList.ofArray source) 6 (Array.zeroCreate 5) 0 3) + ExceptionAssert.Same ( + id, + (fun () -> Array.blit source 6 (Array.zeroCreate 5) 0 3), + (fun () -> FlatList.blit (FlatList.ofArray source) 6 (Array.zeroCreate 5) 0 3), "FlatList.blit beyond the source must throw as Array.blit does" + ) [] member _.``ofSeq of a flat list shares its storage`` () = diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTupleTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTupleTests.fs index 19496141..6358a773 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTupleTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTupleTests.fs @@ -96,14 +96,15 @@ type FlatListTupleTests () = [] member _.``zip throws for different lengths as the default does`` () = - ExceptionAssert.same - id + ExceptionAssert.Same ( + id, (fun () -> FSharp.Collections.Immutable.FlatList.zip array (FlatList.singleton 1) |> ignore - ) - (fun () -> FlatList.zip array (FlatList.singleton 1) |> ignore) + ), + (fun () -> FlatList.zip array (FlatList.singleton 1) |> ignore), "Tuple.FlatList.zip must throw as the default zip does" + ) [] member _.``zip3 returns reference tuples`` () = From 377bf02fa04c317e14f467a227f9e99a37d6b83a Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 21:16:46 +0200 Subject: [PATCH 15/27] fixup! feat: `FlatList` module with the vocabulary of the FSharp.Core `Array` module A default flat list given as a sequence is rejected with `ArgumentNullException`, as it is when it is given as a flat list (D6). It converts to a sequence without a check and fails with `InvalidOperationException` when a function enumerates it, so every function that takes a sequence calls the new `checkNotDefaultSeq` of `Helper.fs` next to its `null` check: - `concat` and `transpose` for the outer sequence; - `except` for the items to exclude, also when the input is empty, and before the input is checked, as FSharp.Core checks them; - `insertManyAt` for the values, after the check of the index, because FSharp.Core enumerates the values only then; - `FlatListBuilder.addRange`, and `for` of the `flatList` expression; `yield!` of a sequence uses the helper instead of its own copy of the check. The signatures document the exceptions. Co-Authored-By: Claude Sonnet 5.5 --- .../FlatList/FlatList.fs | 12 ++++++++++-- .../FlatList/FlatList.fsi | 6 ++++-- .../FlatList/FlatListBuilder.fs | 1 + .../FlatList/FlatListBuilder.fsi | 1 + .../FlatList/FlatListCE.fs | 8 ++++---- .../FlatList/FlatListCE.fsi | 1 + src/FSharp.Collections.Immutable/Helper.fs | 15 +++++++++++++++ 7 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatList.fs b/src/FSharp.Collections.Immutable/FlatList/FlatList.fs index 2f969b34..e7918184 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatList.fs +++ b/src/FSharp.Collections.Immutable/FlatList/FlatList.fs @@ -360,6 +360,7 @@ let collect (mapping : 'T -> FlatList<'U>) (array : FlatList<'T>) = [] let concat (arrays : seq>) = checkNotNull (nameof arrays) arrays + checkNotDefaultSeq (nameof arrays) arrays arrays |> Seq.map (fun array -> unwrapArray (nameof arrays) array) @@ -383,8 +384,13 @@ let insertAt index (value : 'T) (source : FlatList<'T>) = [] let insertManyAt index (values : seq<'T>) (source : FlatList<'T>) = - Array.insertManyAt index values (unwrapArray (nameof source) source) - |> wrapArray + let array = unwrapArray (nameof source) source + + // Array.insertManyAt checks the index before it enumerates the values, so the check of the values comes after it + if index >= 0 && index <= array.Length then + checkNotDefaultSeq (nameof values) values + + Array.insertManyAt index values array |> wrapArray [] let map (mapping : 'T -> 'U) (array : FlatList<'T>) = @@ -441,6 +447,7 @@ let rev (array : FlatList<'T>) = Array.rev (unwrapArray (nameof array) array) |> [] let transpose (arrays : seq>) = checkNotNull (nameof arrays) arrays + checkNotDefaultSeq (nameof arrays) arrays arrays |> Seq.map (fun array -> unwrapArray (nameof arrays) array) @@ -643,6 +650,7 @@ let distinctBy (projection : 'T -> 'Key) (array : FlatList<'T>) = let except (itemsToExclude : seq<'T>) (array : FlatList<'T>) = // FSharp.Core checks the items to exclude first checkNotNull (nameof itemsToExclude) itemsToExclude + checkNotDefaultSeq (nameof itemsToExclude) itemsToExclude Array.except itemsToExclude (unwrapArray (nameof array) array) |> wrapArray diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi b/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi index 88459c35..f9e6dfbc 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi +++ b/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi @@ -648,7 +648,7 @@ val collect : mapping : ('T -> FlatList<'U>) -> array : FlatList<'T> -> FlatList /// Concatenates a sequence of flat lists. /// The flat lists. /// The concatenation. -/// Thrown when is . +/// Thrown when is or a default flat list, or contains a default flat list. /// O(the total length). [] val concat : arrays : seq> -> FlatList<'T> @@ -683,6 +683,7 @@ val insertAt : index : int -> value : 'T -> source : FlatList<'T> -> FlatList<'T /// The values to insert. /// The input flat list. /// The flat list with the values. +/// Thrown when is or a default flat list. /// Thrown when the index is negative or greater than the length. /// O(n + m). [] @@ -807,6 +808,7 @@ val rev : array : FlatList<'T> -> FlatList<'T> /// /// The flat lists. /// A flat list whose element i holds the element i of every input flat list. +/// Thrown when is or a default flat list, or contains a default flat list. /// Thrown when the flat lists have different lengths. /// /// O(the total length). @@ -1214,7 +1216,7 @@ val distinctBy : projection : ('T -> 'Key) -> array : FlatList<'T> -> FlatList<' /// The items to remove. /// The input flat list. /// The distinct remaining elements, in their order. -/// Thrown when is . +/// Thrown when is or a default flat list. /// /// O(n + m). /// diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs b/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs index 1ed7161f..a6fea50e 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs @@ -38,6 +38,7 @@ let add (value : 'T) (builder : FlatList<'T>.Builder) = [] let addRange (values : seq<'T>) (builder : FlatList<'T>.Builder) = + checkNotDefaultSeq (nameof values) values builder.AddRange values builder diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fsi b/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fsi index 53cb4798..3efb3950 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fsi +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fsi @@ -80,6 +80,7 @@ val add : value : 'T -> builder : FlatList<'T>.Builder -> FlatList<'T>.Builder /// The values to add. /// The builder. /// The same builder. +/// Thrown when is or a default flat list. [] val addRange : values : seq<'T> -> builder : FlatList<'T>.Builder -> FlatList<'T>.Builder diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fs b/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fs index 0976ef5a..f69cfdbb 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fs +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fs @@ -14,10 +14,7 @@ type FlatListCE () = member inline _.YieldFrom (items : seq<'T>) : FlatListBuffer<'T> = // A default flat list given as a sequence is still a default flat list, as in FlatList.ofSeq (ADR 0001, D6) - match items with - | :? ImmutableArray<'T> as array -> checkNotDefault (nameof items) array - | _ -> () - + checkNotDefaultSeq (nameof items) items fun builder -> builder.AddRange items member inline _.YieldFrom (items : 'T array) : FlatListBuffer<'T> = fun builder -> builder.AddRange items @@ -39,6 +36,9 @@ type FlatListCE () = fun builder -> delayed () builder member inline _.For (source : seq<'U>, [] body : 'U -> FlatListBuffer<'T>) : FlatListBuffer<'T> = + // A default flat list is a sequence that fails when it is enumerated; the expression rejects it as yield! does + checkNotDefaultSeq (nameof source) source + fun builder -> for item in source do body item builder diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fsi b/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fsi index a09ea508..1351b0d9 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fsi +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fsi @@ -50,6 +50,7 @@ type FlatListCE = member inline Delay : [] delayed : (unit -> FlatListBuffer<'T>) -> FlatListBuffer<'T> /// Adds the elements of the body for each item of a sequence. + /// Thrown when the sequence is a default flat list. member inline For : source : seq<'U> * [] body : ('U -> FlatListBuffer<'T>) -> FlatListBuffer<'T> /// Adds the elements of the body while the guard holds. diff --git a/src/FSharp.Collections.Immutable/Helper.fs b/src/FSharp.Collections.Immutable/Helper.fs index 8c1e6804..20165a8a 100644 --- a/src/FSharp.Collections.Immutable/Helper.fs +++ b/src/FSharp.Collections.Immutable/Helper.fs @@ -32,6 +32,21 @@ module internal ImmutableCollectionUtil = if array.IsDefault then nullArg name + /// + /// Throws for a default + /// that is given as a sequence. + /// + /// + /// A default immutable array converts to a sequence without a check, and the sequence fails with + /// when a function enumerates it, so a function that takes a sequence + /// calls this check next to its check (ADR 0001, D6). Any other sequence passes, a + /// sequence included. + /// + let inline checkNotDefaultSeq name (source : seq<'T>) = + match source with + | :? ImmutableArray<'T> as array -> checkNotDefault name array + | _ -> () + /// /// The array under an immutable array, without a copy (ADR 0001, D5). /// From c487fb34b8559eaf8b612d707faa2800c9bc2940 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 21:16:46 +0200 Subject: [PATCH 16/27] fixup! test: shared contracts of the sequence-like modules, run for `FlatList` A default flat list given as a sequence throws `ArgumentNullException` that names the parameter: `concat`, `transpose`, `insertManyAt` and `except` are the rows of one test, and `addRange` and `for` have a test each. Rows and tests pin the order of the checks as well: `insertManyAt` checks its source first and its index before the values, and `except` checks the items before the array and also for an empty input, as the FSharp.Core functions do. Co-Authored-By: Claude Sonnet 5.5 --- .../FlatList/FlatListBuilderTests.fs | 17 ++++ .../FlatListComputationExpressionTests.fs | 16 ++++ .../FlatList/FlatListTests.fs | 86 +++++++++++++++++-- 3 files changed, 113 insertions(+), 6 deletions(-) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListBuilderTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListBuilderTests.fs index 298f4c47..93a754f7 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListBuilderTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListBuilderTests.fs @@ -20,6 +20,23 @@ type FlatListBuilderTests () = let array = FlatList.build (fun builder -> builder.AddRange Inputs.Several) CollectionAssert.AreEqual (Inputs.Several, FlatList.toArray array, "FlatList.build must return the added elements") + [] + member _.``addRange rejects a default flat list, which it takes as a sequence`` () = + let ex = + Assert.ThrowsExactly( + Action (fun () -> + FlatListBuilder.addRange (Unchecked.defaultof> :> seq<_>) (FlatList.builder()) + |> ignore + ), + "FlatListBuilder.addRange must reject a default flat list" + ) + + Assert.AreEqual( + "values", + ex.ParamName, + "FlatListBuilder.addRange must name the parameter of the default flat list" + ) + [] member _.``builder starts empty`` () = let builder = FlatList.builder() diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs index a46a96e5..d9a945d1 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs @@ -77,6 +77,22 @@ type FlatListComputationExpressionTests () = CollectionAssert.AreEqual ([| 10; 20; 30 |], FlatList.toArray array, "flatList must run the body of for for each item") + [] + member _.``for rejects a default flat list, which it takes as a sequence`` () = + let ex = + Assert.ThrowsExactly( + Action (fun () -> + flatList { + for item in defaultArray do + yield item + } + |> ignore + ), + "flatList must reject a default flat list in for" + ) + + Assert.AreEqual("source", ex.ParamName, "flatList must name the parameter of the default flat list") + [] member _.``while adds the elements while the guard holds`` () = let mutable count = 0 diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs index e68e89a2..eb11736c 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs @@ -17,6 +17,17 @@ type FlatListTests () = /// The array under a flat list, to tell whether two flat lists share their storage. static let underlying (array : FlatList) = ImmutableCollectionsMarshal.AsArray array + /// Asserts that an action rejects a default flat list with an ArgumentNullException that names the parameter. + static let assertRejectsDefault (name : string) (paramName : string) (actual : unit -> unit) = + let ex = + Assert.ThrowsExactly(Action actual, $"FlatList.%s{name} must reject a default flat list") + + Assert.AreEqual( + paramName, + ex.ParamName, + $"FlatList.%s{name} must name the parameter of the default flat list" + ) + /// The functions that mirror an Array function, by name, with that function on a null array and the function on a /// default flat list. The rows of the test are the names: a function has no readable name in a row. static let arrayRejections = @@ -209,6 +220,52 @@ type FlatListTests () = |> Array.map (fun struct (name, paramName, actual) -> name, struct (paramName, actual)) |> Map.ofArray + /// The functions that take a sequence, by name, with the parameter that names a default flat list given as the + /// sequence and the function on such a flat list. A default flat list converts to a sequence without a check and + /// fails when the sequence is enumerated, so the other inputs are valid and the function would enumerate it. + static let sequenceRejections = + let defaultOuter = Unchecked.defaultof>> + + [| + struct (nameof FlatList.concat, "arrays", (fun () -> FlatList.concat (defaultOuter :> seq<_>) |> ignore)) + struct (nameof FlatList.transpose, "arrays", (fun () -> FlatList.transpose (defaultOuter :> seq<_>) |> ignore)) + struct (nameof FlatList.insertManyAt, + "values", + (fun () -> + FlatList.insertManyAt 1 (defaultArray :> seq<_>) (FlatList.ofArray Inputs.Several) + |> ignore + )) + // Array.insertManyAt checks the source before the values + struct ($"%s{nameof FlatList.insertManyAt} (default source)", + "source", + (fun () -> + FlatList.insertManyAt 1 (defaultArray :> seq<_>) defaultArray + |> ignore + )) + struct (nameof FlatList.except, + "itemsToExclude", + (fun () -> + FlatList.except (defaultArray :> seq<_>) (FlatList.ofArray Inputs.Several) + |> ignore + )) + // Array.except returns an empty input without enumerating the items, but it checks the items for null first + struct ($"%s{nameof FlatList.except} (empty array)", + "itemsToExclude", + (fun () -> + FlatList.except (defaultArray :> seq<_>) FlatList.empty + |> ignore + )) + // Array.except checks the items to exclude before the array + struct ($"%s{nameof FlatList.except} (default array)", + "itemsToExclude", + (fun () -> + FlatList.except (defaultArray :> seq<_>) defaultArray + |> ignore + )) + |] + |> Array.map (fun struct (name, paramName, actual) -> name, struct (paramName, actual)) + |> Map.ofArray + /// The functions that take a range, by name, on the whole range of the input with several elements. static let wholeRanges : Map -> FlatList> = Map.ofArray [| @@ -245,6 +302,9 @@ type FlatListTests () = /// The names of the functions without an Array counterpart, as the rows of their test. static member OtherRejections = otherRejections.Keys + /// The names of the functions that take a sequence, as the rows of the test of a default flat list given as the sequence. + static member SequenceRejections = sequenceRejections.Keys + /// The names of the functions that take a range, as the rows of the test of the whole range. static member WholeRanges = wholeRanges.Keys @@ -287,14 +347,28 @@ type FlatListTests () = [] member _.``functions without an Array counterpart reject a default flat list`` (name : string) = let struct (paramName, actual) = otherRejections[name] + assertRejectsDefault name paramName actual - let ex = - Assert.ThrowsExactly(Action actual, $"FlatList.%s{name} must reject a default flat list") + [] + member _.``functions that take a sequence reject a default flat list given as the sequence`` (name : string) = + let struct (paramName, actual) = sequenceRejections[name] + assertRejectsDefault name paramName actual - Assert.AreEqual( - paramName, - ex.ParamName, - $"FlatList.%s{name} must name the parameter of the default flat list" + [] + member _.``insertManyAt rejects an index out of range before default values as Array.insertManyAt does`` (index : int) = + // The input has 8 elements, so the indices -1 and 9 are out of range. Array.insertManyAt checks the index + // before it enumerates the values + ExceptionAssert.Same ( + id, + (fun () -> + Array.insertManyAt index Unchecked.defaultof> Inputs.Several + |> ignore + ), + (fun () -> + FlatList.insertManyAt index (defaultArray :> seq<_>) (FlatList.ofArray Inputs.Several) + |> ignore + ), + "FlatList.insertManyAt must check the index before the values as Array.insertManyAt does" ) [] From 7935f61d8fe77670338a5a3cfb61eaca17281e5c Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 21:42:00 +0200 Subject: [PATCH 17/27] fixup! feat: `FlatList` module with the vocabulary of the FSharp.Core `Array` module ADR 0001, D1 promises every applicable function of the `Array` module and the wrappers of the BCL members, so the functions that the rewrite had dropped come back: - `sub list startIndex count` mirrors `Array.sub` (compiled `GetSubArray`): the same checks in the same order and the same exceptions. It wraps `ImmutableArray.Slice`, which copies unless the range is empty or the whole flat list. - `sortRange`, `sortRangeWith` and `sortRangeWithComparer` wrap `Sort (index, count, comparer)`; `sortRange` compares with F# generic comparison. They are not stable and throw what the BCL member throws for a range out of range. - `remove` and `removeWith` remove the first element that equals a value, `removeMany` and `removeManyWith` one element for each of the given values (`Remove` and `RemoveRange`); the input itself comes back when nothing matches. The remarks of `removeManyAt` and `filter` say that they are `RemoveRange (index, length)` and, with a negated predicate, `RemoveAll`. The check of a sequence is one call, `checkNotNullOrDefaultSeq` of `Helper.fs`, which throws `ArgumentNullException` for a null sequence and for a default flat list given as one. It replaces `checkNotDefaultSeq` in `concat`, `transpose`, `except`, `insertManyAt`, `FlatListBuilder.addRange` and the `yield!` and `for` of `flatList { }`, and it is used by `removeMany` and `removeManyWith`. A null sequence reports the parameter of the function: `values` for `insertManyAt` (`Array.insertManyAt` says `source`) and `addRange` (the BCL says `items`), and `for` over a null sequence throws `ArgumentNullException` instead of `NullReferenceException`. Co-Authored-By: Claude Sonnet 5.5 --- .../FlatList/FlatList.fs | 68 ++++++- .../FlatList/FlatList.fsi | 166 +++++++++++++++++- .../FlatList/FlatListBuilder.fs | 2 +- .../FlatList/FlatListCE.fs | 4 +- .../FlatList/FlatListCE.fsi | 2 +- src/FSharp.Collections.Immutable/Helper.fs | 14 +- 6 files changed, 234 insertions(+), 22 deletions(-) diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatList.fs b/src/FSharp.Collections.Immutable/FlatList/FlatList.fs index e7918184..f9877f32 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatList.fs +++ b/src/FSharp.Collections.Immutable/FlatList/FlatList.fs @@ -359,8 +359,7 @@ let collect (mapping : 'T -> FlatList<'U>) (array : FlatList<'T>) = [] let concat (arrays : seq>) = - checkNotNull (nameof arrays) arrays - checkNotDefaultSeq (nameof arrays) arrays + checkNotNullOrDefaultSeq (nameof arrays) arrays arrays |> Seq.map (fun array -> unwrapArray (nameof arrays) array) @@ -386,9 +385,10 @@ let insertAt index (value : 'T) (source : FlatList<'T>) = let insertManyAt index (values : seq<'T>) (source : FlatList<'T>) = let array = unwrapArray (nameof source) source - // Array.insertManyAt checks the index before it enumerates the values, so the check of the values comes after it + // Array.insertManyAt checks the index before it enumerates the values, and it names the values source then, as + // Seq.toArray does; the check of the values comes after the one of the index and names the parameter of this function if index >= 0 && index <= array.Length then - checkNotDefaultSeq (nameof values) values + checkNotNullOrDefaultSeq (nameof values) values Array.insertManyAt index values array |> wrapArray @@ -431,23 +431,44 @@ let permute indexMap (array : FlatList<'T>) = Array.permute indexMap (unwrapArray (nameof array) array) |> wrapArray +[] +let remove (item : 'T) (list : FlatList<'T>) = + checkNotDefault (nameof list) list + list.Remove item + [] let removeAt index (source : FlatList<'T>) = Array.removeAt index (unwrapArray (nameof source) source) |> wrapArray +[] +let removeMany (items : seq<'T>) (list : FlatList<'T>) = + checkNotDefault (nameof list) list + checkNotNullOrDefaultSeq (nameof items) items + list.RemoveRange items + [] let removeManyAt index count (source : FlatList<'T>) = Array.removeManyAt index count (unwrapArray (nameof source) source) |> wrapArray +[] +let removeManyWith (comparer : IEqualityComparer<'T>) (items : seq<'T>) (list : FlatList<'T>) = + checkNotDefault (nameof list) list + checkNotNullOrDefaultSeq (nameof items) items + list.RemoveRange (items, comparer) + +[] +let removeWith (comparer : IEqualityComparer<'T>) (item : 'T) (list : FlatList<'T>) = + checkNotDefault (nameof list) list + list.Remove (item, comparer) + [] let rev (array : FlatList<'T>) = Array.rev (unwrapArray (nameof array) array) |> wrapArray [] let transpose (arrays : seq>) = - checkNotNull (nameof arrays) arrays - checkNotDefaultSeq (nameof arrays) arrays + checkNotNullOrDefaultSeq (nameof arrays) arrays arrays |> Seq.map (fun array -> unwrapArray (nameof arrays) array) @@ -649,8 +670,7 @@ let distinctBy (projection : 'T -> 'Key) (array : FlatList<'T>) = [] let except (itemsToExclude : seq<'T>) (array : FlatList<'T>) = // FSharp.Core checks the items to exclude first - checkNotNull (nameof itemsToExclude) itemsToExclude - checkNotDefaultSeq (nameof itemsToExclude) itemsToExclude + checkNotNullOrDefaultSeq (nameof itemsToExclude) itemsToExclude Array.except itemsToExclude (unwrapArray (nameof array) array) |> wrapArray @@ -725,6 +745,23 @@ let splitInto count (array : FlatList<'T>) = |> Array.map wrapArray |> wrapArray +[] +let sub (list : FlatList<'T>) startIndex count = + checkNotDefault (nameof list) list + + if startIndex < 0 then + Raise.invalidArgInputMustBeNonNegative (nameof startIndex) startIndex + + if count < 0 then + Raise.invalidArgInputMustBeNonNegative (nameof count) count + + // Array.sub compares the sum of the start index and the count with the length, which can overflow; the difference + // cannot, because the start index is not negative + if count > list.Length - startIndex then + Raise.invalidArgOutOfRange (nameof count) count "list.Length" list.Length + + list.Slice (startIndex, count) + [] let take count (array : FlatList<'T>) = checkNotDefault (nameof array) array @@ -794,6 +831,21 @@ let sortDescending (array : FlatList<'T>) = Array.sortDescending (unwrapArray (nameof array) array) |> wrapArray +[] +let sortRange index count (list : FlatList<'T>) = + checkNotDefault (nameof list) list + list.Sort (index, count, DefaultComparer<'T>.Instance) + +[] +let sortRangeWith (comparison : 'T -> 'T -> int) index count (list : FlatList<'T>) = + checkNotDefault (nameof list) list + list.Sort (index, count, Comparer<'T>.Create(Comparison<'T> comparison)) + +[] +let sortRangeWithComparer (comparer : IComparer<'T>) index count (list : FlatList<'T>) = + checkNotDefault (nameof list) list + list.Sort (index, count, comparer) + [] let sortWith comparer (array : FlatList<'T>) = Array.sortWith comparer (unwrapArray (nameof array) array) diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi b/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi index f9e6dfbc..f50d566e 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi +++ b/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi @@ -653,11 +653,17 @@ val collect : mapping : ('T -> FlatList<'U>) -> array : FlatList<'T> -> FlatList [] val concat : arrays : seq> -> FlatList<'T> -/// Returns the elements that satisfy the predicate. +/// +/// Returns the elements that satisfy the predicate. +/// /// The function to test the elements. /// The input flat list. /// The elements that satisfy the predicate, in their order. -/// O(n). +/// +/// O(n). To remove the elements that satisfy a predicate, as +/// does, filter with the +/// negated predicate. +/// [] val filter : predicate : ('T -> bool) -> array : FlatList<'T> -> FlatList<'T> @@ -777,6 +783,32 @@ val pairwise : array : FlatList<'T> -> FlatList [] val permute : indexMap : (int -> int) -> array : FlatList<'T> -> FlatList<'T> +/// +/// Returns the flat list without the first element that equals a value, in +/// . +/// +/// The value to remove. +/// The input flat list. +/// The flat list without the element; the input itself when no element equals the value. +/// +/// +/// O(n). The function wraps . +/// +/// +/// To remove several values, use +/// ; +/// to remove by index, use +/// +/// or +/// ; +/// to remove the elements that satisfy a predicate, use +/// +/// with the negated predicate. +/// +/// +[] +val remove : item : 'T -> list : FlatList<'T> -> FlatList<'T> + /// Returns a flat list without the element at an index. /// The index of the removed element. /// The input flat list. @@ -786,16 +818,69 @@ val permute : indexMap : (int -> int) -> array : FlatList<'T> -> FlatList<'T> [] val removeAt : index : int -> source : FlatList<'T> -> FlatList<'T> -/// Returns a flat list without a range of elements. +/// +/// Returns the flat list without one element for each of the given values, in +/// . +/// +/// The values to remove. +/// The input flat list. +/// The flat list without the elements; the input itself when no element equals any of the values. +/// Thrown when is or a default flat list. +/// +/// O(n * m) for m values. Each value removes the first element that equals it and that no earlier value has removed; a +/// value that equals no remaining element removes nothing. The function wraps +/// . +/// +[] +val removeMany : items : seq<'T> -> list : FlatList<'T> -> FlatList<'T> + +/// +/// Returns a flat list without a range of elements. +/// /// The index of the first removed element. /// The number of removed elements. /// The input flat list. /// The flat list without the range. /// Thrown when the range is out of range. -/// O(n). +/// +/// O(n). The function corresponds to +/// . To remove the +/// elements that equal given values, use +/// . +/// [] val removeManyAt : index : int -> count : int -> source : FlatList<'T> -> FlatList<'T> +/// +/// Returns the flat list without one element for each of the given values, in a given equality comparer. +/// +/// The equality comparer. +/// The values to remove. +/// The input flat list. +/// The flat list without the elements; the input itself when no element equals any of the values. +/// Thrown when is or a default flat list. +/// +/// O(n * m) for m values. Each value removes the first element that equals it and that no earlier value has removed; a +/// value that equals no remaining element removes nothing. The function wraps +/// . +/// +[] +val removeManyWith : comparer : IEqualityComparer<'T> -> items : seq<'T> -> list : FlatList<'T> -> FlatList<'T> + +/// +/// Returns the flat list without the first element that equals a value in a given equality comparer. +/// +/// The equality comparer. +/// The value to remove. +/// The input flat list. +/// The flat list without the element; the input itself when no element equals the value. +/// +/// O(n). The function wraps +/// . +/// +[] +val removeWith : comparer : IEqualityComparer<'T> -> item : 'T -> list : FlatList<'T> -> FlatList<'T> + /// Returns the elements in reverse order. /// The input flat list. /// The reversed flat list. @@ -1316,6 +1401,29 @@ val splitAt : index : int -> array : FlatList<'T> -> struct (FlatList<'T> * Flat [] val splitInto : count : int -> array : FlatList<'T> -> FlatList> +/// +/// Returns the elements of a range of the flat list. +/// +/// The input flat list. +/// The index of the first element of the range. +/// The number of elements of the range. +/// The elements of the range, in their order. +/// Thrown when or is negative. +/// Thrown when the range extends beyond the end of the flat list. +/// +/// +/// O(count). The result is a copy, unless it is empty or the whole flat list. To view a range without a copy, use +/// or +/// . +/// +/// +/// The function mirrors +/// and wraps . +/// +/// +[] +val sub : list : FlatList<'T> -> startIndex : int -> count : int -> FlatList<'T> + /// /// Returns the first elements of the flat list. /// @@ -1416,6 +1524,56 @@ val sortByDescending : projection : ('T -> 'Key) -> array : FlatList<'T> -> Flat [] val sortDescending : array : FlatList<'T> -> FlatList<'T> when 'T : comparison +/// +/// Sorts a range of the elements in the order of F# generic comparison. +/// +/// The index of the first element of the range. +/// The number of elements of the range. +/// The input flat list. +/// The flat list with the sorted range; the input itself when the range needs no sorting. +/// Thrown when or is negative, or the range extends beyond the end of the flat list. +/// +/// O(count log count). This is not a stable sort. The elements outside the range keep their places. The function wraps +/// +/// and throws what that member throws for a range out of range. +/// +[] +val sortRange : index : int -> count : int -> list : FlatList<'T> -> FlatList<'T> when 'T : comparison + +/// +/// Sorts a range of the elements with a comparison function. +/// +/// The function that compares two elements. +/// The index of the first element of the range. +/// The number of elements of the range. +/// The input flat list. +/// The flat list with the sorted range; the input itself when the range needs no sorting. +/// Thrown when or is negative, or the range extends beyond the end of the flat list. +/// +/// O(count log count). This is not a stable sort. The elements outside the range keep their places. The function wraps +/// +/// and throws what that member throws for a range out of range. +/// +[] +val sortRangeWith : comparison : ('T -> 'T -> int) -> index : int -> count : int -> list : FlatList<'T> -> FlatList<'T> + +/// +/// Sorts a range of the elements with a comparer. +/// +/// The comparer. +/// The index of the first element of the range. +/// The number of elements of the range. +/// The input flat list. +/// The flat list with the sorted range; the input itself when the range needs no sorting. +/// Thrown when or is negative, or the range extends beyond the end of the flat list. +/// +/// O(count log count). This is not a stable sort. The elements outside the range keep their places. The function wraps +/// +/// and throws what that member throws for a range out of range. +/// +[] +val sortRangeWithComparer : comparer : IComparer<'T> -> index : int -> count : int -> list : FlatList<'T> -> FlatList<'T> + /// Sorts the elements with a comparison function. /// The function that compares two elements. /// The input flat list. diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs b/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs index a6fea50e..c034a484 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs @@ -38,7 +38,7 @@ let add (value : 'T) (builder : FlatList<'T>.Builder) = [] let addRange (values : seq<'T>) (builder : FlatList<'T>.Builder) = - checkNotDefaultSeq (nameof values) values + checkNotNullOrDefaultSeq (nameof values) values builder.AddRange values builder diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fs b/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fs index f69cfdbb..8834ade9 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fs +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fs @@ -14,7 +14,7 @@ type FlatListCE () = member inline _.YieldFrom (items : seq<'T>) : FlatListBuffer<'T> = // A default flat list given as a sequence is still a default flat list, as in FlatList.ofSeq (ADR 0001, D6) - checkNotDefaultSeq (nameof items) items + checkNotNullOrDefaultSeq (nameof items) items fun builder -> builder.AddRange items member inline _.YieldFrom (items : 'T array) : FlatListBuffer<'T> = fun builder -> builder.AddRange items @@ -37,7 +37,7 @@ type FlatListCE () = member inline _.For (source : seq<'U>, [] body : 'U -> FlatListBuffer<'T>) : FlatListBuffer<'T> = // A default flat list is a sequence that fails when it is enumerated; the expression rejects it as yield! does - checkNotDefaultSeq (nameof source) source + checkNotNullOrDefaultSeq (nameof source) source fun builder -> for item in source do diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fsi b/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fsi index 1351b0d9..33c851fa 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fsi +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListCE.fsi @@ -50,7 +50,7 @@ type FlatListCE = member inline Delay : [] delayed : (unit -> FlatListBuffer<'T>) -> FlatListBuffer<'T> /// Adds the elements of the body for each item of a sequence. - /// Thrown when the sequence is a default flat list. + /// Thrown when the sequence is or a default flat list. member inline For : source : seq<'U> * [] body : ('U -> FlatListBuffer<'T>) -> FlatListBuffer<'T> /// Adds the elements of the body while the guard holds. diff --git a/src/FSharp.Collections.Immutable/Helper.fs b/src/FSharp.Collections.Immutable/Helper.fs index 20165a8a..038a183c 100644 --- a/src/FSharp.Collections.Immutable/Helper.fs +++ b/src/FSharp.Collections.Immutable/Helper.fs @@ -33,16 +33,18 @@ module internal ImmutableCollectionUtil = nullArg name /// - /// Throws for a default - /// that is given as a sequence. + /// Throws for a sequence and for a default + /// that is given as a sequence. /// /// /// A default immutable array converts to a sequence without a check, and the sequence fails with - /// when a function enumerates it, so a function that takes a sequence - /// calls this check next to its check (ADR 0001, D6). Any other sequence passes, a - /// sequence included. + /// when a function enumerates it. A function that takes a sequence + /// calls this check, which treats a default immutable array as FSharp.Core treats a sequence + /// (ADR 0001, D6). Any other sequence passes. /// - let inline checkNotDefaultSeq name (source : seq<'T>) = + let inline checkNotNullOrDefaultSeq name (source : seq<'T>) = + checkNotNull name source + match source with | :? ImmutableArray<'T> as array -> checkNotDefault name array | _ -> () From 0121a2ad18794c63f630751f528e1677f23a5490 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 21:42:00 +0200 Subject: [PATCH 18/27] fixup! test: shared contracts of the sequence-like modules, run for `FlatList` Tests of the restored functions and of the checks of a sequence, as rows: - `sub` against `Array.sub` for every range of the contract inputs, and for the ranges that `Array.sub` rejects with the exception compared by `ExceptionAssert.Same` (its `array` is the `list` of `FlatList.sub`); a default flat list; the empty and the whole range. - The three range sorts against `Array.sub`, `Array.sortWith` and `Array.concat` over the sorted part and the parts that stay, for ranges out of range and for ranges that need no sorting. - The removals by value against oracles written by hand (the first equal element, one element for each value), in the default equality and with a case-insensitive comparer. - A null sequence and a default flat list given as a sequence, in the tables of the inputs that a function rejects; `concat`, `transpose`, `except` and `insertManyAt` compare a null sequence with the `Array` oracle, and the order of the checks of `insertManyAt` is pinned. - The pipelines of the tests that start with a flat list use LINQ `Select`, which reads the struct without a copy, and end with `Seq.toArray`. Co-Authored-By: Claude Sonnet 5.5 --- .../FlatList/FlatListBuilderTests.fs | 17 + .../FlatListComputationExpressionTests.fs | 26 + .../FlatList/FlatListOps.fs | 7 +- .../FlatList/FlatListTests.fs | 559 +++++++++++++++++- .../FlatList/FlatListTupleTests.fs | 7 +- 5 files changed, 583 insertions(+), 33 deletions(-) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListBuilderTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListBuilderTests.fs index 93a754f7..b50802c3 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListBuilderTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListBuilderTests.fs @@ -37,6 +37,23 @@ type FlatListBuilderTests () = "FlatListBuilder.addRange must name the parameter of the default flat list" ) + [] + member _.``addRange rejects a null sequence and names the parameter of the function`` () = + let ex = + Assert.ThrowsExactly( + Action (fun () -> + FlatListBuilder.addRange Unchecked.defaultof> (FlatList.builder()) + |> ignore + ), + "FlatListBuilder.addRange must reject a null sequence" + ) + + Assert.AreEqual( + "values", + ex.ParamName, + "FlatListBuilder.addRange must name its own parameter, not the one of the BCL" + ) + [] member _.``builder starts empty`` () = let builder = FlatList.builder() diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs index d9a945d1..b2523fdc 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs @@ -68,6 +68,16 @@ type FlatListComputationExpressionTests () = Assert.AreEqual("items", ex.ParamName, "flatList must name the parameter of the default flat list") + [] + member _.``yield! rejects a null sequence`` () = + let ex = + Assert.ThrowsExactly( + Action (fun () -> flatList { yield! Unchecked.defaultof> } |> ignore), + "flatList must reject a null sequence in yield!" + ) + + Assert.AreEqual("items", ex.ParamName, "flatList must name the parameter of the null sequence") + [] member _.``for adds the elements of the body for each item`` () = let array = flatList { @@ -93,6 +103,22 @@ type FlatListComputationExpressionTests () = Assert.AreEqual("source", ex.ParamName, "flatList must name the parameter of the default flat list") + [] + member _.``for rejects a null sequence`` () = + let ex = + Assert.ThrowsExactly( + Action (fun () -> + flatList { + for item in Unchecked.defaultof> do + yield item + } + |> ignore + ), + "flatList must reject a null sequence in for" + ) + + Assert.AreEqual("source", ex.ParamName, "flatList must name the parameter of the null sequence") + [] member _.``while adds the elements while the guard holds`` () = let mutable count = 0 diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOps.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOps.fs index b44a2853..91ec63d4 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOps.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOps.fs @@ -3,9 +3,10 @@ module FSharp.Collections.Immutable.Tests.FlatListOps open System +open System.Linq open FSharp.Collections.Immutable -let private toArrays (arrays : FlatList>) = arrays |> FlatList.toArray |> Array.map FlatList.toArray +let private toArrays (arrays : FlatList>) = arrays |> _.Select(FlatList.toArray) |> Seq.toArray let private toArrayPair (struct (first : FlatList<'T1>, second : FlatList<'T2>)) = struct (FlatList.toArray first, FlatList.toArray second) @@ -110,8 +111,8 @@ let seqLike : SeqOps> = { GroupBy = fun projection array -> FlatList.groupBy projection array - |> FlatList.toArray - |> Array.map (fun struct (key, items) -> struct (key, FlatList.toArray items)) + |> _.Select(fun struct (key, items) -> struct (key, FlatList.toArray items)) + |> Seq.toArray Partition = FlatList.partition PartitionWith = fun partitioner array -> FlatList.partitionWith partitioner array |> toArrayPair Skip = FlatList.skip diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs index eb11736c..4930f79f 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs @@ -15,19 +15,45 @@ type FlatListTests () = static let nullArray = Unchecked.defaultof /// The array under a flat list, to tell whether two flat lists share their storage. - static let underlying (array : FlatList) = ImmutableCollectionsMarshal.AsArray array + static let underlying (array : FlatList<'T>) = ImmutableCollectionsMarshal.AsArray array - /// Asserts that an action rejects a default flat list with an ArgumentNullException that names the parameter. - static let assertRejectsDefault (name : string) (paramName : string) (actual : unit -> unit) = + /// Asserts that an action rejects its input with an ArgumentNullException that names the parameter. + static let assertRejects (name : string) (paramName : string) (actual : unit -> unit) = let ex = - Assert.ThrowsExactly(Action actual, $"FlatList.%s{name} must reject a default flat list") + Assert.ThrowsExactly(Action actual, $"FlatList.%s{name} must throw ArgumentNullException") Assert.AreEqual( paramName, ex.ParamName, - $"FlatList.%s{name} must name the parameter of the default flat list" + $"FlatList.%s{name} must name the parameter of the rejected input" ) + /// Maps the parameter names of the Array oracles to the ones of the flat list functions that name a sequence of + /// values: Array.insertManyAt names its values source, as Seq.toArray does, where the flat list function names them values. + static let valuesParamName (name : string) = + match name with + | "source" -> "values" + | other -> other + + /// Maps the parameter names of Array.sub to the ones of FlatList.sub, which names the flat list list. + static let subParamName (name : string) = + match name with + | "array" -> "list" + | other -> other + + /// The elements without the first one that the equality finds equal to the item: the oracle of the removal by value. + static let removeFirst (equal : 'T -> 'T -> bool) (item : 'T) (elements : 'T array) = + match Array.tryFindIndex (equal item) elements with + | Some index -> Array.removeAt index elements + | None -> elements + + /// Tells whether two strings are equal when the case is ignored. + static let equalIgnoringCase (left : string) (right : string) = + String.Equals (left, right, StringComparison.OrdinalIgnoreCase) + + /// The strings of the removal by value; "b" and "B" are equal when the case is ignored. + static let names () = [| "a"; "b"; "B"; "c" |] + /// The functions that mirror an Array function, by name, with that function on a null array and the function on a /// default flat list. The rows of the test are the names: a function has no readable name in a row. static let arrayRejections = @@ -204,7 +230,29 @@ type FlatListTests () = |> ignore )) struct (nameof FlatList.length, "array", (fun () -> FlatList.length defaultArray |> ignore)) + struct (nameof FlatList.remove, "list", (fun () -> FlatList.remove 1 defaultArray |> ignore)) + struct (nameof FlatList.removeMany, "list", (fun () -> FlatList.removeMany [| 1 |] defaultArray |> ignore)) + struct (nameof FlatList.removeManyWith, + "list", + (fun () -> + FlatList.removeManyWith EqualityComparer.Default [| 1 |] defaultArray + |> ignore + )) + struct (nameof FlatList.removeWith, + "list", + (fun () -> + FlatList.removeWith EqualityComparer.Default 1 defaultArray + |> ignore + )) struct (nameof FlatList.skipLast, "array", (fun () -> FlatList.skipLast 0 defaultArray |> ignore)) + struct (nameof FlatList.sortRange, "list", (fun () -> FlatList.sortRange 0 0 defaultArray |> ignore)) + struct (nameof FlatList.sortRangeWith, "list", (fun () -> FlatList.sortRangeWith compare 0 0 defaultArray |> ignore)) + struct (nameof FlatList.sortRangeWithComparer, + "list", + (fun () -> + FlatList.sortRangeWithComparer Comparer.Default 0 0 defaultArray + |> ignore + )) struct (nameof FlatList.sortWithComparer, "array", (fun () -> @@ -220,52 +268,141 @@ type FlatListTests () = |> Array.map (fun struct (name, paramName, actual) -> name, struct (paramName, actual)) |> Map.ofArray - /// The functions that take a sequence, by name, with the parameter that names a default flat list given as the - /// sequence and the function on such a flat list. A default flat list converts to a sequence without a check and - /// fails when the sequence is enumerated, so the other inputs are valid and the function would enumerate it. + /// The functions that take a sequence, by name and by the input that they reject, with the parameter that names the + /// input and the function on it. A default flat list converts to a sequence without a check and fails when the + /// sequence is enumerated, so the other inputs are valid and the function would enumerate it. static let sequenceRejections = let defaultOuter = Unchecked.defaultof>> + let nullItems = Unchecked.defaultof> [| - struct (nameof FlatList.concat, "arrays", (fun () -> FlatList.concat (defaultOuter :> seq<_>) |> ignore)) - struct (nameof FlatList.transpose, "arrays", (fun () -> FlatList.transpose (defaultOuter :> seq<_>) |> ignore)) - struct (nameof FlatList.insertManyAt, + struct ($"%s{nameof FlatList.concat} (default outer)", + "arrays", + (fun () -> FlatList.concat (defaultOuter :> seq<_>) |> ignore)) + struct ($"%s{nameof FlatList.transpose} (default outer)", + "arrays", + (fun () -> FlatList.transpose (defaultOuter :> seq<_>) |> ignore)) + struct ($"%s{nameof FlatList.insertManyAt} (default values)", "values", (fun () -> FlatList.insertManyAt 1 (defaultArray :> seq<_>) (FlatList.ofArray Inputs.Several) |> ignore )) // Array.insertManyAt checks the source before the values - struct ($"%s{nameof FlatList.insertManyAt} (default source)", + struct ($"%s{nameof FlatList.insertManyAt} (default source and values)", "source", (fun () -> FlatList.insertManyAt 1 (defaultArray :> seq<_>) defaultArray |> ignore )) - struct (nameof FlatList.except, + struct ($"%s{nameof FlatList.except} (default items)", "itemsToExclude", (fun () -> FlatList.except (defaultArray :> seq<_>) (FlatList.ofArray Inputs.Several) |> ignore )) // Array.except returns an empty input without enumerating the items, but it checks the items for null first - struct ($"%s{nameof FlatList.except} (empty array)", + struct ($"%s{nameof FlatList.except} (default items, empty array)", "itemsToExclude", (fun () -> FlatList.except (defaultArray :> seq<_>) FlatList.empty |> ignore )) // Array.except checks the items to exclude before the array - struct ($"%s{nameof FlatList.except} (default array)", + struct ($"%s{nameof FlatList.except} (default items and array)", "itemsToExclude", (fun () -> FlatList.except (defaultArray :> seq<_>) defaultArray |> ignore )) + struct ($"%s{nameof FlatList.removeMany} (default items)", + "items", + (fun () -> + FlatList.removeMany (defaultArray :> seq<_>) (FlatList.ofArray Inputs.Several) + |> ignore + )) + // ImmutableArray.RemoveRange checks the flat list before the items + struct ($"%s{nameof FlatList.removeMany} (default list and items)", + "list", + (fun () -> + FlatList.removeMany (defaultArray :> seq<_>) defaultArray + |> ignore + )) + struct ($"%s{nameof FlatList.removeMany} (null items)", + "items", + (fun () -> + FlatList.removeMany nullItems (FlatList.ofArray Inputs.Several) + |> ignore + )) + struct ($"%s{nameof FlatList.removeManyWith} (default items)", + "items", + (fun () -> + FlatList.removeManyWith + EqualityComparer.Default + (defaultArray :> seq<_>) + (FlatList.ofArray Inputs.Several) + |> ignore + )) + struct ($"%s{nameof FlatList.removeManyWith} (default list and items)", + "list", + (fun () -> + FlatList.removeManyWith EqualityComparer.Default (defaultArray :> seq<_>) defaultArray + |> ignore + )) + struct ($"%s{nameof FlatList.removeManyWith} (null items)", + "items", + (fun () -> + FlatList.removeManyWith EqualityComparer.Default nullItems (FlatList.ofArray Inputs.Several) + |> ignore + )) |] |> Array.map (fun struct (name, paramName, actual) -> name, struct (paramName, actual)) |> Map.ofArray + /// The functions that take a sequence and mirror an Array function, by name, with that function on a null sequence and + /// the function on a null sequence. The other inputs are valid. + static let nullSequenceMirrors = + let nullArrays = Unchecked.defaultof> + let nullOuter = Unchecked.defaultof>> + let nullItems = Unchecked.defaultof> + + [| + struct (nameof FlatList.concat, + (fun () -> Array.concat nullArrays |> ignore), + (fun () -> FlatList.concat nullOuter |> ignore)) + struct (nameof FlatList.transpose, + (fun () -> Array.transpose nullArrays |> ignore), + (fun () -> FlatList.transpose nullOuter |> ignore)) + struct (nameof FlatList.except, + (fun () -> Array.except nullItems Inputs.Several |> ignore), + (fun () -> + FlatList.except nullItems (FlatList.ofArray Inputs.Several) + |> ignore + )) + struct (nameof FlatList.insertManyAt, + (fun () -> Array.insertManyAt 1 nullItems Inputs.Several |> ignore), + (fun () -> + FlatList.insertManyAt 1 nullItems (FlatList.ofArray Inputs.Several) + |> ignore + )) + |] + |> Array.map (fun struct (name, oracle, actual) -> name, struct (oracle, actual)) + |> Map.ofArray + + /// The functions that sort a range, by name, with the function and the sort of the range in the oracle. The inputs + /// have no two different elements with the same absolute value, and the comparer of `sortRangeWithComparer` orders by + /// the absolute value, so the result does not depend on the stability of the sort. + static let rangeSorts : Map int -> FlatList -> FlatList) * (int array -> int array))> = + let descending (left : int) (right : int) = compare right left + let byAbsolute (left : int) (right : int) = compare (abs left) (abs right) + + Map.ofArray [| + nameof FlatList.sortRange, struct (FlatList.sortRange, Array.sortWith compare) + nameof FlatList.sortRangeWith, struct (FlatList.sortRangeWith descending, Array.sortWith descending) + nameof FlatList.sortRangeWithComparer, + struct (FlatList.sortRangeWithComparer (Comparer.Create(Comparison byAbsolute)), Array.sortWith byAbsolute) + |] + /// The functions that take a range, by name, on the whole range of the input with several elements. static let wholeRanges : Map -> FlatList> = Map.ofArray [| @@ -276,6 +413,7 @@ type FlatListTests () = nameof FlatList.skip, FlatList.skip -1 nameof FlatList.skipLast, FlatList.skipLast 0 nameof FlatList.skipWhile, FlatList.skipWhile (fun _ -> false) + nameof FlatList.sub, (fun array -> FlatList.sub array 0 array.Length) |] /// The functions that take a range, by name, on a partial range of the input with several elements. @@ -283,6 +421,7 @@ type FlatListTests () = Map.ofArray [| nameof FlatList.take, FlatList.take 3 nameof FlatList.skip, FlatList.skip 3 + nameof FlatList.sub, (fun array -> FlatList.sub array 3 3) ($"%s{nameof FlatList.splitAt} (first part)", fun array -> let struct (before, _) = FlatList.splitAt 3 array @@ -302,9 +441,135 @@ type FlatListTests () = /// The names of the functions without an Array counterpart, as the rows of their test. static member OtherRejections = otherRejections.Keys - /// The names of the functions that take a sequence, as the rows of the test of a default flat list given as the sequence. + /// The names of the functions that take a sequence, as the rows of the test of the sequences that they reject. static member SequenceRejections = sequenceRejections.Keys + /// The names of the functions that take a sequence and mirror an Array function, as the rows of the test of a null sequence. + static member NullSequenceMirrors = nullSequenceMirrors.Keys + + /// Every range of every input, as the index of its first element and its length. + static member SubRanges = seq { + for input in Inputs.All do + for startIndex in 0 .. input.Length do + for count in 0 .. input.Length - startIndex do + struct (input, startIndex, count) + } + + /// Every input with ranges that Array.sub rejects: a negative start or count, a count beyond the length, and a start + /// beyond the length or at its end with an element to take. + static member SubRangesOutOfRange = seq { + for input in Inputs.All do + let ranges = + [| + struct (-1, 0) + struct (0, -1) + struct (-1, -1) + struct (0, input.Length + 1) + struct (input.Length + 1, 0) + struct (input.Length, 1) + struct (1, input.Length) + |] + |> Array.distinct + + for struct (startIndex, count) in ranges do + struct (input, startIndex, count) + } + + /// The ranges that the functions sort: none, one element, all of them, a prefix, a suffix and the inside of the input + /// with several elements, and the ranges of the smaller inputs. + static member SortRanges = seq { + let ranges = [| + struct (Inputs.Empty, 0, 0) + struct (Inputs.Singleton, 0, 0) + struct (Inputs.Singleton, 0, 1) + struct (Inputs.Singleton, 1, 0) + struct (Inputs.Several, 0, 0) + struct (Inputs.Several, 0, 1) + struct (Inputs.Several, 0, 8) + struct (Inputs.Several, 8, 0) + struct (Inputs.Several, 4, 0) + struct (Inputs.Several, 0, 3) + struct (Inputs.Several, 2, 4) + struct (Inputs.Several, 5, 3) + struct (Inputs.Several, 7, 1) + |] + + for name in rangeSorts.Keys do + for struct (input, index, count) in ranges do + struct (name, input, index, count) + } + + /// The ranges of the input with several elements that the sorts reject, with the parameter that the BCL names. + static member SortRangesOutOfRange = seq { + let ranges = [| + struct (-1, 1, "index") + struct (0, -1, "count") + struct (0, 9, "count") + struct (5, 4, "count") + struct (9, 0, "count") + |] + + for name in rangeSorts.Keys do + for struct (index, count, paramName) in ranges do + struct (name, index, count, paramName) + } + + /// The ranges of the input with several elements that need no sorting: they have at most one element. + static member SortRangesWithoutSorting = seq { + for name in rangeSorts.Keys do + for struct (index, count) in [| struct (0, 0); struct (2, 1); struct (8, 0) |] do + struct (name, index, count) + } + + /// Every input with each value to remove: the first of two equal elements, the only element, the last element, and + /// a value that no input has. + static member RemovalValues = seq { + for input in Inputs.All do + for value in [| 42; 5; -3; 8; 7; 1000 |] do + struct (input, value) + } + + /// Every input with values that none of them has. + static member AbsentValues = seq { + for input in Inputs.All do + for value in [| 1000; 99 |] do + struct (input, value) + } + + /// Every input with the values to remove: none, one, a value as often as the input with several elements has it and + /// more often, and several values of which some are missing. + static member RemovalItems = seq { + for input in Inputs.All do + for items in [| [||]; [| 5 |]; [| 5; 5 |]; [| 5; 5; 5 |]; [| -3; 5; 1000 |]; [| 7; 5; -3; 8; 5; 0 |] |] do + struct (input, items) + } + + /// Every input with sequences of values that none of them has. + static member AbsentItems = seq { + for input in Inputs.All do + for items in [| [||]; [| 1000 |]; [| 1000; 99 |] |] do + struct (input, items) + } + + /// The values to remove from the strings with a case-insensitive comparer: equal in the same case, equal in another + /// case, and missing. + static member NameValues = seq { + "a" + "b" + "B" + "C" + "d" + } + + /// The sequences of values to remove from the strings with a case-insensitive comparer. + static member NameItems : seq> = seq { + TestDataRow Array.empty + TestDataRow [| "B" |] + TestDataRow [| "b"; "B" |] + TestDataRow [| "B"; "b"; "b" |] + TestDataRow [| "C"; "d"; "A" |] + } + /// The names of the functions that take a range, as the rows of the test of the whole range. static member WholeRanges = wholeRanges.Keys @@ -347,25 +612,42 @@ type FlatListTests () = [] member _.``functions without an Array counterpart reject a default flat list`` (name : string) = let struct (paramName, actual) = otherRejections[name] - assertRejectsDefault name paramName actual + assertRejects name paramName actual [] - member _.``functions that take a sequence reject a default flat list given as the sequence`` (name : string) = + member _.``functions that take a sequence reject a default flat list given as the sequence and a null sequence`` + (name : string) + = let struct (paramName, actual) = sequenceRejections[name] - assertRejectsDefault name paramName actual + assertRejects name paramName actual + + [] + member _.``functions that take a sequence reject a null sequence as Array does`` (name : string) = + let struct (oracle, actual) = nullSequenceMirrors[name] - [] - member _.``insertManyAt rejects an index out of range before default values as Array.insertManyAt does`` (index : int) = + ExceptionAssert.Same ( + valuesParamName, + oracle, + actual, + $"FlatList.%s{name} must reject a null sequence as Array.%s{name} does" + ) + + [] + member _.``insertManyAt rejects an index out of range before the values as Array.insertManyAt does`` + (index : int, kind : string) + = // The input has 8 elements, so the indices -1 and 9 are out of range. Array.insertManyAt checks the index - // before it enumerates the values + // before it enumerates the values, and the default flat list is as good as a null sequence for the oracle + let values = + match kind with + | "null" -> Unchecked.defaultof> + | _ -> defaultArray :> seq<_> + ExceptionAssert.Same ( id, + (fun () -> Array.insertManyAt index values Inputs.Several |> ignore), (fun () -> - Array.insertManyAt index Unchecked.defaultof> Inputs.Several - |> ignore - ), - (fun () -> - FlatList.insertManyAt index (defaultArray :> seq<_>) (FlatList.ofArray Inputs.Several) + FlatList.insertManyAt index values (FlatList.ofArray Inputs.Several) |> ignore ), "FlatList.insertManyAt must check the index before the values as Array.insertManyAt does" @@ -488,3 +770,226 @@ type FlatListTests () = underlying (FlatList.sortWithComparer Comparer.Default array), "FlatList.sortWithComparer must not copy a sorted flat list" ) + + // ---- sub ---- + + [] + member _.``sub returns the range of the elements as Array.sub does`` (input : int array, startIndex : int, count : int) = + CollectionAssert.AreEqual ( + Array.sub input startIndex count, + FlatList.toArray (FlatList.sub (FlatList.ofArray input) startIndex count), + "FlatList.sub must return the range of the elements as Array.sub does" + ) + + [] + member _.``sub throws for a range out of range as Array.sub does`` (input : int array, startIndex : int, count : int) = + ExceptionAssert.Same ( + subParamName, + (fun () -> Array.sub input startIndex count |> ignore), + (fun () -> + FlatList.sub (FlatList.ofArray input) startIndex count + |> ignore + ), + "FlatList.sub must throw as Array.sub does for a range out of range" + ) + + [] + member _.``sub rejects a default flat list before the range as Array.sub rejects a null array`` + (startIndex : int, count : int) + = + ExceptionAssert.Same ( + subParamName, + (fun () -> Array.sub nullArray startIndex count |> ignore), + (fun () -> FlatList.sub defaultArray startIndex count |> ignore), + "FlatList.sub must reject a default flat list as Array.sub rejects a null array" + ) + + [] + member _.``sub returns the empty flat list for an empty range`` (startIndex : int) = + let array = FlatList.ofArray Inputs.Several + + Assert.AreSame ( + underlying FlatList.empty, + underlying (FlatList.sub array startIndex 0), + "FlatList.sub of an empty range must return the empty flat list" + ) + + // ---- sortRange, sortRangeWith and sortRangeWithComparer ---- + + [] + member _.``functions that sort a range sort the range and keep the other elements`` + (name : string, input : int array, index : int, count : int) + = + let struct (sortRange, sortPart) = rangeSorts[name] + + let expected = + Array.concat [| + Array.sub input 0 index + sortPart (Array.sub input index count) + Array.sub input (index + count) (input.Length - index - count) + |] + + CollectionAssert.AreEqual ( + expected, + FlatList.toArray (sortRange index count (FlatList.ofArray input)), + $"FlatList.%s{name} must sort the range and keep the other elements" + ) + + [] + member _.``functions that sort a range throw for a range out of range as the BCL does`` + (name : string, index : int, count : int, paramName : string) + = + let struct (sortRange, _) = rangeSorts[name] + + let ex = + Assert.ThrowsExactly( + Action (fun () -> + sortRange index count (FlatList.ofArray Inputs.Several) + |> ignore + ), + $"FlatList.%s{name} must throw ArgumentOutOfRangeException for a range out of range" + ) + + Assert.AreEqual(paramName, ex.ParamName, $"FlatList.%s{name} must name the parameter that the BCL names") + + [] + member _.``functions that sort a range return the input itself for a range of at most one element`` + (name : string, index : int, count : int) + = + let struct (sortRange, _) = rangeSorts[name] + let array = FlatList.ofArray Inputs.Several + + Assert.AreSame ( + underlying array, + underlying (sortRange index count array), + $"FlatList.%s{name} must not copy a range that needs no sorting" + ) + + [] + member _.``sortRange returns the input itself when the range is already sorted`` () = + let array = FlatList.ofArray [| 1; 2; 3; 9; 5 |] + + Assert.AreSame ( + underlying array, + underlying (FlatList.sortRange 0 3 array), + "FlatList.sortRange must not copy a range that is already sorted" + ) + + [] + member _.``sortRange orders strings ordinally, as F# generic comparison does`` () = + let array = FlatList.ofArray (strings ()) + + CollectionAssert.AreEqual ( + [| "A"; "B"; "a"; "b" |], + FlatList.toArray (FlatList.sortRange 0 4 array), + "FlatList.sortRange must sort strings ordinally" + ) + + // ---- remove, removeWith, removeMany and removeManyWith ---- + + [] + member _.``remove removes the first equal element`` (input : int array, value : int) = + CollectionAssert.AreEqual ( + removeFirst (=) value input, + FlatList.toArray (FlatList.remove value (FlatList.ofArray input)), + "FlatList.remove must remove the first equal element" + ) + + [] + member _.``remove returns the input itself when no element equals the value`` (input : int array, value : int) = + let array = FlatList.ofArray input + + Assert.AreSame ( + underlying array, + underlying (FlatList.remove value array), + "FlatList.remove must not copy a flat list that has no equal element" + ) + + [] + member _.``remove compares in the default equality, which tells b from B`` () = + CollectionAssert.AreEqual ( + [| "a"; "b"; "c" |], + FlatList.toArray (FlatList.remove "B" (FlatList.ofArray (names ()))), + "FlatList.remove must not find B equal to b" + ) + + [] + member _.``removeWith removes the first element that the comparer finds equal`` (value : string) = + CollectionAssert.AreEqual ( + removeFirst equalIgnoringCase value (names ()), + FlatList.toArray (FlatList.removeWith StringComparer.OrdinalIgnoreCase value (FlatList.ofArray (names ()))), + "FlatList.removeWith must remove the first element that the comparer finds equal" + ) + + [] + member _.``removeWith returns the input itself when no element is equal`` (value : string) = + let array = FlatList.ofArray (names ()) + + Assert.AreSame ( + underlying array, + underlying (FlatList.removeWith StringComparer.OrdinalIgnoreCase value array), + "FlatList.removeWith must not copy a flat list that has no equal element" + ) + + [] + member _.``removeMany removes one element for each value`` (input : int array, items : int array) = + let expected = Array.fold (fun elements item -> removeFirst (=) item elements) input items + + CollectionAssert.AreEqual ( + expected, + FlatList.toArray (FlatList.removeMany items (FlatList.ofArray input)), + "FlatList.removeMany must remove one element for each value" + ) + + [] + member _.``removeMany takes the values as any sequence`` () = + let array = FlatList.ofArray Inputs.Several + // The values 5, -3 and 5 remove the first 5, the first -3 and the second 5 + let expected = [| 8; 0; -3; 12; 7 |] + + CollectionAssert.AreEqual ( + expected, + FlatList.toArray (FlatList.removeMany [| 5; -3; 5 |] array), + "FlatList.removeMany must take the values as an array" + ) + + CollectionAssert.AreEqual ( + expected, + FlatList.toArray (FlatList.removeMany [ 5; -3; 5 ] array), + "FlatList.removeMany must take the values as a list" + ) + + CollectionAssert.AreEqual ( + expected, + FlatList.toArray (FlatList.removeMany (Seq.map id [| 5; -3; 5 |]) array), + "FlatList.removeMany must take the values as a lazy sequence" + ) + + [] + member _.``removeMany returns the input itself when no value is in it`` (input : int array, items : int array) = + let array = FlatList.ofArray input + + Assert.AreSame ( + underlying array, + underlying (FlatList.removeMany items array), + "FlatList.removeMany must not copy a flat list that has none of the values" + ) + + [] + member _.``removeMany compares in the default equality, which tells b from B`` () = + CollectionAssert.AreEqual ( + [| "a"; "b"; "c" |], + FlatList.toArray (FlatList.removeMany [| "B" |] (FlatList.ofArray (names ()))), + "FlatList.removeMany must not find B equal to b" + ) + + [] + member _.``removeManyWith removes one element for each value that the comparer finds equal`` (items : string array) = + let expected = + Array.fold (fun elements item -> removeFirst equalIgnoringCase item elements) (names ()) items + + CollectionAssert.AreEqual ( + expected, + FlatList.toArray (FlatList.removeManyWith StringComparer.OrdinalIgnoreCase items (FlatList.ofArray (names ()))), + "FlatList.removeManyWith must remove one element for each value that the comparer finds equal" + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTupleTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTupleTests.fs index 6358a773..2b0a34a2 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTupleTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTupleTests.fs @@ -1,5 +1,6 @@ namespace FSharp.Collections.Immutable.Tests +open System.Linq open FSharp.Collections.Immutable open FSharp.Collections.Immutable.Tuple open Microsoft.VisualStudio.TestTools.UnitTesting @@ -161,15 +162,15 @@ type FlatListTupleTests () = // compared apart CollectionAssert.AreEqual ( Array.map fst expected, - groups |> FlatList.toArray |> Array.map fst, + groups |> _.Select(fst) |> Seq.toArray, "Tuple.FlatList.groupBy must return the keys as Array.groupBy does" ) CollectionAssert.AreEqual ( Array.map snd expected, groups - |> FlatList.toArray - |> Array.map (fun (_, items) -> FlatList.toArray items), + |> _.Select(fun (_, items) -> FlatList.toArray items) + |> Seq.toArray, "Tuple.FlatList.groupBy must group the elements as Array.groupBy does" ) From 85201fee6043b1732d9a63f065693d2360c87577 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 21:42:01 +0200 Subject: [PATCH 19/27] fixup! docs: `FlatList` in the changelog and the collection table `remove`, `removeMany`, `removeManyWith` and `removeWith` are added. `sub` takes the meaning of `Array.sub`, which changes its arguments, and `sortRange`, `sortRangeWith` and `sortRangeWithComparer` stay. `removeAll`, `removeAllWith` and `removeRange` are removed with their replacements named. Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09b4494d..8432344b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * `FlatList` has every function of the FSharp.Core `Array` module that applies to an immutable array, with its semantics and exceptions, plus `add`, `asMemory`, `asSpan`, `binarySearch`, `indexOf`, `indexOfWith`, `lastIndexOf`, - `lastIndexOfWith`, `ofReadOnlySpan`, `ofSpan`, `skipLast`, `sortWithComparer`, `takeLast` and `tryTail`. + `lastIndexOfWith`, `ofReadOnlySpan`, `ofSpan`, `remove`, `removeMany`, `removeManyWith`, `removeWith`, `skipLast`, + `sortWithComparer`, `takeLast` and `tryTail`. `remove` and `removeMany` remove by value in + `EqualityComparer<'T>.Default`, and the `With` variants take an equality comparer. * The `FSharp.Collections.Immutable.Option.FlatList` and `FSharp.Collections.Immutable.Tuple.FlatList` modules repeat the functions that use value options or struct tuples with options and reference tuples. * The `FlatListBuilder` module works on the builder of a flat list, and `flatList { }` builds a flat list. @@ -23,14 +25,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * `FlatList`: the `try*` functions return `voption`; `choose`, `pick`, `tryPick` and `unfold` take functions that return `voption`; the functions that return tuples return struct tuples. * `FlatList`: every function throws what the FSharp.Core `Array` function of the same name throws. `take` and `splitAt` - throw for a count beyond the length, and a default `ImmutableArray` throws `ArgumentNullException`. + throw for a count beyond the length, and a default `ImmutableArray` throws `ArgumentNullException`, also when it is + given as a sequence. +* `FlatList.sub` has the meaning of `Array.sub`: `sub list startIndex count` returns `count` elements from `startIndex`. + It was `sub start stop list`. ### Removed * `FlatList.index`, `indexWith`, `indexFrom`, `indexFromWith`, `indexRange`, `indexRangeWith` and their `lastIndex` counterparts: use `indexOf`, `indexOfWith`, `lastIndexOf` and `lastIndexOfWith`. -* `FlatList.removeAll`, `removeAllWith`, `removeRange`, `sortRange`, `sortRangeWith`, `sortRangeWithComparer` and `sub`, - and the `ImmutableArray` module alias of `FlatList`. +* `FlatList.removeAll`, `removeAllWith` and `removeRange`: use `removeMany`, `removeManyWith` and `removeManyAt`, and + `filter` with the negated predicate to remove the elements that satisfy a predicate. +* The `ImmutableArray` module alias of `FlatList`. * The `Seq` module of conversions between the collections. ## [1.0.0] - 2020-02-23 From 353a93800e6113e204d9764f18d7506342b342e3 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 22:01:54 +0200 Subject: [PATCH 20/27] fixup! feat: `FlatList` module with the vocabulary of the FSharp.Core `Array` module The flat list parameter of the eight functions that came back has the name that the neighbours use, so that named arguments and the documentation read alike in the module: - `sub` and the range sorts (`sortRange`, `sortRangeWith`, `sortRangeWithComparer`) call it `array`, as the `Array` functions do. `Array.sub` names it `array`, so the check of a default flat list reports the same parameter, and the message of the check of the range names `array.Length`, word for word as `Array.sub` does. - `remove`, `removeWith`, `removeMany` and `removeManyWith` call it `source`, as `removeAt`, `removeManyAt`, `insertAt`, `insertManyAt` and `updateAt` do. The signature file, its parameter documentation and the names that the checks report follow. Co-Authored-By: Claude Sonnet 5.5 --- .../FlatList/FlatList.fs | 52 +++++++++---------- .../FlatList/FlatList.fsi | 32 ++++++------ 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatList.fs b/src/FSharp.Collections.Immutable/FlatList/FlatList.fs index f9877f32..45a0e465 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatList.fs +++ b/src/FSharp.Collections.Immutable/FlatList/FlatList.fs @@ -432,9 +432,9 @@ let permute indexMap (array : FlatList<'T>) = |> wrapArray [] -let remove (item : 'T) (list : FlatList<'T>) = - checkNotDefault (nameof list) list - list.Remove item +let remove (item : 'T) (source : FlatList<'T>) = + checkNotDefault (nameof source) source + source.Remove item [] let removeAt index (source : FlatList<'T>) = @@ -442,10 +442,10 @@ let removeAt index (source : FlatList<'T>) = |> wrapArray [] -let removeMany (items : seq<'T>) (list : FlatList<'T>) = - checkNotDefault (nameof list) list +let removeMany (items : seq<'T>) (source : FlatList<'T>) = + checkNotDefault (nameof source) source checkNotNullOrDefaultSeq (nameof items) items - list.RemoveRange items + source.RemoveRange items [] let removeManyAt index count (source : FlatList<'T>) = @@ -453,15 +453,15 @@ let removeManyAt index count (source : FlatList<'T>) = |> wrapArray [] -let removeManyWith (comparer : IEqualityComparer<'T>) (items : seq<'T>) (list : FlatList<'T>) = - checkNotDefault (nameof list) list +let removeManyWith (comparer : IEqualityComparer<'T>) (items : seq<'T>) (source : FlatList<'T>) = + checkNotDefault (nameof source) source checkNotNullOrDefaultSeq (nameof items) items - list.RemoveRange (items, comparer) + source.RemoveRange (items, comparer) [] -let removeWith (comparer : IEqualityComparer<'T>) (item : 'T) (list : FlatList<'T>) = - checkNotDefault (nameof list) list - list.Remove (item, comparer) +let removeWith (comparer : IEqualityComparer<'T>) (item : 'T) (source : FlatList<'T>) = + checkNotDefault (nameof source) source + source.Remove (item, comparer) [] let rev (array : FlatList<'T>) = Array.rev (unwrapArray (nameof array) array) |> wrapArray @@ -746,8 +746,8 @@ let splitInto count (array : FlatList<'T>) = |> wrapArray [] -let sub (list : FlatList<'T>) startIndex count = - checkNotDefault (nameof list) list +let sub (array : FlatList<'T>) startIndex count = + checkNotDefault (nameof array) array if startIndex < 0 then Raise.invalidArgInputMustBeNonNegative (nameof startIndex) startIndex @@ -757,10 +757,10 @@ let sub (list : FlatList<'T>) startIndex count = // Array.sub compares the sum of the start index and the count with the length, which can overflow; the difference // cannot, because the start index is not negative - if count > list.Length - startIndex then - Raise.invalidArgOutOfRange (nameof count) count "list.Length" list.Length + if count > array.Length - startIndex then + Raise.invalidArgOutOfRange (nameof count) count "array.Length" array.Length - list.Slice (startIndex, count) + array.Slice (startIndex, count) [] let take count (array : FlatList<'T>) = @@ -832,19 +832,19 @@ let sortDescending (array : FlatList<'T>) = |> wrapArray [] -let sortRange index count (list : FlatList<'T>) = - checkNotDefault (nameof list) list - list.Sort (index, count, DefaultComparer<'T>.Instance) +let sortRange index count (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.Sort (index, count, DefaultComparer<'T>.Instance) [] -let sortRangeWith (comparison : 'T -> 'T -> int) index count (list : FlatList<'T>) = - checkNotDefault (nameof list) list - list.Sort (index, count, Comparer<'T>.Create(Comparison<'T> comparison)) +let sortRangeWith (comparison : 'T -> 'T -> int) index count (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.Sort (index, count, Comparer<'T>.Create(Comparison<'T> comparison)) [] -let sortRangeWithComparer (comparer : IComparer<'T>) index count (list : FlatList<'T>) = - checkNotDefault (nameof list) list - list.Sort (index, count, comparer) +let sortRangeWithComparer (comparer : IComparer<'T>) index count (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.Sort (index, count, comparer) [] let sortWith comparer (array : FlatList<'T>) = diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi b/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi index f50d566e..2eb14626 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi +++ b/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi @@ -788,7 +788,7 @@ val permute : indexMap : (int -> int) -> array : FlatList<'T> -> FlatList<'T> /// . /// /// The value to remove. -/// The input flat list. +/// The input flat list. /// The flat list without the element; the input itself when no element equals the value. /// /// @@ -807,7 +807,7 @@ val permute : indexMap : (int -> int) -> array : FlatList<'T> -> FlatList<'T> /// /// [] -val remove : item : 'T -> list : FlatList<'T> -> FlatList<'T> +val remove : item : 'T -> source : FlatList<'T> -> FlatList<'T> /// Returns a flat list without the element at an index. /// The index of the removed element. @@ -823,7 +823,7 @@ val removeAt : index : int -> source : FlatList<'T> -> FlatList<'T> /// . /// /// The values to remove. -/// The input flat list. +/// The input flat list. /// The flat list without the elements; the input itself when no element equals any of the values. /// Thrown when is or a default flat list. /// @@ -832,7 +832,7 @@ val removeAt : index : int -> source : FlatList<'T> -> FlatList<'T> /// . /// [] -val removeMany : items : seq<'T> -> list : FlatList<'T> -> FlatList<'T> +val removeMany : items : seq<'T> -> source : FlatList<'T> -> FlatList<'T> /// /// Returns a flat list without a range of elements. @@ -856,7 +856,7 @@ val removeManyAt : index : int -> count : int -> source : FlatList<'T> -> FlatLi /// /// The equality comparer. /// The values to remove. -/// The input flat list. +/// The input flat list. /// The flat list without the elements; the input itself when no element equals any of the values. /// Thrown when is or a default flat list. /// @@ -865,21 +865,21 @@ val removeManyAt : index : int -> count : int -> source : FlatList<'T> -> FlatLi /// . /// [] -val removeManyWith : comparer : IEqualityComparer<'T> -> items : seq<'T> -> list : FlatList<'T> -> FlatList<'T> +val removeManyWith : comparer : IEqualityComparer<'T> -> items : seq<'T> -> source : FlatList<'T> -> FlatList<'T> /// /// Returns the flat list without the first element that equals a value in a given equality comparer. /// /// The equality comparer. /// The value to remove. -/// The input flat list. +/// The input flat list. /// The flat list without the element; the input itself when no element equals the value. /// /// O(n). The function wraps /// . /// [] -val removeWith : comparer : IEqualityComparer<'T> -> item : 'T -> list : FlatList<'T> -> FlatList<'T> +val removeWith : comparer : IEqualityComparer<'T> -> item : 'T -> source : FlatList<'T> -> FlatList<'T> /// Returns the elements in reverse order. /// The input flat list. @@ -1404,7 +1404,7 @@ val splitInto : count : int -> array : FlatList<'T> -> FlatList> /// /// Returns the elements of a range of the flat list. /// -/// The input flat list. +/// The input flat list. /// The index of the first element of the range. /// The number of elements of the range. /// The elements of the range, in their order. @@ -1422,7 +1422,7 @@ val splitInto : count : int -> array : FlatList<'T> -> FlatList> /// /// [] -val sub : list : FlatList<'T> -> startIndex : int -> count : int -> FlatList<'T> +val sub : array : FlatList<'T> -> startIndex : int -> count : int -> FlatList<'T> /// /// Returns the first elements of the flat list. @@ -1529,7 +1529,7 @@ val sortDescending : array : FlatList<'T> -> FlatList<'T> when 'T : comparison /// /// The index of the first element of the range. /// The number of elements of the range. -/// The input flat list. +/// The input flat list. /// The flat list with the sorted range; the input itself when the range needs no sorting. /// Thrown when or is negative, or the range extends beyond the end of the flat list. /// @@ -1538,7 +1538,7 @@ val sortDescending : array : FlatList<'T> -> FlatList<'T> when 'T : comparison /// and throws what that member throws for a range out of range. /// [] -val sortRange : index : int -> count : int -> list : FlatList<'T> -> FlatList<'T> when 'T : comparison +val sortRange : index : int -> count : int -> array : FlatList<'T> -> FlatList<'T> when 'T : comparison /// /// Sorts a range of the elements with a comparison function. @@ -1546,7 +1546,7 @@ val sortRange : index : int -> count : int -> list : FlatList<'T> -> FlatList<'T /// The function that compares two elements. /// The index of the first element of the range. /// The number of elements of the range. -/// The input flat list. +/// The input flat list. /// The flat list with the sorted range; the input itself when the range needs no sorting. /// Thrown when or is negative, or the range extends beyond the end of the flat list. /// @@ -1555,7 +1555,7 @@ val sortRange : index : int -> count : int -> list : FlatList<'T> -> FlatList<'T /// and throws what that member throws for a range out of range. /// [] -val sortRangeWith : comparison : ('T -> 'T -> int) -> index : int -> count : int -> list : FlatList<'T> -> FlatList<'T> +val sortRangeWith : comparison : ('T -> 'T -> int) -> index : int -> count : int -> array : FlatList<'T> -> FlatList<'T> /// /// Sorts a range of the elements with a comparer. @@ -1563,7 +1563,7 @@ val sortRangeWith : comparison : ('T -> 'T -> int) -> index : int -> count : int /// The comparer. /// The index of the first element of the range. /// The number of elements of the range. -/// The input flat list. +/// The input flat list. /// The flat list with the sorted range; the input itself when the range needs no sorting. /// Thrown when or is negative, or the range extends beyond the end of the flat list. /// @@ -1572,7 +1572,7 @@ val sortRangeWith : comparison : ('T -> 'T -> int) -> index : int -> count : int /// and throws what that member throws for a range out of range. /// [] -val sortRangeWithComparer : comparer : IComparer<'T> -> index : int -> count : int -> list : FlatList<'T> -> FlatList<'T> +val sortRangeWithComparer : comparer : IComparer<'T> -> index : int -> count : int -> array : FlatList<'T> -> FlatList<'T> /// Sorts the elements with a comparison function. /// The function that compares two elements. From 3225f5e3e18b1cd5f108d1b232a333868d242541 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 22:01:54 +0200 Subject: [PATCH 21/27] fixup! test: shared contracts of the sequence-like modules, run for `FlatList` The tests follow the new parameter names: `array` for `sub` and the range sorts, `source` for the removals by value. `sub` is compared with `Array.sub` without a mapping of the parameter name now, so the `subParamName` helper goes away. The rows of `removeMany` and `removeManyWith` for a default flat list together with default items say "default source", as the rows of `insertManyAt` do. Co-Authored-By: Claude Sonnet 5.5 --- .../FlatList/FlatListTests.fs | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs index 4930f79f..e93e180d 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs @@ -35,12 +35,6 @@ type FlatListTests () = | "source" -> "values" | other -> other - /// Maps the parameter names of Array.sub to the ones of FlatList.sub, which names the flat list list. - static let subParamName (name : string) = - match name with - | "array" -> "list" - | other -> other - /// The elements without the first one that the equality finds equal to the item: the oracle of the removal by value. static let removeFirst (equal : 'T -> 'T -> bool) (item : 'T) (elements : 'T array) = match Array.tryFindIndex (equal item) elements with @@ -230,25 +224,25 @@ type FlatListTests () = |> ignore )) struct (nameof FlatList.length, "array", (fun () -> FlatList.length defaultArray |> ignore)) - struct (nameof FlatList.remove, "list", (fun () -> FlatList.remove 1 defaultArray |> ignore)) - struct (nameof FlatList.removeMany, "list", (fun () -> FlatList.removeMany [| 1 |] defaultArray |> ignore)) + struct (nameof FlatList.remove, "source", (fun () -> FlatList.remove 1 defaultArray |> ignore)) + struct (nameof FlatList.removeMany, "source", (fun () -> FlatList.removeMany [| 1 |] defaultArray |> ignore)) struct (nameof FlatList.removeManyWith, - "list", + "source", (fun () -> FlatList.removeManyWith EqualityComparer.Default [| 1 |] defaultArray |> ignore )) struct (nameof FlatList.removeWith, - "list", + "source", (fun () -> FlatList.removeWith EqualityComparer.Default 1 defaultArray |> ignore )) struct (nameof FlatList.skipLast, "array", (fun () -> FlatList.skipLast 0 defaultArray |> ignore)) - struct (nameof FlatList.sortRange, "list", (fun () -> FlatList.sortRange 0 0 defaultArray |> ignore)) - struct (nameof FlatList.sortRangeWith, "list", (fun () -> FlatList.sortRangeWith compare 0 0 defaultArray |> ignore)) + struct (nameof FlatList.sortRange, "array", (fun () -> FlatList.sortRange 0 0 defaultArray |> ignore)) + struct (nameof FlatList.sortRangeWith, "array", (fun () -> FlatList.sortRangeWith compare 0 0 defaultArray |> ignore)) struct (nameof FlatList.sortRangeWithComparer, - "list", + "array", (fun () -> FlatList.sortRangeWithComparer Comparer.Default 0 0 defaultArray |> ignore @@ -322,8 +316,8 @@ type FlatListTests () = |> ignore )) // ImmutableArray.RemoveRange checks the flat list before the items - struct ($"%s{nameof FlatList.removeMany} (default list and items)", - "list", + struct ($"%s{nameof FlatList.removeMany} (default source and items)", + "source", (fun () -> FlatList.removeMany (defaultArray :> seq<_>) defaultArray |> ignore @@ -343,8 +337,8 @@ type FlatListTests () = (FlatList.ofArray Inputs.Several) |> ignore )) - struct ($"%s{nameof FlatList.removeManyWith} (default list and items)", - "list", + struct ($"%s{nameof FlatList.removeManyWith} (default source and items)", + "source", (fun () -> FlatList.removeManyWith EqualityComparer.Default (defaultArray :> seq<_>) defaultArray |> ignore @@ -784,7 +778,7 @@ type FlatListTests () = [] member _.``sub throws for a range out of range as Array.sub does`` (input : int array, startIndex : int, count : int) = ExceptionAssert.Same ( - subParamName, + id, (fun () -> Array.sub input startIndex count |> ignore), (fun () -> FlatList.sub (FlatList.ofArray input) startIndex count @@ -798,7 +792,7 @@ type FlatListTests () = (startIndex : int, count : int) = ExceptionAssert.Same ( - subParamName, + id, (fun () -> Array.sub nullArray startIndex count |> ignore), (fun () -> FlatList.sub defaultArray startIndex count |> ignore), "FlatList.sub must reject a default flat list as Array.sub rejects a null array" From 502f2cfa522a3bda79c6644faad2f7aebfe26f09 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 22:01:55 +0200 Subject: [PATCH 22/27] fixup! docs: `FlatList` in the changelog and the collection table The changelog writes `sub array startIndex count`, with the name that the flat list parameter has now. Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8432344b..5cc2c9d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * `FlatList`: every function throws what the FSharp.Core `Array` function of the same name throws. `take` and `splitAt` throw for a count beyond the length, and a default `ImmutableArray` throws `ArgumentNullException`, also when it is given as a sequence. -* `FlatList.sub` has the meaning of `Array.sub`: `sub list startIndex count` returns `count` elements from `startIndex`. +* `FlatList.sub` has the meaning of `Array.sub`: `sub array startIndex count` returns `count` elements from `startIndex`. It was `sub start stop list`. ### Removed From 256046c2b7793967c49aacc0a26dc22dfd7945f0 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 23:56:15 +0200 Subject: [PATCH 23/27] fixup! feat: `FlatList` module with the vocabulary of the FSharp.Core `Array` module The gap analysis found members of `ImmutableArray<'T>` and of its builder that no function wrapped, and the checks of a default flat list given as a sequence of a base type: - `FlatList` gets `indexOfRange`, `indexOfRangeWith`, `lastIndexOfRange` and `lastIndexOfRangeWith` (the overloads of `IndexOf` and `LastIndexOf` with a range, which the `indexFrom` and `indexRange` families of 1.0.0 were), `replace` and `replaceWith` (`Replace`), and `binarySearchRange`, `binarySearchRangeWithComparer` and `binarySearchWithComparer` (`ImmutableArray.BinarySearch`). The range search without a comparer uses the cached comparer of F# generic comparison, as `binarySearch` does. The BCL calls the count of a range `length`, so the two range searches check the index and the count first, with the type and the message of the BCL, and name the parameter `count`. - `FlatListBuilder` gets `indexOfRange`, `indexOfRangeWith`, `lastIndexOfRange`, `lastIndexOfRangeWith`, `insertManyAt`, `removeAll`, `removeMany`, `removeManyAt`, `removeManyWith`, `removeWith`, `replace`, `replaceWith`, `sortRange`, `sortRangeWith`, `sortRangeWithComparer` and `blit`. They return the builder, except `removeWith`, which reports the removal as `remove` does. `replace` and `replaceWith` leave the builder as it is when no element equals the old value, as the BCL does, and the documentation says so. `removeManyAt` and `blit` name the count `count` and the target `target` and `targetIndex` where the BCL says `length`, `destination` and `destinationIndex`, and they keep the order of the checks, the exceptions and the messages of the BCL. `insertManyAt` checks the index before the values, as `InsertRange` does. - The parameter of a sequence is checked by `checkNotNullOrDefaultSeq`: a default `FlatList` seen as a `seq`, by the covariance of `seq`, is an `ImmutableArray`, and the type test for `ImmutableArray` did not see it, so it failed in the enumerator with `InvalidOperationException`. The type test stays the first step. For any other object the check asks for the generic type and reads `IsDefault` through reflection. The check is inline, because the public members of the computation expression call it, and an inline member cannot call an internal function. `ofSeq` takes the same path. Co-Authored-By: Claude Sonnet 5.5 --- .../FlatList/FlatList.fs | 61 +++- .../FlatList/FlatList.fsi | 228 +++++++++++++ .../FlatList/FlatListBuilder.fs | 113 +++++++ .../FlatList/FlatListBuilder.fsi | 319 ++++++++++++++++++ src/FSharp.Collections.Immutable/Helper.fs | 42 ++- 5 files changed, 759 insertions(+), 4 deletions(-) diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatList.fs b/src/FSharp.Collections.Immutable/FlatList/FlatList.fs index 45a0e465..9ac7375d 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatList.fs +++ b/src/FSharp.Collections.Immutable/FlatList/FlatList.fs @@ -37,7 +37,10 @@ let ofSeq (source : seq<'T>) = | :? ImmutableArray<'T> as array -> checkNotDefault (nameof source) array array - | _ -> Array.ofSeq source |> wrapArray + | _ -> + // A default flat list of a derived type is also a sequence of its base type, and Array.ofSeq would fail on it + checkNotNullOrDefaultSeq (nameof source) source + Array.ofSeq source |> wrapArray [] let ofSpan (span : Span<'T>) = ImmutableArray.Create<'T> span @@ -167,11 +170,37 @@ let tryTail (array : FlatList<'T>) = // ---- Search ---- +// Array.BinarySearch calls the count of a range length. The functions of this module report it as count, the name of +// their own parameter, with the type and the message of the BCL; the check of a range beyond the end stays with the BCL +let private checkBinarySearchRange index count = + if index < 0 then + raise (ArgumentOutOfRangeException (nameof index, ErrorStrings.NeedNonNegativeNumber)) + + if count < 0 then + raise (ArgumentOutOfRangeException (nameof count, ErrorStrings.NeedNonNegativeNumber)) + [] let binarySearch (value : 'T) (array : FlatList<'T>) = checkNotDefault (nameof array) array ImmutableArray.BinarySearch (array, value, DefaultComparer<'T>.Instance) +[] +let binarySearchRange index count (value : 'T) (array : FlatList<'T>) = + checkNotDefault (nameof array) array + checkBinarySearchRange index count + ImmutableArray.BinarySearch (array, index, count, value, DefaultComparer<'T>.Instance) + +[] +let binarySearchRangeWithComparer (comparer : IComparer<'T>) index count (value : 'T) (array : FlatList<'T>) = + checkNotDefault (nameof array) array + checkBinarySearchRange index count + ImmutableArray.BinarySearch (array, index, count, value, comparer) + +[] +let binarySearchWithComparer (comparer : IComparer<'T>) (value : 'T) (array : FlatList<'T>) = + checkNotDefault (nameof array) array + ImmutableArray.BinarySearch (array, value, comparer) + [] let contains (value : 'T) (array : FlatList<'T>) = Array.contains value (unwrapArray (nameof array) array) @@ -206,6 +235,16 @@ let indexOf (value : 'T) (array : FlatList<'T>) = checkNotDefault (nameof array) array array.IndexOf value +[] +let indexOfRange (startIndex : int) (count : int) (value : 'T) (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.IndexOf (value, startIndex, count) + +[] +let indexOfRangeWith (comparer : IEqualityComparer<'T>) (startIndex : int) (count : int) (value : 'T) (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.IndexOf (value, startIndex, count, comparer) + [] let indexOfWith (comparer : IEqualityComparer<'T>) (value : 'T) (array : FlatList<'T>) = checkNotDefault (nameof array) array @@ -216,6 +255,16 @@ let lastIndexOf (value : 'T) (array : FlatList<'T>) = checkNotDefault (nameof array) array array.LastIndexOf value +[] +let lastIndexOfRange (startIndex : int) (count : int) (value : 'T) (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.LastIndexOf (value, startIndex, count) + +[] +let lastIndexOfRangeWith (comparer : IEqualityComparer<'T>) (startIndex : int) (count : int) (value : 'T) (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.LastIndexOf (value, startIndex, count, comparer) + [] let lastIndexOfWith (comparer : IEqualityComparer<'T>) (value : 'T) (array : FlatList<'T>) = checkNotDefault (nameof array) array @@ -463,6 +512,16 @@ let removeWith (comparer : IEqualityComparer<'T>) (item : 'T) (source : FlatList checkNotDefault (nameof source) source source.Remove (item, comparer) +[] +let replace (oldValue : 'T) (newValue : 'T) (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.Replace (oldValue, newValue) + +[] +let replaceWith (comparer : IEqualityComparer<'T>) (oldValue : 'T) (newValue : 'T) (array : FlatList<'T>) = + checkNotDefault (nameof array) array + array.Replace (oldValue, newValue, comparer) + [] let rev (array : FlatList<'T>) = Array.rev (unwrapArray (nameof array) array) |> wrapArray diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi b/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi index 2eb14626..12a4f77c 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi +++ b/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi @@ -348,6 +348,89 @@ val tryTail : array : FlatList<'T> -> FlatList<'T> voption [] val binarySearch : value : 'T -> array : FlatList<'T> -> int when 'T : comparison +/// +/// Searches a range of a sorted flat list for a value with the binary search algorithm. +/// +/// The index of the first element of the range. +/// The number of elements of the range. +/// The value to search for. +/// The input flat list, sorted in the order of F# generic comparison. +/// +/// The index of the value, or the bitwise complement of the index of the first larger element of the range, or the +/// bitwise complement of the index after the range when no element is larger. +/// +/// Thrown when or is negative. +/// Thrown when the range extends beyond the end of the flat list. +/// +/// +/// O(log count). The result is an index of the flat list, not of the range. The function compares as +/// +/// does. +/// +/// +/// The function wraps +/// +/// and throws what it throws. It names the parameter where the BCL names it length. +/// +/// +[] +val binarySearchRange : index : int -> count : int -> value : 'T -> array : FlatList<'T> -> int when 'T : comparison + +/// +/// Searches a range of a flat list that a comparer sorts for a value with the binary search algorithm. +/// +/// +/// The comparer that orders the flat list. stands for +/// . +/// +/// The index of the first element of the range. +/// The number of elements of the range. +/// The value to search for. +/// The input flat list, sorted in the order of the comparer. +/// +/// The index of the value, or the bitwise complement of the index of the first larger element of the range, or the +/// bitwise complement of the index after the range when no element is larger. +/// +/// Thrown when or is negative. +/// Thrown when the range extends beyond the end of the flat list. +/// Thrown when the comparison fails: the comparer throws, which becomes the inner exception, or the comparer is and the elements are not comparable. +/// +/// +/// O(log count). The result is an index of the flat list, not of the range. +/// +/// +/// The function wraps +/// +/// and throws what it throws. It names the parameter where the BCL names it length. +/// +/// +[] +val binarySearchRangeWithComparer : + comparer : IComparer<'T> -> index : int -> count : int -> value : 'T -> array : FlatList<'T> -> int + +/// +/// Searches a flat list that a comparer sorts for a value with the binary search algorithm. +/// +/// +/// The comparer that orders the flat list. stands for +/// . +/// +/// The value to search for. +/// The input flat list, sorted in the order of the comparer. +/// +/// The index of the value, or the bitwise complement of the index of the first larger element, or the bitwise +/// complement of the length when no element is larger. +/// +/// Thrown when the comparison fails: the comparer throws, which becomes the inner exception, or the comparer is and the elements are not comparable. +/// +/// O(log n). The function wraps +/// . +/// Unlike +/// it does not need F# comparison, so a comparer of a key or of a culture can order the flat list. +/// +[] +val binarySearchWithComparer : comparer : IComparer<'T> -> value : 'T -> array : FlatList<'T> -> int + /// /// Returns whether the flat list contains an element equal to a value, in F# structural equality. /// @@ -460,6 +543,56 @@ val forall2 : predicate : ('T1 -> 'T2 -> bool) -> array1 : FlatList<'T1> -> arra [] val indexOf : value : 'T -> array : FlatList<'T> -> int +/// +/// Returns the index of the first element equal to a value in a range, in +/// . +/// +/// The index of the first element of the range. +/// The number of elements of the range. +/// The value to find. +/// The input flat list. +/// The index in the flat list of the first equal element of the range, or -1. +/// Thrown when or is outside the flat list. +/// +/// +/// O(count). The function wraps +/// . +/// +/// +/// The start index must be less than the length, as in the BCL member, also for an empty range. An empty flat list +/// accepts only the start index 0 with the count 0, and the result is -1. +/// +/// +[] +val indexOfRange : startIndex : int -> count : int -> value : 'T -> array : FlatList<'T> -> int + +/// +/// Returns the index of the first element equal to a value in a range, in a given equality comparer. +/// +/// +/// The equality comparer. stands for +/// . +/// +/// The index of the first element of the range. +/// The number of elements of the range. +/// The value to find. +/// The input flat list. +/// The index in the flat list of the first equal element of the range, or -1. +/// Thrown when or is outside the flat list. +/// +/// +/// O(count). The function wraps +/// . +/// +/// +/// The start index must be less than the length, as in the BCL member, also for an empty range. An empty flat list +/// accepts only the start index 0 with the count 0, and the result is -1. +/// +/// +[] +val indexOfRangeWith : + comparer : IEqualityComparer<'T> -> startIndex : int -> count : int -> value : 'T -> array : FlatList<'T> -> int + /// /// Returns the index of the first element equal to a value in a given equality comparer. /// @@ -486,6 +619,59 @@ val indexOfWith : comparer : IEqualityComparer<'T> -> value : 'T -> array : Flat [] val lastIndexOf : value : 'T -> array : FlatList<'T> -> int +/// +/// Returns the index of the last element equal to a value in a range that is searched backward, in +/// . +/// +/// The index of the last element of the range, where the search starts. +/// The number of elements of the range, counted backward from the start index. +/// The value to find. +/// The input flat list. +/// The index in the flat list of the last equal element of the range, or -1. +/// Thrown when or is outside the flat list. +/// +/// +/// O(count). The range holds the elements from down to +/// startIndex - count + 1. The function wraps +/// . +/// +/// +/// The start index must be less than the length, as in the BCL member, also for an empty range. An empty flat list +/// accepts only the start index 0 with the count 0, and the result is -1. +/// +/// +[] +val lastIndexOfRange : startIndex : int -> count : int -> value : 'T -> array : FlatList<'T> -> int + +/// +/// Returns the index of the last element equal to a value in a range that is searched backward, in a given equality +/// comparer. +/// +/// +/// The equality comparer. stands for +/// . +/// +/// The index of the last element of the range, where the search starts. +/// The number of elements of the range, counted backward from the start index. +/// The value to find. +/// The input flat list. +/// The index in the flat list of the last equal element of the range, or -1. +/// Thrown when or is outside the flat list. +/// +/// +/// O(count). The range holds the elements from down to +/// startIndex - count + 1. The function wraps +/// . +/// +/// +/// The start index must be less than the length, as in the BCL member, also for an empty range. An empty flat list +/// accepts only the start index 0 with the count 0, and the result is -1. +/// +/// +[] +val lastIndexOfRangeWith : + comparer : IEqualityComparer<'T> -> startIndex : int -> count : int -> value : 'T -> array : FlatList<'T> -> int + /// /// Returns the index of the last element equal to a value in a given equality comparer. /// @@ -881,6 +1067,48 @@ val removeManyWith : comparer : IEqualityComparer<'T> -> items : seq<'T> -> sour [] val removeWith : comparer : IEqualityComparer<'T> -> item : 'T -> source : FlatList<'T> -> FlatList<'T> +/// +/// Replaces the first element that equals a value with another value, in +/// . +/// +/// The value to replace. +/// The new value. +/// The input flat list. +/// The flat list with the new value in the place of the first element that equals the old value. +/// Thrown when no element equals . +/// +/// +/// O(n). The function wraps . It returns a +/// new flat list also when the new value equals the old one. +/// +/// +/// To replace the element at an index, use +/// . +/// +/// +[] +val replace : oldValue : 'T -> newValue : 'T -> array : FlatList<'T> -> FlatList<'T> + +/// +/// Replaces the first element that equals a value with another value, in a given equality comparer. +/// +/// +/// The equality comparer. stands for +/// . +/// +/// The value to replace. +/// The new value. +/// The input flat list. +/// The flat list with the new value in the place of the first element that equals the old value. +/// Thrown when no element equals . +/// +/// O(n). The function wraps +/// . +/// It returns a new flat list also when the new value equals the old one. +/// +[] +val replaceWith : comparer : IEqualityComparer<'T> -> oldValue : 'T -> newValue : 'T -> array : FlatList<'T> -> FlatList<'T> + /// Returns the elements in reverse order. /// The input flat list. /// The reversed flat list. diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs b/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs index c034a484..1cf9030b 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fs @@ -26,9 +26,37 @@ let contains (value : 'T) (builder : FlatList<'T>.Builder) = builder.Contains va [] let indexOf (value : 'T) (builder : FlatList<'T>.Builder) = builder.IndexOf value +[] +let indexOfRange (startIndex : int) (count : int) (value : 'T) (builder : FlatList<'T>.Builder) = + builder.IndexOf (value, startIndex, count) + +[] +let indexOfRangeWith + (comparer : IEqualityComparer<'T>) + (startIndex : int) + (count : int) + (value : 'T) + (builder : FlatList<'T>.Builder) + = + builder.IndexOf (value, startIndex, count, comparer) + [] let lastIndexOf (value : 'T) (builder : FlatList<'T>.Builder) = builder.LastIndexOf value +[] +let lastIndexOfRange (startIndex : int) (count : int) (value : 'T) (builder : FlatList<'T>.Builder) = + builder.LastIndexOf (value, startIndex, count) + +[] +let lastIndexOfRangeWith + (comparer : IEqualityComparer<'T>) + (startIndex : int) + (count : int) + (value : 'T) + (builder : FlatList<'T>.Builder) + = + builder.LastIndexOf (value, startIndex, count, comparer) + // ---- Transformation ---- [] @@ -52,14 +80,65 @@ let insertAt index (value : 'T) (builder : FlatList<'T>.Builder) = builder.Insert (index, value) builder +[] +let insertManyAt index (values : seq<'T>) (builder : FlatList<'T>.Builder) = + // The BCL checks the index before the values, so an index out of range wins over a missing sequence + if index >= 0 && index <= builder.Count then + checkNotNullOrDefaultSeq (nameof values) values + + builder.InsertRange (index, values) + builder + [] let remove (value : 'T) (builder : FlatList<'T>.Builder) = builder.Remove value +[] +let removeAll (predicate : 'T -> bool) (builder : FlatList<'T>.Builder) = + builder.RemoveAll (Predicate<'T> predicate) + builder + [] let removeAt index (builder : FlatList<'T>.Builder) = builder.RemoveAt index builder +[] +let removeMany (items : seq<'T>) (builder : FlatList<'T>.Builder) = + checkNotNullOrDefaultSeq (nameof items) items + builder.RemoveRange items + builder + +[] +let removeManyAt index count (builder : FlatList<'T>.Builder) = + // The BCL calls the count length. The checks keep its order, its exception and its message + if index < 0 || index > builder.Count then + raise (ArgumentOutOfRangeException (nameof index)) + + if count < 0 || index > builder.Count - count then + raise (ArgumentOutOfRangeException (nameof count)) + + builder.RemoveRange (index, count) + builder + +[] +let removeManyWith (comparer : IEqualityComparer<'T>) (items : seq<'T>) (builder : FlatList<'T>.Builder) = + checkNotNullOrDefaultSeq (nameof items) items + builder.RemoveRange (items, comparer) + builder + +[] +let removeWith (comparer : IEqualityComparer<'T>) (value : 'T) (builder : FlatList<'T>.Builder) = builder.Remove (value, comparer) + +[] +let replace (oldValue : 'T) (newValue : 'T) (builder : FlatList<'T>.Builder) = + builder.Replace (oldValue, newValue) + builder + +[] +let replaceWith (comparer : IEqualityComparer<'T>) (oldValue : 'T) (newValue : 'T) (builder : FlatList<'T>.Builder) = + builder.Replace (oldValue, newValue, comparer) + builder + [] let rev (builder : FlatList<'T>.Builder) = builder.Reverse () @@ -82,6 +161,21 @@ let sort (builder : FlatList<'T>.Builder) = builder.Sort DefaultComparer<'T>.Instance builder +[] +let sortRange index count (builder : FlatList<'T>.Builder) = + builder.Sort (index, count, DefaultComparer<'T>.Instance) + builder + +[] +let sortRangeWith (comparison : 'T -> 'T -> int) index count (builder : FlatList<'T>.Builder) = + builder.Sort (index, count, Comparer<'T>.Create(Comparison<'T> comparison)) + builder + +[] +let sortRangeWithComparer (comparer : IComparer<'T>) index count (builder : FlatList<'T>.Builder) = + builder.Sort (index, count, comparer) + builder + [] let sortWith (comparer : 'T -> 'T -> int) (builder : FlatList<'T>.Builder) = builder.Sort (Comparison<'T> comparer) @@ -94,6 +188,25 @@ let sortWithComparer (comparer : IComparer<'T>) (builder : FlatList<'T>.Builder) // ---- Conversion ---- +[] +let blit (source : FlatList<'T>.Builder) sourceIndex (target : 'T array) targetIndex count = + // The BCL calls the target destination, its index destinationIndex and the count length. The checks keep its + // order, its exceptions and its messages + match withNull target with + | null -> nullArg (nameof target) + | _ -> () + + if count < 0 then + raise (ArgumentOutOfRangeException (nameof count)) + + if sourceIndex < 0 || sourceIndex > source.Count - count then + raise (ArgumentOutOfRangeException (nameof sourceIndex)) + + if targetIndex < 0 || targetIndex > target.Length - count then + raise (ArgumentOutOfRangeException (nameof targetIndex)) + + source.CopyTo (sourceIndex, target, targetIndex, count) + [] let drainToFlatList (builder : FlatList<'T>.Builder) : FlatList<'T> = builder.DrainToImmutable () diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fsi b/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fsi index 3efb3950..a5a56f3b 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fsi +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fsi @@ -58,6 +58,56 @@ val contains : value : 'T -> builder : FlatList<'T>.Builder -> bool [] val indexOf : value : 'T -> builder : FlatList<'T>.Builder -> int +/// +/// Returns the index of the first element equal to a value in a range, in +/// . +/// +/// The index of the first element of the range. +/// The number of elements of the range. +/// The value to find. +/// The builder. +/// The index in the builder of the first equal element of the range, or -1. +/// Thrown when or is outside the builder. +/// +/// +/// O(count). The function wraps +/// . +/// +/// +/// The start index must be less than the count of the builder, as in the BCL member, also for an empty range. An empty +/// builder accepts only the start index 0 with the count 0, and the result is -1. +/// +/// +[] +val indexOfRange : startIndex : int -> count : int -> value : 'T -> builder : FlatList<'T>.Builder -> int + +/// +/// Returns the index of the first element equal to a value in a range, in a given equality comparer. +/// +/// +/// The equality comparer. stands for +/// . +/// +/// The index of the first element of the range. +/// The number of elements of the range. +/// The value to find. +/// The builder. +/// The index in the builder of the first equal element of the range, or -1. +/// Thrown when or is outside the builder. +/// +/// +/// O(count). The function wraps +/// . +/// +/// +/// The start index must be less than the count of the builder, as in the BCL member, also for an empty range. An empty +/// builder accepts only the start index 0 with the count 0, and the result is -1. +/// +/// +[] +val indexOfRangeWith : + comparer : IEqualityComparer<'T> -> startIndex : int -> count : int -> value : 'T -> builder : FlatList<'T>.Builder -> int + /// /// Returns the index of the last element equal to a value, or -1. /// @@ -67,6 +117,59 @@ val indexOf : value : 'T -> builder : FlatList<'T>.Builder -> int [] val lastIndexOf : value : 'T -> builder : FlatList<'T>.Builder -> int +/// +/// Returns the index of the last element equal to a value in a range that is searched backward, in +/// . +/// +/// The index of the last element of the range, where the search starts. +/// The number of elements of the range, counted backward from the start index. +/// The value to find. +/// The builder. +/// The index in the builder of the last equal element of the range, or -1. +/// Thrown when or is outside the builder. +/// +/// +/// O(count). The range holds the elements from down to +/// startIndex - count + 1. The function wraps +/// . +/// +/// +/// The start index must be less than the count of the builder, as in the BCL member, also for an empty range. An empty +/// builder accepts only the start index 0 with the count 0, and the result is -1. +/// +/// +[] +val lastIndexOfRange : startIndex : int -> count : int -> value : 'T -> builder : FlatList<'T>.Builder -> int + +/// +/// Returns the index of the last element equal to a value in a range that is searched backward, in a given equality +/// comparer. +/// +/// +/// The equality comparer. stands for +/// . +/// +/// The index of the last element of the range, where the search starts. +/// The number of elements of the range, counted backward from the start index. +/// The value to find. +/// The builder. +/// The index in the builder of the last equal element of the range, or -1. +/// Thrown when or is outside the builder. +/// +/// +/// O(count). The range holds the elements from down to +/// startIndex - count + 1. The function wraps +/// . +/// +/// +/// The start index must be less than the count of the builder, as in the BCL member, also for an empty range. An empty +/// builder accepts only the start index 0 with the count 0, and the result is -1. +/// +/// +[] +val lastIndexOfRangeWith : + comparer : IEqualityComparer<'T> -> startIndex : int -> count : int -> value : 'T -> builder : FlatList<'T>.Builder -> int + // ---- Transformation ---- /// Adds a value after the last element. @@ -99,6 +202,23 @@ val clear : builder : FlatList<'T>.Builder -> FlatList<'T>.Builder [] val insertAt : index : int -> value : 'T -> builder : FlatList<'T>.Builder -> FlatList<'T>.Builder +/// +/// Inserts values at an index. +/// +/// The index of the first inserted value, from 0 to the count. +/// The values to insert. +/// The builder. +/// The same builder. +/// Thrown when the index is out of range. +/// Thrown when is or a default flat list. +/// +/// O(n + m) for m values. The function wraps +/// . +/// The index is checked before the values, as the BCL member does. +/// +[] +val insertManyAt : index : int -> values : seq<'T> -> builder : FlatList<'T>.Builder -> FlatList<'T>.Builder + /// /// Removes the first element equal to a value, in . /// @@ -108,6 +228,19 @@ val insertAt : index : int -> value : 'T -> builder : FlatList<'T>.Builder -> Fl [] val remove : value : 'T -> builder : FlatList<'T>.Builder -> bool +/// +/// Removes every element that satisfies a predicate. +/// +/// The function to test the elements. +/// The builder. +/// The same builder. +/// +/// O(n). The function wraps +/// . +/// +[] +val removeAll : predicate : ('T -> bool) -> builder : FlatList<'T>.Builder -> FlatList<'T>.Builder + /// Removes the element at an index. /// The index of the removed element. /// The builder. @@ -116,6 +249,122 @@ val remove : value : 'T -> builder : FlatList<'T>.Builder -> bool [] val removeAt : index : int -> builder : FlatList<'T>.Builder -> FlatList<'T>.Builder +/// +/// Removes one element for each of the given values, in +/// . +/// +/// The values to remove. +/// The builder. +/// The same builder. +/// Thrown when is or a default flat list. +/// +/// O(n * m) for m values. Each value removes the first remaining element that equals it; a value that equals no remaining +/// element removes nothing. The function wraps +/// . +/// +[] +val removeMany : items : seq<'T> -> builder : FlatList<'T>.Builder -> FlatList<'T>.Builder + +/// +/// Removes a range of elements. +/// +/// The index of the first removed element. +/// The number of removed elements. +/// The builder. +/// The same builder. +/// Thrown when is out of range, or is negative or the range extends beyond the end of the builder. +/// +/// O(n). The function wraps +/// . It +/// names the parameter where the BCL names it length. +/// +[] +val removeManyAt : index : int -> count : int -> builder : FlatList<'T>.Builder -> FlatList<'T>.Builder + +/// +/// Removes one element for each of the given values, in a given equality comparer. +/// +/// +/// The equality comparer. stands for +/// . +/// +/// The values to remove. +/// The builder. +/// The same builder. +/// Thrown when is or a default flat list. +/// +/// O(n * m) for m values. Each value removes the first remaining element that equals it; a value that equals no remaining +/// element removes nothing. The function wraps +/// . +/// +[] +val removeManyWith : comparer : IEqualityComparer<'T> -> items : seq<'T> -> builder : FlatList<'T>.Builder -> FlatList<'T>.Builder + +/// +/// Removes the first element equal to a value, in a given equality comparer. +/// +/// +/// The equality comparer. stands for +/// . +/// +/// The value to remove. +/// The builder. +/// when an element was removed. +/// +/// O(n). The function wraps +/// . +/// +[] +val removeWith : comparer : IEqualityComparer<'T> -> value : 'T -> builder : FlatList<'T>.Builder -> bool + +/// +/// Replaces the first element that equals a value with another value, in +/// . +/// +/// The value to replace. +/// The new value. +/// The builder. +/// The same builder. +/// +/// +/// O(n). The function wraps +/// . +/// +/// +/// The builder stays as it is when no element equals the old value: the BCL member does not throw. The function +/// +/// of the flat list throws in that case. +/// +/// +[] +val replace : oldValue : 'T -> newValue : 'T -> builder : FlatList<'T>.Builder -> FlatList<'T>.Builder + +/// +/// Replaces the first element that equals a value with another value, in a given equality comparer. +/// +/// +/// The equality comparer. stands for +/// . +/// +/// The value to replace. +/// The new value. +/// The builder. +/// The same builder. +/// +/// +/// O(n). The function wraps +/// . +/// +/// +/// The builder stays as it is when no element equals the old value: the BCL member does not throw. The function +/// +/// of the flat list throws in that case. +/// +/// +[] +val replaceWith : + comparer : IEqualityComparer<'T> -> oldValue : 'T -> newValue : 'T -> builder : FlatList<'T>.Builder -> FlatList<'T>.Builder + /// Reverses the order of the elements. /// The builder. /// The same builder. @@ -150,6 +399,57 @@ val updateAt : index : int -> value : 'T -> builder : FlatList<'T>.Builder -> Fl [] val sort : builder : FlatList<'T>.Builder -> FlatList<'T>.Builder when 'T : comparison +/// +/// Sorts a range of the elements in the order of F# generic comparison. +/// +/// The index of the first element of the range. +/// The number of elements of the range. +/// The builder. +/// The same builder. +/// Thrown when or is negative, or the range extends beyond the end of the builder. +/// +/// O(count log count). This is not a stable sort. The elements outside the range keep their places. The function wraps +/// . +/// +[] +val sortRange : index : int -> count : int -> builder : FlatList<'T>.Builder -> FlatList<'T>.Builder when 'T : comparison + +/// +/// Sorts a range of the elements with a comparison function. +/// +/// The function that compares two elements. +/// The index of the first element of the range. +/// The number of elements of the range. +/// The builder. +/// The same builder. +/// Thrown when or is negative, or the range extends beyond the end of the builder. +/// +/// O(count log count). This is not a stable sort. The elements outside the range keep their places. The function wraps +/// . +/// +[] +val sortRangeWith : + comparison : ('T -> 'T -> int) -> index : int -> count : int -> builder : FlatList<'T>.Builder -> FlatList<'T>.Builder + +/// +/// Sorts a range of the elements with a comparer. +/// +/// +/// The comparer. stands for . +/// +/// The index of the first element of the range. +/// The number of elements of the range. +/// The builder. +/// The same builder. +/// Thrown when or is negative, or the range extends beyond the end of the builder. +/// +/// O(count log count). This is not a stable sort. The elements outside the range keep their places. The function wraps +/// . +/// +[] +val sortRangeWithComparer : + comparer : IComparer<'T> -> index : int -> count : int -> builder : FlatList<'T>.Builder -> FlatList<'T>.Builder + /// Sorts the elements with a comparison function. /// The function that compares two elements. /// The builder. @@ -168,6 +468,25 @@ val sortWithComparer : comparer : IComparer<'T> -> builder : FlatList<'T>.Builde // ---- Conversion ---- +/// +/// Copies a range of the builder into an array. +/// +/// The builder. +/// The index of the first element to copy. +/// The array to copy to. +/// The index in the target array of the first copied element. +/// The number of elements to copy. +/// Thrown when is . +/// Thrown when a range is outside its builder or array. +/// +/// O(count). The function wraps +/// . +/// It names the parameters , and where +/// the BCL names them destination, destinationIndex and length. +/// +[] +val blit : source : FlatList<'T>.Builder -> sourceIndex : int -> target : 'T array -> targetIndex : int -> count : int -> unit + /// /// Returns the contents of the builder as a flat list and empties the builder. /// diff --git a/src/FSharp.Collections.Immutable/Helper.fs b/src/FSharp.Collections.Immutable/Helper.fs index 038a183c..efb31d80 100644 --- a/src/FSharp.Collections.Immutable/Helper.fs +++ b/src/FSharp.Collections.Immutable/Helper.fs @@ -32,22 +32,53 @@ module internal ImmutableCollectionUtil = if array.IsDefault then nullArg name + /// + /// Tells whether an object is a default , of any element type. + /// + /// + /// + /// is covariant, so a sequence of a reference type is also a + /// sequence of its base types: a default ImmutableArray<string> can be a seq<obj>. The type + /// test :? ImmutableArray<obj> does not recognize it, because the type is another instantiation. + /// + /// + /// The function finds the instantiation from the type of the object and reads the property IsDefault through + /// reflection. It allocates nothing for an object of another type. It is inline, because the public inline members of + /// the computation expression call the check, and an inline member cannot call an internal function. + /// + /// + let inline isDefaultImmutableArray (source : obj) : bool = + let sourceType = source.GetType () + + sourceType.IsGenericType + && Type.(=) (sourceType.GetGenericTypeDefinition (), typedefof>) + && unbox((nonNull (sourceType.GetProperty "IsDefault")).GetValue source) + /// /// Throws for a sequence and for a default /// that is given as a sequence. /// /// + /// /// A default immutable array converts to a sequence without a check, and the sequence fails with /// when a function enumerates it. A function that takes a sequence /// calls this check, which treats a default immutable array as FSharp.Core treats a sequence /// (ADR 0001, D6). Any other sequence passes. + /// + /// + /// An immutable array of the element type is recognized by its type, without reflection. A default immutable array of + /// a derived reference type, which arrives as a sequence of a base type by covariance, is recognized by its generic + /// type and by reading IsDefault through reflection. + /// /// let inline checkNotNullOrDefaultSeq name (source : seq<'T>) = checkNotNull name source match source with | :? ImmutableArray<'T> as array -> checkNotDefault name array - | _ -> () + | _ -> + if isDefaultImmutableArray source then + nullArg name /// /// The array under an immutable array, without a copy (ADR 0001, D5). @@ -111,11 +142,12 @@ module internal Comparers = static member Instance : IComparer<'T> = comparer /// -/// The exception messages of FSharp.Core (FSCore.resx), in English. +/// The exception messages of FSharp.Core (FSCore.resx) and of the .NET libraries, in English. /// /// /// The modules throw what the FSharp.Core function that they mirror throws (ADR 0001, D6). FSharp.Core keeps its resources -/// internal, so the texts are copied. +/// internal, so the texts are copied. The texts of the .NET libraries are for the functions that report a BCL exception +/// under the name of their own parameter. /// module internal ErrorStrings = @@ -156,6 +188,10 @@ module internal ErrorStrings = [] let ListsHaveDifferentLengths = "The lists have different lengths." + /// The message of the .NET libraries for a negative argument, as `Array.BinarySearch` reports it. + [] + let NeedNonNegativeNumber = "Non-negative number required." + [] let NotAPermutation = "The function did not compute a permutation." From 9ee3a95b9ad563348fb6deaf750418b713c68880 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 23:56:16 +0200 Subject: [PATCH 24/27] fixup! test: shared contracts of the sequence-like modules, run for `FlatList` Tests of the new functions, as rows: - `FlatListSearchTests` runs the searches of a range of the flat list and of its builder on every valid range of the inputs, against a search written by hand, with the comparer that ignores the case for the functions with a comparer. The ranges that the BCL rejects run against the BCL member (`ExceptionAssert.Same`), with the parameter names of the functions. The binary searches of a range run on every valid range of sorted integers, in ascending and in descending order, and on strings that a comparer sorts; the ordinal order of the range search without a comparer, `null` for the default comparer, and a comparer that throws are pinned. - `replace` and `replaceWith` against a first-occurrence oracle, the exception for a value that no element equals, and a new flat list for equal values. - The functions of the builder against `Array` and the oracles: `insertManyAt`, `removeManyAt`, `removeMany`, `removeManyWith`, `removeWith`, `removeAll`, `replace`, `replaceWith`, the range sorts and `blit`. The ranges out of range run against the BCL member, with the parameter names of the functions, and a test proves that every function that changes the builder returns it. - A default `FlatList` seen as a `seq` runs through `ofSeq`, `insertManyAt`, `except`, `removeMany`, `removeManyWith`, the functions of the builder, `yield!` and `for`, with the parameter names, and a flat list of strings that is not default must pass. - `sub` also compares the second line of the message, which names the arguments and the length. The oracles move to `FlatListOracles` and the assertion of a rejected null to `FlatListAssert`, so that the tests of the flat list and of the builder share them. Co-Authored-By: Claude Sonnet 5.5 --- .../FSharp.Collections.Immutable.Tests.fsproj | 3 + .../FlatList/FlatListAssert.fs | 15 + .../FlatList/FlatListBuilderTests.fs | 533 ++++++++++++++++++ .../FlatListComputationExpressionTests.fs | 47 ++ .../FlatList/FlatListOracles.fs | 99 ++++ .../FlatList/FlatListSearchTests.fs | 458 +++++++++++++++ .../FlatList/FlatListTests.fs | 267 +++++++-- 7 files changed, 1376 insertions(+), 46 deletions(-) create mode 100644 tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListAssert.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOracles.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListSearchTests.fs 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 fb5c688f..67aef272 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj +++ b/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj @@ -36,8 +36,11 @@ + + + diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListAssert.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListAssert.fs new file mode 100644 index 00000000..414447a4 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListAssert.fs @@ -0,0 +1,15 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// The assertions of the tests of the flat list and of its builder. +[] +type FlatListAssert = + + /// Asserts that an action rejects its input with an ArgumentNullException that names the parameter. + static member RejectsNull (label : string, paramName : string, actual : unit -> unit) = + let ex = + Assert.ThrowsExactly(Action actual, $"%s{label} must throw ArgumentNullException") + + Assert.AreEqual(paramName, ex.ParamName, $"%s{label} must name the parameter of the rejected input") diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListBuilderTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListBuilderTests.fs index b50802c3..b9e80eb2 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListBuilderTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListBuilderTests.fs @@ -15,6 +15,201 @@ type FlatListBuilderTests () = builder.AddRange Inputs.Several builder + + /// The length of the arrays that the blits copy into. + static let targetLength = 5 + + /// The functions of the builder that take a sequence, by name and by the input that they reject, with the parameter + /// that names the input and the function on a builder. + static let sequenceRejections : Map unit))> = + let nullValues = Unchecked.defaultof> + let defaultValues = Unchecked.defaultof> :> seq + let integers () = FlatList.builder() + let objects () = FlatList.builder() + let comparer = EqualityComparer.Default + + Map.ofArray [| + "addRange (default flat list of strings as objects)", + struct ("values", + (fun () -> + FlatListBuilder.addRange FlatListOracles.defaultStringsAsObjects (objects ()) + |> ignore + )) + "insertManyAt (null sequence)", + struct ("values", + (fun () -> + FlatListBuilder.insertManyAt 0 nullValues (integers ()) + |> ignore + )) + "insertManyAt (default flat list)", + struct ("values", + (fun () -> + FlatListBuilder.insertManyAt 0 defaultValues (integers ()) + |> ignore + )) + "insertManyAt (default flat list of strings as objects)", + struct ("values", + (fun () -> + FlatListBuilder.insertManyAt 0 FlatListOracles.defaultStringsAsObjects (objects ()) + |> ignore + )) + "removeMany (null sequence)", + struct ("items", + (fun () -> + FlatListBuilder.removeMany nullValues (integers ()) + |> ignore + )) + "removeMany (default flat list)", + struct ("items", + (fun () -> + FlatListBuilder.removeMany defaultValues (integers ()) + |> ignore + )) + "removeMany (default flat list of strings as objects)", + struct ("items", + (fun () -> + FlatListBuilder.removeMany FlatListOracles.defaultStringsAsObjects (objects ()) + |> ignore + )) + "removeManyWith (null sequence)", + struct ("items", + (fun () -> + FlatListBuilder.removeManyWith comparer nullValues (integers ()) + |> ignore + )) + "removeManyWith (default flat list)", + struct ("items", + (fun () -> + FlatListBuilder.removeManyWith comparer defaultValues (integers ()) + |> ignore + )) + "removeManyWith (default flat list of strings as objects)", + struct ("items", + (fun () -> + FlatListBuilder.removeManyWith + EqualityComparer.Default + FlatListOracles.defaultStringsAsObjects + (objects ()) + |> ignore + )) + |] + + /// The functions of the builder that change it and return it, by name, applied with valid arguments to a builder of + /// the input with several elements. + static let chainedFunctions : Map.Builder -> FlatList.Builder> = + let comparer = EqualityComparer.Default + + Map.ofArray [| + nameof FlatListBuilder.insertManyAt, FlatListBuilder.insertManyAt 1 [| 100 |] + nameof FlatListBuilder.removeAll, FlatListBuilder.removeAll (fun item -> item < 0) + nameof FlatListBuilder.removeMany, FlatListBuilder.removeMany [| 5 |] + nameof FlatListBuilder.removeManyAt, FlatListBuilder.removeManyAt 1 2 + nameof FlatListBuilder.removeManyWith, FlatListBuilder.removeManyWith comparer [| 5 |] + nameof FlatListBuilder.replace, FlatListBuilder.replace 5 99 + nameof FlatListBuilder.replaceWith, FlatListBuilder.replaceWith comparer 5 99 + nameof FlatListBuilder.sortRange, FlatListBuilder.sortRange 1 4 + nameof FlatListBuilder.sortRangeWith, FlatListBuilder.sortRangeWith compare 1 4 + nameof FlatListBuilder.sortRangeWithComparer, FlatListBuilder.sortRangeWithComparer Comparer.Default 1 4 + |] + + /// The functions that sort a range, by name, with the function and the sort of the range in the oracle. The names are + /// the ones of the table of FlatList, so that the rows of its tests run here too. + static let rangeSorts + : Map int -> FlatList.Builder -> FlatList.Builder) * (int array -> int array))> = + Map.ofArray [| + nameof FlatList.sortRange, struct (FlatListBuilder.sortRange, Array.sortWith compare) + nameof FlatList.sortRangeWith, + struct (FlatListBuilder.sortRangeWith FlatListOracles.descending, Array.sortWith FlatListOracles.descending) + nameof FlatList.sortRangeWithComparer, + struct (FlatListBuilder.sortRangeWithComparer (Comparer.Create(Comparison FlatListOracles.byAbsolute)), + Array.sortWith FlatListOracles.byAbsolute) + |] + + /// The predicates of removeAll, by name. + static let removalPredicates : Map bool> = + Map.ofArray [| + "negative", (fun item -> item < 0) + "positive", (fun item -> item > 0) + "every element", (fun _ -> true) + "no element", (fun _ -> false) + |] + + /// The names of the functions that take a sequence, as the rows of the test of the sequences that they reject. + static member SequenceRejections = sequenceRejections.Keys + + /// The names of the functions that change the builder and return it, as the rows of their test. + static member ChainedFunctions = chainedFunctions.Keys + + /// Every index of every input, with the values to insert: none, one and two. + static member InsertionPoints = seq { + for input in Inputs.All do + for index in 0 .. input.Length do + for values in [| [||]; [| 100 |]; [| 100; 200 |] |] do + struct (input, index, values) + } + + /// Every input with an index below its first element and one beyond its end. + static member InsertionPointsOutOfRange = seq { + for input in Inputs.All do + for index in [| -1; input.Length + 1 |] do + struct (input, index) + } + + /// Every range of every input, as the index of its first element and its length. + static member RemovalRanges = seq { + for input in Inputs.All do + for index in 0 .. input.Length do + for count in 0 .. input.Length - index do + struct (input, index, count) + } + + /// The ranges that the removal of a range rejects: an index out of range, and a count that is negative or beyond the + /// end. + static member RemovalRangesOutOfRange = seq { + struct (Inputs.Empty, 1, 0) + struct (Inputs.Empty, 0, 1) + struct (Inputs.Empty, -1, 0) + struct (Inputs.Several, -1, 0) + struct (Inputs.Several, 9, 0) + struct (Inputs.Several, 0, -1) + struct (Inputs.Several, 0, 9) + struct (Inputs.Several, 5, 4) + struct (Inputs.Several, 8, 1) + struct (Inputs.Several, -1, -1) + } + + /// Every predicate of removeAll on every input. + static member RemovalPredicates = seq { + for name in removalPredicates.Keys do + for input in Inputs.All do + struct (name, input) + } + + /// Every range of every input that fits a target of five elements, copied to the first and to the last place that fits. + static member BlitRanges = seq { + for input in Inputs.All do + for sourceIndex in 0 .. input.Length do + for count in 0 .. min (input.Length - sourceIndex) targetLength do + for targetIndex in Array.distinct [| 0; targetLength - count |] do + struct (input, sourceIndex, targetIndex, count) + } + + /// The ranges that the blit rejects, for a target of five elements: in the order of the checks of the BCL, a negative + /// count, a source range out of range and a target range out of range. + static member BlitRangesOutOfRange = seq { + struct (Inputs.Empty, 1, 0, 0) + struct (Inputs.Empty, 0, 0, 1) + struct (Inputs.Several, 6, 0, 3) + struct (Inputs.Several, 0, 3, 3) + struct (Inputs.Several, 0, 6, 0) + struct (Inputs.Several, -1, 0, 1) + struct (Inputs.Several, 0, -1, 1) + struct (Inputs.Several, 0, 0, -1) + struct (Inputs.Several, 9, 0, 0) + struct (Inputs.Several, 0, 0, 9) + struct (Inputs.Several, -1, -1, -1) + } + [] member _.``build returns the elements that the action adds`` () = let array = FlatList.build (fun builder -> builder.AddRange Inputs.Several) @@ -232,3 +427,341 @@ type FlatListBuilderTests () = FlatListBuilder.toArray builder, "FlatListBuilder.toArray must copy the elements" ) + + [] + member _.``functions that change the builder return it`` (name : string) = + let builder = filled () + let result = chainedFunctions[name] builder + Assert.AreSame (builder, result, $"FlatListBuilder.%s{name} must return the builder that it changed") + + [] + member _.``functions that take a sequence reject a null sequence, a default flat list and a default flat list of strings`` + (name : string) + = + let struct (paramName, actual) = sequenceRejections[name] + FlatListAssert.RejectsNull ($"FlatListBuilder.%s{name}", paramName, actual) + + [] + member _.``a flat list of strings that is not default passes as a sequence of objects`` () = + let widened = FlatListOracles.stringsAsObjects (FlatList.ofArray [| "a"; "b" |]) + let builder = FlatList.builder() + + // Add the elements, insert them again after the first one, then remove one element for each of them + builder + |> FlatListBuilder.addRange widened + |> FlatListBuilder.insertManyAt 1 widened + |> FlatListBuilder.removeMany widened + |> ignore + + CollectionAssert.AreEqual ( + [| "a" :> obj; "b" :> obj |], + Seq.toArray builder, + "addRange, insertManyAt and removeMany must take the elements of a flat list of strings as objects" + ) + [] + member _.``insertManyAt inserts the values at the index as Array.insertManyAt does`` + (input : int array, index : int, values : int array) + = + let builder = FlatListOracles.builderOf input + FlatListBuilder.insertManyAt index values builder |> ignore + + CollectionAssert.AreEqual ( + Array.insertManyAt index values input, + Seq.toArray builder, + "FlatListBuilder.insertManyAt must insert the values at the index" + ) + + [] + member _.``insertManyAt throws for an index out of range as the BCL does`` (input : int array, index : int) = + ExceptionAssert.Same ( + id, + (fun () -> (FlatListOracles.builderOf input).InsertRange(index, [| 100 |])), + (fun () -> + FlatListBuilder.insertManyAt index [| 100 |] (FlatListOracles.builderOf input) + |> ignore + ), + "FlatListBuilder.insertManyAt must throw as InsertRange does for an index out of range" + ) + + [] + member _.``insertManyAt checks the index before the values as the BCL does`` (index : int) = + let values = Unchecked.defaultof> + + ExceptionAssert.Same ( + id, + (fun () -> (filled ()).InsertRange(index, values)), + (fun () -> + FlatListBuilder.insertManyAt index values (filled ()) + |> ignore + ), + "FlatListBuilder.insertManyAt must report the index out of range, not the missing values" + ) + + [] + member _.``removeManyAt removes the range as Array.removeManyAt does`` (input : int array, index : int, count : int) = + let builder = FlatListOracles.builderOf input + FlatListBuilder.removeManyAt index count builder |> ignore + + CollectionAssert.AreEqual ( + Array.removeManyAt index count input, + Seq.toArray builder, + "FlatListBuilder.removeManyAt must remove the range" + ) + + [] + member _.``removeManyAt throws for a range out of range as the BCL does`` (input : int array, index : int, count : int) = + // RemoveRange calls the count length, and the function calls it count + let countOfLength (parameter : string) = + match parameter with + | "length" -> "count" + | other -> other + + ExceptionAssert.Same ( + countOfLength, + (fun () -> (FlatListOracles.builderOf input).RemoveRange(index, count)), + (fun () -> + FlatListBuilder.removeManyAt index count (FlatListOracles.builderOf input) + |> ignore + ), + "FlatListBuilder.removeManyAt must throw as RemoveRange does, with the parameter name count" + ) + + [)>] + member _.``removeMany removes one element for each value`` (input : int array, items : int array) = + let builder = FlatListOracles.builderOf input + FlatListBuilder.removeMany items builder |> ignore + + CollectionAssert.AreEqual ( + Array.fold (fun elements item -> FlatListOracles.removeFirst (=) item elements) input items, + Seq.toArray builder, + "FlatListBuilder.removeMany must remove one element for each value" + ) + + [] + member _.``removeMany compares in the default equality, which tells b from B`` () = + let builder = FlatListOracles.builderOf (FlatListOracles.names ()) + FlatListBuilder.removeMany [| "B" |] builder |> ignore + CollectionAssert.AreEqual ( + [| "a"; "b"; "c" |], + Seq.toArray builder, + "FlatListBuilder.removeMany must not find B equal to b" + ) + + [)>] + member _.``removeManyWith removes one element for each value that the comparer finds equal`` (items : string array) = + let builder = FlatListOracles.builderOf (FlatListOracles.names ()) + FlatListBuilder.removeManyWith StringComparer.OrdinalIgnoreCase items builder + |> ignore + + CollectionAssert.AreEqual ( + Array.fold + (fun elements item -> FlatListOracles.removeFirst FlatListOracles.equalIgnoringCase item elements) + (FlatListOracles.names ()) + items, + Seq.toArray builder, + "FlatListBuilder.removeManyWith must remove one element for each value that the comparer finds equal" + ) + + [)>] + member _.``removeWith removes the first element that the comparer finds equal and reports it`` (value : string) = + let names = FlatListOracles.names () + let builder = FlatListOracles.builderOf names + let removed = FlatListBuilder.removeWith StringComparer.OrdinalIgnoreCase value builder + let expected = FlatListOracles.removeFirst FlatListOracles.equalIgnoringCase value names + + Assert.AreEqual( + expected.Length < names.Length, + removed, + "FlatListBuilder.removeWith must report whether it removed an element" + ) + + CollectionAssert.AreEqual ( + expected, + Seq.toArray builder, + "FlatListBuilder.removeWith must remove the first element that the comparer finds equal" + ) + + [] + member _.``removeAll removes the elements that satisfy the predicate`` (name : string, input : int array) = + let predicate = removalPredicates[name] + let builder = FlatListOracles.builderOf input + FlatListBuilder.removeAll predicate builder |> ignore + + CollectionAssert.AreEqual ( + input |> Array.filter (predicate >> not), + Seq.toArray builder, + $"FlatListBuilder.removeAll must remove the elements of the predicate: %s{name}" + ) + + [)>] + member _.``replace replaces the first equal element`` (input : int array, oldValue : int) = + let builder = FlatListOracles.builderOf input + FlatListBuilder.replace oldValue 99 builder |> ignore + + CollectionAssert.AreEqual ( + FlatListOracles.replaceFirst (=) oldValue 99 input + |> ValueOption.get, + Seq.toArray builder, + "FlatListBuilder.replace must replace the first equal element" + ) + + [)>] + member _.``replace leaves the builder as it is when no element equals the value, as the BCL does`` + (input : int array, value : int) + = + let builder = FlatListOracles.builderOf input + FlatListBuilder.replace value 0 builder |> ignore + + CollectionAssert.AreEqual ( + input, + Seq.toArray builder, + "FlatListBuilder.replace must not change the builder and must not throw when no element equals the value" + ) + + [)>] + member _.``replaceWith replaces the first element that the comparer finds equal`` (oldValue : string) = + let names = FlatListOracles.names () + let builder = FlatListOracles.builderOf names + FlatListBuilder.replaceWith StringComparer.OrdinalIgnoreCase oldValue "X" builder + |> ignore + + CollectionAssert.AreEqual ( + FlatListOracles.replaceFirst FlatListOracles.equalIgnoringCase oldValue "X" names + |> ValueOption.get, + Seq.toArray builder, + "FlatListBuilder.replaceWith must replace the first element that the comparer finds equal" + ) + + [] + member _.``replaceWith leaves the builder as it is when no element equals the value, as the BCL does`` () = + let names = FlatListOracles.names () + let builder = FlatListOracles.builderOf names + FlatListBuilder.replaceWith StringComparer.OrdinalIgnoreCase "d" "X" builder + |> ignore + + CollectionAssert.AreEqual ( + names, + Seq.toArray builder, + "FlatListBuilder.replaceWith must not change the builder and must not throw when no element equals the value" + ) + + [] + member _.``replace and replaceWith compare in the default equality, which tells b from B, for a null comparer`` () = + let comparer = Unchecked.defaultof> + + let replaced = + FlatListOracles.builderOf (FlatListOracles.names ()) + |> FlatListBuilder.replaceWith comparer "B" "X" + + let plain = + FlatListOracles.builderOf (FlatListOracles.names ()) + |> FlatListBuilder.replace "B" "X" + + CollectionAssert.AreEqual ( + [| "a"; "b"; "X"; "c" |], + Seq.toArray replaced, + "FlatListBuilder.replaceWith must compare in the default equality for a null comparer" + ) + + CollectionAssert.AreEqual ( + [| "a"; "b"; "X"; "c" |], + Seq.toArray plain, + "FlatListBuilder.replace must not find B equal to b" + ) + + [)>] + member _.``functions that sort a range sort the range and keep the other elements`` + (name : string, input : int array, index : int, count : int) + = + let struct (sortRange, sortPart) = rangeSorts[name] + let builder = FlatListOracles.builderOf input + sortRange index count builder |> ignore + + let expected = + Array.concat [| + Array.sub input 0 index + sortPart (Array.sub input index count) + Array.sub input (index + count) (input.Length - index - count) + |] + + CollectionAssert.AreEqual ( + expected, + Seq.toArray builder, + $"FlatListBuilder.%s{name} must sort the range and keep the other elements" + ) + + [)>] + member _.``functions that sort a range throw for a range out of range as the BCL does`` + (name : string, index : int, count : int, paramName : string) + = + let struct (sortRange, _) = rangeSorts[name] + + let ex = + Assert.ThrowsExactly( + Action (fun () -> sortRange index count (filled ()) |> ignore), + $"FlatListBuilder.%s{name} must throw ArgumentOutOfRangeException for a range out of range" + ) + + Assert.AreEqual( + paramName, + ex.ParamName, + $"FlatListBuilder.%s{name} must name the parameter that the BCL names" + ) + + [] + member _.``sortRange orders strings ordinally, as F# generic comparison does`` () = + let builder = FlatListOracles.builderOf [| "b"; "A"; "a"; "B" |] + FlatListBuilder.sortRange 0 4 builder |> ignore + + CollectionAssert.AreEqual ( + [| "A"; "B"; "a"; "b" |], + Seq.toArray builder, + "FlatListBuilder.sortRange must sort strings ordinally" + ) + + [] + member _.``blit copies the range into the array as Array.blit does`` + (input : int array, sourceIndex : int, targetIndex : int, count : int) + = + let target = Array.create targetLength (-1) + let expected = Array.copy target + Array.blit input sourceIndex expected targetIndex count + FlatListBuilder.blit (FlatListOracles.builderOf input) sourceIndex target targetIndex count + + CollectionAssert.AreEqual (expected, target, "FlatListBuilder.blit must copy the range into the array") + + [] + member _.``blit throws for a range out of range as the BCL does`` + (input : int array, sourceIndex : int, targetIndex : int, count : int) + = + // CopyTo calls the target destination, its index destinationIndex and the count length + let ofBcl (parameter : string) = + match parameter with + | "destination" -> "target" + | "destinationIndex" -> "targetIndex" + | "length" -> "count" + | other -> other + + ExceptionAssert.Same ( + ofBcl, + (fun () -> (FlatListOracles.builderOf input).CopyTo(sourceIndex, Array.zeroCreate targetLength, targetIndex, count)), + (fun () -> + FlatListBuilder.blit + (FlatListOracles.builderOf input) + sourceIndex + (Array.zeroCreate targetLength) + targetIndex + count + ), + "FlatListBuilder.blit must throw as CopyTo does, with the parameter names of the function" + ) + + [] + member _.``blit rejects a null array before the ranges as the BCL does`` (sourceIndex : int, targetIndex : int, count : int) = + let target = Unchecked.defaultof + + FlatListAssert.RejectsNull ( + "FlatListBuilder.blit", + "target", + (fun () -> FlatListBuilder.blit (filled ()) sourceIndex target targetIndex count) + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs index b2523fdc..93e6248f 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListComputationExpressionTests.fs @@ -10,6 +10,7 @@ type FlatListComputationExpressionTests () = static let defaultArray = Unchecked.defaultof> + [] member _.``an expression without elements is empty`` () = let array : FlatList = flatList { () } @@ -78,6 +79,19 @@ type FlatListComputationExpressionTests () = Assert.AreEqual("items", ex.ParamName, "flatList must name the parameter of the null sequence") + [] + member _.``yield! rejects a default flat list of strings given as a sequence of objects`` () = + let ex = + Assert.ThrowsExactly( + Action (fun () -> + flatList { yield! FlatListOracles.defaultStringsAsObjects } + |> ignore + ), + "flatList must reject a default flat list of strings in yield! of a sequence of objects" + ) + + Assert.AreEqual("items", ex.ParamName, "flatList must name the parameter of the default flat list") + [] member _.``for adds the elements of the body for each item`` () = let array = flatList { @@ -119,6 +133,39 @@ type FlatListComputationExpressionTests () = Assert.AreEqual("source", ex.ParamName, "flatList must name the parameter of the null sequence") + [] + member _.``for rejects a default flat list of strings given as a sequence of objects`` () = + let ex = + Assert.ThrowsExactly( + Action (fun () -> + flatList { + for item in FlatListOracles.defaultStringsAsObjects do + yield item + } + |> ignore + ), + "flatList must reject a default flat list of strings in for over a sequence of objects" + ) + + Assert.AreEqual("source", ex.ParamName, "flatList must name the parameter of the default flat list") + + [] + member _.``yield! and for take a flat list of strings that is not default as a sequence of objects`` () = + let widened = FlatListOracles.stringsAsObjects (FlatList.ofArray [| "a"; "b" |]) + + let array = flatList { + yield! widened + + for item in widened do + yield item + } + + CollectionAssert.AreEqual ( + [| "a" :> obj; "b" :> obj; "a" :> obj; "b" :> obj |], + FlatList.toArray array, + "flatList must take the elements of a flat list of strings as objects in yield! and for" + ) + [] member _.``while adds the elements while the guard holds`` () = let mutable count = 0 diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOracles.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOracles.fs new file mode 100644 index 00000000..e55f5207 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListOracles.fs @@ -0,0 +1,99 @@ +/// The expectations that the tests of the flat list and of its builder compute in plain F#, over arrays. +[] +module FSharp.Collections.Immutable.Tests.FlatListOracles + +open System +open System.Collections.Generic +open FSharp.Collections.Immutable + +/// A flat list of strings as a sequence of objects. A flat list of strings is also a sequence of objects, by the +/// covariance of seq: the element type of the sequence is not the one of the flat list, and a type test for the flat +/// list of objects does not see it. +let stringsAsObjects (strings : FlatList) : seq = + match box strings with + | :? seq as widened -> widened + | _ -> invalidOp "A flat list of strings must be a sequence of objects" + +/// A default flat list of strings as a sequence of objects. +let defaultStringsAsObjects : seq = stringsAsObjects Unchecked.defaultof> +/// A builder that holds a copy of the elements. +let builderOf (elements : 'T array) = FlatList.ofArray elements |> FlatList.toBuilder + +/// The elements without the first one that the equality finds equal to the item: the oracle of the removal by value. +let removeFirst (equal : 'T -> 'T -> bool) (item : 'T) (elements : 'T array) = + match Array.tryFindIndex (equal item) elements with + | Some index -> Array.removeAt index elements + | None -> elements + +/// The elements with the first one that the equality finds equal to the old value replaced by the new value, or +/// ValueNone when no element is equal: the oracle of the replacement. +let replaceFirst (equal : 'T -> 'T -> bool) (oldValue : 'T) (newValue : 'T) (elements : 'T array) = + match Array.tryFindIndex (equal oldValue) elements with + | Some index -> ValueSome (Array.updateAt index newValue elements) + | None -> ValueNone + +/// Tells whether two strings are equal when the case is ignored. +let equalIgnoringCase (left : string) (right : string) = String.Equals (left, right, StringComparison.OrdinalIgnoreCase) + +/// The strings of the removal by value; "b" and "B" are equal when the case is ignored. +let names () = [| "a"; "b"; "B"; "c" |] + +/// The strings of the searches of a range: "b" is there twice, "B" once, and the three are equal when the case is +/// ignored. +let rangeNames () = [| "a"; "b"; "B"; "c"; "b" |] + +/// The comparison of the descending order. +let descending (left : int) (right : int) = compare right left + +/// The comparison of the order of the absolute values. +let byAbsolute (left : int) (right : int) = compare (abs left) (abs right) + +/// The lines of the message of the exception that an action throws after the first line, which is empty when the +/// message has one line. FSharp.Core writes the names and the values of the arguments there, and it does not localize +/// them. The action must throw. +let messageDetail (action : unit -> unit) = + let thrown = + try + action () + ValueNone + with ex -> + ValueSome ex + + match thrown with + | ValueSome ex -> + match ex.Message.IndexOf '\n' with + | -1 -> "" + | newline -> ex.Message.Substring (newline + 1) + | ValueNone -> invalidOp "The action must throw" + +/// The index of the first element of a range that the equality finds equal to the value, or -1: the oracle of the +/// searches from the front. +let firstInRange (equal : 'T -> 'T -> bool) startIndex count (value : 'T) (elements : 'T array) = + seq { startIndex .. startIndex + count - 1 } + |> Seq.tryFind (fun index -> equal elements[index] value) + |> Option.defaultValue -1 + +/// The index of the first element that the equality finds equal to the value, searching backward from the start index +/// over the count of elements, or -1: the oracle of the searches from the back. +let lastInRange (equal : 'T -> 'T -> bool) startIndex count (value : 'T) (elements : 'T array) = + seq { startIndex .. -1 .. startIndex - count + 1 } + |> Seq.tryFind (fun index -> equal elements[index] value) + |> Option.defaultValue -1 + +/// The result of a binary search of a range of elements that a comparer sorts and that are all different: the index of +/// the value, or the complement of the index of the first larger element of the range. +let searchRange (comparer : IComparer<'T>) index count (value : 'T) (sorted : 'T array) = + let range = seq { index .. index + count - 1 } + + match + range + |> Seq.tryFind (fun at -> comparer.Compare (sorted[at], value) = 0) + with + | Some found -> found + | None -> + let smaller = + range + |> Seq.filter (fun at -> comparer.Compare (sorted[at], value) < 0) + |> Seq.length + + ~~~(index + smaller) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListSearchTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListSearchTests.fs new file mode 100644 index 00000000..12af03d4 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListSearchTests.fs @@ -0,0 +1,458 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open System.Collections.Generic +open System.Collections.Immutable +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// The searches of a range and the binary searches of `FlatList`, and the searches of a range of its builder. +[] +type FlatListSearchTests () = + + /// The inputs of the searches of a range: none, one element and several, with a value twice and in two cases. + static let searchInputs () = [| [||]; [| "b" |]; FlatListOracles.rangeNames () |] + + /// The values that the searches of a range look for: in the case of the elements, in another case, and a value that + /// no input has. + static let valuesToFind = [| "B"; "b"; "Z" |] + + /// Every valid range for the searches from the front. The start index is less than the length, as the BCL requires, + /// except for the empty range at the start index 0. + static let frontRanges (length : int) = seq { + for startIndex in 0 .. max 0 (length - 1) do + for count in 0 .. length - startIndex do + struct (startIndex, count) + } + + /// Every valid range for the searches from the back: from the start index down over the count of elements. + static let backRanges (length : int) = seq { + for startIndex in 0 .. max 0 (length - 1) do + for count in 0 .. min (startIndex + 1) length do + struct (startIndex, count) + } + + /// The searches of a range from the front, by name, with the function, the BCL member that it wraps and the equality + /// that it searches in. The functions with a comparer get the comparer that ignores the case. + static let frontSearches + : Map< + string, + struct ((string array -> int -> int -> string -> int) * + (string array -> int -> int -> string -> int) * + (string -> string -> bool)) + > = + let comparer = StringComparer.OrdinalIgnoreCase + + Map.ofArray [| + "FlatList.indexOfRange", + struct ((fun input startIndex count value -> FlatList.indexOfRange startIndex count value (FlatList.ofArray input)), + (fun input startIndex count value -> (FlatList.ofArray input).IndexOf(value, startIndex, count)), + (=)) + "FlatList.indexOfRangeWith", + struct ((fun input startIndex count value -> + FlatList.indexOfRangeWith comparer startIndex count value (FlatList.ofArray input) + ), + (fun input startIndex count value -> (FlatList.ofArray input).IndexOf(value, startIndex, count, comparer)), + FlatListOracles.equalIgnoringCase) + "FlatListBuilder.indexOfRange", + struct ((fun input startIndex count value -> + FlatListBuilder.indexOfRange startIndex count value (FlatListOracles.builderOf input) + ), + (fun input startIndex count value -> (FlatListOracles.builderOf input).IndexOf(value, startIndex, count)), + (=)) + "FlatListBuilder.indexOfRangeWith", + struct ((fun input startIndex count value -> + FlatListBuilder.indexOfRangeWith comparer startIndex count value (FlatListOracles.builderOf input) + ), + (fun input startIndex count value -> + (FlatListOracles.builderOf input).IndexOf(value, startIndex, count, comparer) + ), + FlatListOracles.equalIgnoringCase) + |] + + /// The searches of a range from the back, as the table of the searches from the front. + static let backSearches + : Map< + string, + struct ((string array -> int -> int -> string -> int) * + (string array -> int -> int -> string -> int) * + (string -> string -> bool)) + > = + let comparer = StringComparer.OrdinalIgnoreCase + + Map.ofArray [| + "FlatList.lastIndexOfRange", + struct ((fun input startIndex count value -> + FlatList.lastIndexOfRange startIndex count value (FlatList.ofArray input) + ), + (fun input startIndex count value -> (FlatList.ofArray input).LastIndexOf(value, startIndex, count)), + (=)) + "FlatList.lastIndexOfRangeWith", + struct ((fun input startIndex count value -> + FlatList.lastIndexOfRangeWith comparer startIndex count value (FlatList.ofArray input) + ), + (fun input startIndex count value -> (FlatList.ofArray input).LastIndexOf(value, startIndex, count, comparer)), + FlatListOracles.equalIgnoringCase) + "FlatListBuilder.lastIndexOfRange", + struct ((fun input startIndex count value -> + FlatListBuilder.lastIndexOfRange startIndex count value (FlatListOracles.builderOf input) + ), + (fun input startIndex count value -> (FlatListOracles.builderOf input).LastIndexOf(value, startIndex, count)), + (=)) + "FlatListBuilder.lastIndexOfRangeWith", + struct ((fun input startIndex count value -> + FlatListBuilder.lastIndexOfRangeWith comparer startIndex count value (FlatListOracles.builderOf input) + ), + (fun input startIndex count value -> + (FlatListOracles.builderOf input).LastIndexOf(value, startIndex, count, comparer) + ), + FlatListOracles.equalIgnoringCase) + |] + + /// The elements of the binary searches: sorted, ascending, and all different. + static let sortedValues () = [| 1; 3; 5; 7; 9; 11; 13; 15 |] + + /// The values that the binary searches look for: below every element, in the elements when the range has them, between + /// two elements, and above every element. + static let searchValues = [| 0; 7; 8; 16 |] + + /// Strings that are sorted and different when the case is ignored. The ordinal order puts "B" and "D" before "a". + static let sortedNames () = [| "a"; "B"; "c"; "D"; "e" |] + + /// The binary searches of a range, by name, with an input that the comparer of the search sorts, the comparer, and the + /// function on the input. + static let binarySearchRanges : Map * (int array -> int -> int -> int -> int))> = + let descending = Comparer.Create(Comparison FlatListOracles.descending) + + Map.ofArray [| + nameof FlatList.binarySearchRange, + struct (sortedValues (), + Comparer.Default :> IComparer, + (fun sorted index count value -> FlatList.binarySearchRange index count value (FlatList.ofArray sorted))) + nameof FlatList.binarySearchRangeWithComparer, + struct (Array.rev (sortedValues ()), + descending :> IComparer, + (fun sorted index count value -> + FlatList.binarySearchRangeWithComparer descending index count value (FlatList.ofArray sorted) + )) + |] + + /// Every valid range of every search from the front on every input, with each value to find. + static member FrontRanges = seq { + for name in frontSearches.Keys do + for input in searchInputs () do + for struct (startIndex, count) in frontRanges input.Length do + for value in valuesToFind do + struct (name, input, startIndex, count, value) + } + + /// Every valid range of every search from the back on every input, with each value to find. + static member BackRanges = seq { + for name in backSearches.Keys do + for input in searchInputs () do + for struct (startIndex, count) in backRanges input.Length do + for value in valuesToFind do + struct (name, input, startIndex, count, value) + } + + /// The ranges that the searches from the front reject: a start index that is negative or not less than the length, + /// except for the range 0, 0, and a count that is negative or beyond the end. + static member FrontRangesOutOfRange = seq { + let ranges = [| + struct ([||], 0, 1) + struct ([||], 1, 0) + struct ([||], -1, 0) + struct ([| "b" |], 1, 0) + struct ([| "b" |], 0, 2) + struct (FlatListOracles.rangeNames (), 5, 0) + struct (FlatListOracles.rangeNames (), 6, 0) + struct (FlatListOracles.rangeNames (), -1, 0) + struct (FlatListOracles.rangeNames (), -1, 2) + struct (FlatListOracles.rangeNames (), 0, -1) + struct (FlatListOracles.rangeNames (), 0, 6) + struct (FlatListOracles.rangeNames (), 2, 4) + struct (FlatListOracles.rangeNames (), 5, 1) + struct (FlatListOracles.rangeNames (), 3, 3) + |] + + for name in frontSearches.Keys do + for struct (input, startIndex, count) in ranges do + struct (name, input, startIndex, count) + } + + /// The ranges that the searches from the back reject: a start index that is negative or not less than the length, + /// except for the range 0, 0, and a count that is negative or beyond the start index plus one. + static member BackRangesOutOfRange = seq { + let ranges = [| + struct ([||], 0, 1) + struct ([||], 1, 0) + struct ([||], -1, 0) + struct ([| "b" |], 1, 0) + struct ([| "b" |], 0, 2) + struct (FlatListOracles.rangeNames (), 5, 1) + struct (FlatListOracles.rangeNames (), 5, 0) + struct (FlatListOracles.rangeNames (), -1, 0) + struct (FlatListOracles.rangeNames (), 3, 5) + struct (FlatListOracles.rangeNames (), 3, -1) + struct (FlatListOracles.rangeNames (), 0, 2) + struct (FlatListOracles.rangeNames (), 4, 6) + struct (FlatListOracles.rangeNames (), 2, 4) + |] + + for name in backSearches.Keys do + for struct (input, startIndex, count) in ranges do + struct (name, input, startIndex, count) + } + + /// Every valid range of the sorted integers, with each value to search for, for each binary search of a range. + static member BinarySearchRanges = seq { + let length = (sortedValues ()).Length + + for name in binarySearchRanges.Keys do + for index in 0..length do + for count in 0 .. length - index do + for value in searchValues do + struct (name, index, count, value) + } + + /// Every range that the binary searches of a range reject: a negative index or count, and a range beyond the end. + static member BinarySearchRangesOutOfRange = seq { + let ranges = [| + struct (-1, 2) + struct (0, -1) + struct (-1, -1) + struct (9, 0) + struct (8, 1) + struct (0, 9) + struct (4, 5) + struct (7, 2) + struct (3, 6) + |] + + for name in binarySearchRanges.Keys do + for struct (index, count) in ranges do + struct (name, index, count) + } + + /// Every valid range of the sorted strings, with each value to search for. + static member BinarySearchRangesOfNames = seq { + let length = (sortedNames ()).Length + + for index in 0..length do + for count in 0 .. length - index do + for value in [| "b"; "D"; "z" |] do + struct (index, count, value) + } + + /// The values to search for in the sorted integers. + static member SearchValues = searchValues + + /// The values to search for in the sorted strings: in the other case, in the same case, below, and above. + static member SearchNames = [| "A"; "b"; "C"; "d"; "E"; "f"; "0" |] + + [] + member _.``range searches from the front return the first equal element of the range`` + (name : string, input : string array, startIndex : int, count : int, value : string) + = + let struct (search, _, equal) = frontSearches[name] + + Assert.AreEqual( + FlatListOracles.firstInRange equal startIndex count value input, + search input startIndex count value, + $"%s{name} must return the index of the first equal element of the range" + ) + + [] + member _.``range searches from the back return the last equal element of the range`` + (name : string, input : string array, startIndex : int, count : int, value : string) + = + let struct (search, _, equal) = backSearches[name] + + Assert.AreEqual( + FlatListOracles.lastInRange equal startIndex count value input, + search input startIndex count value, + $"%s{name} must return the index of the last equal element of the range" + ) + + [] + member _.``range searches from the front throw for a range out of range as the BCL does`` + (name : string, input : string array, startIndex : int, count : int) + = + let struct (search, bcl, _) = frontSearches[name] + + ExceptionAssert.Same ( + id, + (fun () -> bcl input startIndex count "b" |> ignore), + (fun () -> search input startIndex count "b" |> ignore), + $"%s{name} must throw as the BCL member does for a range out of range" + ) + + [] + member _.``range searches from the back throw for a range out of range as the BCL does`` + (name : string, input : string array, startIndex : int, count : int) + = + let struct (search, bcl, _) = backSearches[name] + + ExceptionAssert.Same ( + id, + (fun () -> bcl input startIndex count "b" |> ignore), + (fun () -> search input startIndex count "b" |> ignore), + $"%s{name} must throw as the BCL member does for a range out of range" + ) + + [] + member _.``range searches with a comparer take null for the default comparer`` (name : string) = + let names = FlatListOracles.rangeNames () + let array = FlatList.ofArray names + let comparer = Unchecked.defaultof> + + // "B" is there once, at the index 2, where the ignoring comparer would find "b" at the index 1 or 4 + let found, expected = + match name with + | "indexOfRangeWith" -> FlatList.indexOfRangeWith comparer 0 5 "B" array, 2 + | _ -> FlatList.lastIndexOfRangeWith comparer 4 5 "B" array, 2 + + Assert.AreEqual(expected, found, $"FlatList.%s{name} must compare in the default equality for a null comparer") + + let builder = FlatListOracles.builderOf names + + let foundInBuilder = + match name with + | "indexOfRangeWith" -> FlatListBuilder.indexOfRangeWith comparer 0 5 "B" builder + | _ -> FlatListBuilder.lastIndexOfRangeWith comparer 4 5 "B" builder + + Assert.AreEqual( + expected, + foundInBuilder, + $"FlatListBuilder.%s{name} must compare in the default equality for a null comparer" + ) + + [] + member _.``binary searches of a range find the value or the complement of its insertion point in the range`` + (name : string, index : int, count : int, value : int) + = + let struct (sorted, comparer, search) = binarySearchRanges[name] + + Assert.AreEqual( + FlatListOracles.searchRange comparer index count value sorted, + search sorted index count value, + $"FlatList.%s{name} must search the range in the order of its comparison" + ) + + [] + member _.``binary searches of a range throw for a range out of range as the BCL does`` + (name : string, index : int, count : int) + = + let struct (sorted, comparer, search) = binarySearchRanges[name] + + // Array.BinarySearch calls the count of the range length, and the functions call it count + let countOfLength (parameter : string) = + match parameter with + | "length" -> "count" + | other -> other + + ExceptionAssert.Same ( + countOfLength, + (fun () -> + ImmutableArray.BinarySearch (FlatList.ofArray sorted, index, count, 7, comparer) + |> ignore + ), + (fun () -> search sorted index count 7 |> ignore), + $"FlatList.%s{name} must throw as the BCL does, with the parameter name count" + ) + + [] + member _.``binarySearchRange compares strings as F# generic comparison does, which is ordinal`` + (index : int, count : int, value : string, expected : int) + = + // The ordinal order of these strings is not the order of the current culture, which Comparer.Default would use + let sorted = FlatList.ofArray [| "A"; "B"; "a"; "b" |] + + Assert.AreEqual( + expected, + FlatList.binarySearchRange index count value sorted, + "FlatList.binarySearchRange must search in the ordinal order of the strings" + ) + + [] + member _.``binarySearchRangeWithComparer searches the range in the order of the comparer`` + (index : int, count : int, value : string) + = + let sorted = sortedNames () + + Assert.AreEqual( + FlatListOracles.searchRange StringComparer.OrdinalIgnoreCase index count value sorted, + FlatList.binarySearchRangeWithComparer StringComparer.OrdinalIgnoreCase index count value (FlatList.ofArray sorted), + "FlatList.binarySearchRangeWithComparer must search in the order of the comparer" + ) + + [] + member _.``binarySearchWithComparer searches the flat list in the order of the comparer`` (value : int) = + let descending = Comparer.Create(Comparison FlatListOracles.descending) + let sorted = Array.rev (sortedValues ()) + + Assert.AreEqual( + FlatListOracles.searchRange descending 0 sorted.Length value sorted, + FlatList.binarySearchWithComparer descending value (FlatList.ofArray sorted), + "FlatList.binarySearchWithComparer must search in the order of the comparer" + ) + + [] + member _.``binarySearchWithComparer finds a string that differs in case when the comparer ignores the case`` + (value : string) + = + let sorted = sortedNames () + + Assert.AreEqual( + FlatListOracles.searchRange StringComparer.OrdinalIgnoreCase 0 sorted.Length value sorted, + FlatList.binarySearchWithComparer StringComparer.OrdinalIgnoreCase value (FlatList.ofArray sorted), + "FlatList.binarySearchWithComparer must compare as the comparer does" + ) + + [] + member _.``binary searches with a comparer take null for the default comparer`` (value : int) = + let sorted = FlatList.ofArray (sortedValues ()) + let comparer = Unchecked.defaultof> + let expected = FlatListOracles.searchRange Comparer.Default 0 sorted.Length value (sortedValues ()) + + Assert.AreEqual( + expected, + FlatList.binarySearchWithComparer comparer value sorted, + "FlatList.binarySearchWithComparer must compare in the default order for a null comparer" + ) + + Assert.AreEqual( + expected, + FlatList.binarySearchRangeWithComparer comparer 0 sorted.Length value sorted, + "FlatList.binarySearchRangeWithComparer must compare in the default order for a null comparer" + ) + + [] + member _.``binary searches with a comparer that throws throw InvalidOperationException with the exception inside`` + (name : string) + = + let failure = InvalidOperationException "the comparer fails" + + let comparer = { + new IComparer with + member _.Compare (_, _) = raise failure + } + + let sorted = FlatList.ofArray (sortedValues ()) + + let search () = + match name with + | "binarySearchWithComparer" -> FlatList.binarySearchWithComparer comparer 7 sorted + | _ -> FlatList.binarySearchRangeWithComparer comparer 0 8 7 sorted + + let ex = + Assert.ThrowsExactly( + Action (search >> ignore), + $"FlatList.%s{name} must throw InvalidOperationException for a comparer that throws" + ) + + Assert.AreSame( + failure, + ex.InnerException, + $"FlatList.%s{name} must keep the exception of the comparer inside" + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs index e93e180d..560bd80e 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTests.fs @@ -17,16 +17,6 @@ type FlatListTests () = /// The array under a flat list, to tell whether two flat lists share their storage. static let underlying (array : FlatList<'T>) = ImmutableCollectionsMarshal.AsArray array - /// Asserts that an action rejects its input with an ArgumentNullException that names the parameter. - static let assertRejects (name : string) (paramName : string) (actual : unit -> unit) = - let ex = - Assert.ThrowsExactly(Action actual, $"FlatList.%s{name} must throw ArgumentNullException") - - Assert.AreEqual( - paramName, - ex.ParamName, - $"FlatList.%s{name} must name the parameter of the rejected input" - ) /// Maps the parameter names of the Array oracles to the ones of the flat list functions that name a sequence of /// values: Array.insertManyAt names its values source, as Seq.toArray does, where the flat list function names them values. @@ -35,18 +25,6 @@ type FlatListTests () = | "source" -> "values" | other -> other - /// The elements without the first one that the equality finds equal to the item: the oracle of the removal by value. - static let removeFirst (equal : 'T -> 'T -> bool) (item : 'T) (elements : 'T array) = - match Array.tryFindIndex (equal item) elements with - | Some index -> Array.removeAt index elements - | None -> elements - - /// Tells whether two strings are equal when the case is ignored. - static let equalIgnoringCase (left : string) (right : string) = - String.Equals (left, right, StringComparison.OrdinalIgnoreCase) - - /// The strings of the removal by value; "b" and "B" are equal when the case is ignored. - static let names () = [| "a"; "b"; "B"; "c" |] /// The functions that mirror an Array function, by name, with that function on a null array and the function on a /// default flat list. The rows of the test are the names: a function has no readable name in a row. @@ -196,6 +174,22 @@ type FlatListTests () = struct (nameof FlatList.add, "array", (fun () -> FlatList.add 1 defaultArray |> ignore)) struct (nameof FlatList.asMemory, "array", (fun () -> FlatList.asMemory defaultArray |> ignore)) struct (nameof FlatList.binarySearch, "array", (fun () -> FlatList.binarySearch 1 defaultArray |> ignore)) + // The default flat list is checked before the range, which would be rejected as well + struct (nameof FlatList.binarySearchRange, + "array", + (fun () -> FlatList.binarySearchRange -1 -1 1 defaultArray |> ignore)) + struct (nameof FlatList.binarySearchRangeWithComparer, + "array", + (fun () -> + FlatList.binarySearchRangeWithComparer Comparer.Default -1 -1 1 defaultArray + |> ignore + )) + struct (nameof FlatList.binarySearchWithComparer, + "array", + (fun () -> + FlatList.binarySearchWithComparer Comparer.Default 1 defaultArray + |> ignore + )) struct (nameof FlatList.blit, "source", (fun () -> FlatList.blit defaultArray 0 [| 0 |] 0 0)) // Array.concat dereferences a null element array without a check; a default element is a default flat list (D6) struct (nameof FlatList.concat, "arrays", (fun () -> FlatList.concat [ defaultArray ] |> ignore)) @@ -210,6 +204,13 @@ type FlatListTests () = // A default flat list passed as a sequence is still a default flat list struct (nameof FlatList.ofSeq, "source", (fun () -> FlatList.ofSeq (defaultArray :> seq) |> ignore)) struct (nameof FlatList.indexOf, "array", (fun () -> FlatList.indexOf 1 defaultArray |> ignore)) + struct (nameof FlatList.indexOfRange, "array", (fun () -> FlatList.indexOfRange -1 -1 1 defaultArray |> ignore)) + struct (nameof FlatList.indexOfRangeWith, + "array", + (fun () -> + FlatList.indexOfRangeWith EqualityComparer.Default -1 -1 1 defaultArray + |> ignore + )) struct (nameof FlatList.indexOfWith, "array", (fun () -> @@ -217,6 +218,15 @@ type FlatListTests () = |> ignore )) struct (nameof FlatList.lastIndexOf, "array", (fun () -> FlatList.lastIndexOf 1 defaultArray |> ignore)) + struct (nameof FlatList.lastIndexOfRange, + "array", + (fun () -> FlatList.lastIndexOfRange -1 -1 1 defaultArray |> ignore)) + struct (nameof FlatList.lastIndexOfRangeWith, + "array", + (fun () -> + FlatList.lastIndexOfRangeWith EqualityComparer.Default -1 -1 1 defaultArray + |> ignore + )) struct (nameof FlatList.lastIndexOfWith, "array", (fun () -> @@ -238,6 +248,13 @@ type FlatListTests () = FlatList.removeWith EqualityComparer.Default 1 defaultArray |> ignore )) + struct (nameof FlatList.replace, "array", (fun () -> FlatList.replace 1 2 defaultArray |> ignore)) + struct (nameof FlatList.replaceWith, + "array", + (fun () -> + FlatList.replaceWith EqualityComparer.Default 1 2 defaultArray + |> ignore + )) struct (nameof FlatList.skipLast, "array", (fun () -> FlatList.skipLast 0 defaultArray |> ignore)) struct (nameof FlatList.sortRange, "array", (fun () -> FlatList.sortRange 0 0 defaultArray |> ignore)) struct (nameof FlatList.sortRangeWith, "array", (fun () -> FlatList.sortRangeWith compare 0 0 defaultArray |> ignore)) @@ -269,6 +286,10 @@ type FlatListTests () = let defaultOuter = Unchecked.defaultof>> let nullItems = Unchecked.defaultof> + // The flat list of strings is a sequence of objects, which a type test for the flat list of objects does not see + let widenedItems = FlatListOracles.defaultStringsAsObjects + let objects = FlatList.ofArray [| "a" :> obj |] + [| struct ($"%s{nameof FlatList.concat} (default outer)", "arrays", @@ -349,6 +370,24 @@ type FlatListTests () = FlatList.removeManyWith EqualityComparer.Default nullItems (FlatList.ofArray Inputs.Several) |> ignore )) + struct ($"%s{nameof FlatList.ofSeq} (default flat list of strings as objects)", + "source", + (fun () -> FlatList.ofSeq widenedItems |> ignore)) + struct ($"%s{nameof FlatList.insertManyAt} (default flat list of strings as objects)", + "values", + (fun () -> FlatList.insertManyAt 1 widenedItems objects |> ignore)) + struct ($"%s{nameof FlatList.except} (default flat list of strings as objects)", + "itemsToExclude", + (fun () -> FlatList.except widenedItems objects |> ignore)) + struct ($"%s{nameof FlatList.removeMany} (default flat list of strings as objects)", + "items", + (fun () -> FlatList.removeMany widenedItems objects |> ignore)) + struct ($"%s{nameof FlatList.removeManyWith} (default flat list of strings as objects)", + "items", + (fun () -> + FlatList.removeManyWith EqualityComparer.Default widenedItems objects + |> ignore + )) |] |> Array.map (fun struct (name, paramName, actual) -> name, struct (paramName, actual)) |> Map.ofArray @@ -387,14 +426,13 @@ type FlatListTests () = /// have no two different elements with the same absolute value, and the comparer of `sortRangeWithComparer` orders by /// the absolute value, so the result does not depend on the stability of the sort. static let rangeSorts : Map int -> FlatList -> FlatList) * (int array -> int array))> = - let descending (left : int) (right : int) = compare right left - let byAbsolute (left : int) (right : int) = compare (abs left) (abs right) - Map.ofArray [| nameof FlatList.sortRange, struct (FlatList.sortRange, Array.sortWith compare) - nameof FlatList.sortRangeWith, struct (FlatList.sortRangeWith descending, Array.sortWith descending) + nameof FlatList.sortRangeWith, + struct (FlatList.sortRangeWith FlatListOracles.descending, Array.sortWith FlatListOracles.descending) nameof FlatList.sortRangeWithComparer, - struct (FlatList.sortRangeWithComparer (Comparer.Create(Comparison byAbsolute)), Array.sortWith byAbsolute) + struct (FlatList.sortRangeWithComparer (Comparer.Create(Comparison FlatListOracles.byAbsolute)), + Array.sortWith FlatListOracles.byAbsolute) |] /// The functions that take a range, by name, on the whole range of the input with several elements. @@ -564,6 +602,21 @@ type FlatListTests () = TestDataRow [| "C"; "d"; "A" |] } + /// Every input with an element, with each of its different elements: the value to replace. + static member ReplacedValues = seq { + for input in Inputs.NonEmpty do + for oldValue in Array.distinct input do + struct (input, oldValue) + } + + /// The values to replace in the strings with a case-insensitive comparer: equal in the same case and in another case. + static member ReplacedNames = seq { + "a" + "b" + "B" + "C" + } + /// The names of the functions that take a range, as the rows of the test of the whole range. static member WholeRanges = wholeRanges.Keys @@ -606,15 +659,44 @@ type FlatListTests () = [] member _.``functions without an Array counterpart reject a default flat list`` (name : string) = let struct (paramName, actual) = otherRejections[name] - assertRejects name paramName actual + FlatListAssert.RejectsNull ($"FlatList.%s{name}", paramName, actual) [] member _.``functions that take a sequence reject a default flat list given as the sequence and a null sequence`` (name : string) = let struct (paramName, actual) = sequenceRejections[name] - assertRejects name paramName actual + FlatListAssert.RejectsNull ($"FlatList.%s{name}", paramName, actual) + + [] + member _.``a flat list of strings that is not default passes as a sequence of objects`` () = + let widened = FlatListOracles.stringsAsObjects (FlatList.ofArray [| "a"; "b" |]) + let asObjects (strings : string array) = strings |> Array.map (fun item -> item :> obj) + let objects = FlatList.ofArray (asObjects [| "c" |]) + + CollectionAssert.AreEqual ( + asObjects [| "a"; "b" |], + FlatList.toArray (FlatList.ofSeq widened), + "FlatList.ofSeq must take the elements of a flat list of strings as objects" + ) + + CollectionAssert.AreEqual ( + asObjects [| "a"; "b"; "c" |], + FlatList.toArray (FlatList.insertManyAt 0 widened objects), + "FlatList.insertManyAt must take the elements of a flat list of strings as objects" + ) + + CollectionAssert.AreEqual ( + asObjects [| "c" |], + FlatList.toArray (FlatList.except widened objects), + "FlatList.except must take the elements of a flat list of strings as objects" + ) + CollectionAssert.AreEqual ( + asObjects [| "c" |], + FlatList.toArray (FlatList.removeMany widened (FlatList.ofArray (asObjects [| "a"; "c"; "b" |]))), + "FlatList.removeMany must take the elements of a flat list of strings as objects" + ) [] member _.``functions that take a sequence reject a null sequence as Array does`` (name : string) = let struct (oracle, actual) = nullSequenceMirrors[name] @@ -777,14 +859,20 @@ type FlatListTests () = [] member _.``sub throws for a range out of range as Array.sub does`` (input : int array, startIndex : int, count : int) = - ExceptionAssert.Same ( - id, - (fun () -> Array.sub input startIndex count |> ignore), - (fun () -> - FlatList.sub (FlatList.ofArray input) startIndex count - |> ignore - ), - "FlatList.sub must throw as Array.sub does for a range out of range" + let oracle () = Array.sub input startIndex count |> ignore + + let actual () = + FlatList.sub (FlatList.ofArray input) startIndex count + |> ignore + + ExceptionAssert.Same (id, oracle, actual, "FlatList.sub must throw as Array.sub does for a range out of range") + + // ExceptionAssert compares the first line of the messages. The second line names the argument and, for a range + // beyond the end, the length: "count = 9, array.Length = 8" + Assert.AreEqual( + FlatListOracles.messageDetail oracle, + FlatListOracles.messageDetail actual, + "FlatList.sub must write the arguments of the message as Array.sub does" ) [] @@ -884,7 +972,7 @@ type FlatListTests () = [] member _.``remove removes the first equal element`` (input : int array, value : int) = CollectionAssert.AreEqual ( - removeFirst (=) value input, + FlatListOracles.removeFirst (=) value input, FlatList.toArray (FlatList.remove value (FlatList.ofArray input)), "FlatList.remove must remove the first equal element" ) @@ -903,21 +991,23 @@ type FlatListTests () = member _.``remove compares in the default equality, which tells b from B`` () = CollectionAssert.AreEqual ( [| "a"; "b"; "c" |], - FlatList.toArray (FlatList.remove "B" (FlatList.ofArray (names ()))), + FlatList.toArray (FlatList.remove "B" (FlatList.ofArray (FlatListOracles.names ()))), "FlatList.remove must not find B equal to b" ) [] member _.``removeWith removes the first element that the comparer finds equal`` (value : string) = CollectionAssert.AreEqual ( - removeFirst equalIgnoringCase value (names ()), - FlatList.toArray (FlatList.removeWith StringComparer.OrdinalIgnoreCase value (FlatList.ofArray (names ()))), + FlatListOracles.removeFirst FlatListOracles.equalIgnoringCase value (FlatListOracles.names ()), + FlatList.toArray ( + FlatList.removeWith StringComparer.OrdinalIgnoreCase value (FlatList.ofArray (FlatListOracles.names ())) + ), "FlatList.removeWith must remove the first element that the comparer finds equal" ) [] member _.``removeWith returns the input itself when no element is equal`` (value : string) = - let array = FlatList.ofArray (names ()) + let array = FlatList.ofArray (FlatListOracles.names ()) Assert.AreSame ( underlying array, @@ -927,7 +1017,8 @@ type FlatListTests () = [] member _.``removeMany removes one element for each value`` (input : int array, items : int array) = - let expected = Array.fold (fun elements item -> removeFirst (=) item elements) input items + let expected = + Array.fold (fun elements item -> FlatListOracles.removeFirst (=) item elements) input items CollectionAssert.AreEqual ( expected, @@ -973,17 +1064,101 @@ type FlatListTests () = member _.``removeMany compares in the default equality, which tells b from B`` () = CollectionAssert.AreEqual ( [| "a"; "b"; "c" |], - FlatList.toArray (FlatList.removeMany [| "B" |] (FlatList.ofArray (names ()))), + FlatList.toArray (FlatList.removeMany [| "B" |] (FlatList.ofArray (FlatListOracles.names ()))), "FlatList.removeMany must not find B equal to b" ) [] member _.``removeManyWith removes one element for each value that the comparer finds equal`` (items : string array) = let expected = - Array.fold (fun elements item -> removeFirst equalIgnoringCase item elements) (names ()) items + Array.fold + (fun elements item -> FlatListOracles.removeFirst FlatListOracles.equalIgnoringCase item elements) + (FlatListOracles.names ()) + items CollectionAssert.AreEqual ( expected, - FlatList.toArray (FlatList.removeManyWith StringComparer.OrdinalIgnoreCase items (FlatList.ofArray (names ()))), + FlatList.toArray ( + FlatList.removeManyWith StringComparer.OrdinalIgnoreCase items (FlatList.ofArray (FlatListOracles.names ())) + ), "FlatList.removeManyWith must remove one element for each value that the comparer finds equal" ) + + // ---- replace and replaceWith ---- + + [] + member _.``replace replaces the first equal element`` (input : int array, oldValue : int) = + CollectionAssert.AreEqual ( + FlatListOracles.replaceFirst (=) oldValue 99 input + |> ValueOption.get, + FlatList.toArray (FlatList.replace oldValue 99 (FlatList.ofArray input)), + "FlatList.replace must replace the first equal element" + ) + + [] + member _.``replace throws for a value that no element equals as the BCL does`` (input : int array, value : int) = + ExceptionAssert.Same ( + id, + (fun () -> (FlatList.ofArray input).Replace(value, 0) |> ignore), + (fun () -> FlatList.replace value 0 (FlatList.ofArray input) |> ignore), + "FlatList.replace must throw as ImmutableArray.Replace does when no element equals the value" + ) + + [] + member _.``replace returns a new flat list also when the new value equals the old one`` (value : int) = + let array = FlatList.ofArray Inputs.Several + let replaced = FlatList.replace value value array + + CollectionAssert.AreEqual (Inputs.Several, FlatList.toArray replaced, "FlatList.replace must keep the elements") + + Assert.AreNotSame ( + underlying array, + underlying replaced, + "FlatList.replace must return a new flat list, as ImmutableArray.Replace does" + ) + + [] + member _.``replace compares in the default equality, which tells b from B`` () = + CollectionAssert.AreEqual ( + [| "a"; "b"; "X"; "c" |], + FlatList.toArray (FlatList.replace "B" "X" (FlatList.ofArray (FlatListOracles.names ()))), + "FlatList.replace must not find B equal to b" + ) + + [] + member _.``replaceWith replaces the first element that the comparer finds equal`` (oldValue : string) = + let names = FlatListOracles.names () + + CollectionAssert.AreEqual ( + FlatListOracles.replaceFirst FlatListOracles.equalIgnoringCase oldValue "X" names + |> ValueOption.get, + FlatList.toArray (FlatList.replaceWith StringComparer.OrdinalIgnoreCase oldValue "X" (FlatList.ofArray names)), + "FlatList.replaceWith must replace the first element that the comparer finds equal" + ) + + [] + member _.``replaceWith throws for a value that no element equals as the BCL does`` (oldValue : string) = + let array = FlatList.ofArray (FlatListOracles.names ()) + + ExceptionAssert.Same ( + id, + (fun () -> + array.Replace (oldValue, "X", StringComparer.OrdinalIgnoreCase) + |> ignore + ), + (fun () -> + FlatList.replaceWith StringComparer.OrdinalIgnoreCase oldValue "X" array + |> ignore + ), + "FlatList.replaceWith must throw as ImmutableArray.Replace does when no element equals the value" + ) + + [] + member _.``replaceWith takes null for the default comparer`` () = + let comparer = Unchecked.defaultof> + + CollectionAssert.AreEqual ( + [| "a"; "b"; "X"; "c" |], + FlatList.toArray (FlatList.replaceWith comparer "B" "X" (FlatList.ofArray (FlatListOracles.names ()))), + "FlatList.replaceWith must compare in the default equality for a null comparer" + ) From c1dd50a1fe77ead461e77d6c48e52dcf7c99603d Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 23:56:16 +0200 Subject: [PATCH 25/27] fixup! docs: `FlatList` in the changelog and the collection table The changelog lists the new functions of `FlatList` and of `FlatListBuilder` and names the replacement of each removed function: `indexOf` and its counterparts for the `index` and `lastIndex` functions, `indexOfRange` and its counterparts for the `indexRange` and `indexFrom` functions, `removeMany` for `removeAll` and `removeManyAt` for `removeRange`, with the change of the default equality from F# structural equality to `EqualityComparer<'T>.Default`. The nested `FlatList.Builder` module is now `FlatListBuilder`. The entries were checked against the published 1.0.0. `Seq`, the module of conversions between the collections, was never released and the `ImmutableArray` module alias cannot be used from another assembly, so neither is listed as removed. `unfold` is new, so it is not listed as changed, `sortWithComparer` and `tryTail` are not new, and `take` and `splitAt` threw in 1.0.0: the entry says what changed. Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 49 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cc2c9d6..a7d6d007 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,34 +10,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added * `FlatList` has every function of the FSharp.Core `Array` module that applies to an immutable array, with its semantics - and exceptions, plus `add`, `asMemory`, `asSpan`, `binarySearch`, `indexOf`, `indexOfWith`, `lastIndexOf`, - `lastIndexOfWith`, `ofReadOnlySpan`, `ofSpan`, `remove`, `removeMany`, `removeManyWith`, `removeWith`, `skipLast`, - `sortWithComparer`, `takeLast` and `tryTail`. `remove` and `removeMany` remove by value in - `EqualityComparer<'T>.Default`, and the `With` variants take an equality comparer. + and exceptions, plus `add`, `asMemory`, `asSpan`, `binarySearch`, `binarySearchRange`, `binarySearchRangeWithComparer`, + `binarySearchWithComparer`, `indexOf`, `indexOfRange`, `indexOfRangeWith`, `indexOfWith`, `lastIndexOf`, + `lastIndexOfRange`, `lastIndexOfRangeWith`, `lastIndexOfWith`, `ofReadOnlySpan`, `ofSpan`, `remove`, `removeMany`, + `removeManyWith`, `removeWith`, `replace`, `replaceWith`, `skipLast` and `takeLast`. `remove` and `removeMany` remove by + value in `EqualityComparer<'T>.Default`, and the `With` variants take an equality comparer. The functions that wrap a + member of `ImmutableArray<'T>` throw what the member throws. * The `FSharp.Collections.Immutable.Option.FlatList` and `FSharp.Collections.Immutable.Tuple.FlatList` modules repeat the functions that use value options or struct tuples with options and reference tuples. -* The `FlatListBuilder` module works on the builder of a flat list, and `flatList { }` builds a flat list. +* The `FlatListBuilder` module works on the builder of a flat list: `add`, `addRange`, `blit`, `capacity`, `clear`, + `contains`, `drainToFlatList`, `indexOf`, `indexOfRange`, `indexOfRangeWith`, `insertAt`, `insertManyAt`, `item`, + `lastIndexOf`, `lastIndexOfRange`, `lastIndexOfRangeWith`, `length`, `remove`, `removeAll`, `removeAt`, `removeMany`, + `removeManyAt`, `removeManyWith`, `removeWith`, `replace`, `replaceWith`, `rev`, `setCapacity`, `sort`, `sortRange`, + `sortRangeWith`, `sortRangeWithComparer`, `sortWith`, `sortWithComparer`, `toArray` and `updateAt`. The functions that + change the builder return it. `replace` and `replaceWith` leave the builder as it is when no element equals the old + value, as `ImmutableArray<'T>.Builder.Replace` does, where `FlatList.replace` throws `ArgumentException`. +* `flatList { }` builds a flat list. ### Changed * The package targets `net8.0` and `net10.0`. * The package requires FSharp.Core 10.1.301 or later, the first release with `partitionWith`. -* `FlatList`: the `try*` functions return `voption`; `choose`, `pick`, `tryPick` and `unfold` take functions that return - `voption`; the functions that return tuples return struct tuples. +* `FlatList`: the `try*` functions return `voption`; `choose`, `pick` and `tryPick` take functions that return `voption`; + the functions that return tuples return struct tuples. * `FlatList`: every function throws what the FSharp.Core `Array` function of the same name throws. `take` and `splitAt` - throw for a count beyond the length, and a default `ImmutableArray` throws `ArgumentNullException`, also when it is - given as a sequence. + throw `InvalidOperationException` for a count beyond the length, where they threw `ArgumentOutOfRangeException`. A + default `ImmutableArray` throws `ArgumentNullException`, where it threw `ArgumentException`, and also when it is given + as a sequence, where it threw `InvalidOperationException`. * `FlatList.sub` has the meaning of `Array.sub`: `sub array startIndex count` returns `count` elements from `startIndex`. It was `sub start stop list`. +* The nested `FlatList.Builder` module is now the `FlatListBuilder` module. ### Removed -* `FlatList.index`, `indexWith`, `indexFrom`, `indexFromWith`, `indexRange`, `indexRangeWith` and their `lastIndex` - counterparts: use `indexOf`, `indexOfWith`, `lastIndexOf` and `lastIndexOfWith`. -* `FlatList.removeAll`, `removeAllWith` and `removeRange`: use `removeMany`, `removeManyWith` and `removeManyAt`, and - `filter` with the negated predicate to remove the elements that satisfy a predicate. -* The `ImmutableArray` module alias of `FlatList`. -* The `Seq` module of conversions between the collections. +* `FlatList.index`, `indexWith`, `lastIndex` and `lastIndexWith`: use `indexOf`, `indexOfWith`, `lastIndexOf` and + `lastIndexOfWith`. They compare in `EqualityComparer<'T>.Default`, where the removed functions compared in F# structural + equality: elements that are arrays, or types whose `Equals` differs from F# equality, compare differently. +* `FlatList.indexRange`, `indexRangeWith`, `lastIndexRange` and `lastIndexRangeWith`: use `indexOfRange`, + `indexOfRangeWith`, `lastIndexOfRange` and `lastIndexOfRangeWith`, which take the same arguments and compare as + `indexOf` does. +* `FlatList.indexFrom`, `indexFromWith`, `lastIndexFrom` and `lastIndexFromWith`: use `indexOfRange` and + `indexOfRangeWith` with the count `length - startIndex`, and `lastIndexOfRange` and `lastIndexOfRangeWith` with the + count `startIndex + 1`. +* `FlatList.removeAll` and `removeAllWith`: use `removeMany` and `removeManyWith`. They remove one element for each of the + given values, as the removed functions did, and `removeMany` compares in `EqualityComparer<'T>.Default` where `removeAll` + compared in F# structural equality. The removed functions did not remove the elements that satisfy a predicate: use + `filter` with the negated predicate for that. +* `FlatList.removeRange`: use `removeManyAt`. ## [1.0.0] - 2020-02-23 From eac71ec2894672a33ba5d88d63ad90fcd46af43f Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Mon, 19 Oct 2026 00:02:16 +0200 Subject: [PATCH 26/27] fixup! feat: `FlatList` module with the vocabulary of the FSharp.Core `Array` module The documentation of the searches of a range said that the start index must be less than the length. That is how the BCL of .NET 8 and .NET 10 behaves: the forward search of .NET 11 accepts an empty range at a start index that equals the length. The remarks now say that the function throws what the member throws, and name the versions of the behavior that they describe. Co-Authored-By: Claude Sonnet 5.5 --- .../FlatList/FlatList.fsi | 20 +++++++++++-------- .../FlatList/FlatListBuilder.fsi | 20 +++++++++++-------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi b/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi index 12a4f77c..72a7904f 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi +++ b/src/FSharp.Collections.Immutable/FlatList/FlatList.fsi @@ -559,8 +559,9 @@ val indexOf : value : 'T -> array : FlatList<'T> -> int /// . /// /// -/// The start index must be less than the length, as in the BCL member, also for an empty range. An empty flat list -/// accepts only the start index 0 with the count 0, and the result is -1. +/// The function throws what the BCL member throws. The member of .NET 8 and .NET 10 rejects also an empty range at a +/// start index that equals the length, and it accepts for an empty flat list only the start index 0 with the count 0, +/// which gives -1. /// /// [] @@ -585,8 +586,9 @@ val indexOfRange : startIndex : int -> count : int -> value : 'T -> array : Flat /// . /// /// -/// The start index must be less than the length, as in the BCL member, also for an empty range. An empty flat list -/// accepts only the start index 0 with the count 0, and the result is -1. +/// The function throws what the BCL member throws. The member of .NET 8 and .NET 10 rejects also an empty range at a +/// start index that equals the length, and it accepts for an empty flat list only the start index 0 with the count 0, +/// which gives -1. /// /// [] @@ -636,8 +638,9 @@ val lastIndexOf : value : 'T -> array : FlatList<'T> -> int /// . /// /// -/// The start index must be less than the length, as in the BCL member, also for an empty range. An empty flat list -/// accepts only the start index 0 with the count 0, and the result is -1. +/// The function throws what the BCL member throws. The member of .NET 8 and .NET 10 rejects also an empty range at a +/// start index that equals the length, and it accepts for an empty flat list only the start index 0 with the count 0, +/// which gives -1. /// /// [] @@ -664,8 +667,9 @@ val lastIndexOfRange : startIndex : int -> count : int -> value : 'T -> array : /// . /// /// -/// The start index must be less than the length, as in the BCL member, also for an empty range. An empty flat list -/// accepts only the start index 0 with the count 0, and the result is -1. +/// The function throws what the BCL member throws. The member of .NET 8 and .NET 10 rejects also an empty range at a +/// start index that equals the length, and it accepts for an empty flat list only the start index 0 with the count 0, +/// which gives -1. /// /// [] diff --git a/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fsi b/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fsi index a5a56f3b..44aeb55e 100644 --- a/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fsi +++ b/src/FSharp.Collections.Immutable/FlatList/FlatListBuilder.fsi @@ -74,8 +74,9 @@ val indexOf : value : 'T -> builder : FlatList<'T>.Builder -> int /// . /// /// -/// The start index must be less than the count of the builder, as in the BCL member, also for an empty range. An empty -/// builder accepts only the start index 0 with the count 0, and the result is -1. +/// The function throws what the BCL member throws. The member of .NET 8 and .NET 10 rejects also an empty range at a +/// start index that equals the count of the builder, and it accepts for an empty builder only the start index 0 with +/// the count 0, which gives -1. /// /// [] @@ -100,8 +101,9 @@ val indexOfRange : startIndex : int -> count : int -> value : 'T -> builder : Fl /// . /// /// -/// The start index must be less than the count of the builder, as in the BCL member, also for an empty range. An empty -/// builder accepts only the start index 0 with the count 0, and the result is -1. +/// The function throws what the BCL member throws. The member of .NET 8 and .NET 10 rejects also an empty range at a +/// start index that equals the count of the builder, and it accepts for an empty builder only the start index 0 with +/// the count 0, which gives -1. /// /// [] @@ -134,8 +136,9 @@ val lastIndexOf : value : 'T -> builder : FlatList<'T>.Builder -> int /// . /// /// -/// The start index must be less than the count of the builder, as in the BCL member, also for an empty range. An empty -/// builder accepts only the start index 0 with the count 0, and the result is -1. +/// The function throws what the BCL member throws. The member of .NET 8 and .NET 10 rejects also an empty range at a +/// start index that equals the count of the builder, and it accepts for an empty builder only the start index 0 with +/// the count 0, which gives -1. /// /// [] @@ -162,8 +165,9 @@ val lastIndexOfRange : startIndex : int -> count : int -> value : 'T -> builder /// . /// /// -/// The start index must be less than the count of the builder, as in the BCL member, also for an empty range. An empty -/// builder accepts only the start index 0 with the count 0, and the result is -1. +/// The function throws what the BCL member throws. The member of .NET 8 and .NET 10 rejects also an empty range at a +/// start index that equals the count of the builder, and it accepts for an empty builder only the start index 0 with +/// the count 0, which gives -1. /// /// [] From c921c916f9fb1492064dcd4cb3e56eb2f165ecde Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Mon, 19 Oct 2026 00:02:17 +0200 Subject: [PATCH 27/27] fixup! test: shared contracts of the sequence-like modules, run for `FlatList` The comments of the rows of the searches of a range say which BCL they describe: the rows with an empty range at a start index that equals the length throw on .NET 8 and .NET 10, the targets of the project, and not in the forward search of .NET 11. Co-Authored-By: Claude Sonnet 5.5 --- .../FlatList/FlatListSearchTests.fs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListSearchTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListSearchTests.fs index 12af03d4..2fd5f812 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListSearchTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListSearchTests.fs @@ -17,8 +17,8 @@ type FlatListSearchTests () = /// no input has. static let valuesToFind = [| "B"; "b"; "Z" |] - /// Every valid range for the searches from the front. The start index is less than the length, as the BCL requires, - /// except for the empty range at the start index 0. + /// Every valid range for the searches from the front. The start index is less than the length, as the BCL of .NET 8 + /// and .NET 10 requires, except for the empty range at the start index 0. static let frontRanges (length : int) = seq { for startIndex in 0 .. max 0 (length - 1) do for count in 0 .. length - startIndex do @@ -156,7 +156,9 @@ type FlatListSearchTests () = } /// The ranges that the searches from the front reject: a start index that is negative or not less than the length, - /// except for the range 0, 0, and a count that is negative or beyond the end. + /// except for the range 0, 0, and a count that is negative or beyond the end. The BCL of .NET 11 accepts an empty + /// range at a start index that equals the length, so the rows with such a range do not throw there. The project + /// targets .NET 8 and .NET 10. static member FrontRangesOutOfRange = seq { let ranges = [| struct ([||], 0, 1)