From 330b29575b210ab42cbbfd4952353e2edb1d0f10 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 13:20:56 +0200 Subject: [PATCH 1/4] feat: `IndexedList` and `IIndexedList` modules with the vocabulary of the FSharp.Core `List` module `IndexedList` (`ImmutableList<'T>`) replaces the `ImmutableList` module. It has every function of `List` that applies to an immutable list, with the parameter names, the checks and the exceptions of `List` and with stable sorts, plus the extras of ADR 0001: `add`, `binarySearch`, `blit`, `cons`, `uncons`, `tryUncons`, the struct partial patterns `Cons` and `Nil`, `indexOf`, `lastIndexOf`, `skipLast`, `takeLast`, `tryTail` and `sortWithComparer`. `IIndexedList` has the same functions for `IImmutableList<'T>` without creation, builder, `binarySearch` and `blit`, plus `clear`; a result with the element type of the input keeps the implementation of the input. The `Option` and `Tuple` namespaces repeat the functions that use value options or struct tuples, `IndexedListBuilder` works on the builder of an indexed list, and `indexedList { }` builds one. `Helper.fs` gains three internal modules for the modules that mirror `List`: `ListChecks` throws the exceptions of `List` for lengths and indices with the parameter names that the caller passes, `ListArrays` runs the functions of two and three lists over arrays and checks the lengths after the common prefix, as `List` does, and `Snapshot` copies a collection into an array. The `ImmutableList` and `IndexedSeq` modules, the `ReadOnlyList` alias and the `IIndexedSeq<'T>` abbreviation are removed. Co-Authored-By: Claude Opus 5.5 --- .../ComputationExpressions.fs | 2 + .../ComputationExpressions.fsi | 14 + .../FSharp.Collections.Immutable.fsproj | 26 +- .../Helper/Collectors.fs | 93 + .../Helper/DescendingSorts.fs | 77 + .../Helper/Lengths.fs | 88 + .../Helper/ListArrays.fs | 344 +++ .../Helper/ListChecks.fs | 581 +++++ .../Helper/ListRanges.fs | 191 ++ .../Helper/Raise.fs | 57 + .../Helper/Snapshot.fs | 44 + .../ImmutableList.fs | 353 --- .../IndexedList/IIndexedList.fs | 1173 +++++++++ .../IndexedList/IIndexedList.fsi | 1808 +++++++++++++ .../IndexedList/IIndexedListOption.fs | 80 + .../IndexedList/IIndexedListOption.fsi | 183 ++ .../IndexedList/IIndexedListTuple.fs | 126 + .../IndexedList/IIndexedListTuple.fsi | 191 ++ .../IndexedList/IndexedList.fs | 1413 +++++++++++ .../IndexedList/IndexedList.fsi | 2253 +++++++++++++++++ .../IndexedList/IndexedListBuilder.fs | 235 ++ .../IndexedList/IndexedListBuilder.fsi | 580 +++++ .../IndexedList/IndexedListCE.fs | 78 + .../IndexedList/IndexedListCE.fsi | 73 + .../IndexedList/IndexedListOption.fs | 101 + .../IndexedList/IndexedListOption.fsi | 240 ++ .../IndexedList/IndexedListTuple.fs | 137 + .../IndexedList/IndexedListTuple.fsi | 210 ++ .../IndexedList/IndexedListTypes.fs | 23 + .../IndexedSeq.fs | 17 - 30 files changed, 10419 insertions(+), 372 deletions(-) create mode 100644 src/FSharp.Collections.Immutable/Helper/Collectors.fs create mode 100644 src/FSharp.Collections.Immutable/Helper/DescendingSorts.fs create mode 100644 src/FSharp.Collections.Immutable/Helper/Lengths.fs create mode 100644 src/FSharp.Collections.Immutable/Helper/ListArrays.fs create mode 100644 src/FSharp.Collections.Immutable/Helper/ListChecks.fs create mode 100644 src/FSharp.Collections.Immutable/Helper/ListRanges.fs create mode 100644 src/FSharp.Collections.Immutable/Helper/Snapshot.fs delete mode 100644 src/FSharp.Collections.Immutable/ImmutableList.fs create mode 100644 src/FSharp.Collections.Immutable/IndexedList/IIndexedList.fs create mode 100644 src/FSharp.Collections.Immutable/IndexedList/IIndexedList.fsi create mode 100644 src/FSharp.Collections.Immutable/IndexedList/IIndexedListOption.fs create mode 100644 src/FSharp.Collections.Immutable/IndexedList/IIndexedListOption.fsi create mode 100644 src/FSharp.Collections.Immutable/IndexedList/IIndexedListTuple.fs create mode 100644 src/FSharp.Collections.Immutable/IndexedList/IIndexedListTuple.fsi create mode 100644 src/FSharp.Collections.Immutable/IndexedList/IndexedList.fs create mode 100644 src/FSharp.Collections.Immutable/IndexedList/IndexedList.fsi create mode 100644 src/FSharp.Collections.Immutable/IndexedList/IndexedListBuilder.fs create mode 100644 src/FSharp.Collections.Immutable/IndexedList/IndexedListBuilder.fsi create mode 100644 src/FSharp.Collections.Immutable/IndexedList/IndexedListCE.fs create mode 100644 src/FSharp.Collections.Immutable/IndexedList/IndexedListCE.fsi create mode 100644 src/FSharp.Collections.Immutable/IndexedList/IndexedListOption.fs create mode 100644 src/FSharp.Collections.Immutable/IndexedList/IndexedListOption.fsi create mode 100644 src/FSharp.Collections.Immutable/IndexedList/IndexedListTuple.fs create mode 100644 src/FSharp.Collections.Immutable/IndexedList/IndexedListTuple.fsi create mode 100644 src/FSharp.Collections.Immutable/IndexedList/IndexedListTypes.fs delete mode 100644 src/FSharp.Collections.Immutable/IndexedSeq.fs diff --git a/src/FSharp.Collections.Immutable/ComputationExpressions.fs b/src/FSharp.Collections.Immutable/ComputationExpressions.fs index 6db0b059..972f77ec 100644 --- a/src/FSharp.Collections.Immutable/ComputationExpressions.fs +++ b/src/FSharp.Collections.Immutable/ComputationExpressions.fs @@ -4,3 +4,5 @@ module FSharp.Collections.Immutable.ComputationExpressions // ---- Creation ---- let flatList = FlatListCE () + +let indexedList = IndexedListCE () diff --git a/src/FSharp.Collections.Immutable/ComputationExpressions.fsi b/src/FSharp.Collections.Immutable/ComputationExpressions.fsi index c0086e56..e588c81b 100644 --- a/src/FSharp.Collections.Immutable/ComputationExpressions.fsi +++ b/src/FSharp.Collections.Immutable/ComputationExpressions.fsi @@ -19,3 +19,17 @@ module FSharp.Collections.Immutable.ComputationExpressions /// Evaluates to a flat list of 0, 1, 4, 9, 100 and 200. /// val flatList : FlatListCE + +/// Builds an indexed list. +/// +/// +/// let squares = indexedList { +/// yield 0 +/// for i in 1..3 do +/// yield i * i +/// yield! [ 100; 200 ] +/// } +/// +/// Evaluates to an indexed list of 0, 1, 4, 9, 100 and 200. +/// +val indexedList : IndexedListCE diff --git a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj index 293de773..f66316ea 100644 --- a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj +++ b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj @@ -33,6 +33,13 @@ + + + + + + + @@ -44,10 +51,25 @@ + + + + + + + + + + + + + + + + + - - diff --git a/src/FSharp.Collections.Immutable/Helper/Collectors.fs b/src/FSharp.Collections.Immutable/Helper/Collectors.fs new file mode 100644 index 00000000..2e544de7 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Helper/Collectors.fs @@ -0,0 +1,93 @@ +/// +/// The accumulators that build an from many collections, for +/// the functions that concatenate them (ADR 0001, D5). +/// +/// +/// +/// An indexed list is a balanced tree. Appending another indexed list to a builder joins the tree of the appended list to +/// the tree of the builder: it takes O(log n) and allocates a few nodes, whatever the length of the appended list, and the +/// result shares the nodes of the appended list, as any immutable tree shares its nodes. Appending any other collection +/// builds a subtree from its elements. +/// +/// +/// The join costs about a microsecond, so it pays only for an indexed list of some length. The functions of this module +/// measure how long: with 100 000 elements in all and fresh collections of 1, 3, 10, 30, 100 and 1000 elements, copying +/// the elements into a buffer and building the tree once, as +/// +/// does, is 1.2 to 1.8 times as fast as joining for collections of 1, 3 and 10 elements, and joining is 1.4 times as fast +/// for 30 and 2.4 times for 100 and 1000. Concatenating 100 indexed lists of 1000 elements takes 0.1 to 0.4 ms with joins +/// and 3 to 7 ms with copies, and allocates 76 KB instead of 5.8 MB; one indexed list of 100 000 elements takes 4 µs +/// instead of 3 to 7 ms. +/// +/// +module internal FSharp.Collections.Immutable.Collectors + +open System.Collections.Immutable + +/// +/// The least length of an indexed list that the accumulator joins to its result instead of copying it. +/// +/// +/// The break-even of the measurements in the remarks of the module lies between 10 and 30 elements. +/// +[] +let private JoinedLength = 16 + +/// +/// Appends collections to an indexed list under construction: it copies the short ones, and joins the long indexed lists as +/// they are. +/// +/// The type of the elements. +/// +/// +/// The copied elements wait in a buffer until a long indexed list comes or the result is asked for, and then the builder +/// takes them in one piece, so that a run of short collections costs what +/// +/// costs, and a long indexed list costs a join. The elements keep the order in which the collections were added. +/// +/// +/// It exists because a builder alone is 1.6 to 1.8 times slower than the buffer for collections of one element, and the buffer +/// alone copies every element of a long indexed list that the join would have shared (see the remarks of +/// ). Functions that add the elements one at a time, such as +/// , +/// need neither: the buffer is 2.3 times as fast as +/// for 100 000 elements. +/// +/// +[] +type ListCollector<'T> () = + + let builder = ImmutableList.CreateBuilder<'T>() + let pending = ResizeArray<'T>() + + /// Moves the copied elements into the builder. + member private _.Flush () = + if pending.Count > 0 then + builder.AddRange pending + pending.Clear () + + /// + /// Appends the elements of a collection to the result. + /// + /// The collection, which can be an indexed list. + /// + /// is . + /// + /// + /// An indexed list of at least 16 elements is joined to the result, which then shares its nodes; any other collection + /// is copied. + /// + member this.Add (items : seq<'T>) = + match items with + | :? ImmutableList<'T> as list when list.Count >= JoinedLength -> + this.Flush () + builder.AddRange list + | _ -> pending.AddRange items + + /// + /// The indexed list of the elements that were added. + /// + /// The elements of the collections, in the order in which the collections were added. + member this.ToImmutable () = + this.Flush () + builder.ToImmutable () diff --git a/src/FSharp.Collections.Immutable/Helper/DescendingSorts.fs b/src/FSharp.Collections.Immutable/Helper/DescendingSorts.fs new file mode 100644 index 00000000..8f1182ed --- /dev/null +++ b/src/FSharp.Collections.Immutable/Helper/DescendingSorts.fs @@ -0,0 +1,77 @@ +/// +/// The descending sorts of the modules that mirror : stable sorts +/// in the order of F# generic comparison, with the comparer that the type caches, and without the boxes of the inline +/// sorts of (ADR 0001, D5). +/// +/// +/// +/// and +/// +/// are inline. Compiled into a generic function, they compare with the generic operator, which boxes the elements, or the +/// keys, of a value type in every comparison: at 10 000 integers, a sort allocates 7.5 MB, eleven times as much as a sort +/// with a comparison function. +/// +/// +/// The sorts here call +/// , +/// which is not inline and runs the stable sort of , with the comparer that +/// caches for the type. The comparer compares +/// as the generic operator does, so the comparisons, their order and the exceptions that they throw are those of +/// +/// and +/// +/// (ADR 0001, D6): a comparison or a projection that throws makes the sort throw an +/// that holds the exception. +/// +/// +module internal FSharp.Collections.Immutable.DescendingSorts + +/// +/// The elements of a sequence in descending order of F# generic comparison. The sequence sorts the elements stably +/// when it is read. +/// +/// The elements, which the caller has checked for . +/// The elements, equal elements in their order. +/// +/// +/// The comparer compares the second element with the first, as +/// +/// does. The sort allocates the arrays +/// of FSharp.Core and nothing per comparison, for a type that has a primitive comparer. +/// +/// +/// It exists instead of +/// , +/// which boxes the elements of a value type in every comparison (see the +/// remarks of the module). +/// +/// +let sortDescending (source : seq<'T>) = + let comparer = DefaultComparer<'T>.Instance + // The annotations keep the arguments non-nullable: IComparer<'T>.Compare takes nullable ones, and the lambda + // would get them, and the sequence of the result an element type that is nullable too + Seq.sortWith (fun (left : 'T) (right : 'T) -> comparer.Compare (right, left)) source + +/// +/// The elements of a sequence in descending order of a projection, in F# generic comparison. The sequence sorts the +/// elements stably when it is read. +/// +/// The function that returns the key of an element. +/// The elements, which the caller has checked for . +/// The elements, those with equal keys in their order. +/// +/// +/// The projection runs in the comparison, twice for each comparison and for the second element first, as it does in +/// ; +/// the keys are not computed beforehand. +/// +/// +/// It exists instead of +/// , +/// which boxes the keys of a value type in every comparison (see the remarks +/// of the module). +/// +/// +let sortByDescending (projection : 'T -> 'Key) (source : seq<'T>) = + let comparer = DefaultComparer<'Key>.Instance + Seq.sortWith (fun (left : 'T) (right : 'T) -> comparer.Compare (projection right, projection left)) source diff --git a/src/FSharp.Collections.Immutable/Helper/Lengths.fs b/src/FSharp.Collections.Immutable/Helper/Lengths.fs new file mode 100644 index 00000000..342d2eb2 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Helper/Lengths.fs @@ -0,0 +1,88 @@ +/// +/// The lengths of the queues and the stacks, and +/// , which do not store their length, counted only as far as +/// a caller needs them (ADR 0001, D5 and D6). +/// +/// +/// +/// The checks of take a length, but most of them only tell an empty +/// input from one with elements, or an input of one element from a longer one, and the sorts return an input of at most one +/// element unchanged. A count of a whole queue or stack would make each of them O(n). +/// +/// +/// The modules of the queues and the stacks pass a limit instead: 1 to tell an empty input from one with elements, 2 to +/// tell an input of one element from a longer one. The functions take the interfaces, which the concrete queue and stack +/// implement, so that one function serves a module and its interface module. +/// +/// +module internal FSharp.Collections.Immutable.Lengths + +open System.Collections.Immutable + +/// +/// Counts the elements of a collection up to a limit, by stepping from the collection to the collection without its first +/// element. +/// +/// Tells whether a collection has no element. +/// Returns the collection without its first element; the collection is not empty. +/// The largest number to count. +/// The collection to count. +/// The smaller of the number of elements and the limit, and 0 for a limit that is not positive. +/// +/// +/// The step runs at most limit - 1 times, so a limit of 1 only reads whether the collection is empty. +/// +/// +/// The queue and the stack differ only in how they tell that no element is left and how they step, so the loop exists +/// once. It is inline and inlines its two functions, so that a count contains the calls of the interface members +/// themselves, not calls of closures. +/// +/// +let inline private countUpTo + ([] isEmpty : 'Collection -> bool) + ([] rest : 'Collection -> 'Collection) + (limit : int) + (collection : 'Collection) + = + let mutable remaining = collection + let mutable count = 0 + + while count < limit && not (isEmpty remaining) do + count <- count + 1 + + if count < limit then + remaining <- rest remaining + + count + +/// +/// The number of elements of a queue, counted up to a limit: the length, or the limit for a longer queue. +/// +/// The largest number to count; a limit that is not positive gives 0. +/// The queue, which the caller has checked for . +/// The smaller of the length of the queue and the limit, and 0 for a limit that is not positive. +/// +/// O(1) for a limit of 1, which only reads . A +/// larger limit calls at most limit - 1 times. +/// Each call allocates a queue, and the call that takes the last element of the front of an +/// reverses its back once, so the function suits small +/// limits. +/// +let queueUpTo (limit : int) (queue : IImmutableQueue<'T>) = + countUpTo (fun (queue : IImmutableQueue<'T>) -> queue.IsEmpty) (fun queue -> queue.Dequeue ()) limit queue + +/// +/// The number of elements of a stack, counted up to a limit: the length, or the limit for a longer stack. +/// +/// +/// The largest number to count; a limit that is not positive gives 0, and counts +/// the whole stack. +/// +/// The stack, which the caller has checked for . +/// The smaller of the length of the stack and the limit, and 0 for a limit that is not positive. +/// +/// O(min(n, limit)), without an allocation: returns +/// the stack below the top element, which exists already. +/// +let stackUpTo (limit : int) (stack : IImmutableStack<'T>) = + countUpTo (fun (stack : IImmutableStack<'T>) -> stack.IsEmpty) (fun stack -> stack.Pop ()) limit stack diff --git a/src/FSharp.Collections.Immutable/Helper/ListArrays.fs b/src/FSharp.Collections.Immutable/Helper/ListArrays.fs new file mode 100644 index 00000000..d2caa831 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Helper/ListArrays.fs @@ -0,0 +1,344 @@ +/// +/// The functions of the FSharp.Core module that walk two or three +/// lists together, over arrays, for the modules that mirror it and work on snapshots of their collections (ADR 0001, D5 +/// and D6). +/// +/// +/// +/// The FSharp.Core module has these functions too, but it handles +/// arrays of different lengths differently. Its functions compare the lengths before they call the callback once, and +/// report arrays. The functions of walk the lists together, call +/// the callback for the common prefix, compare the lengths when the shorter list ends, and report lists. A callback with +/// a side effect shows the difference, and so does a predicate that decides before the end. +/// +/// +/// Each function here applies its callback to the elements of the common prefix in order and then checks the lengths with +/// the names that the caller passes (see ), so that the callback +/// sees what it sees in a list before the exception. A predicate of +/// +/// or +/// +/// that decides early ends the walk before the check, as in a list. +/// +/// +/// The summary of each function, the descriptions of its parameters and of its result are those of the function of +/// (FSharp.Core 10.1), with arrays where the original says lists or +/// collections. The remarks say what differs and why the function exists. +/// +/// +module internal FSharp.Collections.Immutable.ListArrays + +/// +/// Tests if any pair of corresponding elements of the arrays satisfies the given predicate. +/// +/// The name to report for the second array; FSharp.Core uses "list2". +/// The function to test the input elements. +/// The first input array. +/// The second input array. +/// True if any pair of elements satisfy the predicate. +/// +/// No pair of the common prefix satisfies the predicate, and the arrays differ in length. The exception is the one of +/// . +/// +/// +/// +/// The predicate is applied to matching elements in the two arrays up to the lesser of the two lengths. If any application +/// returns true then the overall result is true and no further elements are tested. Otherwise, if one array is longer than +/// the other then the exception is raised. Otherwise, false is returned. This is +/// an O(n) operation in the worst case, where n is the length of the arrays. +/// +/// +/// It exists because +/// +/// compares the lengths before it calls the predicate and reports arrays, where +/// +/// calls the predicate for the common prefix first and reports lists. +/// +/// +let exists2 (name2 : string) (predicate : 'T1 -> 'T2 -> bool) (array1 : 'T1 array) (array2 : 'T2 array) = + let common = min array1.Length array2.Length + let mutable found = false + let mutable index = 0 + + while not found && index < common do + found <- predicate array1[index] array2[index] + index <- index + 1 + + if not found then + ListChecks.exists2 name2 array1.Length array2.Length + + found + +/// +/// Applies a function to corresponding elements of two collections, threading an accumulator argument through the +/// computation. The collections must have identical sizes. If the input function is f and the elements are +/// i0...iN and j0...jN then computes f (... (f s i0 j0)...) iN jN. +/// +/// The name to report for the first array; FSharp.Core uses "list1". +/// The name to report for the second array; FSharp.Core uses "list2". +/// The function to update the state given the input elements. +/// The initial state. +/// The first input array. +/// The second input array. +/// The final state value. +/// +/// The arrays differ in length. The folder has folded the pairs of the common prefix by then. The exception is the one of +/// . +/// +/// +/// +/// This is an O(n) operation, where n is the length of the arrays. +/// +/// +/// It exists because +/// +/// compares the lengths before it calls the folder and reports arrays, where +/// +/// folds the common prefix first and reports lists. +/// +/// +let fold2 + (name1 : string) + (name2 : string) + (folder : 'State -> 'T1 -> 'T2 -> 'State) + (state : 'State) + (array1 : 'T1 array) + (array2 : 'T2 array) + = + let mutable state = state + + for index = 0 to min array1.Length array2.Length - 1 do + state <- folder state array1[index] array2[index] + + ListChecks.sameLengths name1 array1.Length name2 array2.Length + state + +/// +/// Tests if all corresponding elements of the arrays satisfy the given predicate pairwise. +/// +/// The name to report for the first array; FSharp.Core uses "list1". +/// The name to report for the second array; FSharp.Core uses "list2". +/// The function to test the input elements. +/// The first input array. +/// The second input array. +/// True if all of the pairs of elements satisfy the predicate. +/// +/// Every pair of the common prefix satisfies the predicate, and the arrays differ in length. The exception is the one of +/// . +/// +/// +/// +/// The predicate is applied to matching elements in the two arrays up to the lesser of the two lengths. If any application +/// returns false then the overall result is false and no further elements are tested. Otherwise, if one array is longer +/// than the other then the exception is raised. Otherwise, true is returned. This +/// is an O(n) operation in the worst case, where n is the length of the arrays. +/// +/// +/// It exists because +/// +/// compares the lengths before it calls the predicate and reports arrays, where +/// +/// calls the predicate for the common prefix first and reports lists. +/// +/// +let forall2 (name1 : string) (name2 : string) (predicate : 'T1 -> 'T2 -> bool) (array1 : 'T1 array) (array2 : 'T2 array) = + let common = min array1.Length array2.Length + let mutable holds = true + let mutable index = 0 + + while holds && index < common do + holds <- predicate array1[index] array2[index] + index <- index + 1 + + if holds then + ListChecks.sameLengths name1 array1.Length name2 array2.Length + + holds + +/// +/// Applies the given function to two collections simultaneously. The collections must have identical sizes. +/// +/// The name to report for the first array; FSharp.Core uses "list1". +/// The name to report for the second array; FSharp.Core uses "list2". +/// The function to apply to pairs of elements from the input arrays. +/// The first input array. +/// The second input array. +/// +/// The arrays differ in length. The action has been applied to the pairs of the common prefix by then. The exception is the +/// one of +/// . +/// +/// +/// +/// This is an O(n) operation, where n is the length of the arrays. +/// +/// +/// It exists because +/// +/// compares the lengths before it calls the action and reports arrays, where +/// +/// applies the action to the common prefix first and reports lists. +/// +/// +let iter2 (name1 : string) (name2 : string) (action : 'T1 -> 'T2 -> unit) (array1 : 'T1 array) (array2 : 'T2 array) = + for index = 0 to min array1.Length array2.Length - 1 do + action array1[index] array2[index] + + ListChecks.sameLengths name1 array1.Length name2 array2.Length + +/// +/// Applies the given function to two collections simultaneously. The collections must have identical sizes. The integer +/// passed to the function indicates the index of the element. +/// +/// The name to report for the first array; FSharp.Core uses "list1". +/// The name to report for the second array; FSharp.Core uses "list2". +/// The function to apply to a pair of elements from the input arrays along with their index. +/// The first input array. +/// The second input array. +/// +/// The arrays differ in length. The action has been applied to the pairs of the common prefix by then. The exception is the +/// one of +/// . +/// +/// +/// +/// This is an O(n) operation, where n is the length of the arrays. +/// +/// +/// It exists because +/// +/// compares the lengths before it calls the action and reports arrays, where +/// +/// applies the action to the common prefix first and reports lists. +/// +/// +let iteri2 (name1 : string) (name2 : string) (action : int -> 'T1 -> 'T2 -> unit) (array1 : 'T1 array) (array2 : 'T2 array) = + for index = 0 to min array1.Length array2.Length - 1 do + action index array1[index] array2[index] + + ListChecks.sameLengths name1 array1.Length name2 array2.Length + +/// +/// Builds a new array whose elements are the results of applying the given function to the corresponding elements of the +/// two arrays pairwise. +/// +/// The name to report for the first array; FSharp.Core uses "list1". +/// The name to report for the second array; FSharp.Core uses "list2". +/// The function to transform pairs of elements from the input arrays. +/// The first input array. +/// The second input array. +/// The array of transformed elements. +/// +/// The arrays differ in length. The mapping has been applied to the pairs of the common prefix by then. The exception is +/// the one of +/// . +/// +/// +/// +/// This is an O(n) operation, where n is the length of the arrays. +/// +/// +/// It exists because +/// +/// compares the lengths before it calls the mapping and reports arrays, where +/// +/// applies the mapping to the common prefix first and reports lists. +/// +/// +let map2 (name1 : string) (name2 : string) (mapping : 'T1 -> 'T2 -> 'U) (array1 : 'T1 array) (array2 : 'T2 array) : 'U array = + let result = Array.zeroCreate (min array1.Length array2.Length) + + for index = 0 to result.Length - 1 do + result[index] <- mapping array1[index] array2[index] + + ListChecks.sameLengths name1 array1.Length name2 array2.Length + result + +/// +/// Builds a new array whose elements are the results of applying the given function to the corresponding elements of the +/// three arrays simultaneously. +/// +/// The name to report for the first array; FSharp.Core uses "list1". +/// The name to report for the second array; FSharp.Core uses "list2". +/// The name to report for the third array; FSharp.Core uses "list3". +/// The function to transform triples of elements from the input arrays. +/// The first input array. +/// The second input array. +/// The third input array. +/// The array of transformed elements. +/// +/// The arrays differ in length. The mapping has been applied to the triples of the common prefix by then. The exception is +/// the one of +/// . +/// +/// +/// +/// This is an O(n) operation, where n is the length of the arrays. +/// +/// +/// It exists because +/// +/// compares the lengths before it calls the mapping and reports arrays, where +/// +/// applies the mapping to the common prefix first and reports lists. +/// +/// +let map3 + (name1 : string) + (name2 : string) + (name3 : string) + (mapping : 'T1 -> 'T2 -> 'T3 -> 'U) + (array1 : 'T1 array) + (array2 : 'T2 array) + (array3 : 'T3 array) + : 'U array = + let result = Array.zeroCreate (min array1.Length (min array2.Length array3.Length)) + + for index = 0 to result.Length - 1 do + result[index] <- mapping array1[index] array2[index] array3[index] + + ListChecks.map3 name1 array1.Length name2 array2.Length name3 array3.Length + result + +/// +/// Like +/// , +/// but mapping corresponding elements from two arrays of equal length. +/// +/// The name to report for the first array; FSharp.Core uses "list1". +/// The name to report for the second array; FSharp.Core uses "list2". +/// The function to transform pairs of elements from the two arrays and their index. +/// The first input array. +/// The second input array. +/// The array of transformed elements. +/// +/// The arrays differ in length. The mapping has been applied to the pairs of the common prefix by then. The exception is +/// the one of +/// . +/// +/// +/// +/// This is an O(n) operation, where n is the length of the arrays. +/// +/// +/// It exists because +/// +/// compares the lengths before it calls the mapping and reports arrays, where +/// +/// applies the mapping to the common prefix first and reports lists. +/// +/// +let mapi2 + (name1 : string) + (name2 : string) + (mapping : int -> 'T1 -> 'T2 -> 'U) + (array1 : 'T1 array) + (array2 : 'T2 array) + : 'U array = + let result = Array.zeroCreate (min array1.Length array2.Length) + + for index = 0 to result.Length - 1 do + result[index] <- mapping index array1[index] array2[index] + + ListChecks.sameLengths name1 array1.Length name2 array2.Length + result diff --git a/src/FSharp.Collections.Immutable/Helper/ListChecks.fs b/src/FSharp.Collections.Immutable/Helper/ListChecks.fs new file mode 100644 index 00000000..cd390d48 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Helper/ListChecks.fs @@ -0,0 +1,581 @@ +/// +/// The argument checks of the functions of the FSharp.Core module, +/// for the modules that mirror it: , +/// , the modules of the queues and the stacks, and their interface +/// modules (ADR 0001, D6). +/// +/// +/// +/// A module that mirrors throws what its functions throw for the +/// same input: the same type and the same message, with the name of the parameter of the mirroring function (ADR 0001, D6). +/// Each check takes the lengths or the indices to test and the names to report, and throws what the functions that it names +/// throw. The checks are here, once, so that no module spells the type, the message and the parameter name of every +/// exception again. +/// +/// +/// The documentation of each check gives the name that the FSharp.Core function reports for the parameter. +/// and +/// pass those names; a module with other parameter names passes +/// its own. +/// +/// +/// The FSharp.Core functions of two or three lists compare the lengths when the shorter list ends, so a function with a +/// callback applies it to the common prefix before it calls the check of the lengths. +/// does that over arrays. +/// +/// +module internal FSharp.Collections.Immutable.ListChecks + +open System + +/// +/// The message that +/// , +/// , +/// , +/// and +/// write as a literal. +/// +/// +/// It is not a constant of , whose every constant is the text of +/// one resource: no resource of FSharp.Core holds this text. +/// +[] +let private IndexOutsideListBounds = "index must be within bounds of the list" + +/// +/// Rejects an empty list, as +/// , +/// , +/// , +/// and +/// do. +/// +/// The name of the parameter to report: "list" for these functions. +/// The length of the list. +/// +/// The length is 0. The message is , and the +/// parameter name is . +/// +/// +/// It is a check of its own, besides +/// , because the +/// functions of the two groups give an empty input different messages. +/// +let notEmpty (name : string) (length : int) = + if length = 0 then + invalidArg name ErrorStrings.InputListWasEmpty + +/// +/// Rejects an empty input with the message of an empty sequence, as +/// , +/// , +/// and +/// ("list") and +/// , +/// and +/// ("source") do. +/// +/// The name of the parameter to report: "list" or "source", as above. +/// The length of the input. +/// +/// The length is 0. The message is , and the +/// parameter name is . +/// +/// +/// It is a check of its own, besides +/// , because the messages +/// differ: these functions say "sequence" where the functions of the other check say "list". +/// +let notEmptySequence (name : string) (length : int) = + if length = 0 then + invalidArg name ErrorStrings.InputSequenceEmpty + +/// +/// Rejects an input without exactly one element, as +/// does. +/// +/// The name of the parameter to report: "source" for this function. +/// The length of the input. +/// +/// The length is not 1. The message is for no +/// element and for more than one, and the +/// parameter name is . +/// +let exactlyOne (name : string) (length : int) = + if length = 0 then + invalidArg name ErrorStrings.InputSequenceEmpty + elif length > 1 then + invalidArg name ErrorStrings.InputSequenceTooLong + +/// +/// Rejects an index outside the list, as +/// does. +/// +/// The name of the parameter to report: "index" for this function. +/// The index to test. +/// The length of the list. +/// +/// The index is negative or not less than the length. The message is +/// , and the parameter name is +/// . +/// +/// +/// It is a check of its own, besides +/// , because the +/// message differs, although the indices that both reject are the same. +/// +let item (name : string) (index : int) (length : int) = + if index < 0 || index >= length then + invalidArg name ErrorStrings.IndexOutOfBounds + +/// +/// Rejects an index outside the range from 0 to the length, as +/// and +/// do. +/// +/// The name of the parameter to report: "index" for these functions. +/// The index to test. +/// The length of the list. +/// +/// The index is negative or greater than the length. The message is +/// , and the parameter name is +/// . +/// +/// +/// The length itself is a valid index, because an element can be inserted after the last one. +/// +let insertAt (name : string) (index : int) (length : int) = + if index < 0 || index > length then + invalidArg name IndexOutsideListBounds + +/// +/// Rejects an index outside the list, as +/// and +/// do. +/// +/// The name of the parameter to report: "index" for these functions. +/// The index to test. +/// The length of the list. +/// +/// The index is negative or not less than the length. The message is +/// , and the parameter name is +/// . +/// +/// +/// Unlike for +/// , the length +/// itself is not a valid index: no element is there to remove or to replace. +/// +let removeAt (name : string) (index : int) (length : int) = + if index < 0 || index >= length then + invalidArg name IndexOutsideListBounds + +/// +/// Rejects a negative index and a range that ends beyond the list, as +/// does. +/// +/// The name of the parameter to report: "index" for this function. +/// The index of the first element of the range. +/// The number of elements of the range. +/// The length of the list. +/// +/// The index is negative, or the index plus the count is greater than the length. The message is +/// , and the parameter name is +/// . +/// +/// +/// +/// walks the first index + count elements and keeps the ones before the index, so a count that is not positive +/// removes nothing, even with an index beyond the list, as long as index + count does not exceed the length. The +/// check tests the same sum, which wraps around on overflow there as it does here. +/// +let removeManyAt (name : string) (index : int) (count : int) (length : int) = + if index < 0 || index + count > length then + invalidArg name IndexOutsideListBounds + +/// +/// Rejects a negative count, as +/// , +/// and +/// do. +/// +/// The name of the parameter to report: "count" for these functions. +/// The count to test. +/// +/// The count is negative. The message is the one of +/// , +/// which gives the name and the value of the count, and the parameter name is . +/// +/// +/// +/// gives the message without the value, so it has a check of its own, +/// . +/// +let nonNegative (name : string) (count : int) = + if count < 0 then + Raise.invalidArgInputMustBeNonNegative name count + +/// +/// Rejects a negative count, as +/// does. +/// +/// The name of the parameter to report: "count" for this function. +/// The count to test. +/// +/// The count is negative. The message is +/// , and the parameter name is +/// . +/// +/// +/// It is a check of its own, besides +/// , because unlike +/// +/// it reports the message without the value. +/// +let replicate (name : string) (count : int) = + if count < 0 then + invalidArg name ErrorStrings.InputMustBeNonNegative + +/// +/// Rejects a size that is not positive, as +/// ("chunkSize"), +/// +/// ("windowSize") and +/// ("count") do. +/// +/// The name of the parameter to report: the one in the brackets above. +/// The size to test. +/// +/// The size is 0 or negative. The message is the one of +/// , which +/// gives the name and the value of the size, and the parameter name is . +/// +let positive (name : string) (size : int) = + if size <= 0 then + Raise.invalidArgInputMustBePositive name size + +/// +/// Rejects a negative count and a count beyond the length, as +/// does. +/// +/// The name of the parameter to report: "count" for this function. +/// The count to test. +/// The length of the list. +/// +/// The count is negative. The message is the one of +/// , and +/// the parameter name is . +/// +/// +/// The count is greater than the length. The message is the one of +/// , which says how many +/// elements are missing. +/// +let take (name : string) (count : int) (length : int) = + if count < 0 then + Raise.invalidArgInputMustBeNonNegative name count + elif count > length then + Raise.invalidOpListNotEnoughElements (count - length) + +/// +/// Rejects a count beyond the length, as +/// does. +/// +/// The name of the parameter to report: "count" for this function. +/// The count to test. +/// The length of the list. +/// +/// The count is greater than the length. The message is the one of +/// , +/// which gives the count and the distance past the list, and the parameter name is . +/// +/// +/// A negative count skips nothing, so the function does not reject it, as +/// does not. +/// +let skip (name : string) (count : int) (length : int) = + if count > length then + Raise.invalidArgOutOfRange name count "distance past the list" (count - length) + +/// +/// Rejects a negative index and an index beyond the length, as +/// does. +/// +/// The name of the parameter to report: "index" for this function. +/// The index to test. +/// The length of the list. +/// +/// The index is negative. The message is the one of +/// , and +/// the parameter name is . +/// +/// +/// The index is greater than the length. The message is the one of +/// for an empty list and the one of +/// for a longer one. +/// +/// +/// An empty list has a message of its own because +/// gives it one. +/// +let splitAt (name : string) (index : int) (length : int) = + if index < 0 then + Raise.invalidArgInputMustBeNonNegative name index + elif index > length then + if length = 0 then + Raise.invalidOpListEmpty () + else + Raise.invalidOpListNotEnoughElements (index - length) + +/// +/// Rejects lists of different lengths, as +/// , +/// , +/// , +/// , +/// , +/// and +/// +/// do ("list1", "list2"). +/// +/// The name of the parameter of the first list. +/// The length of the first list. +/// The name of the parameter of the second list. +/// The length of the second list. +/// +/// The lengths differ. The exception is the one of +/// : +/// the parameter name is the name of the shorter list, and the message says how much shorter it is. +/// +let sameLengths (name1 : string) (length1 : int) (name2 : string) (length2 : int) = + if length1 < length2 then + Raise.invalidArgDifferentListLength name1 name2 (length2 - length1) + elif length2 < length1 then + Raise.invalidArgDifferentListLength name2 name1 (length1 - length2) + +/// +/// Rejects lists of different lengths with a message without lengths, as +/// does. +/// +/// The name of the parameter of the second list: "list2" for this function. +/// The length of the first list. +/// The length of the second list. +/// +/// The lengths differ. The message is , +/// and the parameter name is always the name of the second list, . +/// +/// +/// It is a check of its own, besides +/// , +/// because the exception of +/// +/// neither names the shorter list nor says how much shorter it is. +/// +let exists2 (name2 : string) (length1 : int) (length2 : int) = + if length1 <> length2 then + invalidArg name2 ErrorStrings.ListsHadDifferentLengths + +/// +/// Rejects lists of different lengths before the folder is called, as +/// does +/// ("list1", "list2"). +/// +/// The name of the parameter of the first list. +/// The length of the first list. +/// The name of the parameter of the second list. +/// The length of the second list. +/// +/// The lengths differ. When one of the lists is empty, the exception is the one of +/// . +/// Otherwise it is the one of +/// , +/// which names both lists and gives both lengths. +/// +/// +/// +/// reports the same mistake in two ways. An empty list against a list with elements is reported as the other functions +/// of two lists report it. Two lists with elements reach the fallback of the function, which copies them into arrays to +/// fold them from the end, and it reports their lengths. Hence the two branches of the check. +/// +let foldBack2 (name1 : string) (length1 : int) (name2 : string) (length2 : int) = + if length1 <> length2 then + if length1 = 0 || length2 = 0 then + sameLengths name1 length1 name2 length2 + else + Raise.invalidArg2ListsDifferent name1 length1 name2 length2 + +/// +/// Rejects lists of different lengths and reports their lengths, as +/// does +/// ("list1", "list2", "list3"). +/// +/// The name of the parameter of the first list. +/// The length of the first list. +/// The name of the parameter of the second list. +/// The length of the second list. +/// The name of the parameter of the third list. +/// The length of the third list. +/// +/// The lengths differ. The exception is the one of +/// : +/// it names the three lists and gives their lengths. +/// +let map3 (name1 : string) (length1 : int) (name2 : string) (length2 : int) (name3 : string) (length3 : int) = + if length1 <> length2 || length1 <> length3 then + Raise.invalidArg3ListsDifferent name1 name2 name3 length1 length2 length3 + +/// +/// Rejects lists of different lengths and reports the lengths that remain after their common prefix, as +/// +/// does ("list1", "list2", "list3"). +/// +/// The name of the parameter of the first list. +/// The length of the first list. +/// The name of the parameter of the second list. +/// The length of the second list. +/// The name of the parameter of the third list. +/// The length of the third list. +/// +/// The lengths differ. The exception is the one of +/// , +/// with each length reduced by the length of the shortest list. +/// +/// +/// It is a check of its own, besides +/// , +/// because +/// +/// walks the three lists together and reports what is left of each when the shortest one ends. +/// +let zip3 (name1 : string) (length1 : int) (name2 : string) (length2 : int) (name3 : string) (length3 : int) = + if length1 <> length2 || length1 <> length3 then + let common = min length1 (min length2 length3) + Raise.invalidArg3ListsDifferent name1 name2 name3 (length1 - common) (length2 - common) (length3 - common) + +/// +/// Rejects lists of different lengths, as +/// does. +/// +/// The name of the parameter of the list of lists: "list" for this function. +/// The lengths of the lists, in the order of the list of lists. +/// +/// The lengths differ. The exception is the one of +/// : +/// the parameter name is the name of the shorter list of a pair that includes the first list, and the message says how +/// much shorter it is. The lists are named by their positions: for the name "list", "list.[0]", +/// "list.[1]" and so on. +/// +/// +/// +/// +/// takes the first element of every list in each round, and fails in the first round in which a list has none, which is +/// the round of the length of the shortest list. It then compares each list with the first one. When the first list is +/// among the shortest, the pair is the first list and the first list that goes on, which is longer. Otherwise the pair is +/// the first list that has run out, which is shorter, and the first list. The check takes the lengths at once and finds +/// the same pair, so the caller does not take the lists apart. +/// +/// +/// Fewer than two lists cannot differ, so they pass. +/// +/// +let transpose (name : string) (lengths : int array) = + if lengths.Length > 1 then + let shortest = Array.min lengths + let first = lengths[0] + let element (index : int) = String.Format ("{0}.[{1}]", name, index) + + if first = shortest then + // The first list ends first, so the first list that goes on is longer + let mutable index = 1 + + while index < lengths.Length && lengths[index] = shortest do + index <- index + 1 + + if index < lengths.Length then + Raise.invalidArgDifferentListLength (element 0) (element index) (lengths[index] - shortest) + else + // The first list goes on, so the first list that ends is shorter + let mutable index = 1 + + while lengths[index] <> shortest do + index <- index + 1 + + Raise.invalidArgDifferentListLength (element index) (element 0) (first - shortest) + +/// +/// Rejects a negative count and, for a positive count, an empty input, as +/// , +/// and +/// do. +/// +/// The name of the parameter of the count: "count" for these functions. +/// The count to test. +/// The name of the parameter of the input: "source" for these functions. +/// The length of the input. +/// +/// The count is negative (the exception of +/// ), or the count is +/// positive and the input is empty (the message is +/// , and the parameter name is +/// ). +/// +/// +/// A count of 0 draws nothing, so it is valid for an empty input. +/// +let randomChoices (countName : string) (count : int) (sourceName : string) (length : int) = + nonNegative countName count + + if length = 0 && count > 0 then + invalidArg sourceName ErrorStrings.InputSequenceEmpty + +/// +/// Rejects what +/// +/// rejects and a count beyond the length, as +/// , +/// and +/// do. +/// +/// The name of the parameter of the count: "count" for these functions. +/// The count to test. +/// The name of the parameter of the input: "source" for these functions. +/// The length of the input. +/// +/// The input is not empty and shorter than the count. The message is +/// , and the parameter name is +/// . +/// +/// +/// A sample draws each element at most once, so unlike the choices it cannot be larger than the input. +/// +let randomSample (countName : string) (count : int) (sourceName : string) (length : int) = + randomChoices countName count sourceName length + + if length > 0 && count > length then + invalidArg countName ErrorStrings.NotEnoughElements + +/// +/// The index in a list of the given length that the functions with a randomizer draw, as +/// does. +/// +/// The name of the parameter of the randomizer: "randomizer" for these functions. +/// The function that returns a number from 0 up to, but not including, 1. +/// The length of the list. +/// The number that the randomizer returned, times the length, without its fraction. +/// +/// The randomizer returned a number outside [0, 1), or not a number. The message is the one of +/// , and +/// the parameter name is . +/// +/// +/// The check and the index are one function because the FSharp.Core functions with a randomizer test every number that it +/// returns before they use it. +/// +let randomIndex (name : string) (randomizer : unit -> float) (length : int) = + let value = randomizer () + + if value >= 0.0 && value < 1.0 then + int (value * float length) + else + Raise.invalidArgRandomizerOutOfRange name value diff --git a/src/FSharp.Collections.Immutable/Helper/ListRanges.fs b/src/FSharp.Collections.Immutable/Helper/ListRanges.fs new file mode 100644 index 00000000..9af79ed3 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Helper/ListRanges.fs @@ -0,0 +1,191 @@ +/// +/// The range checks of the functions that wrap the members of +/// and of its builder, with the parameter names of the callers (ADR 0001, D6 and D14). +/// +/// +/// +/// The members add an index and a count and compare the sum with the length. The sum wraps around at +/// , so a range that starts near the end of the numbers passes the check of some +/// members, which then do nothing, or answer with an index that is not the index of an element. The checks here compare +/// the count with the difference between the length and the index, which cannot overflow once the index is not negative. +/// +/// +/// A range that the members of reject is rejected with the +/// same exception and the name that the member gives the parameter, except that the caller passes the names of its own +/// parameters. The checks add the ranges that the members accept and that the documentation calls invalid. +/// +/// +/// The functions of , +/// and +/// that search, reverse, sort or copy a range call the +/// checks of this module before they call the member, so that the three modules reject the same ranges in the same way. +/// +/// +module internal FSharp.Collections.Immutable.ListRanges + +open System + +/// +/// The message of the for a range that does not fit in the list. +/// +/// +/// The runtime throws this exception from +/// , +/// with a message that says "array" and "source collection". The text here says "list". It is not a constant of +/// , because no resource holds it. +/// +[] +let private OffsetAndLengthOutOfBounds = + "Offset and length were out of bounds for the list or count is greater than the number of elements from index to the end of the list." + +/// +/// Checks a range of elements that starts at in a list. +/// +/// The name of the parameter of the index. +/// The index of the first element of the range. +/// The name of the parameter of the count. +/// The number of elements of the range. +/// +/// The name that the exception for a range beyond the end of the list gives: the members name the count, except +/// , which names the +/// index. +/// +/// The length of the list. +/// +/// The index or the count is negative, or the range ends beyond the list: the checks of +/// , +/// , +/// , +/// , +/// +/// and . +/// A range of no elements may start at the end of the list. +/// +/// +/// It serves the functions that look at a range from its first element, such as the ones that search forward for an +/// item or for an element that satisfies a predicate, reverse a range or sort it, and, through +/// , +/// copy it. The members compare the sum of the index and the count with the length, which wraps around (see the remarks of +/// the module). +/// +let forward (indexName : string) (index : int) (countName : string) (count : int) (beyondName : string) (length : int) = + if index < 0 then + raise (ArgumentOutOfRangeException indexName) + + if count < 0 then + raise (ArgumentOutOfRangeException countName) + + // The index and the length are not negative, so the difference cannot overflow + if count > length - index then + raise (ArgumentOutOfRangeException beyondName) + +/// +/// Checks a range of elements that ends at in a list, which a +/// backward search walks from its end to its start. +/// +/// The name of the parameter of the index of the last element of the range. +/// The index of the first element that the search looks at. +/// The name of the parameter of the count. +/// The number of elements of the range. +/// The length of the list. +/// +/// The start index or the count is negative, the count exceeds the length, or a range of elements does not lie inside +/// the list: the checks of +/// +/// and , +/// which also accept a last element at or beyond the end of the list, and answer with the index of another element. +/// +/// +/// +/// A range of no elements lies anywhere, so a start index at or beyond the end of the list passes for it. +/// +/// +/// It is a check of its own, besides +/// , +/// because a backward search gives the index of the last element of the range, not of the first, and the members accept a +/// last element at or beyond the end of the list. It serves the functions that search backward for the last index of an +/// item or of an element that satisfies a predicate. +/// +/// +let backward (startIndexName : string) (startIndex : int) (countName : string) (count : int) (length : int) = + if startIndex < 0 then + raise (ArgumentOutOfRangeException startIndexName) + + if count < 0 || count > length then + raise (ArgumentOutOfRangeException countName) + + // The range starts at startIndex - count + 1: the count 1 starts at the start index itself. The subtraction cannot + // overflow, because the count is positive. + if + count > 0 + && (startIndex >= length || count - 1 > startIndex) + then + raise (ArgumentOutOfRangeException startIndexName) + +/// +/// Checks the range of a binary search, which the members do not check against the length of the list. +/// +/// The name of the parameter of the index. +/// The index of the first element of the range. +/// The name of the parameter of the count. +/// The number of elements of the range. +/// The length of the list. +/// The index or the count is negative. +/// +/// The range extends beyond the list. The documentation of +/// +/// promises this exception, and the member returns a result instead (ADR 0001, D14). +/// +/// +/// It is a check of its own, besides +/// , +/// because the exception for a range beyond the end is an , as +/// +/// throws it, where the other checks of the module throw an +/// . It serves the functions that search a range of a sorted list. +/// +let search (indexName : string) (index : int) (countName : string) (count : int) (length : int) = + if index < 0 then + raise (ArgumentOutOfRangeException indexName) + + if count < 0 then + raise (ArgumentOutOfRangeException countName) + + if count > length - index then + raise (ArgumentException (OffsetAndLengthOutOfBounds, countName)) + +/// +/// Checks the ranges of a copy of elements from a list into an array: the checks of +/// . +/// +/// The name of the parameter of the index in the list. +/// The index of the first element to copy. +/// The name of the parameter of the count. +/// The number of elements to copy. +/// The length of the list. +/// The name of the parameter of the index in the array. +/// The index in the array at which the copy starts. +/// The length of the array. +/// +/// The source index, the count or the target index is negative, or a range lies beyond the list or the array. +/// +/// +/// It checks the range in the list as +/// +/// does, with the count as the name for a range beyond the end, and then the range in the array. It serves the copying +/// functions of the list and of its builder. +/// +let blit + (sourceIndexName : string) + (sourceIndex : int) + (countName : string) + (count : int) + (sourceLength : int) + (targetIndexName : string) + (targetIndex : int) + (targetLength : int) + = + forward sourceIndexName sourceIndex countName count countName sourceLength + + if targetIndex < 0 || count > targetLength - targetIndex then + raise (ArgumentOutOfRangeException targetIndexName) diff --git a/src/FSharp.Collections.Immutable/Helper/Raise.fs b/src/FSharp.Collections.Immutable/Helper/Raise.fs index b36a1141..f9515d00 100644 --- a/src/FSharp.Collections.Immutable/Helper/Raise.fs +++ b/src/FSharp.Collections.Immutable/Helper/Raise.fs @@ -25,6 +25,30 @@ let invalidArgDifferentListLength (arg1 : string) (arg2 : string) (diff : int) : raise (ArgumentException (message, arg1)) +/// +/// Raises the exception for two lists that both have elements and differ in length, as the fallback of +/// +/// does. +/// +/// The name of the parameter of the first list. +/// The length of the first list. +/// The name of the parameter of the second list. +/// The length of the second list. +/// Never: the function always raises. The result is generic so that the call can end a branch of any type. +/// +/// Always. The message is , a line break +/// and the lengths of both lists, and the parameter name is both names, separated by a comma. +/// +/// +/// It is not , +/// which names the shorter list and says how much shorter it is: the fallback of the function copies the lists into +/// arrays, compares their lengths and reports both. +/// +let invalidArg2ListsDifferent (arg1 : string) (len1 : int) (arg2 : string) (len2 : int) : 'T = + let message = $"{ErrorStrings.ListsHadDifferentLengths}\n{arg1}.Length = {len1}, {arg2}.Length = {len2}" + + raise (ArgumentException (message, $"{arg1}, {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 = let message = @@ -53,6 +77,39 @@ let invalidArgOutOfRange (arg : string) (index : int) (text : string) (bound : i raise (ArgumentOutOfRangeException (arg, message)) +/// +/// Raises the exception for a randomizer that returned a number outside [0, 1), which the functions of FSharp.Core with a +/// randomizer, such as +/// , +/// throw. +/// +/// The name of the parameter of the randomizer. +/// The number that the randomizer returned. +/// Never: the function always raises. The result is generic so that the call can end a branch of any type. +/// +/// Always. The message is , a line break and the number +/// with the range that is allowed, and the parameter name is . +/// +let invalidArgRandomizerOutOfRange (arg : string) (value : float) : 'T = + let message = $"{ErrorStrings.OutOfRange}\n{arg} returned {value}, should be in range [0.0, 1.0)." + + raise (ArgumentOutOfRangeException (arg, message)) + +/// +/// Raises the exception for an empty list and an index beyond it, as +/// +/// does. +/// +/// Never: the function always raises. The result is generic so that the call can end a branch of any type. +/// +/// Always. The message is . +/// +/// +/// A longer list that is too short gets +/// instead. +/// +let invalidOpListEmpty () : 'T = raise (InvalidOperationException ErrorStrings.InputListWasEmpty) + /// A list is shorter than an index. let invalidOpListNotEnoughElements (index : int) : 'T = let elements = if index = 1 then "element" else "elements" diff --git a/src/FSharp.Collections.Immutable/Helper/Snapshot.fs b/src/FSharp.Collections.Immutable/Helper/Snapshot.fs new file mode 100644 index 00000000..c1d40399 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Helper/Snapshot.fs @@ -0,0 +1,44 @@ +/// +/// The snapshots that the modules of the collections without an underlying array take before they apply an algorithm of +/// the FSharp.Core module, of +/// or of +/// (ADR 0001, D5). +/// +/// +/// The algorithms work on arrays. A collection such as an , or +/// a queue or a stack, has none, so the module copies its elements into a new array first, applies the algorithm and +/// builds the result from the array it gets. +/// +module internal FSharp.Collections.Immutable.Snapshot + +open System.Linq + +/// +/// The elements of a collection in a new array, in the order in which the collection enumerates them. +/// +/// The name of the parameter of the collection to report: the caller passes the name of its own. +/// The collection. +/// A new array that holds the elements, which the caller may change in place. +/// +/// The collection is . The parameter name is . +/// +/// +/// +/// This is an O(n) operation, where n is the number of elements. A collection that implements +/// copies its elements with +/// in one pass, without the growing +/// buffers of a collection that only enumerates; an empty one gives the shared empty array, which has nothing to change. +/// +/// +/// It exists to check the collection and to copy it in one place. The null check reports the name of the parameter of the +/// caller, where +/// and +/// +/// report "source" (ADR 0001, D6). The copy goes through +/// , because the priority of +/// ADR 0001, D5 puts System.Linq before FSharp.Core. +/// +/// +let ofSeq (name : string) (source : seq<'T>) : 'T array = + checkNotNull name source + Enumerable.ToArray source diff --git a/src/FSharp.Collections.Immutable/ImmutableList.fs b/src/FSharp.Collections.Immutable/ImmutableList.fs deleted file mode 100644 index 6683f84a..00000000 --- a/src/FSharp.Collections.Immutable/ImmutableList.fs +++ /dev/null @@ -1,353 +0,0 @@ -#if INTERACTIVE -namespace global -#else -namespace FSharp.Collections.Immutable - -open FSharp.Collections.Immutable.ImmutableCollectionUtil -#endif -open System.Collections.Immutable - -[] -module ImmutableList = - - ////////// Factory ////////// - - let inline internal check (list : IImmutableList<_>) = checkNotNull (nameof list) list - - let inline empty<'T> = ImmutableList.Create<'T>() - let inline singleton<'T> (item : 'T) : ImmutableList<'T> = ImmutableList.Create<'T>(item) - - let inline ofSeq source = - checkNotNull (nameof source) source - ImmutableList.CreateRange source - - let inline ofArray (source : _ array) = - checkNotNull (nameof source) source - ImmutableList.CreateRange source - - let inline ofList (list : _ list) = ofSeq list - - let inline toSeq (list : ImmutableList<_>) = list :> seq<_> - - let inline toArray (list : ImmutableList<_>) = - check list - Seq.toArray list - - ////////// Building ////////// - - let inline ofBuilder (builder : ImmutableList<_>.Builder) = builder.ToImmutable () - - let inline builder () = ImmutableList.CreateBuilder () - - let toBuilder (list : ImmutableList<_>) = - check list - list.ToBuilder () - - let inline build f = - let builder = builder () - f builder - builder.ToImmutable () - - let inline update f list = - let builder = toBuilder list - f builder - builder.ToImmutable () - - - open System.Collections.Generic - open System - - ////////// IReadOnly* ////////// - - let length list = - check list - list.Count - - let item index list = - check list - list.[index] - - ////////// ImmutableList ////////// - - let contains item (list : ImmutableList<_>) = list.Contains (item) - - let reverse (list : ImmutableList<_>) = list.Reverse () - - let reverseRange (index, count) (list : ImmutableList<_>) = list.Reverse (index, count) - - ////////// IImmutableList ////////// - - /// Replaces an element in the list at a given position with the specified element. - let withItem index value list = - check list - list.SetItem (index, value) - - /// Returns a new list with the first matching element in the list replaced with the specified element with - /// the given comparer. - let replaceWith comparer oldValue value list = - check list - list.Replace (oldValue, value, comparer) - - /// Returns a new list with the first matching element in the list replaced with the specified element. - let replace oldValue value list = replaceWith HashIdentity.Structural oldValue value list - - - /// Creates a list with all the items removed, but with the same sorting and ordering semantics as - /// this list. - let clear list = - check list - list.Clear () - - /// Makes a copy of the list, and adds the specified object to the end of the copied list. - let add item list = - check list - list.Add item - - - /// Makes a copy of the list and adds the specified objects to the end of the copied list. - let append list items = - check list - list.AddRange items - - /// Inserts the specified element at the specified index in a immutable list. - let insert index item list = - check list - list.Insert (index, item) - - /// Inserts the specified elements at the specified index in the immutable list. - let insertRange index items list = - check list - list.InsertRange (index, items) // TODO: rename - - - /// Removes the first occurrence of a specified object from this immutable list using the given comparer. - let removeWith comparer item list = - check list - list.Remove (item, comparer) - - /// Removes the first occurrence of a specified object from this immutable list. - let remove item list = removeWith HashIdentity.Structural item list - - - /// Removes the specified objects from the list with the given comparer. - let exceptWith (comparer : IEqualityComparer<_>) items list = - check list - list.RemoveRange (items, comparer) - - /// Removes the specified objects from the list. - let except items list = exceptWith HashIdentity.Structural items list - - - /// Removes all the elements that do not match the conditions defined by the specified predicate. - let filter predicate list = - check list - Predicate (not << predicate) |> list.RemoveAll - - /// Removes a range of elements from the System.Collections.Immutable.IImmutableList`1. - let removeRange index (count : int) list = - check list - list.RemoveRange (index, count) - - /// Removes the element at the specified index of the immutable list. - let removeAt index list = - check list - list.RemoveAt index - - /// 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 - - ////////// Filter-based ////////// - - let filterFold (predicate : 'State -> 'T -> bool * 'State) initial list = - let state = ref initial - - filter - (fun item -> - let condition, state' = predicate !state item - state := state' - condition - ) - list, - !state - - let skipWhile predicate list = - let condition = ref true - - filter - (fun item -> - if !condition then - condition := !condition && predicate item - !condition - else - false - ) - list - - let skipUntil predicate list = skipWhile (not << predicate) list - - let takeWhile predicate list = - let condition = ref true - - filter - (fun item -> - if !condition then - condition := !condition && predicate item - not !condition - else - true - ) - list - - let takeUntil predicate list = takeWhile (not << predicate) list - - ////////// Loop-based ////////// - - let concat lists = - checkNotNull (nameof lists) lists - - build - <| fun result -> - for list in lists do - result.AddRange list - - let map mapping list = - check list - - build - <| fun builder -> - for item in list do - builder.Add (mapping item) - - let choose chooser list = - check list - - build - <| fun builder -> - for item in list do - match chooser item with - | Some item -> builder.Add item - | None -> () - - - ////////// Based on other operations ////////// - - let isEmpty list = length list = 0 - - let take count list = removeRange count (length list - count) list - - let skip index list = removeRange 0 index list - - 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 last (list : IImmutableList<_>) = list.[length list - 1] - - let tail list = removeAt 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 tryLast list = tryItem (length list - 1) list - - let tryTail list = if isEmpty list then None else Some <| tail list - - let collect mapping list = concat <| map mapping list - - let cons head list = insert 0 head list - - //let ofArray (array: 'T array) = ImmutableList.Create<'T>(items = [||]) - - let init count initializer = - if count < 0 then - // throw the same exception - try - Seq.init count initializer |> ignore - with exn -> - raise exn // get the right stack trace - - build - <| fun builder -> - for i = 0 to count - 1 do - builder.Add <| initializer i - - let unfold generator state = - let rec unfoldLoop state (builder : ImmutableList<_>.Builder) = - match generator state with - | Some (state, item) -> - builder.Add (item) - unfoldLoop state builder - | None -> () - - build <| unfoldLoop state - - - ////////// Seq-based ////////// - - let find predicate list = - check list - Seq.find predicate list - - let tryFind predicate list = - check list - Seq.tryFind predicate list - - let findIndex predicate list = - check list - Seq.findIndex predicate list - - let tryFindIndex predicate list = - check list - Seq.tryFindIndex predicate list - - let pick chooser list = - check list - Seq.pick chooser list - - let fold folder state list = - check list - Seq.fold folder state list - - let forall predicate list = - check list - Seq.forall predicate list - - let forall2 predicate (list1 : IImmutableList<_>) (list2 : IImmutableList<_>) = - checkNotNull (nameof list1) list1 - checkNotNull (nameof list2) list2 - Seq.forall2 predicate list1 list2 - - let iter action list = - check list - Seq.iter action list diff --git a/src/FSharp.Collections.Immutable/IndexedList/IIndexedList.fs b/src/FSharp.Collections.Immutable/IndexedList/IIndexedList.fs new file mode 100644 index 00000000..5e3356ff --- /dev/null +++ b/src/FSharp.Collections.Immutable/IndexedList/IIndexedList.fs @@ -0,0 +1,1173 @@ +[] +module FSharp.Collections.Immutable.IIndexedList + +open System +open System.Collections.Generic +open System.Collections.Immutable +open System.Linq + +// The interface has no members of ImmutableList such as ConvertAll or GetRange, so the functions take a snapshot of the +// elements, apply an Array function or a shared algorithm and build the result. A result with the element type of the +// input keeps the implementation of the input: it comes from Clear and AddRange or another member of the input. The +// checks of ListChecks throw what the List functions throw (ADR 0001, D5 and D6). + +/// A list of the implementation of the input with the given elements. +let private rebuild (list : IIndexedList<'T>) (items : seq<'T>) = list.Clear().AddRange items + +/// A range of a list in a list of its implementation: the list itself for the whole list. An indexed list copies the range +/// as IndexedList does, in O(log n + count); the other implementations are read from their start through the interface. +let private keepRange (list : IIndexedList<'T>) (start : int) (count : int) = + if count = list.Count then + list + elif count = 0 then + list.Clear () + else + match list with + | :? IndexedList<'T> as indexed -> IndexedList.sub indexed start count :> IIndexedList<'T> + | _ -> list.Clear().AddRange(Enumerable.Take (Enumerable.Skip (list, start), count)) + +/// The index and the first element that satisfies the predicate, or ValueNone. +let private firstMatch (predicate : 'T -> bool) (list : IIndexedList<'T>) = + use enumerator = list.GetEnumerator () + let mutable result = ValueNone + let mutable index = 0 + + while result.IsNone && enumerator.MoveNext () do + let item = enumerator.Current + + if predicate item then + result <- ValueSome (struct (index, item)) + + index <- index + 1 + + result + +/// The index and the last element that satisfies the predicate, or ValueNone. An indexed list searches from its end, where +/// the other implementations are copied first, as List searches a copy from the end too: reading the copy takes O(n) when +/// the last element satisfies the predicate (about 3 ms against 0.005 ms for 100 000 integers), and the indexer of an +/// implementation can take O(n) for each element, so the copy is the safe way to go back through the interface. +let private lastMatch (predicate : 'T -> bool) (list : IIndexedList<'T>) = + match list with + | :? IndexedList<'T> as indexed -> + let index = indexed.FindLastIndex (Predicate predicate) + + if index < 0 then + ValueNone + else + ValueSome (struct (index, indexed[index])) + | _ -> + let array = Snapshot.ofSeq (nameof list) list + let mutable index = array.Length - 1 + + while index >= 0 && not (predicate array[index]) do + index <- index - 1 + + if index < 0 then + ValueNone + else + ValueSome (struct (index, array[index])) + +/// The first value that the chooser returns for the elements, in their order. +let private firstChoice (chooser : 'T -> 'U voption) (list : IIndexedList<'T>) = + use enumerator = list.GetEnumerator () + let mutable result = ValueNone + + while result.IsNone && enumerator.MoveNext () do + result <- chooser enumerator.Current + + result + +// ---- Properties ---- + +[] +let isEmpty (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + list.Count = 0 + +[] +let length (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + list.Count + +// ---- Access ---- + +[] +let exactlyOne (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + // List.exactlyOne reports its input under the name "source" + ListChecks.exactlyOne "source" list.Count + list[0] + +[] +let head (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + ListChecks.notEmpty (nameof list) list.Count + list[0] + +[] +let item index (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + ListChecks.item (nameof index) index list.Count + list[index] + +[] +let last (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + ListChecks.notEmpty (nameof list) list.Count + list[list.Count - 1] + +[] +let tail (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + ListChecks.notEmpty (nameof list) list.Count + list.RemoveAt 0 + +[] +let tryExactlyOne (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + if list.Count = 1 then ValueSome list[0] else ValueNone + +[] +let tryHead (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + if list.Count = 0 then ValueNone else ValueSome list[0] + +[] +let tryItem index (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + + if index < 0 || index >= list.Count then + ValueNone + else + ValueSome list[index] + +[] +let tryLast (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + + if list.Count = 0 then + ValueNone + else + ValueSome list[list.Count - 1] + +[] +let tryTail (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + if list.Count = 0 then + ValueNone + else + ValueSome (list.RemoveAt 0) + +[] +let tryUncons (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + + if list.Count = 0 then + ValueNone + else + ValueSome (struct (list[0], list.RemoveAt 0)) + +[] +let uncons (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + // An empty list throws what List.head throws + ListChecks.notEmpty (nameof list) list.Count + struct (list[0], list.RemoveAt 0) + +[] +[] +let (|Cons|_|) (list : IIndexedList<'T>) = tryUncons list + +[] +let (|Nil|_|) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + list.Count = 0 + +// ---- Search ---- + +[] +let contains (value : 'T) (source : IIndexedList<'T>) = + checkNotNull (nameof source) source + // F# equality with the value on the left, as List.contains compares + Seq.contains value source + +[] +let exists (predicate : 'T -> bool) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + Seq.exists predicate list + +// exists2, forall2 and compareWith can decide before the end. Two indexed lists are walked together by the functions of +// IndexedList, which read the elements up to the pair that decides. The other implementations are copied first, because +// an enumerator reached through the interface takes more than twice as long as the copy on a walk to the end (see +// IndexedList). + +[] +let exists2 predicate (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) = + match list1, list2 with + | (:? IndexedList<'T1> as first), (:? IndexedList<'T2> as second) -> IndexedList.exists2 predicate first second + | _ -> ListArrays.exists2 (nameof list2) predicate (Snapshot.ofSeq (nameof list1) list1) (Snapshot.ofSeq (nameof list2) list2) + +[] +let find (predicate : 'T -> bool) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + + match firstMatch predicate list with + | ValueSome (struct (_, item)) -> item + | ValueNone -> Raise.indexNotFound () + +[] +let findBack (predicate : 'T -> bool) (list : IIndexedList<'T>) = + match lastMatch predicate list with + | ValueSome (struct (_, item)) -> item + | ValueNone -> Raise.indexNotFound () + +[] +let findIndex (predicate : 'T -> bool) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + + match firstMatch predicate list with + | ValueSome (struct (index, _)) -> index + | ValueNone -> Raise.indexNotFound () + +[] +let findIndexBack (predicate : 'T -> bool) (list : IIndexedList<'T>) = + match lastMatch predicate list with + | ValueSome (struct (index, _)) -> index + | ValueNone -> Raise.indexNotFound () + +[] +let forall (predicate : 'T -> bool) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + Seq.forall predicate list + +[] +let forall2 predicate (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) = + match list1, list2 with + | (:? IndexedList<'T1> as first), (:? IndexedList<'T2> as second) -> IndexedList.forall2 predicate first second + | _ -> + ListArrays.forall2 + (nameof list1) + (nameof list2) + predicate + (Snapshot.ofSeq (nameof list1) list1) + (Snapshot.ofSeq (nameof list2) list2) + +[] +let indexOf (value : 'T) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + ImmutableList.IndexOf (list, value) + +[] +let indexOfRange startIndex count (value : 'T) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + ListRanges.forward (nameof startIndex) startIndex (nameof count) count (nameof count) list.Count + ImmutableList.IndexOf (list, value, startIndex, count) + +[] +let indexOfRangeWith (comparer : IEqualityComparer<'T>) startIndex count (value : 'T) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + ListRanges.forward (nameof startIndex) startIndex (nameof count) count (nameof count) list.Count + list.IndexOf (value, startIndex, count, comparer) + +[] +let indexOfWith (comparer : IEqualityComparer<'T>) (value : 'T) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + ImmutableList.IndexOf (list, value, comparer) + +[] +let lastIndexOf (value : 'T) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + ImmutableList.LastIndexOf (list, value) + +[] +let lastIndexOfRange startIndex count (value : 'T) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + ListRanges.backward (nameof startIndex) startIndex (nameof count) count list.Count + + // An empty range has no equal element; the member of ImmutableList rejects a start index of Int32.MaxValue for it + if count = 0 then + -1 + else + ImmutableList.LastIndexOf (list, value, startIndex, count) + +[] +let lastIndexOfRangeWith (comparer : IEqualityComparer<'T>) startIndex count (value : 'T) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + ListRanges.backward (nameof startIndex) startIndex (nameof count) count list.Count + + if count = 0 then + -1 + else + list.LastIndexOf (value, startIndex, count, comparer) + +[] +let lastIndexOfWith (comparer : IEqualityComparer<'T>) (value : 'T) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + ImmutableList.LastIndexOf (list, value, comparer) + +[] +let pick (chooser : 'T -> 'U voption) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + + match firstChoice chooser list with + | ValueSome value -> value + | ValueNone -> Raise.indexNotFound () + +[] +let tryFind (predicate : 'T -> bool) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + + match firstMatch predicate list with + | ValueSome (struct (_, item)) -> ValueSome item + | ValueNone -> ValueNone + +[] +let tryFindBack (predicate : 'T -> bool) (list : IIndexedList<'T>) = + match lastMatch predicate list with + | ValueSome (struct (_, item)) -> ValueSome item + | ValueNone -> ValueNone + +[] +let tryFindIndex (predicate : 'T -> bool) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + + match firstMatch predicate list with + | ValueSome (struct (index, _)) -> ValueSome index + | ValueNone -> ValueNone + +[] +let tryFindIndexBack (predicate : 'T -> bool) (list : IIndexedList<'T>) = + match lastMatch predicate list with + | ValueSome (struct (index, _)) -> ValueSome index + | ValueNone -> ValueNone + +[] +let tryPick (chooser : 'T -> 'U voption) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + firstChoice chooser list + +// ---- Transformation ---- + +[] +let add (value : 'T) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + list.Add value + +[] +let allPairs (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) = + // Every element of list1 meets every element of list2, so list2 is read again for each of them. Two snapshots read + // each list once and then index arrays; reading list2 through an enumerator for each element of list1 took 1.4 to + // 2.5 times as long, and a snapshot of list2 alone was not clearly faster (see IndexedList.allPairs). + Arrays.allPairs structPair (Snapshot.ofSeq (nameof list1) list1) (Snapshot.ofSeq (nameof list2) list2) + |> IndexedList.ofArray + +[] +let append (list1 : IIndexedList<'T>) (list2 : IIndexedList<'T>) = + checkNotNull (nameof list1) list1 + checkNotNull (nameof list2) list2 + list1.AddRange list2 + +[] +let choose (chooser : 'T -> 'U voption) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + // The number of chosen elements is not known in advance, so they wait in a ResizeArray and CreateRange builds the + // balanced tree once. A builder rebalances its tree after every Add and took 2.3 times as long for 100 000 + // elements; there is no tree of an input to join to its tree here, as collect and concat have (see Collectors). + let chosen = ResizeArray<'U>() + + for item in list do + match chooser item with + | ValueSome value -> chosen.Add value + | ValueNone -> () + + ImmutableList.CreateRange chosen + +[] +let chunkBySize chunkSize (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + ListChecks.positive (nameof chunkSize) chunkSize + + // One copy of the list and one tree for each chunk. Cutting the chunks from the copy with spans, instead of the arrays + // that Array.chunkBySize allocates, was between 1.2 times faster and as fast for chunks of 2, 100 and 10 000 elements + // of a list of a million, so the array function stays. + Array.chunkBySize chunkSize (Snapshot.ofSeq (nameof list) list) + |> Seq.map IndexedList.ofArray + |> IndexedList.ofSeq + +[] +let collect (mapping : 'T -> IIndexedList<'U>) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + // The collector copies the short results and joins the long indexed lists to its tree as they are (see Collectors) + let collector = Collectors.ListCollector<'U>() + + for item in list do + collector.Add (mapping item) + + collector.ToImmutable () + +[] +let concat (lists : seq>) = + checkNotNullOrDefaultSeq (nameof lists) lists + // The collector copies the short lists and joins the long indexed lists to its tree as they are (see Collectors) + let collector = Collectors.ListCollector<'T>() + + for list in lists do + // A null list is a null input, and so is a default flat list, which implements the interface and fails when it is + // read, as a default element is for FlatList.concat (ADR 0001, D6) + checkNotNullOrDefaultSeq (nameof lists) list + collector.Add list + + collector.ToImmutable () + +[] +let cons (value : 'T) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + list.Insert (0, value) + +[] +let filter (predicate : 'T -> bool) (list : IIndexedList<'T>) = + let array = Snapshot.ofSeq (nameof list) list + rebuild list (Array.filter predicate array) + +[] +let indexed (list : IIndexedList<'T>) = + Arrays.indexed structPair (Snapshot.ofSeq (nameof list) list) + |> IndexedList.ofArray + +[] +let insertAt index (value : 'T) (source : IIndexedList<'T>) = + checkNotNull (nameof source) source + ListChecks.insertAt (nameof index) index source.Count + source.Insert (index, value) + +[] +let insertManyAt index (values : seq<'T>) (source : IIndexedList<'T>) = + checkNotNull (nameof source) source + // List checks the index before it reads the values + ListChecks.insertAt (nameof index) index source.Count + checkNotNullOrDefaultSeq (nameof values) values + source.InsertRange (index, values) + +[] +let map (mapping : 'T -> 'U) (list : IIndexedList<'T>) = + Array.map mapping (Snapshot.ofSeq (nameof list) list) + |> IndexedList.ofArray + +[] +let map2 (mapping : 'T1 -> 'T2 -> 'U) (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) = + ListArrays.map2 + (nameof list1) + (nameof list2) + mapping + (Snapshot.ofSeq (nameof list1) list1) + (Snapshot.ofSeq (nameof list2) list2) + |> IndexedList.ofArray + +[] +let map3 (mapping : 'T1 -> 'T2 -> 'T3 -> 'U) (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) (list3 : IIndexedList<'T3>) = + ListArrays.map3 + (nameof list1) + (nameof list2) + (nameof list3) + mapping + (Snapshot.ofSeq (nameof list1) list1) + (Snapshot.ofSeq (nameof list2) list2) + (Snapshot.ofSeq (nameof list3) list3) + |> IndexedList.ofArray + +[] +let mapi (mapping : int -> 'T -> 'U) (list : IIndexedList<'T>) = + Array.mapi mapping (Snapshot.ofSeq (nameof list) list) + |> IndexedList.ofArray + +[] +let mapi2 (mapping : int -> 'T1 -> 'T2 -> 'U) (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) = + ListArrays.mapi2 + (nameof list1) + (nameof list2) + mapping + (Snapshot.ofSeq (nameof list1) list1) + (Snapshot.ofSeq (nameof list2) list2) + |> IndexedList.ofArray + +[] +let pairwise (list : IIndexedList<'T>) = + Arrays.pairwise structPair (Snapshot.ofSeq (nameof list) list) + |> IndexedList.ofArray + +[] +let permute indexMap (list : IIndexedList<'T>) = + // List.permute applies the algorithm of Array.permute to a copy of the list, with the same checks + let array = Snapshot.ofSeq (nameof list) list + rebuild list (Array.permute indexMap array) + +[] +let remove (item : 'T) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + ImmutableList.Remove (list, item) + +[] +let removeAt index (source : IIndexedList<'T>) = + checkNotNull (nameof source) source + ListChecks.removeAt (nameof index) index source.Count + source.RemoveAt index + +[] +let removeMany (items : seq<'T>) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + checkNotNullOrDefaultSeq (nameof items) items + ImmutableList.RemoveRange (list, items) + +[] +let removeManyAt index count (source : IIndexedList<'T>) = + checkNotNull (nameof source) source + ListChecks.removeManyAt (nameof index) index count source.Count + // List walks the first index + count elements, so it removes nothing for a count that is not positive or for a sum + // that overflows to a negative number + if count <= 0 || index + count < 0 then + source + else + source.RemoveRange (index, count) + +[] +let removeManyWith (comparer : IEqualityComparer<'T>) (items : seq<'T>) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + checkNotNullOrDefaultSeq (nameof items) items + list.RemoveRange (items, comparer) + +[] +let removeWith (comparer : IEqualityComparer<'T>) (item : 'T) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + list.Remove (item, comparer) + +[] +let replace (oldValue : 'T) (newValue : 'T) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + ImmutableList.Replace (list, oldValue, newValue) + +[] +let replaceWith (comparer : IEqualityComparer<'T>) (oldValue : 'T) (newValue : 'T) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + list.Replace (oldValue, newValue, comparer) + +[] +let rev (list : IIndexedList<'T>) = + // The copy is fresh, so it is reversed in place, as IndexedList.rev does (ADR 0001, D5) + let array = Snapshot.ofSeq (nameof list) list + Array.Reverse array + rebuild list array + +[] +let transpose (lists : seq>) = + checkNotNullOrDefaultSeq (nameof lists) lists + + let arrays = + lists + |> Seq.map (fun list -> + // A null list, or a default flat list that implements the interface, is a null input named after the sequence + checkNotNullOrDefaultSeq (nameof lists) list + Snapshot.ofSeq (nameof lists) list + ) + |> Seq.toArray + + // List.transpose names the lists "list.[0]", "list.[1]", …; once their lengths agree, Array.transpose agrees with it + ListChecks.transpose "list" (arrays |> Array.map _.Length) + + Array.transpose arrays + |> Seq.map IndexedList.ofArray + |> IndexedList.ofSeq + +[] +let unzip (list : IIndexedList) = + let struct (first, second) = Arrays.unzip id (Snapshot.ofSeq (nameof list) list) + struct (IndexedList.ofArray first, IndexedList.ofArray second) + +[] +let unzip3 (list : IIndexedList) = + let struct (first, second, third) = Arrays.unzip3 id (Snapshot.ofSeq (nameof list) list) + struct (IndexedList.ofArray first, IndexedList.ofArray second, IndexedList.ofArray third) + +[] +let updateAt index (value : 'T) (source : IIndexedList<'T>) = + checkNotNull (nameof source) source + ListChecks.removeAt (nameof index) index source.Count + source.SetItem (index, value) + +[] +let where (predicate : 'T -> bool) (list : IIndexedList<'T>) = + let array = Snapshot.ofSeq (nameof list) list + rebuild list (Array.filter predicate array) + +[] +let windowed windowSize (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + ListChecks.positive (nameof windowSize) windowSize + + Array.windowed windowSize (Snapshot.ofSeq (nameof list) list) + |> Seq.map IndexedList.ofArray + |> IndexedList.ofSeq + +[] +let zip (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) = + checkNotNull (nameof list1) list1 + checkNotNull (nameof list2) list2 + ListChecks.sameLengths (nameof list1) list1.Count (nameof list2) list2.Count + + Arrays.zip structPair (Snapshot.ofSeq (nameof list1) list1) (Snapshot.ofSeq (nameof list2) list2) + |> IndexedList.ofArray + +[] +let zip3 (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) (list3 : IIndexedList<'T3>) = + checkNotNull (nameof list1) list1 + checkNotNull (nameof list2) list2 + checkNotNull (nameof list3) list3 + ListChecks.zip3 (nameof list1) list1.Count (nameof list2) list2.Count (nameof list3) list3.Count + + Arrays.zip3 + structTriple + (Snapshot.ofSeq (nameof list1) list1) + (Snapshot.ofSeq (nameof list2) list2) + (Snapshot.ofSeq (nameof list3) list3) + |> IndexedList.ofArray + +// ---- Folding ---- + +// The inline functions can use public members only, so they check the list themselves; the empty list throws what +// List.average and List.averageBy throw, with the name "source" +[] +let inline average (list : IIndexedList< ^T >) : ^T = + if obj.ReferenceEquals (list, null) then + nullArg (nameof list) + + if list.Count = 0 then + invalidArg "source" ErrorStrings.InputSequenceEmpty + + let mutable sum = LanguagePrimitives.GenericZero< ^T> + + for item in list do + sum <- Checked.(+) sum item + + LanguagePrimitives.DivideByInt< ^T> sum list.Count + +[] +let inline averageBy ([] projection : 'T -> ^U) (list : IIndexedList<'T>) : ^U = + if obj.ReferenceEquals (list, null) then + nullArg (nameof list) + + if list.Count = 0 then + invalidArg "source" ErrorStrings.InputSequenceEmpty + + let mutable sum = LanguagePrimitives.GenericZero< ^U> + + for item in list do + sum <- Checked.(+) sum (projection item) + + LanguagePrimitives.DivideByInt< ^U> sum list.Count + +[] +let compareWith (comparer : 'T -> 'T -> int) (list1 : IIndexedList<'T>) (list2 : IIndexedList<'T>) = + match list1, list2 with + | (:? IndexedList<'T> as first), (:? IndexedList<'T> as second) -> IndexedList.compareWith comparer first second + | _ -> + // Array.compareWith compares as List.compareWith does: the first difference, then the lengths + Array.compareWith comparer (Snapshot.ofSeq (nameof list1) list1) (Snapshot.ofSeq (nameof list2) list2) + +[] +let fold (folder : 'State -> 'T -> 'State) (state : 'State) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + let mutable state = state + + for item in list do + state <- folder state item + + state + +[] +let fold2 (folder : 'State -> 'T1 -> 'T2 -> 'State) (state : 'State) (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) = + ListArrays.fold2 + (nameof list1) + (nameof list2) + folder + state + (Snapshot.ofSeq (nameof list1) list1) + (Snapshot.ofSeq (nameof list2) list2) + +[] +let foldBack (folder : 'T -> 'State -> 'State) (list : IIndexedList<'T>) (state : 'State) = + Array.foldBack folder (Snapshot.ofSeq (nameof list) list) state + +[] +let foldBack2 (folder : 'T1 -> 'T2 -> 'State -> 'State) (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) (state : 'State) = + checkNotNull (nameof list1) list1 + checkNotNull (nameof list2) list2 + // List.foldBack2 checks the lengths before it calls the folder + ListChecks.foldBack2 (nameof list1) list1.Count (nameof list2) list2.Count + Array.foldBack2 folder (Snapshot.ofSeq (nameof list1) list1) (Snapshot.ofSeq (nameof list2) list2) state + +[] +let iter (action : 'T -> unit) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + + for item in list do + action item + +[] +let iter2 (action : 'T1 -> 'T2 -> unit) (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) = + ListArrays.iter2 + (nameof list1) + (nameof list2) + action + (Snapshot.ofSeq (nameof list1) list1) + (Snapshot.ofSeq (nameof list2) list2) + +[] +let iteri (action : int -> 'T -> unit) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + let mutable index = 0 + + for item in list do + action index item + index <- index + 1 + +[] +let iteri2 (action : int -> 'T1 -> 'T2 -> unit) (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) = + ListArrays.iteri2 + (nameof list1) + (nameof list2) + action + (Snapshot.ofSeq (nameof list1) list1) + (Snapshot.ofSeq (nameof list2) list2) + +[] +let mapFold (mapping : 'State -> 'T -> struct ('Result * 'State)) (state : 'State) (list : IIndexedList<'T>) = + let source = Snapshot.ofSeq (nameof list) list + 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 (IndexedList.ofArray results, state) + +[] +let mapFoldBack (mapping : 'T -> 'State -> struct ('Result * 'State)) (list : IIndexedList<'T>) (state : 'State) = + let source = Snapshot.ofSeq (nameof list) list + 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 (IndexedList.ofArray results, state) + +// max, maxBy, min and minBy are inline, as the ones of List are, and compare as they do; see IndexedList for the reasons. + +[] +let inline max (list : IIndexedList<'T>) : 'T = + if obj.ReferenceEquals (list, null) then + nullArg (nameof list) + + if list.Count = 0 then + invalidArg (nameof list) ErrorStrings.InputSequenceEmpty + + let mutable first = true + let mutable greatest = Unchecked.defaultof<'T> + + for item in list do + if first then + greatest <- item + first <- false + elif item > greatest then + greatest <- item + + greatest + +[] +let inline maxBy ([] projection : 'T -> 'U) (list : IIndexedList<'T>) : 'T = + if obj.ReferenceEquals (list, null) then + nullArg (nameof list) + + if list.Count = 0 then + invalidArg (nameof list) ErrorStrings.InputSequenceEmpty + + let mutable first = true + let mutable greatest = Unchecked.defaultof<'T> + let mutable greatestKey = Unchecked.defaultof<'U> + + for item in list do + let key = projection item + + if first then + greatest <- item + greatestKey <- key + first <- false + elif key > greatestKey then + greatest <- item + greatestKey <- key + + greatest + +[] +let inline min (list : IIndexedList<'T>) : 'T = + if obj.ReferenceEquals (list, null) then + nullArg (nameof list) + + if list.Count = 0 then + invalidArg (nameof list) ErrorStrings.InputSequenceEmpty + + let mutable first = true + let mutable smallest = Unchecked.defaultof<'T> + + for item in list do + if first then + smallest <- item + first <- false + elif item < smallest then + smallest <- item + + smallest + +[] +let inline minBy ([] projection : 'T -> 'U) (list : IIndexedList<'T>) : 'T = + if obj.ReferenceEquals (list, null) then + nullArg (nameof list) + + if list.Count = 0 then + invalidArg (nameof list) ErrorStrings.InputSequenceEmpty + + let mutable first = true + let mutable smallest = Unchecked.defaultof<'T> + let mutable smallestKey = Unchecked.defaultof<'U> + + for item in list do + let key = projection item + + if first then + smallest <- item + smallestKey <- key + first <- false + elif key < smallestKey then + smallest <- item + smallestKey <- key + + smallest + +[] +let reduce (reduction : 'T -> 'T -> 'T) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + ListChecks.notEmpty (nameof list) list.Count + Seq.reduce reduction list + +[] +let reduceBack (reduction : 'T -> 'T -> 'T) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + ListChecks.notEmpty (nameof list) list.Count + Array.reduceBack reduction (Snapshot.ofSeq (nameof list) list) + +[] +let scan (folder : 'State -> 'T -> 'State) (state : 'State) (list : IIndexedList<'T>) = + Array.scan folder state (Snapshot.ofSeq (nameof list) list) + |> IndexedList.ofArray + +[] +let scanBack (folder : 'T -> 'State -> 'State) (list : IIndexedList<'T>) (state : 'State) = + Array.scanBack folder (Snapshot.ofSeq (nameof list) list) state + |> IndexedList.ofArray + +[] +let inline sum (list : IIndexedList< ^T >) : ^T = + if obj.ReferenceEquals (list, null) then + nullArg (nameof list) + + let mutable sum = LanguagePrimitives.GenericZero< ^T> + + for item in list do + sum <- Checked.(+) sum item + + sum + +[] +let inline sumBy ([] projection : 'T -> ^U) (list : IIndexedList<'T>) : ^U = + if obj.ReferenceEquals (list, null) then + nullArg (nameof list) + + let mutable sum = LanguagePrimitives.GenericZero< ^U> + + for item in list do + sum <- Checked.(+) sum (projection item) + + sum + +// ---- Partitioning and grouping ---- + +[] +let countBy (projection : 'T -> 'Key) (list : IIndexedList<'T>) = + Arrays.countBy structPair projection (Snapshot.ofSeq (nameof list) list) + |> IndexedList.ofArray + +[] +let distinct (list : IIndexedList<'T>) = + let array = Snapshot.ofSeq (nameof list) list + rebuild list (Array.distinct array) + +[] +let distinctBy (projection : 'T -> 'Key) (list : IIndexedList<'T>) = + let array = Snapshot.ofSeq (nameof list) list + rebuild list (Array.distinctBy projection array) + +[] +let except (itemsToExclude : seq<'T>) (list : IIndexedList<'T>) = + // List.except checks the items to exclude first and does not read them for an empty list + checkNotNullOrDefaultSeq (nameof itemsToExclude) itemsToExclude + checkNotNull (nameof list) list + + if list.Count = 0 then + list + else + rebuild list (Array.except itemsToExclude (Snapshot.ofSeq (nameof list) list)) + +[] +let groupBy (projection : 'T -> 'Key) (list : IIndexedList<'T>) = + Arrays.groupBy (fun key items -> struct (key, IndexedList.ofArray items)) projection (Snapshot.ofSeq (nameof list) list) + |> IndexedList.ofArray + +[] +let partition (predicate : 'T -> bool) (list : IIndexedList<'T>) = + let accepted, rejected = Array.partition predicate (Snapshot.ofSeq (nameof list) list) + struct (rebuild list accepted, rebuild list rejected) + +[] +let partitionWith (partitioner : 'T -> Choice<'T1, 'T2>) (list : IIndexedList<'T>) = + let first, second = Array.partitionWith partitioner (Snapshot.ofSeq (nameof list) list) + struct (IndexedList.ofArray first, IndexedList.ofArray second) + +[] +let skip count (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + ListChecks.skip (nameof count) count list.Count + let count = Operators.max count 0 + keepRange list count (list.Count - count) + +[] +let skipLast count (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + // The checks of List.skip + ListChecks.skip (nameof count) count list.Count + keepRange list 0 (list.Count - Operators.max count 0) + +[] +let skipWhile (predicate : 'T -> bool) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + + match firstMatch (fun item -> not (predicate item)) list with + | ValueSome (struct (index, _)) -> keepRange list index (list.Count - index) + | ValueNone -> list.Clear () + +[] +let splitAt index (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + ListChecks.splitAt (nameof index) index list.Count + struct (keepRange list 0 index, keepRange list index (list.Count - index)) + +[] +let splitInto count (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + ListChecks.positive (nameof count) count + + Array.splitInto count (Snapshot.ofSeq (nameof list) list) + |> Seq.map IndexedList.ofArray + |> IndexedList.ofSeq + +[] +let take count (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + ListChecks.take (nameof count) count list.Count + keepRange list 0 count + +[] +let takeLast count (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + // The checks of List.take + ListChecks.take (nameof count) count list.Count + keepRange list (list.Count - count) count + +[] +let takeWhile (predicate : 'T -> bool) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + + match firstMatch (fun item -> not (predicate item)) list with + | ValueSome (struct (index, _)) -> keepRange list 0 index + | ValueNone -> list + +[] +let truncate count (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + keepRange list 0 (Operators.max 0 (Operators.min count list.Count)) + +// ---- Sorting ---- + +// The sorts are stable, as the sorts of List are (ADR 0001, D5). The functions that compare the elements, or that call +// the projection in the comparison as List.sortByDescending does, sort with the Seq functions of FSharp.Core, which share +// the stable sort of List: the same comparisons run in Array.Sort, which wraps the exception of a comparison in an +// InvalidOperationException (D6). Seq.sortDescending and Seq.sortByDescending are inline and box the elements in every +// comparison once they are compiled into a generic function, so the two descending sorts call Seq.sortWith through +// DescendingSorts, with the cached comparer. sortBy and sortWithComparer sort with LINQ, which computes the keys first, as +// List.sortBy does, and wraps the exception of a comparison as Array.Sort does. A list of one element is returned without +// a call of the projection or the comparer, as List returns it. + +[] +let sort (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + if list.Count <= 1 then + list + else + rebuild list (Seq.sort list) + +[] +let sortBy (projection : 'T -> 'Key) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + + if list.Count <= 1 then + list + else + rebuild list (list.OrderBy(Func<'T, 'Key> projection, DefaultComparer<'Key>.Instance).ToArray()) + +[] +let sortByDescending (projection : 'T -> 'Key) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + if list.Count <= 1 then + list + else + rebuild list (DescendingSorts.sortByDescending projection list) + +[] +let sortDescending (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + if list.Count <= 1 then + list + else + rebuild list (DescendingSorts.sortDescending list) + +[] +let sortWith (comparer : 'T -> 'T -> int) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + if list.Count <= 1 then + list + else + rebuild list (Seq.sortWith comparer list) + +[] +let sortWithComparer (comparer : IComparer<'T>) (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + + if list.Count <= 1 then + list + else + rebuild list (list.Order(comparer).ToArray()) + +// ---- Random ---- + +// The functions draw the random numbers as the List functions do; the ones that build a list apply the Array function +// to a snapshot after the List checks + +[] +let randomChoice (source : IIndexedList<'T>) = + checkNotNull (nameof source) source + ListChecks.notEmptySequence (nameof source) source.Count + source[Random.Shared.Next (0, source.Count)] + +[] +let randomChoiceBy (randomizer : unit -> float) (source : IIndexedList<'T>) = + checkNotNull (nameof source) source + ListChecks.notEmptySequence (nameof source) source.Count + source[ListChecks.randomIndex (nameof randomizer) randomizer source.Count] + +[] +let randomChoiceWith (random : Random) (source : IIndexedList<'T>) = + checkNotNull (nameof random) random + checkNotNull (nameof source) source + ListChecks.notEmptySequence (nameof source) source.Count + source[random.Next (0, source.Count)] + +[] +let randomChoices count (source : IIndexedList<'T>) = + checkNotNull (nameof source) source + ListChecks.randomChoices (nameof count) count (nameof source) source.Count + rebuild source (Array.randomChoices count (Snapshot.ofSeq (nameof source) source)) + +[] +let randomChoicesBy (randomizer : unit -> float) count (source : IIndexedList<'T>) = + checkNotNull (nameof source) source + ListChecks.randomChoices (nameof count) count (nameof source) source.Count + rebuild source (Array.randomChoicesBy randomizer count (Snapshot.ofSeq (nameof source) source)) + +[] +let randomChoicesWith (random : Random) count (source : IIndexedList<'T>) = + checkNotNull (nameof random) random + checkNotNull (nameof source) source + ListChecks.randomChoices (nameof count) count (nameof source) source.Count + rebuild source (Array.randomChoicesWith random count (Snapshot.ofSeq (nameof source) source)) + +[] +let randomSample count (source : IIndexedList<'T>) = + checkNotNull (nameof source) source + ListChecks.randomSample (nameof count) count (nameof source) source.Count + rebuild source (Array.randomSample count (Snapshot.ofSeq (nameof source) source)) + +[] +let randomSampleBy (randomizer : unit -> float) count (source : IIndexedList<'T>) = + checkNotNull (nameof source) source + ListChecks.randomSample (nameof count) count (nameof source) source.Count + rebuild source (Array.randomSampleBy randomizer count (Snapshot.ofSeq (nameof source) source)) + +[] +let randomSampleWith (random : Random) count (source : IIndexedList<'T>) = + checkNotNull (nameof random) random + checkNotNull (nameof source) source + ListChecks.randomSample (nameof count) count (nameof source) source.Count + rebuild source (Array.randomSampleWith random count (Snapshot.ofSeq (nameof source) source)) + +[] +let randomShuffle (source : IIndexedList<'T>) = + let array = Snapshot.ofSeq (nameof source) source + Array.randomShuffleInPlace array + rebuild source array + +[] +let randomShuffleBy (randomizer : unit -> float) (source : IIndexedList<'T>) = + let array = Snapshot.ofSeq (nameof source) source + Array.randomShuffleInPlaceBy randomizer array + rebuild source array + +[] +let randomShuffleWith (random : Random) (source : IIndexedList<'T>) = + checkNotNull (nameof random) random + let array = Snapshot.ofSeq (nameof source) source + Array.randomShuffleInPlaceWith random array + rebuild source array + +// ---- Conversion ---- + +[] +let toArray (list : IIndexedList<'T>) = Snapshot.ofSeq (nameof list) list + +[] +let toList (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + List.ofSeq list + +[] +let toSeq (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + list :> seq<'T> + +// ---- Type-specific ---- + +[] +let clear (list : IIndexedList<'T>) = + checkNotNull (nameof list) list + list.Clear () diff --git a/src/FSharp.Collections.Immutable/IndexedList/IIndexedList.fsi b/src/FSharp.Collections.Immutable/IndexedList/IIndexedList.fsi new file mode 100644 index 00000000..9cfc03a1 --- /dev/null +++ b/src/FSharp.Collections.Immutable/IndexedList/IIndexedList.fsi @@ -0,0 +1,1808 @@ +/// +/// Functions for , the interface of the immutable lists. +/// +/// +/// +/// The module mirrors as +/// does: each function has the semantics, the parameter names, +/// the argument checks and the exceptions of the function with the same name in that module (ADR 0001), and its sorting +/// functions are stable. The module has no creation and no builder functions, because an interface cannot create a list. +/// +/// +/// A function whose result has the element type of its input keeps the implementation of the input: it builds the result +/// with the members of the input, such as followed by +/// , +/// or +/// . A function that changes the +/// element type, or that combines several lists into one, returns an . +/// +/// +/// Every function throws for a list. A function that +/// takes a sequence throws it for a sequence and for a default +/// that is given as a sequence, which would fail with +/// when the function enumerates it. The complexities in the remarks are +/// the ones of an . +/// +/// +/// 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 FSharp.Collections.Immutable.IIndexedList + +open System +open System.Collections.Generic + +// ---- Properties ---- + +/// Returns whether the list has no elements. +/// The input list. +/// when the list is empty. +/// O(1). +[] +val isEmpty : list : IIndexedList<'T> -> bool + +/// Returns the number of elements of the list. +/// The input list. +/// The length of the list. +/// O(1). +[] +val length : list : IIndexedList<'T> -> int + +// ---- Access ---- + +/// Returns the only element of the list. +/// The input list. +/// The only element. +/// Thrown when the list does not have exactly one element. +/// O(1). +[] +val exactlyOne : list : IIndexedList<'T> -> 'T + +/// Returns the first element of the list. +/// The input list. +/// The first element. +/// Thrown when the list is empty. +/// O(log n). +[] +val head : list : IIndexedList<'T> -> 'T + +/// Returns the element at an index. +/// The index of the element. +/// The input list. +/// The element at the index. +/// Thrown when the index is negative or not less than the length. +/// O(log n). +[] +val item : index : int -> list : IIndexedList<'T> -> 'T + +/// Returns the last element of the list. +/// The input list. +/// The last element. +/// Thrown when the list is empty. +/// O(log n). +[] +val last : list : IIndexedList<'T> -> 'T + +/// +/// Returns the list without its first element. +/// +/// The input list. +/// The elements after the first one, in a list of the implementation of the input. +/// Thrown when the list is empty. +/// +/// O(log n). The function wraps . +/// +[] +val tail : list : IIndexedList<'T> -> IIndexedList<'T> + +/// +/// Returns the only element of the list, or ValueNone when it does not have exactly one element. +/// +/// The input list. +/// The only element, or ValueNone. +/// +/// O(1). +/// +[] +val tryExactlyOne : list : IIndexedList<'T> -> 'T voption + +/// +/// Returns the first element of the list, or ValueNone when it is empty. +/// +/// The input list. +/// The first element, or ValueNone. +/// +/// O(log n). +/// +[] +val tryHead : list : IIndexedList<'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 list. +/// The element at the index, or ValueNone. +/// +/// O(log n). +/// +[] +val tryItem : index : int -> list : IIndexedList<'T> -> 'T voption + +/// +/// Returns the last element of the list, or ValueNone when it is empty. +/// +/// The input list. +/// The last element, or ValueNone. +/// +/// O(log n). +/// +[] +val tryLast : list : IIndexedList<'T> -> 'T voption + +/// +/// Returns the list without its first element, or ValueNone when it is empty. +/// +/// The input list. +/// The elements after the first one, in a list of the implementation of the input, or ValueNone. +/// +/// O(log n). +/// +[] +val tryTail : list : IIndexedList<'T> -> IIndexedList<'T> voption + +/// +/// Returns the first element and the other elements, or ValueNone when the list is empty. +/// +/// The input list. +/// The first element and the elements after it, or ValueNone. +/// +/// O(log n). +/// +[] +val tryUncons : list : IIndexedList<'T> -> struct ('T * IIndexedList<'T>) voption + +/// +/// Returns the first element and the other elements. +/// +/// The input list. +/// The first element and the elements after it, in a list of the implementation of the input. +/// Thrown when the list is empty. +/// +/// O(log n). The function throws what +/// throws. +/// +[] +val uncons : list : IIndexedList<'T> -> struct ('T * IIndexedList<'T>) + +/// +/// Matches a list with a first element, and returns that element and the other elements. +/// +/// The input list. +/// The first element and the elements after it, or ValueNone for an empty list. +/// +/// O(log n). A struct partial pattern, so that a match allocates no option. +/// +[] +[] +val (|Cons|_|) : list : IIndexedList<'T> -> struct ('T * IIndexedList<'T>) voption + +/// Matches an empty list. +/// The input list. +/// when the list is empty. +/// O(1). +[] +val (|Nil|_|) : list : IIndexedList<'T> -> bool + +// ---- Search ---- + +/// +/// Returns whether the list contains an element equal to a value, in F# structural equality. +/// +/// The value to find. +/// The input list. +/// when an element equals the value. +/// +/// O(n). +/// +[] +val contains : value : 'T -> source : IIndexedList<'T> -> bool when 'T : equality + +/// Returns whether any element satisfies the predicate. +/// The function to test the elements. +/// The input list. +/// when an element satisfies the predicate. +/// O(n). +[] +val exists : predicate : ('T -> bool) -> list : IIndexedList<'T> -> bool + +/// +/// Returns whether any pair of elements at the same index satisfies the predicate. +/// +/// The function to test the pairs. +/// The first list. +/// The second list. +/// when a pair satisfies the predicate. +/// Thrown when no pair satisfies the predicate and the lists have different lengths. +/// +/// O(n). Two indexed lists are walked together, and the walk stops at the first pair that satisfies the predicate; the +/// other implementations are copied first. +/// +[] +val exists2 : predicate : ('T1 -> 'T2 -> bool) -> list1 : IIndexedList<'T1> -> list2 : IIndexedList<'T2> -> bool + +/// Returns the first element that satisfies the predicate. +/// The function to test the elements. +/// The input list. +/// The first element that satisfies the predicate. +/// Thrown when no element satisfies the predicate. +/// O(n). +[] +val find : predicate : ('T -> bool) -> list : IIndexedList<'T> -> 'T + +/// Returns the last element that satisfies the predicate. +/// The function to test the elements. +/// The input list. +/// The last element that satisfies the predicate. +/// Thrown when no element satisfies the predicate. +/// +/// O(n). An indexed list is searched from its end, and the other implementations in a copy. +/// +[] +val findBack : predicate : ('T -> bool) -> list : IIndexedList<'T> -> 'T + +/// +/// Returns the index of the first element that satisfies the predicate. +/// +/// The function to test the elements. +/// The input 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) -> list : IIndexedList<'T> -> int + +/// +/// Returns the index of the last element that satisfies the predicate. +/// +/// The function to test the elements. +/// The input list. +/// The index of the last element that satisfies the predicate. +/// Thrown when no element satisfies the predicate. +/// +/// O(n). An indexed list is searched from its end, and the other implementations in a copy. +/// +[] +val findIndexBack : predicate : ('T -> bool) -> list : IIndexedList<'T> -> int + +/// Returns whether all elements satisfy the predicate. +/// The function to test the elements. +/// The input list. +/// when every element satisfies the predicate. +/// O(n). +[] +val forall : predicate : ('T -> bool) -> list : IIndexedList<'T> -> bool + +/// +/// Returns whether all pairs of elements at the same index satisfy the predicate. +/// +/// The function to test the pairs. +/// The first list. +/// The second list. +/// when every pair satisfies the predicate. +/// Thrown when every pair satisfies the predicate and the lists have different lengths. +/// +/// O(n). Two indexed lists are walked together, and the walk stops at the first pair that does not satisfy the predicate; +/// the other implementations are copied first. +/// +[] +val forall2 : predicate : ('T1 -> 'T2 -> bool) -> list1 : IIndexedList<'T1> -> list2 : IIndexedList<'T2> -> bool + +/// +/// Returns the index of the first element equal to a value, in . +/// +/// The value to find. +/// The input list. +/// The index of the first equal element, or -1. +/// +/// O(n). The function wraps +/// . +/// +[] +val indexOf : value : 'T -> list : IIndexedList<'T> -> int + +/// +/// Returns the index of the first element of a range that equals a value, in +/// . +/// +/// The index of the first element of the range. +/// The number of elements of the range. +/// The value to find. +/// The input list. +/// The index in the list, not in the range, of the first equal element of the range, or -1. +/// +/// Thrown when or is negative, or the range extends beyond the end +/// of the list. +/// +/// +/// O(log n + count). The function checks the range for every implementation of the interface, and wraps +/// . +/// +[] +val indexOfRange : startIndex : int -> count : int -> value : 'T -> list : IIndexedList<'T> -> int + +/// +/// Returns the index of the first element of a range that a given equality comparer finds equal to a value. +/// +/// The equality comparer. +/// The index of the first element of the range. +/// The number of elements of the range. +/// The value to find. +/// The input list. +/// The index in the list, not in the range, of the first equal element of the range, or -1. +/// +/// Thrown when or is negative, or the range extends beyond the end +/// of the list. +/// +/// +/// O(log n + count). The function checks the range for every implementation of the interface, and wraps +/// . +/// +[] +val indexOfRangeWith : + comparer : IEqualityComparer<'T> -> startIndex : int -> count : int -> value : 'T -> list : IIndexedList<'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 list. +/// The index of the first equal element, or -1. +/// +/// O(n). The function wraps +/// . +/// +[] +val indexOfWith : comparer : IEqualityComparer<'T> -> value : 'T -> list : IIndexedList<'T> -> int + +/// +/// Returns the index of the last element equal to a value, in . +/// +/// The value to find. +/// The input list. +/// The index of the last equal element, or -1. +/// +/// O(n). The function wraps +/// . +/// +[] +val lastIndexOf : value : 'T -> list : IIndexedList<'T> -> int + +/// +/// Returns the index of the last element of a range that equals a value, in +/// . The search walks backward from the index of the +/// last element of the range. +/// +/// The index of the last element of the range, where the search starts. +/// The number of elements of the range, which ends at . +/// The value to find. +/// The input list. +/// The index in the list, not in the range, of the last equal element of the range, or -1. +/// +/// Thrown when or is negative, exceeds the +/// length, or a range of elements does not lie in the list: its first element, at startIndex - count + 1, is before +/// the start, or its last element, at , is at or after the length. +/// +/// +/// O(log n + count). A count of 0 is an empty range, which has no equal element, for any start index. The function checks +/// the range for every implementation of the interface, and wraps +/// . +/// +[] +val lastIndexOfRange : startIndex : int -> count : int -> value : 'T -> list : IIndexedList<'T> -> int + +/// +/// Returns the index of the last element of a range that a given equality comparer finds equal to a value. The search +/// walks backward from the index of the last element of the range. +/// +/// The equality comparer. +/// The index of the last element of the range, where the search starts. +/// The number of elements of the range, which ends at . +/// The value to find. +/// The input list. +/// The index in the list, not in the range, of the last equal element of the range, or -1. +/// +/// Thrown when or is negative, exceeds the +/// length, or a range of elements does not lie in the list: its first element, at startIndex - count + 1, is before +/// the start, or its last element, at , is at or after the length. +/// +/// +/// O(log n + count). A count of 0 is an empty range, which has no equal element, for any start index. The function checks +/// the range for every implementation of the interface, and wraps +/// . +/// +[] +val lastIndexOfRangeWith : + comparer : IEqualityComparer<'T> -> startIndex : int -> count : int -> value : 'T -> list : IIndexedList<'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 list. +/// The index of the last equal element, or -1. +/// +/// O(n). The function wraps +/// . +/// +[] +val lastIndexOfWith : comparer : IEqualityComparer<'T> -> value : 'T -> list : IIndexedList<'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 list. +/// The first chosen value. +/// Thrown when the chooser returns ValueNone for every element. +/// +/// O(n). +/// +[] +val pick : chooser : ('T -> 'U voption) -> list : IIndexedList<'T> -> 'U + +/// +/// Returns the first element that satisfies the predicate, or ValueNone. +/// +/// The function to test the elements. +/// The input list. +/// The first element that satisfies the predicate, or ValueNone. +/// +/// O(n). +/// +[] +val tryFind : predicate : ('T -> bool) -> list : IIndexedList<'T> -> 'T voption + +/// +/// Returns the last element that satisfies the predicate, or ValueNone. +/// +/// The function to test the elements. +/// The input list. +/// The last element that satisfies the predicate, or ValueNone. +/// +/// O(n). An indexed list is searched from its end, and the other implementations in a copy. +/// +[] +val tryFindBack : predicate : ('T -> bool) -> list : IIndexedList<'T> -> 'T voption + +/// +/// Returns the index of the first element that satisfies the predicate, or ValueNone. +/// +/// The function to test the elements. +/// The input list. +/// The index of the first element that satisfies the predicate, or ValueNone. +/// +/// O(n). +/// +[] +val tryFindIndex : predicate : ('T -> bool) -> list : IIndexedList<'T> -> int voption + +/// +/// Returns the index of the last element that satisfies the predicate, or ValueNone. +/// +/// The function to test the elements. +/// The input list. +/// The index of the last element that satisfies the predicate, or ValueNone. +/// +/// O(n). An indexed list is searched from its end, and the other implementations in a copy. +/// +[] +val tryFindIndexBack : predicate : ('T -> bool) -> list : IIndexedList<'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 list. +/// The first chosen value, or ValueNone. +/// +/// O(n). +/// +[] +val tryPick : chooser : ('T -> 'U voption) -> list : IIndexedList<'T> -> 'U voption + +// ---- Transformation ---- + +/// +/// Returns a list with a value added after the last element. +/// +/// The value to add. +/// The input list. +/// A list of the implementation of the input with the elements and the value. +/// +/// O(log n). The function wraps . +/// +[] +val add : value : 'T -> list : IIndexedList<'T> -> IIndexedList<'T> + +/// +/// Returns every element of the first list paired with every element of the second one. +/// +/// The first list. +/// The second list. +/// The pairs, in the order of the first list and then of the second one. +/// +/// O(n × m). +/// +[] +val allPairs : list1 : IIndexedList<'T1> -> list2 : IIndexedList<'T2> -> IndexedList + +/// +/// Returns a list of the elements of the first list followed by the elements of the second one. +/// +/// The first list. +/// The second list. +/// The concatenation, in a list of the implementation of the first list. +/// +/// O(m log(n + m)). The function wraps +/// +/// of the first list. +/// +[] +val append : list1 : IIndexedList<'T> -> list2 : IIndexedList<'T> -> IIndexedList<'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 list. +/// The chosen values. +/// +/// O(n). +/// +[] +val choose : chooser : ('T -> 'U voption) -> list : IIndexedList<'T> -> IndexedList<'U> + +/// Divides the list into chunks of a given size. +/// The maximum size of a chunk. +/// The input list. +/// The chunks; the last one can be smaller. +/// Thrown when is not positive. +/// O(n). +[] +val chunkBySize : chunkSize : int -> list : IIndexedList<'T> -> IndexedList> + +/// +/// Concatenates the lists that a function returns for the elements. +/// +/// The function that returns a list for each element. +/// The input list. +/// The concatenation of the returned lists. +/// +/// O(n + the total length of the returned lists that have fewer than 16 elements), and O(log m) for each returned +/// indexed list of at least 16 elements, where m is the length of the result: the result shares the nodes of such a list. +/// +[] +val collect : mapping : ('T -> IIndexedList<'U>) -> list : IIndexedList<'T> -> IndexedList<'U> + +/// Concatenates a sequence of lists. +/// The lists. +/// The concatenation, in an indexed list, because the lists can have different implementations. +/// Thrown when is or a default flat list, or one of its lists is or a default flat list. +/// +/// O(the total length of the lists that have fewer than 16 elements, and of the lists that are not indexed lists), and +/// O(log m) for each indexed list of at least 16 elements, where m is the length of the result: the result shares the +/// nodes of such a list. +/// +[] +val concat : lists : seq> -> IndexedList<'T> + +/// +/// Returns a list with a value added before the first element. +/// +/// The value to add. +/// The input list. +/// A list of the implementation of the input with the value and the elements. +/// +/// O(log n). The function wraps . +/// +[] +val cons : value : 'T -> list : IIndexedList<'T> -> IIndexedList<'T> + +/// Returns the elements that satisfy the predicate. +/// The function to test the elements. +/// The input list. +/// The elements that satisfy the predicate, in their order, in a list of the implementation of the input. +/// O(n). +[] +val filter : predicate : ('T -> bool) -> list : IIndexedList<'T> -> IIndexedList<'T> + +/// Returns every element with its index. +/// The input list. +/// The pairs of index and element. +/// O(n). +[] +val indexed : list : IIndexedList<'T> -> IndexedList + +/// +/// Returns a 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 list. +/// A list of the implementation of the input with the value. +/// Thrown when the index is negative or greater than the length. +/// +/// O(log n). The function wraps . +/// +[] +val insertAt : index : int -> value : 'T -> source : IIndexedList<'T> -> IIndexedList<'T> + +/// +/// Returns a 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 list. +/// A list of the implementation of the input with the values. +/// Thrown when the index is negative or greater than the length. +/// Thrown when is or a default flat list. +/// +/// O(m log(n + m)). The function wraps +/// . +/// +[] +val insertManyAt : index : int -> values : seq<'T> -> source : IIndexedList<'T> -> IIndexedList<'T> + +/// Returns the results of a function applied to each element. +/// The function to transform the elements, called in the order of the elements. +/// The input list. +/// The results, in the order of the elements. +/// O(n). +[] +val map : mapping : ('T -> 'U) -> list : IIndexedList<'T> -> IndexedList<'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 list. +/// The second list. +/// The results. +/// Thrown when the lists have different lengths. +/// +/// O(n). +/// +[] +val map2 : mapping : ('T1 -> 'T2 -> 'U) -> list1 : IIndexedList<'T1> -> list2 : IIndexedList<'T2> -> IndexedList<'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 list. +/// The second list. +/// The third list. +/// The results. +/// Thrown when the lists have different lengths. +/// +/// O(n). +/// +[] +val map3 : + mapping : ('T1 -> 'T2 -> 'T3 -> 'U) -> + list1 : IIndexedList<'T1> -> + list2 : IIndexedList<'T2> -> + list3 : IIndexedList<'T3> -> + IndexedList<'U> + +/// +/// Returns the results of a function applied to each element and its index. +/// +/// The function to transform the elements. +/// The input list. +/// The results. +/// +/// O(n). +/// +[] +val mapi : mapping : (int -> 'T -> 'U) -> list : IIndexedList<'T> -> IndexedList<'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 list. +/// The second list. +/// The results. +/// Thrown when the lists have different lengths. +/// +/// O(n). +/// +[] +val mapi2 : mapping : (int -> 'T1 -> 'T2 -> 'U) -> list1 : IIndexedList<'T1> -> list2 : IIndexedList<'T2> -> IndexedList<'U> + +/// Returns every element with its successor. +/// The input list. +/// The pairs of neighbours; empty for fewer than two elements. +/// O(n). +[] +val pairwise : list : IIndexedList<'T> -> IndexedList + +/// +/// 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 list. +/// The permuted elements, in a list of the implementation of the input. +/// Thrown when the function does not compute a permutation. +/// +/// O(n). +/// +[] +val permute : indexMap : (int -> int) -> list : IIndexedList<'T> -> IIndexedList<'T> + +/// +/// Returns a list without the first element that equals a value, in +/// . +/// +/// The value to remove. +/// The input list. +/// A list of the implementation of the input without the element. +/// +/// +/// O(n). An that has no equal element comes back as it is. +/// The function wraps +/// . +/// +/// +/// To remove several values, use +/// ; +/// to remove by index, use +/// ; +/// to remove the elements that satisfy a predicate, use +/// +/// with the negated predicate. +/// +/// +[] +val remove : item : 'T -> list : IIndexedList<'T> -> IIndexedList<'T> + +/// +/// Returns a list without the element at an index. +/// +/// The index of the removed element. +/// The input list. +/// A list of the implementation of the input without the element. +/// Thrown when the index is out of range. +/// +/// O(log n). The function wraps . +/// +[] +val removeAt : index : int -> source : IIndexedList<'T> -> IIndexedList<'T> + +/// +/// Returns a list without one element for each of the given values, in +/// . +/// +/// The values to remove. +/// The input list. +/// A list of the implementation of the input without the elements. +/// 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, and an +/// that has none of the values comes back as it is. The +/// function wraps +/// . +/// +/// +/// To remove every element that equals one of the values, and the duplicates of the other elements, use +/// . +/// +/// +[] +val removeMany : items : seq<'T> -> list : IIndexedList<'T> -> IIndexedList<'T> + +/// +/// Returns a list without a range of elements. +/// +/// The index of the first removed element. +/// +/// The number of removed elements. A count that is not positive removes nothing, and so does a count whose sum with the +/// index exceeds , as in +/// . +/// +/// The input list. +/// A list of the implementation of the input without the range. +/// Thrown when the index is negative or the range ends beyond the list. +/// +/// O(count × log n). The function wraps +/// . +/// +[] +val removeManyAt : index : int -> count : int -> source : IIndexedList<'T> -> IIndexedList<'T> + +/// +/// Returns a list without one element for each of the given values, in a given equality comparer. +/// +/// The equality comparer. +/// The values to remove. +/// The input list. +/// A list of the implementation of the input without the elements. +/// Thrown when is or a default flat list. +/// +/// O(n × m) for m values. Each value removes the first element that the comparer finds equal to it and that no earlier +/// value has removed; a value that equals no remaining element removes nothing, and an +/// that has none of the values comes back as it is. The +/// function wraps +/// . +/// +[] +val removeManyWith : comparer : IEqualityComparer<'T> -> items : seq<'T> -> list : IIndexedList<'T> -> IIndexedList<'T> + +/// +/// Returns a list without the first element that equals a value in a given equality comparer. +/// +/// The equality comparer. +/// The value to remove. +/// The input list. +/// A list of the implementation of the input without the element. +/// +/// O(n). An that has no equal element comes back as it is. +/// The function wraps +/// . +/// +[] +val removeWith : comparer : IEqualityComparer<'T> -> item : 'T -> list : IIndexedList<'T> -> IIndexedList<'T> + +/// +/// Returns a list with the first element that equals a value replaced by another value, in +/// . +/// +/// The value to replace. +/// The new value. +/// The input list. +/// A list of the implementation of the input with the new value in place of the element. +/// Thrown when no element equals . +/// +/// +/// O(n). The function wraps +/// . +/// +/// +/// To replace every element that equals a value, use +/// ; +/// to replace the element at an index, use +/// . +/// +/// +[] +val replace : oldValue : 'T -> newValue : 'T -> list : IIndexedList<'T> -> IIndexedList<'T> + +/// +/// Returns a list with the first element that equals a value in a given equality comparer replaced by another value. +/// +/// The equality comparer. +/// The value to replace. +/// The new value. +/// The input list. +/// A list of the implementation of the input with the new value in place of the element. +/// +/// Thrown when the comparer finds no element equal to . +/// +/// +/// O(n). The function wraps +/// . +/// +[] +val replaceWith : + comparer : IEqualityComparer<'T> -> oldValue : 'T -> newValue : 'T -> list : IIndexedList<'T> -> IIndexedList<'T> + +/// +/// Returns the elements in reverse order. +/// +/// The input list. +/// The reversed elements, in a list of the implementation of the input. +/// +/// O(n). The function copies the elements to an array and reverses it with +/// , as +/// +/// does. Then it adds the reversed elements with +/// to +/// the empty list that of the input returns. +/// +[] +val rev : list : IIndexedList<'T> -> IIndexedList<'T> + +/// +/// Returns the transpose of a sequence of lists of the same length. +/// +/// The lists. +/// An indexed list whose element i holds the element i of every input list. +/// Thrown when is or a default flat list, or one of its lists is or a default flat list. +/// Thrown when the lists have different lengths. +/// +/// O(the total length). +/// +[] +val transpose : lists : seq> -> IndexedList> + +/// Splits a list of pairs into two indexed lists. +/// The input list. +/// The first components and the second components. +/// O(n). +[] +val unzip : list : IIndexedList -> struct (IndexedList<'T1> * IndexedList<'T2>) + +/// Splits a list of triples into three indexed lists. +/// The input list. +/// The first, the second and the third components. +/// O(n). +[] +val unzip3 : list : IIndexedList -> struct (IndexedList<'T1> * IndexedList<'T2> * IndexedList<'T3>) + +/// +/// Returns a list with the element at an index replaced by a value. +/// +/// The index of the replaced element. +/// The new value. +/// The input list. +/// A list of the implementation of the input with the new value. +/// Thrown when the index is out of range. +/// +/// O(log n). The function wraps . +/// +[] +val updateAt : index : int -> value : 'T -> source : IIndexedList<'T> -> IIndexedList<'T> + +/// +/// Returns the elements that satisfy the predicate. +/// +/// The function to test the elements. +/// The input list. +/// The elements that satisfy the predicate, in their order, in a list of the implementation of the input. +/// +/// O(n). A synonym of . +/// +[] +val where : predicate : ('T -> bool) -> list : IIndexedList<'T> -> IIndexedList<'T> + +/// +/// Returns the windows of a given size that slide over the list. +/// +/// The size of a window. +/// The input list. +/// The windows; empty when the list is shorter than a window. +/// Thrown when is not positive. +/// +/// O(n × windowSize). +/// +[] +val windowed : windowSize : int -> list : IIndexedList<'T> -> IndexedList> + +/// Combines two lists into an indexed list of pairs. +/// The first list. +/// The second list. +/// The pairs of elements at the same index. +/// Thrown when the lists have different lengths. +/// O(n). +[] +val zip : list1 : IIndexedList<'T1> -> list2 : IIndexedList<'T2> -> IndexedList + +/// Combines three lists into an indexed list of triples. +/// The first list. +/// The second list. +/// The third list. +/// The triples of elements at the same index. +/// Thrown when the lists have different lengths. +/// O(n). +[] +val zip3 : + list1 : IIndexedList<'T1> -> list2 : IIndexedList<'T2> -> list3 : IIndexedList<'T3> -> IndexedList + +// ---- Folding ---- + +/// Returns the average of the elements. +/// The input list. +/// The average. +/// Thrown when the list is empty. +/// O(n). +[] +val inline average : + list : IIndexedList< ^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 list. +/// The average of the results. +/// Thrown when the list is empty. +/// +/// O(n). +/// +[] +val inline averageBy : + projection : ('T -> ^U) -> list : IIndexedList<'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 lists element by element with a comparer. +/// The function that compares two elements. +/// The first list. +/// The second list. +/// +/// The first non-zero result of the comparer, or the comparison of the lengths when every common element compares equal. +/// +/// +/// O(n). Two indexed lists are walked together, and the walk stops at the first pair of elements that the comparer does +/// not consider equal; the other implementations are copied first. +/// +[] +val compareWith : comparer : ('T -> 'T -> int) -> list1 : IIndexedList<'T> -> list2 : IIndexedList<'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 list. +/// The final state. +/// +/// O(n). +/// +[] +val fold : folder : ('State -> 'T -> 'State) -> state : 'State -> list : IIndexedList<'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 list. +/// The second list. +/// The final state. +/// Thrown when the lists have different lengths. +/// +/// O(n). +/// +[] +val fold2 : + folder : ('State -> 'T1 -> 'T2 -> 'State) -> + state : 'State -> + list1 : IIndexedList<'T1> -> + list2 : IIndexedList<'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 list. +/// The initial state. +/// The final state. +/// +/// O(n). +/// +[] +val foldBack : folder : ('T -> 'State -> 'State) -> list : IIndexedList<'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 list. +/// The second list. +/// The initial state. +/// The final state. +/// Thrown when the lists have different lengths. +/// +/// O(n). +/// +[] +val foldBack2 : + folder : ('T1 -> 'T2 -> 'State -> 'State) -> + list1 : IIndexedList<'T1> -> + list2 : IIndexedList<'T2> -> + state : 'State -> + 'State + +/// Applies an action to each element. +/// The action. +/// The input list. +/// O(n). +[] +val iter : action : ('T -> unit) -> list : IIndexedList<'T> -> unit + +/// +/// Applies an action to each pair of elements at the same index. +/// +/// The action. +/// The first list. +/// The second list. +/// Thrown when the lists have different lengths. +/// +/// O(n). +/// +[] +val iter2 : action : ('T1 -> 'T2 -> unit) -> list1 : IIndexedList<'T1> -> list2 : IIndexedList<'T2> -> unit + +/// Applies an action to each element and its index. +/// The action. +/// The input list. +/// O(n). +[] +val iteri : action : (int -> 'T -> unit) -> list : IIndexedList<'T> -> unit + +/// +/// Applies an action to the index and the pair of elements at that index. +/// +/// The action. +/// The first list. +/// The second list. +/// Thrown when the lists have different lengths. +/// +/// O(n). +/// +[] +val iteri2 : action : (int -> 'T1 -> 'T2 -> unit) -> list1 : IIndexedList<'T1> -> list2 : IIndexedList<'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 list. +/// The results and the final state. +/// +/// O(n). +/// +[] +val mapFold : + mapping : ('State -> 'T -> struct ('Result * 'State)) -> + state : 'State -> + list : IIndexedList<'T> -> + struct (IndexedList<'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 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)) -> + list : IIndexedList<'T> -> + state : 'State -> + struct (IndexedList<'Result> * 'State) + +/// Returns the greatest element, in F# generic comparison. +/// The input list. +/// The greatest element. +/// Thrown when the list is empty. +/// +/// O(n). The function is inline, as +/// is, so that the comparison is compiled for the type of the elements and boxes nothing. +/// +[] +val inline max : list : IIndexedList<'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 list. +/// The first element with the greatest result. +/// Thrown when the list is empty. +/// +/// O(n). The function is inline, as +/// is, so that the comparison is compiled for the type of the results and boxes nothing. +/// +[] +val inline maxBy : projection : ('T -> 'U) -> list : IIndexedList<'T> -> 'T when 'U : comparison + +/// Returns the smallest element, in F# generic comparison. +/// The input list. +/// The smallest element. +/// Thrown when the list is empty. +/// +/// O(n). The function is inline, as +/// is, so that the comparison is compiled for the type of the elements and boxes nothing. +/// +[] +val inline min : list : IIndexedList<'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 list. +/// The first element with the smallest result. +/// Thrown when the list is empty. +/// +/// O(n). The function is inline, as +/// is, so that the comparison is compiled for the type of the results and boxes nothing. +/// +[] +val inline minBy : projection : ('T -> 'U) -> list : IIndexedList<'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 list. +/// The combined value. +/// Thrown when the list is empty. +/// +/// O(n). +/// +[] +val reduce : reduction : ('T -> 'T -> 'T) -> list : IIndexedList<'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 list. +/// The combined value. +/// Thrown when the list is empty. +/// +/// O(n). +/// +[] +val reduceBack : reduction : ('T -> 'T -> 'T) -> list : IIndexedList<'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 list. +/// The states; one more than the elements. +/// +/// O(n). +/// +[] +val scan : folder : ('State -> 'T -> 'State) -> state : 'State -> list : IIndexedList<'T> -> IndexedList<'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 list. +/// The initial state. +/// The states; one more than the elements. +/// +/// O(n). +/// +[] +val scanBack : folder : ('T -> 'State -> 'State) -> list : IIndexedList<'T> -> state : 'State -> IndexedList<'State> + +/// Returns the sum of the elements. +/// The input list. +/// The sum; zero for an empty list. +/// O(n). +[] +val inline sum : list : IIndexedList< ^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 list. +/// The sum of the results; zero for an empty list. +/// +/// O(n). +/// +[] +val inline sumBy : + projection : ('T -> ^U) -> list : IIndexedList<'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 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) -> list : IIndexedList<'T> -> IndexedList when 'Key : equality + +/// +/// Returns the elements without duplicates, in F# structural equality. +/// +/// The input list. +/// The first occurrence of each element, in their order, in a list of the implementation of the input. +/// +/// O(n). +/// +[] +val distinct : list : IIndexedList<'T> -> IIndexedList<'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 list. +/// The first element of each key, in their order, in a list of the implementation of the input. +/// +/// O(n). The keys compare in F# structural equality. +/// +[] +val distinctBy : projection : ('T -> 'Key) -> list : IIndexedList<'T> -> IIndexedList<'T> when 'Key : equality + +/// +/// Returns the distinct elements that do not equal any of the excluded items. +/// +/// The items to remove. +/// The input list. +/// The distinct remaining elements, in their order, in a list of the implementation of the input. +/// Thrown when is or a default flat list. +/// +/// O(n + m). An empty list is returned without a look at the items to exclude. +/// +[] +val except : itemsToExclude : seq<'T> -> list : IIndexedList<'T> -> IIndexedList<'T> when 'T : equality + +/// Groups the elements by the key of a projection. +/// The function that returns the key of an element. +/// The input 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) -> list : IIndexedList<'T> -> IndexedList)> when 'Key : equality + +/// Splits the elements by a predicate. +/// The function to test the elements. +/// The input list. +/// +/// The elements that satisfy the predicate and the other ones, both in their order, in lists of the implementation of +/// the input. +/// +/// O(n). +[] +val partition : predicate : ('T -> bool) -> list : IIndexedList<'T> -> struct (IIndexedList<'T> * IIndexedList<'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 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>) -> list : IIndexedList<'T> -> struct (IndexedList<'T1> * IndexedList<'T2>) + +/// +/// Returns the list without its first elements. +/// +/// The number of elements to skip; a negative count skips nothing. +/// The input list. +/// The remaining elements, in a list of the implementation of the input. +/// Thrown when the count exceeds the length. +/// +/// O(log n + k) for an indexed list, where k is the length of the result; the other implementations are read from their +/// start. The list itself is returned when nothing is skipped. +/// +[] +val skip : count : int -> list : IIndexedList<'T> -> IIndexedList<'T> + +/// +/// Returns the list without its last elements. +/// +/// The number of elements to skip; a negative count skips nothing. +/// The input list. +/// The remaining elements, in a list of the implementation of the input. +/// Thrown when the count exceeds the length. +/// +/// O(log n + k) for an indexed list, where k is the length of the result; the other implementations are read from their +/// start. The list itself is returned when nothing is skipped. The checks follow +/// . +/// +[] +val skipLast : count : int -> list : IIndexedList<'T> -> IIndexedList<'T> + +/// +/// Returns the list without its first elements that satisfy the predicate. +/// +/// The function to test the elements. +/// The input list. +/// The elements from the first one that does not satisfy the predicate, in a list of the implementation of the input. +/// +/// O(n + k) for an indexed list, where k is the length of the result. +/// +[] +val skipWhile : predicate : ('T -> bool) -> list : IIndexedList<'T> -> IIndexedList<'T> + +/// Splits the list at an index. +/// The index of the first element of the second part. +/// The input list. +/// The elements before the index and the elements from the index, in lists of the implementation of the input. +/// Thrown when the index is negative. +/// Thrown when the index exceeds the length. +/// O(n) for an indexed list. +[] +val splitAt : index : int -> list : IIndexedList<'T> -> struct (IIndexedList<'T> * IIndexedList<'T>) + +/// +/// Splits the list into at most a given number of parts of nearly equal length. +/// +/// The maximum number of parts. +/// The input list. +/// The parts; the first ones are one element longer when the length does not divide evenly. +/// Thrown when is not positive. +/// +/// O(n). +/// +[] +val splitInto : count : int -> list : IIndexedList<'T> -> IndexedList> + +/// +/// Returns the first elements of the list. +/// +/// The number of elements to take. +/// The input list. +/// The first elements, in a list of the implementation of the input. +/// Thrown when the count is negative. +/// Thrown when the count exceeds the length. +/// +/// O(log n + count) for an indexed list; the other implementations are read from their start. The list itself is returned +/// when the count is the length. +/// +[] +val take : count : int -> list : IIndexedList<'T> -> IIndexedList<'T> + +/// +/// Returns the last elements of the list. +/// +/// The number of elements to take. +/// The input list. +/// The last elements, in a list of the implementation of the input. +/// Thrown when the count is negative. +/// Thrown when the count exceeds the length. +/// +/// O(log n + count) for an indexed list; the other implementations are read from their start. The list itself is returned +/// when the count is the length. The checks follow +/// . +/// +[] +val takeLast : count : int -> list : IIndexedList<'T> -> IIndexedList<'T> + +/// +/// Returns the first elements of the list that satisfy the predicate. +/// +/// The function to test the elements. +/// The input list. +/// The elements before the first one that does not satisfy the predicate, in a list of the implementation of the input. +/// +/// O(k) for an indexed list, where k is the length of the result. +/// +[] +val takeWhile : predicate : ('T -> bool) -> list : IIndexedList<'T> -> IIndexedList<'T> + +/// +/// Returns at most a given number of first elements. +/// +/// The maximum number of elements; a count that is not positive returns an empty list. +/// The input list. +/// The first elements, in a list of the implementation of the input. +/// +/// O(log n + k) for an indexed list, where k is the length of the result; the other implementations are read from their +/// start. The list itself is returned when it is not longer than the count. +/// +[] +val truncate : count : int -> list : IIndexedList<'T> -> IIndexedList<'T> + +// ---- Sorting ---- + +/// +/// Sorts the elements in the order of F# generic comparison. +/// +/// The input list. +/// The sorted elements, in a list of the implementation of the input. +/// +/// Thrown when the comparison of two elements throws, as in . +/// +/// +/// O(n log n). This is a stable sort, as : +/// equal elements keep their order. +/// +[] +val sort : list : IIndexedList<'T> -> IIndexedList<'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 list. +/// The sorted elements, in a list of the implementation of the input. +/// +/// O(n log n). This is a stable sort: elements with equal keys keep their order. +/// +[] +val sortBy : projection : ('T -> 'Key) -> list : IIndexedList<'T> -> IIndexedList<'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 list. +/// The sorted elements, in a list of the implementation of the input. +/// +/// Thrown when the projection or the comparison of two keys throws: the projection runs in the comparison, as in +/// . +/// +/// +/// O(n log n). This is a stable sort: elements with equal keys keep their order. +/// +[] +val sortByDescending : projection : ('T -> 'Key) -> list : IIndexedList<'T> -> IIndexedList<'T> when 'Key : comparison + +/// +/// Sorts the elements in descending order of F# generic comparison. +/// +/// The input list. +/// The sorted elements, in a list of the implementation of the input. +/// +/// Thrown when the comparison of two elements throws, as in . +/// +/// +/// O(n log n). This is a stable sort: equal elements keep their order. +/// +[] +val sortDescending : list : IIndexedList<'T> -> IIndexedList<'T> when 'T : comparison + +/// +/// Sorts the elements with a comparison function. +/// +/// The function that compares two elements. +/// The input list. +/// The sorted elements, in a list of the implementation of the input. +/// +/// Thrown when the comparer throws, as in +/// . +/// +/// +/// O(n log n). This is a stable sort: elements that compare equal keep their order. +/// +[] +val sortWith : comparer : ('T -> 'T -> int) -> list : IIndexedList<'T> -> IIndexedList<'T> + +/// +/// Sorts the elements with a comparer. +/// +/// The comparer. +/// The input list. +/// The sorted elements, in a list of the implementation of the input. +/// +/// O(n log n). This is a stable sort: elements that compare equal keep their order. The function wraps +/// . +/// +[] +val sortWithComparer : comparer : IComparer<'T> -> list : IIndexedList<'T> -> IIndexedList<'T> + +// ---- Random ---- + +/// +/// Returns a random element, chosen with . +/// +/// The input list. +/// A random element. +/// Thrown when the list is empty. +/// +/// O(log n). +/// +[] +val randomChoice : source : IIndexedList<'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 list. +/// A random element. +/// Thrown when the list is empty. +/// Thrown when the randomizer returns a number outside [0, 1). +/// +/// O(log n). +/// +[] +val randomChoiceBy : randomizer : (unit -> float) -> source : IIndexedList<'T> -> 'T + +/// +/// Returns a random element, chosen with a given random number generator. +/// +/// The random number generator. +/// The input list. +/// A random element. +/// Thrown when is . +/// Thrown when the list is empty. +/// +/// O(log n). +/// +[] +val randomChoiceWith : random : Random -> source : IIndexedList<'T> -> 'T + +/// +/// Returns random elements, chosen with ; an element can repeat. +/// +/// The number of elements to choose. +/// The input list. +/// The chosen elements, in a list of the implementation of the input. +/// Thrown when the count is negative, or positive for an empty list. +/// +/// O(n + count). +/// +[] +val randomChoices : count : int -> source : IIndexedList<'T> -> IIndexedList<'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 list. +/// The chosen elements, in a list of the implementation of the input. +/// Thrown when the count is negative, or positive for an empty list. +/// Thrown when the randomizer returns a number outside [0, 1). +/// +/// O(n + count). +/// +[] +val randomChoicesBy : randomizer : (unit -> float) -> count : int -> source : IIndexedList<'T> -> IIndexedList<'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 list. +/// The chosen elements, in a list of the implementation of the input. +/// Thrown when is . +/// Thrown when the count is negative, or positive for an empty list. +/// +/// O(n + count). +/// +[] +val randomChoicesWith : random : Random -> count : int -> source : IIndexedList<'T> -> IIndexedList<'T> + +/// +/// Returns random elements at distinct indices, chosen with . +/// +/// The number of elements to choose. +/// The input list. +/// The chosen elements, in a list of the implementation of the input. +/// Thrown when the count is negative or exceeds the length. +/// +/// O(n + count). +/// +[] +val randomSample : count : int -> source : IIndexedList<'T> -> IIndexedList<'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 list. +/// The chosen elements, in a list of the implementation of the input. +/// Thrown when the count is negative or exceeds the length. +/// Thrown when the randomizer returns a number outside [0, 1). +/// +/// O(n + count). +/// +[] +val randomSampleBy : randomizer : (unit -> float) -> count : int -> source : IIndexedList<'T> -> IIndexedList<'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 list. +/// The chosen elements, in a list of the implementation of the input. +/// Thrown when is . +/// Thrown when the count is negative or exceeds the length. +/// +/// O(n + count). +/// +[] +val randomSampleWith : random : Random -> count : int -> source : IIndexedList<'T> -> IIndexedList<'T> + +/// +/// Returns the elements in a random order, chosen with . +/// +/// The input list. +/// The shuffled elements, in a list of the implementation of the input. +/// +/// O(n). +/// +[] +val randomShuffle : source : IIndexedList<'T> -> IIndexedList<'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 list. +/// The shuffled elements, in a list of the implementation of the input. +/// Thrown when the randomizer returns a number outside [0, 1). +/// +/// O(n). +/// +[] +val randomShuffleBy : randomizer : (unit -> float) -> source : IIndexedList<'T> -> IIndexedList<'T> + +/// +/// Returns the elements in a random order, chosen with a given random number generator. +/// +/// The random number generator. +/// The input list. +/// The shuffled elements, in a list of the implementation of the input. +/// Thrown when is . +/// +/// O(n). +/// +[] +val randomShuffleWith : random : Random -> source : IIndexedList<'T> -> IIndexedList<'T> + +// ---- Conversion ---- + +/// Returns a new array with the elements. +/// The input list. +/// A copy of the elements. +/// O(n). +[] +val toArray : list : IIndexedList<'T> -> 'T array + +/// Returns a list of the elements. +/// The input list. +/// The list of the elements. +/// O(n). +[] +val toList : list : IIndexedList<'T> -> 'T list + +/// Returns the list as a sequence. +/// The input list. +/// The sequence of the elements. +/// O(1). +[] +val toSeq : list : IIndexedList<'T> -> seq<'T> + +// ---- Type-specific ---- + +/// +/// Returns an empty list of the implementation of the input. +/// +/// The input list. +/// The empty list. +/// +/// O(1). The function wraps , which keeps the +/// implementation of the list and its settings. +/// +[] +val clear : list : IIndexedList<'T> -> IIndexedList<'T> diff --git a/src/FSharp.Collections.Immutable/IndexedList/IIndexedListOption.fs b/src/FSharp.Collections.Immutable/IndexedList/IIndexedListOption.fs new file mode 100644 index 00000000..39097b46 --- /dev/null +++ b/src/FSharp.Collections.Immutable/IndexedList/IIndexedListOption.fs @@ -0,0 +1,80 @@ +[] +module FSharp.Collections.Immutable.Option.IIndexedList + +open FSharp.Collections.Immutable + +// The functions delegate to the default module and convert its results, or the results of the callbacks that they pass to +// it, so that both modules have the same checks and exceptions. + +// ---- Access ---- + +[] +let tryExactlyOne (list : IIndexedList<'T>) = + FSharp.Collections.Immutable.IIndexedList.tryExactlyOne list + |> ValueOption.toOption + +[] +let tryHead (list : IIndexedList<'T>) = + FSharp.Collections.Immutable.IIndexedList.tryHead list + |> ValueOption.toOption + +[] +let tryItem index (list : IIndexedList<'T>) = + FSharp.Collections.Immutable.IIndexedList.tryItem index list + |> ValueOption.toOption + +[] +let tryLast (list : IIndexedList<'T>) = + FSharp.Collections.Immutable.IIndexedList.tryLast list + |> ValueOption.toOption + +[] +let tryTail (list : IIndexedList<'T>) = + FSharp.Collections.Immutable.IIndexedList.tryTail list + |> ValueOption.toOption + +[] +let tryUncons (list : IIndexedList<'T>) = + match FSharp.Collections.Immutable.IIndexedList.tryUncons list with + | ValueSome (struct (head, tail)) -> Some (head, tail) + | ValueNone -> None + +[] +let (|Cons|_|) (list : IIndexedList<'T>) = tryUncons list + +// ---- Search ---- + +[] +let pick (chooser : 'T -> 'U option) (list : IIndexedList<'T>) = + FSharp.Collections.Immutable.IIndexedList.pick (fun item -> chooser item |> ValueOption.ofOption) list + +[] +let tryFind predicate (list : IIndexedList<'T>) = + FSharp.Collections.Immutable.IIndexedList.tryFind predicate list + |> ValueOption.toOption + +[] +let tryFindBack predicate (list : IIndexedList<'T>) = + FSharp.Collections.Immutable.IIndexedList.tryFindBack predicate list + |> ValueOption.toOption + +[] +let tryFindIndex predicate (list : IIndexedList<'T>) = + FSharp.Collections.Immutable.IIndexedList.tryFindIndex predicate list + |> ValueOption.toOption + +[] +let tryFindIndexBack predicate (list : IIndexedList<'T>) = + FSharp.Collections.Immutable.IIndexedList.tryFindIndexBack predicate list + |> ValueOption.toOption + +[] +let tryPick (chooser : 'T -> 'U option) (list : IIndexedList<'T>) = + FSharp.Collections.Immutable.IIndexedList.tryPick (fun item -> chooser item |> ValueOption.ofOption) list + |> ValueOption.toOption + +// ---- Transformation ---- + +[] +let choose (chooser : 'T -> 'U option) (list : IIndexedList<'T>) = + FSharp.Collections.Immutable.IIndexedList.choose (fun item -> chooser item |> ValueOption.ofOption) list diff --git a/src/FSharp.Collections.Immutable/IndexedList/IIndexedListOption.fsi b/src/FSharp.Collections.Immutable/IndexedList/IIndexedListOption.fsi new file mode 100644 index 00000000..e7f07e69 --- /dev/null +++ b/src/FSharp.Collections.Immutable/IndexedList/IIndexedListOption.fsi @@ -0,0 +1,183 @@ +/// +/// 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, and the checks and the +/// exceptions of the default function. +/// +[] +module FSharp.Collections.Immutable.Option.IIndexedList + +open FSharp.Collections.Immutable + +// ---- Access ---- + +/// +/// Returns the only element of the list, or None when it does not have exactly one element. +/// +/// The input list. +/// The only element, or None. +/// +/// O(1). +/// +[] +val tryExactlyOne : list : IIndexedList<'T> -> 'T option + +/// +/// Returns the first element of the list, or None when it is empty. +/// +/// The input list. +/// The first element, or None. +/// +/// O(log n). +/// +[] +val tryHead : list : IIndexedList<'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 list. +/// The element at the index, or None. +/// +/// O(log n). +/// +[] +val tryItem : index : int -> list : IIndexedList<'T> -> 'T option + +/// +/// Returns the last element of the list, or None when it is empty. +/// +/// The input list. +/// The last element, or None. +/// +/// O(log n). +/// +[] +val tryLast : list : IIndexedList<'T> -> 'T option + +/// +/// Returns the list without its first element, or None when it is empty. +/// +/// The input list. +/// The elements after the first one, in a list of the implementation of the input, or None. +/// +/// O(log n). +/// +[] +val tryTail : list : IIndexedList<'T> -> IIndexedList<'T> option + +/// +/// Returns the first element and the other elements, or None when the list is empty. +/// +/// The input list. +/// The first element and the elements after it, or None. +/// +/// O(log n). +/// +[] +val tryUncons : list : IIndexedList<'T> -> ('T * IIndexedList<'T>) option + +/// +/// Matches a list with a first element, and returns that element and the other elements. +/// +/// The input list. +/// The first element and the elements after it, or None for an empty list. +/// +/// O(log n). +/// +[] +val (|Cons|_|) : list : IIndexedList<'T> -> ('T * IIndexedList<'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 list. +/// The first chosen value. +/// Thrown when the chooser returns None for every element. +/// +/// O(n). +/// +[] +val pick : chooser : ('T -> 'U option) -> list : IIndexedList<'T> -> 'U + +/// +/// Returns the first element that satisfies the predicate, or None. +/// +/// The function to test the elements. +/// The input list. +/// The first element that satisfies the predicate, or None. +/// +/// O(n). +/// +[] +val tryFind : predicate : ('T -> bool) -> list : IIndexedList<'T> -> 'T option + +/// +/// Returns the last element that satisfies the predicate, or None. +/// +/// The function to test the elements. +/// The input list. +/// The last element that satisfies the predicate, or None. +/// +/// O(n). +/// +[] +val tryFindBack : predicate : ('T -> bool) -> list : IIndexedList<'T> -> 'T option + +/// +/// Returns the index of the first element that satisfies the predicate, or None. +/// +/// The function to test the elements. +/// The input list. +/// The index of the first element that satisfies the predicate, or None. +/// +/// O(n). +/// +[] +val tryFindIndex : predicate : ('T -> bool) -> list : IIndexedList<'T> -> int option + +/// +/// Returns the index of the last element that satisfies the predicate, or None. +/// +/// The function to test the elements. +/// The input list. +/// The index of the last element that satisfies the predicate, or None. +/// +/// O(n). +/// +[] +val tryFindIndexBack : predicate : ('T -> bool) -> list : IIndexedList<'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 list. +/// The first chosen value, or None. +/// +/// O(n). +/// +[] +val tryPick : chooser : ('T -> 'U option) -> list : IIndexedList<'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 list. +/// The chosen values. +/// +/// O(n). +/// +[] +val choose : chooser : ('T -> 'U option) -> list : IIndexedList<'T> -> IndexedList<'U> diff --git a/src/FSharp.Collections.Immutable/IndexedList/IIndexedListTuple.fs b/src/FSharp.Collections.Immutable/IndexedList/IIndexedListTuple.fs new file mode 100644 index 00000000..5206919d --- /dev/null +++ b/src/FSharp.Collections.Immutable/IndexedList/IIndexedListTuple.fs @@ -0,0 +1,126 @@ +[] +module FSharp.Collections.Immutable.Tuple.IIndexedList + +open System.Collections.Immutable +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. + +// ---- Access ---- + +[] +let tryUncons (list : IIndexedList<'T>) = + match FSharp.Collections.Immutable.IIndexedList.tryUncons list with + | ValueSome (struct (head, tail)) -> ValueSome (head, tail) + | ValueNone -> ValueNone + +[] +let uncons (list : IIndexedList<'T>) = + let struct (head, tail) = FSharp.Collections.Immutable.IIndexedList.uncons list + head, tail + +[] +[] +let (|Cons|_|) (list : IIndexedList<'T>) = tryUncons list + +// ---- Transformation ---- + +[] +let allPairs (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) = + Arrays.allPairs referencePair (Snapshot.ofSeq (nameof list1) list1) (Snapshot.ofSeq (nameof list2) list2) + |> ImmutableList.CreateRange + +[] +let indexed (list : IIndexedList<'T>) = + Arrays.indexed referencePair (Snapshot.ofSeq (nameof list) list) + |> ImmutableList.CreateRange + +[] +let pairwise (list : IIndexedList<'T>) = + Arrays.pairwise referencePair (Snapshot.ofSeq (nameof list) list) + |> ImmutableList.CreateRange + +[] +let unzip (list : IIndexedList<'T1 * 'T2>) = + let struct (first, second) = + Arrays.unzip (fun (item1, item2) -> struct (item1, item2)) (Snapshot.ofSeq (nameof list) list) + + ImmutableList.CreateRange first, ImmutableList.CreateRange second + +[] +let unzip3 (list : IIndexedList<'T1 * 'T2 * 'T3>) = + let struct (first, second, third) = + Arrays.unzip3 (fun (item1, item2, item3) -> struct (item1, item2, item3)) (Snapshot.ofSeq (nameof list) list) + + ImmutableList.CreateRange first, ImmutableList.CreateRange second, ImmutableList.CreateRange third + +[] +let zip (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) = + checkNotNull (nameof list1) list1 + checkNotNull (nameof list2) list2 + ListChecks.sameLengths (nameof list1) list1.Count (nameof list2) list2.Count + + Arrays.zip referencePair (Snapshot.ofSeq (nameof list1) list1) (Snapshot.ofSeq (nameof list2) list2) + |> ImmutableList.CreateRange + +[] +let zip3 (list1 : IIndexedList<'T1>) (list2 : IIndexedList<'T2>) (list3 : IIndexedList<'T3>) = + checkNotNull (nameof list1) list1 + checkNotNull (nameof list2) list2 + checkNotNull (nameof list3) list3 + ListChecks.zip3 (nameof list1) list1.Count (nameof list2) list2.Count (nameof list3) list3.Count + + Arrays.zip3 + referenceTriple + (Snapshot.ofSeq (nameof list1) list1) + (Snapshot.ofSeq (nameof list2) list2) + (Snapshot.ofSeq (nameof list3) list3) + |> ImmutableList.CreateRange + +// ---- Folding ---- + +[] +let mapFold (mapping : 'State -> 'T -> 'Result * 'State) (state : 'State) (list : IIndexedList<'T>) = + let mapStruct state item = + let result, next = mapping state item + struct (result, next) + + let struct (results, final) = FSharp.Collections.Immutable.IIndexedList.mapFold mapStruct state list + results, final + +[] +let mapFoldBack (mapping : 'T -> 'State -> 'Result * 'State) (list : IIndexedList<'T>) (state : 'State) = + let mapStruct item state = + let result, next = mapping item state + struct (result, next) + + let struct (results, final) = FSharp.Collections.Immutable.IIndexedList.mapFoldBack mapStruct list state + results, final + +// ---- Partitioning and grouping ---- + +[] +let countBy (projection : 'T -> 'Key) (list : IIndexedList<'T>) = + Arrays.countBy referencePair projection (Snapshot.ofSeq (nameof list) list) + |> ImmutableList.CreateRange + +[] +let groupBy (projection : 'T -> 'Key) (list : IIndexedList<'T>) = + Arrays.groupBy (fun key items -> key, ImmutableList.CreateRange items) projection (Snapshot.ofSeq (nameof list) list) + |> ImmutableList.CreateRange + +[] +let partition predicate (list : IIndexedList<'T>) = + let struct (accepted, rejected) = FSharp.Collections.Immutable.IIndexedList.partition predicate list + accepted, rejected + +[] +let partitionWith (partitioner : 'T -> Choice<'T1, 'T2>) (list : IIndexedList<'T>) = + let struct (first, second) = FSharp.Collections.Immutable.IIndexedList.partitionWith partitioner list + first, second + +[] +let splitAt index (list : IIndexedList<'T>) = + let struct (before, after) = FSharp.Collections.Immutable.IIndexedList.splitAt index list + before, after diff --git a/src/FSharp.Collections.Immutable/IndexedList/IIndexedListTuple.fsi b/src/FSharp.Collections.Immutable/IndexedList/IIndexedListTuple.fsi new file mode 100644 index 00000000..5c817219 --- /dev/null +++ b/src/FSharp.Collections.Immutable/IndexedList/IIndexedListTuple.fsi @@ -0,0 +1,191 @@ +/// +/// 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 +/// . Each function has the checks and the exceptions of the default +/// function, and the results keep the implementation of the input where the default function keeps it. +/// +[] +module FSharp.Collections.Immutable.Tuple.IIndexedList + +open FSharp.Collections.Immutable + +// ---- Access ---- + +/// +/// Returns the first element and the other elements, or ValueNone when the list is empty. +/// +/// The input list. +/// The first element and the elements after it, or ValueNone. +/// +/// O(log n). +/// +[] +val tryUncons : list : IIndexedList<'T> -> ('T * IIndexedList<'T>) voption + +/// Returns the first element and the other elements. +/// The input list. +/// The first element and the elements after it, in a list of the implementation of the input. +/// Thrown when the list is empty. +/// O(log n). +[] +val uncons : list : IIndexedList<'T> -> 'T * IIndexedList<'T> + +/// +/// Matches a list with a first element, and returns that element and the other elements. +/// +/// The input list. +/// The first element and the elements after it, or ValueNone for an empty list. +/// +/// O(log n). A struct partial pattern, so that a match allocates no option. +/// +[] +[] +val (|Cons|_|) : list : IIndexedList<'T> -> ('T * IIndexedList<'T>) voption + +// ---- Transformation ---- + +/// +/// Returns every element of the first list paired with every element of the second one. +/// +/// The first list. +/// The second list. +/// The pairs, in the order of the first list and then of the second one. +/// +/// O(n × m). +/// +[] +val allPairs : list1 : IIndexedList<'T1> -> list2 : IIndexedList<'T2> -> IndexedList<'T1 * 'T2> + +/// Returns every element with its index. +/// The input list. +/// The pairs of index and element. +/// O(n). +[] +val indexed : list : IIndexedList<'T> -> IndexedList + +/// Returns every element with its successor. +/// The input list. +/// The pairs of neighbours; empty for fewer than two elements. +/// O(n). +[] +val pairwise : list : IIndexedList<'T> -> IndexedList<'T * 'T> + +/// Splits a list of pairs into two indexed lists. +/// The input list. +/// The first components and the second components. +/// O(n). +[] +val unzip : list : IIndexedList<'T1 * 'T2> -> IndexedList<'T1> * IndexedList<'T2> + +/// Splits a list of triples into three indexed lists. +/// The input list. +/// The first, the second and the third components. +/// O(n). +[] +val unzip3 : list : IIndexedList<'T1 * 'T2 * 'T3> -> IndexedList<'T1> * IndexedList<'T2> * IndexedList<'T3> + +/// Combines two lists into an indexed list of pairs. +/// The first list. +/// The second list. +/// The pairs of elements at the same index. +/// Thrown when the lists have different lengths. +/// O(n). +[] +val zip : list1 : IIndexedList<'T1> -> list2 : IIndexedList<'T2> -> IndexedList<'T1 * 'T2> + +/// Combines three lists into an indexed list of triples. +/// The first list. +/// The second list. +/// The third list. +/// The triples of elements at the same index. +/// Thrown when the lists have different lengths. +/// O(n). +[] +val zip3 : list1 : IIndexedList<'T1> -> list2 : IIndexedList<'T2> -> list3 : IIndexedList<'T3> -> IndexedList<'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 list. +/// The results and the final state. +/// +/// O(n). +/// +[] +val mapFold : + mapping : ('State -> 'T -> 'Result * 'State) -> state : 'State -> list : IIndexedList<'T> -> IndexedList<'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 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) -> list : IIndexedList<'T> -> state : 'State -> IndexedList<'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 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) -> list : IIndexedList<'T> -> IndexedList<'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 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) -> list : IIndexedList<'T> -> IndexedList<'Key * IndexedList<'T>> when 'Key : equality + +/// Splits the elements by a predicate. +/// The function to test the elements. +/// The input list. +/// +/// The elements that satisfy the predicate and the other ones, both in their order, in lists of the implementation of +/// the input. +/// +/// O(n). +[] +val partition : predicate : ('T -> bool) -> list : IIndexedList<'T> -> IIndexedList<'T> * IIndexedList<'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 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>) -> list : IIndexedList<'T> -> IndexedList<'T1> * IndexedList<'T2> + +/// Splits the list at an index. +/// The index of the first element of the second part. +/// The input list. +/// The elements before the index and the elements from the index, in lists of the implementation of the input. +/// Thrown when the index is negative. +/// Thrown when the index exceeds the length. +/// O(n log n). +[] +val splitAt : index : int -> list : IIndexedList<'T> -> IIndexedList<'T> * IIndexedList<'T> diff --git a/src/FSharp.Collections.Immutable/IndexedList/IndexedList.fs b/src/FSharp.Collections.Immutable/IndexedList/IndexedList.fs new file mode 100644 index 00000000..dd8044f5 --- /dev/null +++ b/src/FSharp.Collections.Immutable/IndexedList/IndexedList.fs @@ -0,0 +1,1413 @@ +[] +module FSharp.Collections.Immutable.IndexedList + +open System +open System.Collections.Generic +open System.Collections.Immutable +open System.Linq + +// The functions call a member of ImmutableList where it has the semantics of the List function, and otherwise take a +// snapshot of the elements in an array, apply an Array function or a shared algorithm and build a new tree from the fresh +// result (ADR 0001, D5). The checks of ListChecks throw what the List functions throw (D6). + +/// An indexed list of the elements of a fresh array that nobody changes any more; the tree copies the elements. +let inline private ofFreshArray (array : 'T array) = ImmutableList.CreateRange array + +/// A range of an indexed list: the empty list or the list itself without a new tree, otherwise a new tree. The range is +/// copied into an array and the tree is built from the array: GetRange reads the elements of the range one by one from +/// the middle of the tree, which takes O(count log n), where the copy takes O(log n + count) and the build is one pass. +/// For a range of half a million elements of a list of a million that is twice as fast. +let private range (list : IndexedList<'T>) (start : int) (count : int) = + if count = 0 then + ImmutableList<'T>.Empty + elif count = list.Count then + list + else + let items = Array.zeroCreate count + list.CopyTo (start, items, 0, count) + ofFreshArray items + +/// The first value that the chooser returns for the elements, in their order. +let private firstChoice (chooser : 'T -> 'U voption) (list : IndexedList<'T>) = + let mutable enumerator = list.GetEnumerator () + + try + let mutable result = ValueNone + + while result.IsNone && enumerator.MoveNext () do + result <- chooser enumerator.Current + + result + finally + enumerator.Dispose () + +/// A range of an indexed list sorted stably: the range is sorted in a copy, with LINQ, which sorts stably, and put back +/// in place of the range. ImmutableList.Sort(Int32, Int32, IComparer) is not stable (ADR 0001, D5). +let private sortRangeStably (comparer : IComparer<'T>) index count (list : IndexedList<'T>) = + checkNotNull (nameof list) list + ListRanges.forward (nameof index) index (nameof count) count (nameof count) list.Count + + // A range of at most one element needs no sorting, and the comparer does not run for it + if count <= 1 then + list + else + // The range is copied into an array, where GetRange reads its elements one by one from the middle of the tree: for + // 10 000 elements of a list of a million, that took 2.2 ms instead of 2.8 ms + let items = Array.zeroCreate count + list.CopyTo (index, items, 0, count) + let sorted = items.Order(comparer).ToArray() + list.RemoveRange(index, count).InsertRange(index, sorted) + +// ---- Creation ---- + +[] +[] +let empty<'T> : IndexedList<'T> = ImmutableList<'T>.Empty + +[] +let init length (initializer : int -> 'T) = + // List.init reports the length under the name "count" + ListChecks.nonNegative "count" length + Array.init length initializer |> ofFreshArray + +[] +let ofArray (array : 'T array) = + checkNotNull (nameof array) array + ImmutableList.CreateRange array + +[] +let ofList (list : 'T list) = ImmutableList.CreateRange list + +[] +let ofReadOnlySpan (span : ReadOnlySpan<'T>) = ImmutableList.Create<'T> span + +[] +let ofSeq (source : seq<'T>) = + // A default flat list given as a sequence is a null input; the BCL would fail while it enumerates it (ADR 0001, D6) + checkNotNullOrDefaultSeq (nameof source) source + ImmutableList.CreateRange source + +[] +let ofSpan (span : Span<'T>) = ImmutableList.Create<'T>(Span<'T>.op_Implicit span) + +[] +let replicate count (initial : 'T) = + ListChecks.replicate (nameof count) count + Array.create count initial |> ofFreshArray + +[] +let singleton (value : 'T) = ImmutableList.Create<'T> value + +[] +let unfold (generator : 'State -> struct ('T * 'State) voption) (state : 'State) = + // As in choose, the number of elements is not known in advance: a ResizeArray and one CreateRange + let items = ResizeArray<'T>() + let mutable state = state + let mutable go = true + + while go do + match generator state with + | ValueSome (struct (item, next)) -> + items.Add item + state <- next + | ValueNone -> go <- false + + ImmutableList.CreateRange items + +// ---- Builder ---- + +[] +let build (action : IndexedList<'T>.Builder -> unit) = + let builder = ImmutableList.CreateBuilder<'T>() + action builder + builder.ToImmutable () + +[] +let builder () : IndexedList<'T>.Builder = ImmutableList.CreateBuilder<'T>() + +[] +let ofBuilder (builder : IndexedList<'T>.Builder) = + checkNotNull (nameof builder) builder + builder.ToImmutable () + +[] +let toBuilder (list : IndexedList<'T>) = + checkNotNull (nameof list) list + list.ToBuilder () + +[] +let update (action : IndexedList<'T>.Builder -> unit) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + let builder = list.ToBuilder () + action builder + builder.ToImmutable () + +// ---- Properties ---- + +[] +let isEmpty (list : IndexedList<'T>) = + checkNotNull (nameof list) list + list.IsEmpty + +[] +let length (list : IndexedList<'T>) = + checkNotNull (nameof list) list + list.Count + +// ---- Access ---- + +[] +let exactlyOne (list : IndexedList<'T>) = + checkNotNull (nameof list) list + // List.exactlyOne reports its input under the name "source" + ListChecks.exactlyOne "source" list.Count + list[0] + +[] +let head (list : IndexedList<'T>) = + checkNotNull (nameof list) list + ListChecks.notEmpty (nameof list) list.Count + list[0] + +[] +let item index (list : IndexedList<'T>) = + checkNotNull (nameof list) list + ListChecks.item (nameof index) index list.Count + list[index] + +[] +let last (list : IndexedList<'T>) = + checkNotNull (nameof list) list + ListChecks.notEmpty (nameof list) list.Count + list[list.Count - 1] + +[] +let tail (list : IndexedList<'T>) = + checkNotNull (nameof list) list + ListChecks.notEmpty (nameof list) list.Count + list.RemoveAt 0 + +[] +let tryExactlyOne (list : IndexedList<'T>) = + checkNotNull (nameof list) list + if list.Count = 1 then ValueSome list[0] else ValueNone + +[] +let tryHead (list : IndexedList<'T>) = + checkNotNull (nameof list) list + if list.IsEmpty then ValueNone else ValueSome list[0] + +[] +let tryItem index (list : IndexedList<'T>) = + checkNotNull (nameof list) list + + if index < 0 || index >= list.Count then + ValueNone + else + ValueSome list[index] + +[] +let tryLast (list : IndexedList<'T>) = + checkNotNull (nameof list) list + + if list.IsEmpty then + ValueNone + else + ValueSome list[list.Count - 1] + +[] +let tryTail (list : IndexedList<'T>) = + checkNotNull (nameof list) list + if list.IsEmpty then + ValueNone + else + ValueSome (list.RemoveAt 0) + +[] +let tryUncons (list : IndexedList<'T>) = + checkNotNull (nameof list) list + + if list.IsEmpty then + ValueNone + else + ValueSome (struct (list[0], list.RemoveAt 0)) + +[] +let uncons (list : IndexedList<'T>) = + checkNotNull (nameof list) list + // An empty list throws what List.head throws + ListChecks.notEmpty (nameof list) list.Count + struct (list[0], list.RemoveAt 0) + +[] +[] +let (|Cons|_|) (list : IndexedList<'T>) = tryUncons list + +[] +let (|Nil|_|) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + list.IsEmpty + +// ---- Search ---- + +[] +let binarySearch (value : 'T) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + list.BinarySearch (value, DefaultComparer<'T>.Instance) + +[] +let binarySearchRange index count (value : 'T) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + ListRanges.search (nameof index) index (nameof count) count list.Count + list.BinarySearch (index, count, value, DefaultComparer<'T>.Instance) + +[] +let binarySearchRangeWithComparer (comparer : IComparer<'T>) index count (value : 'T) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + ListRanges.search (nameof index) index (nameof count) count list.Count + list.BinarySearch (index, count, value, comparer) + +[] +let binarySearchWithComparer (comparer : IComparer<'T>) (value : 'T) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + list.BinarySearch (value, comparer) + +[] +let contains (value : 'T) (source : IndexedList<'T>) = + checkNotNull (nameof source) source + // F# equality with the value on the left, as List.contains compares + source.Exists (fun item -> value = item) + +[] +let exists (predicate : 'T -> bool) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + list.Exists (Predicate predicate) + +// exists2, forall2 and compareWith can decide before the end, so they walk the two lists with their enumerators and read +// the elements up to the pair that decides. ListArrays copies both lists first, which takes O(n) when the first pair +// decides (about 4 ms and 800 KB against 0.006 ms and nothing for two lists of 100 000 integers), and an enumerator +// reached through an interface takes more than twice as long as the copy on a walk to the end, so the struct +// enumerators of the lists are used here. The lengths are checked as ListArrays checks them (ADR 0001, D6). + +[] +let exists2 predicate (list1 : IndexedList<'T1>) (list2 : IndexedList<'T2>) = + checkNotNull (nameof list1) list1 + checkNotNull (nameof list2) list2 + let mutable enumerator1 = list1.GetEnumerator () + let mutable enumerator2 = list2.GetEnumerator () + + try + let mutable found = false + + while not found + && enumerator1.MoveNext () + && enumerator2.MoveNext () do + found <- predicate enumerator1.Current enumerator2.Current + + // List.exists2 compares the lengths only when no pair of the common prefix satisfies the predicate + if not found then + ListChecks.exists2 (nameof list2) list1.Count list2.Count + + found + finally + enumerator1.Dispose () + enumerator2.Dispose () + +[] +let find (predicate : 'T -> bool) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + let index = list.FindIndex (Predicate predicate) + if index < 0 then Raise.indexNotFound () else list[index] + +[] +let findBack (predicate : 'T -> bool) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + let index = list.FindLastIndex (Predicate predicate) + if index < 0 then Raise.indexNotFound () else list[index] + +[] +let findIndex (predicate : 'T -> bool) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + let index = list.FindIndex (Predicate predicate) + if index < 0 then Raise.indexNotFound () else index + +[] +let findIndexBack (predicate : 'T -> bool) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + let index = list.FindLastIndex (Predicate predicate) + if index < 0 then Raise.indexNotFound () else index + +[] +let forall (predicate : 'T -> bool) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + list.TrueForAll (Predicate predicate) + +[] +let forall2 predicate (list1 : IndexedList<'T1>) (list2 : IndexedList<'T2>) = + checkNotNull (nameof list1) list1 + checkNotNull (nameof list2) list2 + let mutable enumerator1 = list1.GetEnumerator () + let mutable enumerator2 = list2.GetEnumerator () + + try + let mutable holds = true + + while holds && enumerator1.MoveNext () && enumerator2.MoveNext () do + holds <- predicate enumerator1.Current enumerator2.Current + + // List.forall2 compares the lengths only when every pair of the common prefix satisfies the predicate + if holds then + ListChecks.sameLengths (nameof list1) list1.Count (nameof list2) list2.Count + + holds + finally + enumerator1.Dispose () + enumerator2.Dispose () + +[] +let indexOf (value : 'T) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + list.IndexOf value + +[] +let indexOfRange startIndex count (value : 'T) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + ListRanges.forward (nameof startIndex) startIndex (nameof count) count (nameof count) list.Count + list.IndexOf (value, startIndex, count, EqualityComparer<'T>.Default) + +[] +let indexOfRangeWith (comparer : IEqualityComparer<'T>) startIndex count (value : 'T) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + ListRanges.forward (nameof startIndex) startIndex (nameof count) count (nameof count) list.Count + list.IndexOf (value, startIndex, count, comparer) + +[] +let indexOfWith (comparer : IEqualityComparer<'T>) (value : 'T) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + list.IndexOf (value, 0, list.Count, comparer) + +[] +let lastIndexOf (value : 'T) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + ImmutableList.LastIndexOf (list, value) + +[] +let lastIndexOfRange startIndex count (value : 'T) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + ListRanges.backward (nameof startIndex) startIndex (nameof count) count list.Count + + // An empty range has no equal element; the member rejects a start index of Int32.MaxValue for it + if count = 0 then + -1 + else + list.LastIndexOf (value, startIndex, count, EqualityComparer<'T>.Default) + +[] +let lastIndexOfRangeWith (comparer : IEqualityComparer<'T>) startIndex count (value : 'T) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + ListRanges.backward (nameof startIndex) startIndex (nameof count) count list.Count + + if count = 0 then + -1 + else + list.LastIndexOf (value, startIndex, count, comparer) + +[] +let lastIndexOfWith (comparer : IEqualityComparer<'T>) (value : 'T) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + ImmutableList.LastIndexOf (list, value, comparer) + +[] +let pick (chooser : 'T -> 'U voption) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + + match firstChoice chooser list with + | ValueSome value -> value + | ValueNone -> Raise.indexNotFound () + +[] +let tryFind (predicate : 'T -> bool) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + let index = list.FindIndex (Predicate predicate) + if index < 0 then ValueNone else ValueSome list[index] + +[] +let tryFindBack (predicate : 'T -> bool) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + let index = list.FindLastIndex (Predicate predicate) + if index < 0 then ValueNone else ValueSome list[index] + +[] +let tryFindIndex (predicate : 'T -> bool) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + let index = list.FindIndex (Predicate predicate) + if index < 0 then ValueNone else ValueSome index + +[] +let tryFindIndexBack (predicate : 'T -> bool) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + let index = list.FindLastIndex (Predicate predicate) + if index < 0 then ValueNone else ValueSome index + +[] +let tryFindIndexBackRange startIndex count (predicate : 'T -> bool) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + ListRanges.backward (nameof startIndex) startIndex (nameof count) count list.Count + + // An empty range has no element that satisfies the predicate; the member rejects the start index Int32.MaxValue for it + let index = + if count = 0 then + -1 + else + list.FindLastIndex (startIndex, count, Predicate predicate) + + if index < 0 then ValueNone else ValueSome index + +[] +let tryFindIndexRange startIndex count (predicate : 'T -> bool) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + ListRanges.forward (nameof startIndex) startIndex (nameof count) count (nameof count) list.Count + let index = list.FindIndex (startIndex, count, Predicate predicate) + if index < 0 then ValueNone else ValueSome index + +[] +let tryPick (chooser : 'T -> 'U voption) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + firstChoice chooser list + +// ---- Transformation ---- + +[] +let add (value : 'T) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + list.Add value + +[] +let allPairs (list1 : IndexedList<'T1>) (list2 : IndexedList<'T2>) = + // Every element of list1 meets every element of list2, so list2 is read again for each of them. Two snapshots read + // each list once and then index arrays: against an enumerator of list2 for each element of list1 they took 1.4 to 2.5 + // times less time for 300 x 300, 1000 x 1000, 10 x 100 000 and 100 000 x 10 elements. A snapshot of list2 alone was + // between 2.3 times slower and 17 % faster, which does not pay for an algorithm of its own next to Arrays.allPairs. + Arrays.allPairs structPair (Snapshot.ofSeq (nameof list1) list1) (Snapshot.ofSeq (nameof list2) list2) + |> ofFreshArray + +[] +let append (list1 : IndexedList<'T>) (list2 : IndexedList<'T>) = + checkNotNull (nameof list1) list1 + checkNotNull (nameof list2) list2 + list1.AddRange list2 + +[] +let choose (chooser : 'T -> 'U voption) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + // The number of chosen elements is not known in advance, so they wait in a ResizeArray and CreateRange builds the + // balanced tree once. A builder rebalances its tree after every Add and took 2.3 times as long for 100 000 + // elements; there is no tree of an input to join to its tree here, as collect and concat have (see Collectors). + let chosen = ResizeArray<'U>() + + for item in list do + match chooser item with + | ValueSome value -> chosen.Add value + | ValueNone -> () + + ImmutableList.CreateRange chosen + +[] +let chunkBySize chunkSize (list : IndexedList<'T>) = + checkNotNull (nameof list) list + ListChecks.positive (nameof chunkSize) chunkSize + + if list.IsEmpty then + ImmutableList>.Empty + elif chunkSize >= list.Count then + // The only chunk is the list itself + ImmutableList.Create> list + else + // One copy of the list and one tree for each chunk. Reading each chunk from the tree, with GetRange, took 1.7 to + // 2.2 times as long for chunks of 2, 100 and 10 000 elements of a list of a million. The chunks are mapped to + // indexed lists in a sequence that ofSeq builds the result from, as the BCL copies a sequence into a buffer anyway, + // and an array of the chunks in between is no faster. + Array.chunkBySize chunkSize (Snapshot.ofSeq (nameof list) list) + |> Seq.map ofFreshArray + |> ofSeq + +[] +let collect (mapping : 'T -> IndexedList<'U>) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + // The collector copies the short results and joins the long ones to its tree as they are (see Collectors) + let collector = Collectors.ListCollector<'U>() + + for item in list do + collector.Add (mapping item) + + collector.ToImmutable () + +[] +let concat (lists : seq>) = + checkNotNullOrDefaultSeq (nameof lists) lists + // The collector copies the short lists and joins the long ones to its tree as they are (see Collectors) + let collector = Collectors.ListCollector<'T>() + + for list in lists do + // A null element is a null input, as a default element is for FlatList.concat (ADR 0001, D6) + checkNotNull (nameof lists) list + collector.Add list + + collector.ToImmutable () + +[] +let cons (value : 'T) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + list.Insert (0, value) + +[] +let filter (predicate : 'T -> bool) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + list.FindAll (Predicate predicate) + +[] +let indexed (list : IndexedList<'T>) = + Arrays.indexed structPair (Snapshot.ofSeq (nameof list) list) + |> ofFreshArray + +[] +let insertAt index (value : 'T) (source : IndexedList<'T>) = + checkNotNull (nameof source) source + ListChecks.insertAt (nameof index) index source.Count + source.Insert (index, value) + +[] +let insertManyAt index (values : seq<'T>) (source : IndexedList<'T>) = + checkNotNull (nameof source) source + // List checks the index before it reads the values + ListChecks.insertAt (nameof index) index source.Count + checkNotNullOrDefaultSeq (nameof values) values + source.InsertRange (index, values) + +[] +let map (mapping : 'T -> 'U) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + list.ConvertAll (Func<'T, 'U> mapping) + +[] +let map2 (mapping : 'T1 -> 'T2 -> 'U) (list1 : IndexedList<'T1>) (list2 : IndexedList<'T2>) = + ListArrays.map2 + (nameof list1) + (nameof list2) + mapping + (Snapshot.ofSeq (nameof list1) list1) + (Snapshot.ofSeq (nameof list2) list2) + |> ofFreshArray + +[] +let map3 (mapping : 'T1 -> 'T2 -> 'T3 -> 'U) (list1 : IndexedList<'T1>) (list2 : IndexedList<'T2>) (list3 : IndexedList<'T3>) = + ListArrays.map3 + (nameof list1) + (nameof list2) + (nameof list3) + mapping + (Snapshot.ofSeq (nameof list1) list1) + (Snapshot.ofSeq (nameof list2) list2) + (Snapshot.ofSeq (nameof list3) list3) + |> ofFreshArray + +[] +let mapi (mapping : int -> 'T -> 'U) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + let results = Array.zeroCreate list.Count + let mutable index = 0 + + for item in list do + results[index] <- mapping index item + index <- index + 1 + + ofFreshArray results + +[] +let mapi2 (mapping : int -> 'T1 -> 'T2 -> 'U) (list1 : IndexedList<'T1>) (list2 : IndexedList<'T2>) = + ListArrays.mapi2 + (nameof list1) + (nameof list2) + mapping + (Snapshot.ofSeq (nameof list1) list1) + (Snapshot.ofSeq (nameof list2) list2) + |> ofFreshArray + +[] +let pairwise (list : IndexedList<'T>) = + Arrays.pairwise structPair (Snapshot.ofSeq (nameof list) list) + |> ofFreshArray + +[] +let permute indexMap (list : IndexedList<'T>) = + // List.permute applies the algorithm of Array.permute to a copy of the list, with the same checks + Array.permute indexMap (Snapshot.ofSeq (nameof list) list) + |> ofFreshArray + +[] +let remove (item : 'T) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + list.Remove item + +[] +let removeAt index (source : IndexedList<'T>) = + checkNotNull (nameof source) source + ListChecks.removeAt (nameof index) index source.Count + source.RemoveAt index + +[] +let removeMany (items : seq<'T>) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + // The member names null items as this function does, but it fails while it enumerates a default flat list + checkNotNullOrDefaultSeq (nameof items) items + list.RemoveRange items + +[] +let removeManyAt index count (source : IndexedList<'T>) = + checkNotNull (nameof source) source + ListChecks.removeManyAt (nameof index) index count source.Count + // List walks the first index + count elements, so it removes nothing for a count that is not positive or for a sum + // that overflows to a negative number + if count <= 0 || index + count < 0 then + source + else + source.RemoveRange (index, count) + +[] +let removeManyWith (comparer : IEqualityComparer<'T>) (items : seq<'T>) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + checkNotNullOrDefaultSeq (nameof items) items + list.RemoveRange (items, comparer) + +[] +let removeWith (comparer : IEqualityComparer<'T>) (item : 'T) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + list.Remove (item, comparer) + +[] +let replace (oldValue : 'T) (newValue : 'T) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + list.Replace (oldValue, newValue) + +[] +let replaceWith (comparer : IEqualityComparer<'T>) (oldValue : 'T) (newValue : 'T) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + list.Replace (oldValue, newValue, comparer) + +[] +let rev (list : IndexedList<'T>) = + // A copy reversed in place and a new balanced tree are about 8 times faster than ImmutableList.Reverse at 100 000 + // integers (about 3 ms against 23 ms), and the copy is the only extra allocation (ADR 0001, D5); the copy is fresh, + // so nobody else sees it change + let array = Snapshot.ofSeq (nameof list) list + Array.Reverse array + ofFreshArray array + +[] +let revRange index count (list : IndexedList<'T>) = + checkNotNull (nameof list) list + // ImmutableList.Reverse(Int32, Int32) names the index for a range beyond the end + ListRanges.forward (nameof index) index (nameof count) count (nameof index) list.Count + list.Reverse (index, count) + +[] +let transpose (lists : seq>) = + checkNotNullOrDefaultSeq (nameof lists) lists + + let arrays = + lists + |> Seq.map (fun list -> Snapshot.ofSeq (nameof lists) list) + |> Seq.toArray + + // List.transpose names the lists "list.[0]", "list.[1]", …; once their lengths agree, Array.transpose agrees with it + ListChecks.transpose "list" (arrays |> Array.map _.Length) + + Array.transpose arrays |> Seq.map ofFreshArray |> ofSeq + +[] +let unzip (list : IndexedList) = + let struct (first, second) = Arrays.unzip id (Snapshot.ofSeq (nameof list) list) + struct (ofFreshArray first, ofFreshArray second) + +[] +let unzip3 (list : IndexedList) = + let struct (first, second, third) = Arrays.unzip3 id (Snapshot.ofSeq (nameof list) list) + struct (ofFreshArray first, ofFreshArray second, ofFreshArray third) + +[] +let updateAt index (value : 'T) (source : IndexedList<'T>) = + checkNotNull (nameof source) source + ListChecks.removeAt (nameof index) index source.Count + source.SetItem (index, value) + +[] +let where (predicate : 'T -> bool) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + list.FindAll (Predicate predicate) + +[] +let windowed windowSize (list : IndexedList<'T>) = + checkNotNull (nameof list) list + ListChecks.positive (nameof windowSize) windowSize + + if windowSize > list.Count then + ImmutableList>.Empty + elif windowSize = list.Count then + // The only window is the list itself + ImmutableList.Create> list + else + // One copy of the list and one tree for each window. Reading each window from the tree, with GetRange, took 2.1 + // and 4.5 times as long for windows of 3 and 100 elements of a list of 100 000. + Array.windowed windowSize (Snapshot.ofSeq (nameof list) list) + |> Seq.map ofFreshArray + |> ofSeq + +[] +let zip (list1 : IndexedList<'T1>) (list2 : IndexedList<'T2>) = + checkNotNull (nameof list1) list1 + checkNotNull (nameof list2) list2 + ListChecks.sameLengths (nameof list1) list1.Count (nameof list2) list2.Count + + Arrays.zip structPair (Snapshot.ofSeq (nameof list1) list1) (Snapshot.ofSeq (nameof list2) list2) + |> ofFreshArray + +[] +let zip3 (list1 : IndexedList<'T1>) (list2 : IndexedList<'T2>) (list3 : IndexedList<'T3>) = + checkNotNull (nameof list1) list1 + checkNotNull (nameof list2) list2 + checkNotNull (nameof list3) list3 + ListChecks.zip3 (nameof list1) list1.Count (nameof list2) list2.Count (nameof list3) list3.Count + + Arrays.zip3 + structTriple + (Snapshot.ofSeq (nameof list1) list1) + (Snapshot.ofSeq (nameof list2) list2) + (Snapshot.ofSeq (nameof list3) list3) + |> ofFreshArray + +// ---- Folding ---- + +// The inline functions can use public members only, so they check the list themselves; the empty list throws what +// List.average and List.averageBy throw, with the name "source" +[] +let inline average (list : IndexedList< ^T >) : ^T = + if obj.ReferenceEquals (list, null) then + nullArg (nameof list) + + if list.IsEmpty then + invalidArg "source" ErrorStrings.InputSequenceEmpty + + let mutable sum = LanguagePrimitives.GenericZero< ^T> + + for item in list do + sum <- Checked.(+) sum item + + LanguagePrimitives.DivideByInt< ^T> sum list.Count + +[] +let inline averageBy ([] projection : 'T -> ^U) (list : IndexedList<'T>) : ^U = + if obj.ReferenceEquals (list, null) then + nullArg (nameof list) + + if list.IsEmpty then + invalidArg "source" ErrorStrings.InputSequenceEmpty + + let mutable sum = LanguagePrimitives.GenericZero< ^U> + + for item in list do + sum <- Checked.(+) sum (projection item) + + LanguagePrimitives.DivideByInt< ^U> sum list.Count + +[] +let compareWith (comparer : 'T -> 'T -> int) (list1 : IndexedList<'T>) (list2 : IndexedList<'T>) = + checkNotNull (nameof list1) list1 + checkNotNull (nameof list2) list2 + let mutable enumerator1 = list1.GetEnumerator () + let mutable enumerator2 = list2.GetEnumerator () + + try + let mutable result = 0 + + while result = 0 + && enumerator1.MoveNext () + && enumerator2.MoveNext () do + result <- comparer enumerator1.Current enumerator2.Current + + // As List.compareWith compares: the first difference, then the lengths + if result <> 0 then result + elif list1.Count < list2.Count then -1 + elif list1.Count > list2.Count then 1 + else 0 + finally + enumerator1.Dispose () + enumerator2.Dispose () + +[] +let fold (folder : 'State -> 'T -> 'State) (state : 'State) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + let mutable state = state + + for item in list do + state <- folder state item + + state + +[] +let fold2 (folder : 'State -> 'T1 -> 'T2 -> 'State) (state : 'State) (list1 : IndexedList<'T1>) (list2 : IndexedList<'T2>) = + ListArrays.fold2 + (nameof list1) + (nameof list2) + folder + state + (Snapshot.ofSeq (nameof list1) list1) + (Snapshot.ofSeq (nameof list2) list2) + +[] +let foldBack (folder : 'T -> 'State -> 'State) (list : IndexedList<'T>) (state : 'State) = + Array.foldBack folder (Snapshot.ofSeq (nameof list) list) state + +[] +let foldBack2 (folder : 'T1 -> 'T2 -> 'State -> 'State) (list1 : IndexedList<'T1>) (list2 : IndexedList<'T2>) (state : 'State) = + checkNotNull (nameof list1) list1 + checkNotNull (nameof list2) list2 + // List.foldBack2 checks the lengths before it calls the folder + ListChecks.foldBack2 (nameof list1) list1.Count (nameof list2) list2.Count + Array.foldBack2 folder (Snapshot.ofSeq (nameof list1) list1) (Snapshot.ofSeq (nameof list2) list2) state + +[] +let iter (action : 'T -> unit) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + + // ForEach calls a delegate for every element; the loop calls the function directly + for item in list do + action item + +[] +let iter2 (action : 'T1 -> 'T2 -> unit) (list1 : IndexedList<'T1>) (list2 : IndexedList<'T2>) = + ListArrays.iter2 + (nameof list1) + (nameof list2) + action + (Snapshot.ofSeq (nameof list1) list1) + (Snapshot.ofSeq (nameof list2) list2) + +[] +let iteri (action : int -> 'T -> unit) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + let mutable index = 0 + + for item in list do + action index item + index <- index + 1 + +[] +let iteri2 (action : int -> 'T1 -> 'T2 -> unit) (list1 : IndexedList<'T1>) (list2 : IndexedList<'T2>) = + ListArrays.iteri2 + (nameof list1) + (nameof list2) + action + (Snapshot.ofSeq (nameof list1) list1) + (Snapshot.ofSeq (nameof list2) list2) + +[] +let mapFold (mapping : 'State -> 'T -> struct ('Result * 'State)) (state : 'State) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + let results = Array.zeroCreate list.Count + let mutable state = state + let mutable index = 0 + + for item in list do + let struct (result, next) = mapping state item + results[index] <- result + state <- next + index <- index + 1 + + struct (ofFreshArray results, state) + +[] +let mapFoldBack (mapping : 'T -> 'State -> struct ('Result * 'State)) (list : IndexedList<'T>) (state : 'State) = + let source = Snapshot.ofSeq (nameof list) list + 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 (ofFreshArray results, state) + +// max, maxBy, min and minBy are inline, as the ones of List are: the comparison is compiled for the type of the elements +// at the call site. Compiled into a generic function, Seq.max and the others compare through the generic operator, which +// boxes the elements of a value type in every comparison: 4.8 MB and three times the time for 100 000 integers. As sum and +// average, they check the list themselves, because an inline function can use public members only. They compare as the +// List functions do: a later element replaces the earlier one only when it is greater, or smaller, and the projection runs +// once for each element. + +[] +let inline max (list : IndexedList<'T>) : 'T = + if obj.ReferenceEquals (list, null) then + nullArg (nameof list) + + if list.IsEmpty then + invalidArg (nameof list) ErrorStrings.InputSequenceEmpty + + let mutable first = true + let mutable greatest = Unchecked.defaultof<'T> + + for item in list do + if first then + greatest <- item + first <- false + elif item > greatest then + greatest <- item + + greatest + +[] +let inline maxBy ([] projection : 'T -> 'U) (list : IndexedList<'T>) : 'T = + if obj.ReferenceEquals (list, null) then + nullArg (nameof list) + + if list.IsEmpty then + invalidArg (nameof list) ErrorStrings.InputSequenceEmpty + + let mutable first = true + let mutable greatest = Unchecked.defaultof<'T> + let mutable greatestKey = Unchecked.defaultof<'U> + + for item in list do + let key = projection item + + if first then + greatest <- item + greatestKey <- key + first <- false + elif key > greatestKey then + greatest <- item + greatestKey <- key + + greatest + +[] +let inline min (list : IndexedList<'T>) : 'T = + if obj.ReferenceEquals (list, null) then + nullArg (nameof list) + + if list.IsEmpty then + invalidArg (nameof list) ErrorStrings.InputSequenceEmpty + + let mutable first = true + let mutable smallest = Unchecked.defaultof<'T> + + for item in list do + if first then + smallest <- item + first <- false + elif item < smallest then + smallest <- item + + smallest + +[] +let inline minBy ([] projection : 'T -> 'U) (list : IndexedList<'T>) : 'T = + if obj.ReferenceEquals (list, null) then + nullArg (nameof list) + + if list.IsEmpty then + invalidArg (nameof list) ErrorStrings.InputSequenceEmpty + + let mutable first = true + let mutable smallest = Unchecked.defaultof<'T> + let mutable smallestKey = Unchecked.defaultof<'U> + + for item in list do + let key = projection item + + if first then + smallest <- item + smallestKey <- key + first <- false + elif key < smallestKey then + smallest <- item + smallestKey <- key + + smallest + +[] +let reduce (reduction : 'T -> 'T -> 'T) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + ListChecks.notEmpty (nameof list) list.Count + Seq.reduce reduction list + +[] +let reduceBack (reduction : 'T -> 'T -> 'T) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + ListChecks.notEmpty (nameof list) list.Count + Array.reduceBack reduction (Snapshot.ofSeq (nameof list) list) + +[] +let scan (folder : 'State -> 'T -> 'State) (state : 'State) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + let states = Array.zeroCreate (list.Count + 1) + states[0] <- state + let mutable index = 0 + + for item in list do + states[index + 1] <- folder states[index] item + index <- index + 1 + + ofFreshArray states + +[] +let scanBack (folder : 'T -> 'State -> 'State) (list : IndexedList<'T>) (state : 'State) = + Array.scanBack folder (Snapshot.ofSeq (nameof list) list) state + |> ofFreshArray + +[] +let inline sum (list : IndexedList< ^T >) : ^T = + if obj.ReferenceEquals (list, null) then + nullArg (nameof list) + + let mutable sum = LanguagePrimitives.GenericZero< ^T> + + for item in list do + sum <- Checked.(+) sum item + + sum + +[] +let inline sumBy ([] projection : 'T -> ^U) (list : IndexedList<'T>) : ^U = + if obj.ReferenceEquals (list, null) then + nullArg (nameof list) + + let mutable sum = LanguagePrimitives.GenericZero< ^U> + + for item in list do + sum <- Checked.(+) sum (projection item) + + sum + +// ---- Partitioning and grouping ---- + +[] +let countBy (projection : 'T -> 'Key) (list : IndexedList<'T>) = + Arrays.countBy structPair projection (Snapshot.ofSeq (nameof list) list) + |> ofFreshArray + +[] +let distinct (list : IndexedList<'T>) = + checkNotNull (nameof list) list + ImmutableList.CreateRange (list.Distinct HashIdentity.Structural<'T>) + +[] +let distinctBy (projection : 'T -> 'Key) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + ImmutableList.CreateRange (list.DistinctBy (Func<'T, 'Key> projection, HashIdentity.Structural<'Key>)) + +[] +let except (itemsToExclude : seq<'T>) (list : IndexedList<'T>) = + // List.except checks the items to exclude first and does not read them for an empty list + checkNotNullOrDefaultSeq (nameof itemsToExclude) itemsToExclude + checkNotNull (nameof list) list + + if list.IsEmpty then + list + else + ImmutableList.CreateRange (list.Except (itemsToExclude, HashIdentity.Structural<'T>)) + +[] +let groupBy (projection : 'T -> 'Key) (list : IndexedList<'T>) = + Arrays.groupBy (fun key items -> struct (key, ofFreshArray items)) projection (Snapshot.ofSeq (nameof list) list) + |> ofFreshArray + +[] +let partition (predicate : 'T -> bool) (list : IndexedList<'T>) = + let accepted, rejected = Array.partition predicate (Snapshot.ofSeq (nameof list) list) + struct (ofFreshArray accepted, ofFreshArray rejected) + +[] +let partitionWith (partitioner : 'T -> Choice<'T1, 'T2>) (list : IndexedList<'T>) = + let first, second = Array.partitionWith partitioner (Snapshot.ofSeq (nameof list) list) + struct (ofFreshArray first, ofFreshArray second) + +[] +let skip count (list : IndexedList<'T>) = + checkNotNull (nameof list) list + ListChecks.skip (nameof count) count list.Count + let count = Operators.max count 0 + range list count (list.Count - count) + +[] +let skipLast count (list : IndexedList<'T>) = + checkNotNull (nameof list) list + // The checks of List.skip + ListChecks.skip (nameof count) count list.Count + range list 0 (list.Count - Operators.max count 0) + +[] +let skipWhile (predicate : 'T -> bool) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + let index = list.FindIndex (fun item -> not (predicate item)) + if index < 0 then + empty + else + range list index (list.Count - index) + +[] +let splitAt index (list : IndexedList<'T>) = + checkNotNull (nameof list) list + ListChecks.splitAt (nameof index) index list.Count + struct (range list 0 index, range list index (list.Count - index)) + +[] +let splitInto count (list : IndexedList<'T>) = + checkNotNull (nameof list) list + ListChecks.positive (nameof count) count + + Array.splitInto count (Snapshot.ofSeq (nameof list) list) + |> Seq.map ofFreshArray + |> ofSeq + +[] +let sub (list : IndexedList<'T>) startIndex count = + checkNotNull (nameof list) list + + // The checks of Array.sub, which FlatList.sub follows: GetRange throws ArgumentOutOfRangeException for a negative + // start index or count too + if startIndex < 0 then + Raise.invalidArgInputMustBeNonNegative (nameof startIndex) startIndex + + if count < 0 then + Raise.invalidArgInputMustBeNonNegative (nameof count) count + + // Array.sub adds the start index and the count, which can overflow; the difference cannot, because the start index is + // not negative + if count > list.Count - startIndex then + Raise.invalidArgOutOfRange (nameof count) count "list.Length" list.Count + + range list startIndex count + +[] +let take count (list : IndexedList<'T>) = + checkNotNull (nameof list) list + ListChecks.take (nameof count) count list.Count + range list 0 count + +[] +let takeLast count (list : IndexedList<'T>) = + checkNotNull (nameof list) list + // The checks of List.take + ListChecks.take (nameof count) count list.Count + range list (list.Count - count) count + +[] +let takeWhile (predicate : 'T -> bool) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + let index = list.FindIndex (fun item -> not (predicate item)) + if index < 0 then list else range list 0 index + +[] +let truncate count (list : IndexedList<'T>) = + checkNotNull (nameof list) list + range list 0 (Operators.max 0 (Operators.min count list.Count)) + +// ---- Sorting ---- + +// The sorts are stable, as the sorts of List are, and ImmutableList.Sort is not (ADR 0001, D5). The functions that compare +// the elements, or that call the projection in the comparison as List.sortByDescending does, sort with the Seq functions +// of FSharp.Core, which share the stable sort of List: the same comparisons run in Array.Sort, which wraps the exception +// of a comparison in an InvalidOperationException (D6). Seq.sortDescending and Seq.sortByDescending are inline and box the +// elements in every comparison once they are compiled into a generic function, so the two descending sorts call +// Seq.sortWith through DescendingSorts, with the cached comparer. sortBy and sortWithComparer sort with LINQ, which +// computes the keys first, as List.sortBy does, and wraps the exception of a comparison as Array.Sort does. A list of one +// element is returned without a call of the projection or the comparer, as List returns it. + +[] +let sort (list : IndexedList<'T>) = + checkNotNull (nameof list) list + if list.Count <= 1 then + list + else + ImmutableList.CreateRange (Seq.sort list) + +[] +let sortBy (projection : 'T -> 'Key) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + + if list.Count <= 1 then + list + else + list.OrderBy(Func<'T, 'Key> projection, DefaultComparer<'Key>.Instance).ToArray() + |> ofFreshArray + +[] +let sortByDescending (projection : 'T -> 'Key) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + + if list.Count <= 1 then + list + else + ImmutableList.CreateRange (DescendingSorts.sortByDescending projection list) + +[] +let sortDescending (list : IndexedList<'T>) = + checkNotNull (nameof list) list + if list.Count <= 1 then + list + else + ImmutableList.CreateRange (DescendingSorts.sortDescending list) + +[] +let sortRange index count (list : IndexedList<'T>) = sortRangeStably DefaultComparer<'T>.Instance index count list + +[] +let sortRangeWith (comparison : 'T -> 'T -> int) index count (list : IndexedList<'T>) = + sortRangeStably (ComparisonIdentity.FromFunction comparison) index count list + +[] +let sortRangeWithComparer (comparer : IComparer<'T>) index count (list : IndexedList<'T>) = + sortRangeStably comparer index count list + +[] +let sortWith (comparer : 'T -> 'T -> int) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + if list.Count <= 1 then + list + else + ImmutableList.CreateRange (Seq.sortWith comparer list) + +[] +let sortWithComparer (comparer : IComparer<'T>) (list : IndexedList<'T>) = + checkNotNull (nameof list) list + + if list.Count <= 1 then + list + else + list.Order(comparer).ToArray() |> ofFreshArray + +// ---- Random ---- + +// The functions draw the random numbers as the List functions do; the ones that build an indexed list apply the Array +// function to a snapshot after the List checks + +[] +let randomChoice (source : IndexedList<'T>) = + checkNotNull (nameof source) source + ListChecks.notEmptySequence (nameof source) source.Count + source[Random.Shared.Next (0, source.Count)] + +[] +let randomChoiceBy (randomizer : unit -> float) (source : IndexedList<'T>) = + checkNotNull (nameof source) source + ListChecks.notEmptySequence (nameof source) source.Count + source[ListChecks.randomIndex (nameof randomizer) randomizer source.Count] + +[] +let randomChoiceWith (random : Random) (source : IndexedList<'T>) = + checkNotNull (nameof random) random + checkNotNull (nameof source) source + ListChecks.notEmptySequence (nameof source) source.Count + source[random.Next (0, source.Count)] + +[] +let randomChoices count (source : IndexedList<'T>) = + checkNotNull (nameof source) source + ListChecks.randomChoices (nameof count) count (nameof source) source.Count + + Array.randomChoices count (Snapshot.ofSeq (nameof source) source) + |> ofFreshArray + +[] +let randomChoicesBy (randomizer : unit -> float) count (source : IndexedList<'T>) = + checkNotNull (nameof source) source + ListChecks.randomChoices (nameof count) count (nameof source) source.Count + + Array.randomChoicesBy randomizer count (Snapshot.ofSeq (nameof source) source) + |> ofFreshArray + +[] +let randomChoicesWith (random : Random) count (source : IndexedList<'T>) = + checkNotNull (nameof random) random + checkNotNull (nameof source) source + ListChecks.randomChoices (nameof count) count (nameof source) source.Count + + Array.randomChoicesWith random count (Snapshot.ofSeq (nameof source) source) + |> ofFreshArray + +[] +let randomSample count (source : IndexedList<'T>) = + checkNotNull (nameof source) source + ListChecks.randomSample (nameof count) count (nameof source) source.Count + + Array.randomSample count (Snapshot.ofSeq (nameof source) source) + |> ofFreshArray + +[] +let randomSampleBy (randomizer : unit -> float) count (source : IndexedList<'T>) = + checkNotNull (nameof source) source + ListChecks.randomSample (nameof count) count (nameof source) source.Count + + Array.randomSampleBy randomizer count (Snapshot.ofSeq (nameof source) source) + |> ofFreshArray + +[] +let randomSampleWith (random : Random) count (source : IndexedList<'T>) = + checkNotNull (nameof random) random + checkNotNull (nameof source) source + ListChecks.randomSample (nameof count) count (nameof source) source.Count + + Array.randomSampleWith random count (Snapshot.ofSeq (nameof source) source) + |> ofFreshArray + +[] +let randomShuffle (source : IndexedList<'T>) = + let array = Snapshot.ofSeq (nameof source) source + Array.randomShuffleInPlace array + ofFreshArray array + +[] +let randomShuffleBy (randomizer : unit -> float) (source : IndexedList<'T>) = + let array = Snapshot.ofSeq (nameof source) source + Array.randomShuffleInPlaceBy randomizer array + ofFreshArray array + +[] +let randomShuffleWith (random : Random) (source : IndexedList<'T>) = + checkNotNull (nameof random) random + let array = Snapshot.ofSeq (nameof source) source + Array.randomShuffleInPlaceWith random array + ofFreshArray array + +// ---- Conversion ---- + +[] +let blit (source : IndexedList<'T>) sourceIndex (target : 'T array) targetIndex count = + checkNotNull (nameof source) source + checkNotNull (nameof target) target + + // The checks of ImmutableList.CopyTo, with the names of the parameters of this function (ADR 0001, D6) + ListRanges.blit + (nameof sourceIndex) + sourceIndex + (nameof count) + count + source.Count + (nameof targetIndex) + targetIndex + target.Length + source.CopyTo (sourceIndex, target, targetIndex, count) + +[] +let toArray (list : IndexedList<'T>) = Snapshot.ofSeq (nameof list) list + +[] +let toList (list : IndexedList<'T>) = + checkNotNull (nameof list) list + List.ofSeq list + +[] +let toSeq (list : IndexedList<'T>) = + checkNotNull (nameof list) list + list :> seq<'T> diff --git a/src/FSharp.Collections.Immutable/IndexedList/IndexedList.fsi b/src/FSharp.Collections.Immutable/IndexedList/IndexedList.fsi new file mode 100644 index 00000000..6d2fbbce --- /dev/null +++ b/src/FSharp.Collections.Immutable/IndexedList/IndexedList.fsi @@ -0,0 +1,2253 @@ +/// +/// Functions for the indexed list, the immutable list . +/// +/// +/// +/// The module mirrors : each function has the semantics, the +/// parameter names, the argument checks and the exceptions of the function with the same name in that module (ADR 0001), +/// and its sorting functions are stable. Functions that FSharp.Core lacks wrap a member of +/// or follow the function of that module that they resemble. +/// +/// +/// Every function throws for a indexed list, as +/// does for a array. A function that +/// takes a sequence throws it for a sequence and for a default +/// that is given as a sequence, which would fail with +/// when the function enumerates it. +/// +/// +/// 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 FSharp.Collections.Immutable.IndexedList + +open System +open System.Collections.Generic +open System.Collections.Immutable + +// ---- Creation ---- + +/// +/// The empty indexed list. +/// +/// +/// O(1). The value is . +/// +[] +[] +val empty<'T> : IndexedList<'T> + +/// Creates an indexed list from a function of the index. +/// The length of the indexed list. +/// The function that computes the element at each index, called in index order. +/// The created indexed list. +/// Thrown when is negative. +/// O(n). +[] +val init : length : int -> initializer : (int -> 'T) -> IndexedList<'T> + +/// +/// Creates an indexed list from a copy of an array. +/// +/// The input array. +/// The indexed list of the elements of the array. +/// Thrown when is . +/// +/// O(n). Later changes to the array do not change the indexed list. +/// +[] +val ofArray : array : 'T array -> IndexedList<'T> + +/// Creates an indexed list from a list. +/// The input list. +/// The indexed list of the elements of the list. +/// O(n). +[] +val ofList : list : 'T list -> IndexedList<'T> + +/// +/// Creates an indexed list from a copy of a read-only span. +/// +/// The input span. +/// The indexed list of the elements of the span. +/// +/// O(n). The function wraps . +/// +[] +val ofReadOnlySpan : span : ReadOnlySpan<'T> -> IndexedList<'T> + +/// +/// Creates an indexed list from a sequence. +/// +/// The input sequence. +/// The indexed list of the elements of the sequence. +/// Thrown when is or a default flat list. +/// +/// O(n). The function wraps , +/// which returns an indexed list itself, without a copy. +/// +[] +val ofSeq : source : seq<'T> -> IndexedList<'T> + +/// +/// Creates an indexed list from a copy of a span. +/// +/// The input span. +/// The indexed list of the elements of the span. +/// +/// O(n). The function wraps . +/// +[] +val ofSpan : span : Span<'T> -> IndexedList<'T> + +/// +/// Creates an indexed list whose elements are all the given value. +/// +/// The length of the indexed list. +/// The value of every element. +/// The created indexed list. +/// Thrown when is negative. +/// +/// O(n). +/// +[] +val replicate : count : int -> initial : 'T -> IndexedList<'T> + +/// Creates an indexed list of one element. +/// The element. +/// The indexed list of one element. +/// O(1). +[] +val singleton : value : 'T -> IndexedList<'T> + +/// +/// Creates an indexed 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 indexed list. +/// +/// O(n). +/// +[] +val unfold : generator : ('State -> struct ('T * 'State) voption) -> state : 'State -> IndexedList<'T> + +// ---- Builder ---- + +/// +/// Creates an indexed list with a builder that an action fills. +/// +/// The action that adds the elements to the builder. +/// The indexed list of the elements that the action added. +/// +/// O(n log n) for an action that adds n elements one by one; the result shares the tree of the builder. +/// +[] +val build : action : (IndexedList<'T>.Builder -> unit) -> IndexedList<'T> + +/// +/// Creates an empty builder. +/// +/// An empty builder. +/// +/// O(1). The function wraps . +/// +[] +val builder : unit -> IndexedList<'T>.Builder + +/// +/// Returns the contents of a builder as an indexed list. +/// +/// The builder, which stays usable. +/// The indexed list of the elements of the builder. +/// Thrown when is . +/// +/// O(n) at most: the builder freezes the nodes that it created since it returned its contents last time, without a +/// copy. The function wraps . +/// +[] +val ofBuilder : builder : IndexedList<'T>.Builder -> IndexedList<'T> + +/// +/// Creates a builder that starts with the elements of an indexed list. +/// +/// The input indexed list, which stays unchanged. +/// The builder. +/// +/// O(1): the builder shares the tree of the indexed list until it changes. The function wraps . +/// +[] +val toBuilder : list : IndexedList<'T> -> IndexedList<'T>.Builder + +/// +/// Creates an indexed list from a builder over the elements of an indexed list, after an action changes the builder. +/// +/// The action that changes the builder. +/// The input indexed list, which stays unchanged. +/// The indexed list of the elements of the changed builder. +/// +/// O(k log n) for an action that makes k changes: the builder shares the tree of the indexed list and copies only the +/// nodes that the changes touch. +/// +[] +val update : action : (IndexedList<'T>.Builder -> unit) -> list : IndexedList<'T> -> IndexedList<'T> + +// ---- Properties ---- + +/// Returns whether the indexed list has no elements. +/// The input indexed list. +/// when the indexed list is empty. +/// O(1). +[] +val isEmpty : list : IndexedList<'T> -> bool + +/// Returns the number of elements of the indexed list. +/// The input indexed list. +/// The length of the indexed list. +/// O(1). +[] +val length : list : IndexedList<'T> -> int + +// ---- Access ---- + +/// Returns the only element of the indexed list. +/// The input indexed list. +/// The only element. +/// Thrown when the indexed list does not have exactly one element. +/// O(1). +[] +val exactlyOne : list : IndexedList<'T> -> 'T + +/// Returns the first element of the indexed list. +/// The input indexed list. +/// The first element. +/// Thrown when the indexed list is empty. +/// O(log n). +[] +val head : list : IndexedList<'T> -> 'T + +/// Returns the element at an index. +/// The index of the element. +/// The input indexed list. +/// The element at the index. +/// Thrown when the index is negative or not less than the length. +/// O(log n). +[] +val item : index : int -> list : IndexedList<'T> -> 'T + +/// Returns the last element of the indexed list. +/// The input indexed list. +/// The last element. +/// Thrown when the indexed list is empty. +/// O(log n). +[] +val last : list : IndexedList<'T> -> 'T + +/// Returns the indexed list without its first element. +/// The input indexed list. +/// The elements after the first one. +/// Thrown when the indexed list is empty. +/// O(log n). The result shares most of its tree with the input. +[] +val tail : list : IndexedList<'T> -> IndexedList<'T> + +/// +/// Returns the only element of the indexed list, or ValueNone when it does not have exactly one element. +/// +/// The input indexed list. +/// The only element, or ValueNone. +/// +/// O(1). +/// +[] +val tryExactlyOne : list : IndexedList<'T> -> 'T voption + +/// +/// Returns the first element of the indexed list, or ValueNone when it is empty. +/// +/// The input indexed list. +/// The first element, or ValueNone. +/// +/// O(log n). +/// +[] +val tryHead : list : IndexedList<'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 indexed list. +/// The element at the index, or ValueNone. +/// +/// O(log n). +/// +[] +val tryItem : index : int -> list : IndexedList<'T> -> 'T voption + +/// +/// Returns the last element of the indexed list, or ValueNone when it is empty. +/// +/// The input indexed list. +/// The last element, or ValueNone. +/// +/// O(log n). +/// +[] +val tryLast : list : IndexedList<'T> -> 'T voption + +/// +/// Returns the indexed list without its first element, or ValueNone when it is empty. +/// +/// The input indexed list. +/// The elements after the first one, or ValueNone. +/// +/// O(log n). +/// +[] +val tryTail : list : IndexedList<'T> -> IndexedList<'T> voption + +/// +/// Returns the first element and the other elements, or ValueNone when the indexed list is empty. +/// +/// The input indexed list. +/// The first element and the elements after it, or ValueNone. +/// +/// O(log n). +/// +[] +val tryUncons : list : IndexedList<'T> -> struct ('T * IndexedList<'T>) voption + +/// +/// Returns the first element and the other elements. +/// +/// The input indexed list. +/// The first element and the elements after it. +/// Thrown when the indexed list is empty. +/// +/// O(log n). The function throws what +/// throws. +/// +[] +val uncons : list : IndexedList<'T> -> struct ('T * IndexedList<'T>) + +/// +/// Matches an indexed list with a first element, and returns that element and the other elements. +/// +/// The input indexed list. +/// The first element and the elements after it, or ValueNone for an empty indexed list. +/// +/// O(log n). A struct partial pattern, so that a match allocates no option. +/// +[] +[] +val (|Cons|_|) : list : IndexedList<'T> -> struct ('T * IndexedList<'T>) voption + +/// Matches an empty indexed list. +/// The input indexed list. +/// when the indexed list is empty. +/// O(1). +[] +val (|Nil|_|) : list : IndexedList<'T> -> bool + +// ---- Search ---- + +/// +/// Searches a sorted indexed list for a value with the binary search algorithm. +/// +/// The value to search for. +/// The input indexed 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. The function wraps . +/// +[] +val binarySearch : value : 'T -> list : IndexedList<'T> -> int when 'T : comparison + +/// +/// Searches a range of a sorted indexed 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 indexed 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 of the range is larger. +/// +/// +/// Thrown when or is negative. +/// +/// Thrown when the range extends beyond the end of the indexed list. +/// +/// +/// O(log n). The function compares as +/// +/// does, and wraps +/// . +/// +/// +/// That member does not check that the range lies in the indexed list, although its documentation says that it throws +/// . The function throws, as the documentation says (ADR 0001, D14). +/// +/// +[] +val binarySearchRange : index : int -> count : int -> value : 'T -> list : IndexedList<'T> -> int when 'T : comparison + +/// +/// Searches a range of an indexed list for a value with the binary search algorithm and a given comparer. +/// +/// The comparer, in whose order the indexed list is sorted. +/// The index of the first element of the range. +/// The number of elements of the range. +/// The value to search for. +/// The input indexed list. +/// +/// 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 of the range is larger. +/// +/// +/// Thrown when or is negative. +/// +/// Thrown when the range extends beyond the end of the indexed list. +/// +/// +/// O(log n) comparisons. An exception that the comparer throws is not wrapped. The function wraps +/// . +/// +/// +/// That member does not check that the range lies in the indexed list, although its documentation says that it throws +/// . The function throws, as the documentation says (ADR 0001, D14). +/// +/// +[] +val binarySearchRangeWithComparer : + comparer : IComparer<'T> -> index : int -> count : int -> value : 'T -> list : IndexedList<'T> -> int + +/// +/// Searches a sorted indexed list for a value with the binary search algorithm and a given comparer. +/// +/// The comparer, in whose order the indexed list is sorted. +/// The value to search for. +/// The input indexed list. +/// +/// 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) comparisons. An exception that the comparer throws is not wrapped. The function wraps +/// . +/// +[] +val binarySearchWithComparer : comparer : IComparer<'T> -> value : 'T -> list : IndexedList<'T> -> int + +/// +/// Returns whether the indexed list contains an element equal to a value, in F# structural equality. +/// +/// The value to find. +/// The input indexed list. +/// when an element equals the value. +/// +/// O(n). +/// +[] +val contains : value : 'T -> source : IndexedList<'T> -> bool when 'T : equality + +/// +/// Returns whether any element satisfies the predicate. +/// +/// The function to test the elements. +/// The input indexed list. +/// when an element satisfies the predicate. +/// +/// O(n). The function wraps . +/// +[] +val exists : predicate : ('T -> bool) -> list : IndexedList<'T> -> bool + +/// +/// Returns whether any pair of elements at the same index satisfies the predicate. +/// +/// The function to test the pairs. +/// The first indexed list. +/// The second indexed list. +/// when a pair satisfies the predicate. +/// Thrown when no pair satisfies the predicate and the indexed lists have different lengths. +/// +/// O(n). The function walks the two indexed lists together and stops at the first pair that satisfies the predicate. +/// +[] +val exists2 : predicate : ('T1 -> 'T2 -> bool) -> list1 : IndexedList<'T1> -> list2 : IndexedList<'T2> -> bool + +/// Returns the first element that satisfies the predicate. +/// The function to test the elements. +/// The input indexed list. +/// The first element that satisfies the predicate. +/// Thrown when no element satisfies the predicate. +/// O(n). +[] +val find : predicate : ('T -> bool) -> list : IndexedList<'T> -> 'T + +/// Returns the last element that satisfies the predicate. +/// The function to test the elements. +/// The input indexed list. +/// The last element that satisfies the predicate. +/// Thrown when no element satisfies the predicate. +/// O(n). +[] +val findBack : predicate : ('T -> bool) -> list : IndexedList<'T> -> 'T + +/// +/// Returns the index of the first element that satisfies the predicate. +/// +/// The function to test the elements. +/// The input indexed list. +/// The index of the first element that satisfies the predicate. +/// Thrown when no element satisfies the predicate. +/// +/// O(n). The function wraps . +/// +[] +val findIndex : predicate : ('T -> bool) -> list : IndexedList<'T> -> int + +/// +/// Returns the index of the last element that satisfies the predicate. +/// +/// The function to test the elements. +/// The input indexed list. +/// The index of the last element that satisfies the predicate. +/// Thrown when no element satisfies the predicate. +/// +/// O(n). The function wraps . +/// +[] +val findIndexBack : predicate : ('T -> bool) -> list : IndexedList<'T> -> int + +/// +/// Returns whether all elements satisfy the predicate. +/// +/// The function to test the elements. +/// The input indexed list. +/// when every element satisfies the predicate. +/// +/// O(n). The function wraps . +/// +[] +val forall : predicate : ('T -> bool) -> list : IndexedList<'T> -> bool + +/// +/// Returns whether all pairs of elements at the same index satisfy the predicate. +/// +/// The function to test the pairs. +/// The first indexed list. +/// The second indexed list. +/// when every pair satisfies the predicate. +/// Thrown when every pair satisfies the predicate and the indexed lists have different lengths. +/// +/// O(n). The function walks the two indexed lists together and stops at the first pair that does not satisfy the +/// predicate. +/// +[] +val forall2 : predicate : ('T1 -> 'T2 -> bool) -> list1 : IndexedList<'T1> -> list2 : IndexedList<'T2> -> bool + +/// +/// Returns the index of the first element equal to a value, in . +/// +/// The value to find. +/// The input indexed list. +/// The index of the first equal element, or -1. +/// +/// O(n). The function wraps . +/// +[] +val indexOf : value : 'T -> list : IndexedList<'T> -> int + +/// +/// Returns the index of the first element of a range that equals a value, in +/// . +/// +/// The index of the first element of the range. +/// The number of elements of the range. +/// The value to find. +/// The input indexed list. +/// The index in the indexed list, not in the range, of the first equal element of the range, or -1. +/// +/// Thrown when or is negative, or the range extends beyond the end +/// of the indexed list. +/// +/// +/// O(log n + count). The function wraps +/// . +/// +[] +val indexOfRange : startIndex : int -> count : int -> value : 'T -> list : IndexedList<'T> -> int + +/// +/// Returns the index of the first element of a range that a given equality comparer finds equal to a value. +/// +/// The equality comparer. +/// The index of the first element of the range. +/// The number of elements of the range. +/// The value to find. +/// The input indexed list. +/// The index in the indexed list, not in the range, of the first equal element of the range, or -1. +/// +/// Thrown when or is negative, or the range extends beyond the end +/// of the indexed list. +/// +/// +/// O(log n + count). The function wraps +/// . +/// +[] +val indexOfRangeWith : + comparer : IEqualityComparer<'T> -> startIndex : int -> count : int -> value : 'T -> list : IndexedList<'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 indexed list. +/// The index of the first equal element, or -1. +/// +/// O(n). The function wraps +/// . +/// +[] +val indexOfWith : comparer : IEqualityComparer<'T> -> value : 'T -> list : IndexedList<'T> -> int + +/// +/// Returns the index of the last element equal to a value, in . +/// +/// The value to find. +/// The input indexed list. +/// The index of the last equal element, or -1. +/// +/// O(n). The function wraps +/// . +/// +[] +val lastIndexOf : value : 'T -> list : IndexedList<'T> -> int + +/// +/// Returns the index of the last element of a range that equals a value, in +/// . The search walks backward from the index of the +/// last element of the range. +/// +/// The index of the last element of the range, where the search starts. +/// The number of elements of the range, which ends at . +/// The value to find. +/// The input indexed list. +/// The index in the indexed list, not in the range, of the last equal element of the range, or -1. +/// +/// Thrown when or is negative, exceeds the +/// length, or a range of elements does not lie in the indexed list: its first element, at +/// startIndex - count + 1, is before the start, or its last element, at , is at or after +/// the length. +/// +/// +/// +/// O(log n + count). A count of 0 is an empty range, which has no equal element, for any start index. The function wraps +/// . +/// +/// +/// That member accepts a last element at or after the length and answers with an index that does not belong to the equal +/// element, and it throws a plain for a range that starts before the first +/// element. The function throws for both (ADR 0001, D14). +/// +/// +[] +val lastIndexOfRange : startIndex : int -> count : int -> value : 'T -> list : IndexedList<'T> -> int + +/// +/// Returns the index of the last element of a range that a given equality comparer finds equal to a value. The search +/// walks backward from the index of the last element of the range. +/// +/// The equality comparer. +/// The index of the last element of the range, where the search starts. +/// The number of elements of the range, which ends at . +/// The value to find. +/// The input indexed list. +/// The index in the indexed list, not in the range, of the last equal element of the range, or -1. +/// +/// Thrown when or is negative, exceeds the +/// length, or a range of elements does not lie in the indexed list: its first element, at +/// startIndex - count + 1, is before the start, or its last element, at , is at or after +/// the length. +/// +/// +/// +/// O(log n + count). A count of 0 is an empty range, which has no equal element, for any start index. The function wraps +/// . +/// +/// +/// That member accepts a last element at or after the length and answers with an index that does not belong to the equal +/// element, and it throws a plain for a range that starts before the first +/// element. The function throws for both (ADR 0001, D14). +/// +/// +[] +val lastIndexOfRangeWith : + comparer : IEqualityComparer<'T> -> startIndex : int -> count : int -> value : 'T -> list : IndexedList<'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 indexed list. +/// The index of the last equal element, or -1. +/// +/// O(n). The function wraps +/// . +/// +[] +val lastIndexOfWith : comparer : IEqualityComparer<'T> -> value : 'T -> list : IndexedList<'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 indexed list. +/// The first chosen value. +/// Thrown when the chooser returns ValueNone for every element. +/// +/// O(n). +/// +[] +val pick : chooser : ('T -> 'U voption) -> list : IndexedList<'T> -> 'U + +/// +/// Returns the first element that satisfies the predicate, or ValueNone. +/// +/// The function to test the elements. +/// The input indexed list. +/// The first element that satisfies the predicate, or ValueNone. +/// +/// O(n). +/// +[] +val tryFind : predicate : ('T -> bool) -> list : IndexedList<'T> -> 'T voption + +/// +/// Returns the last element that satisfies the predicate, or ValueNone. +/// +/// The function to test the elements. +/// The input indexed list. +/// The last element that satisfies the predicate, or ValueNone. +/// +/// O(n). +/// +[] +val tryFindBack : predicate : ('T -> bool) -> list : IndexedList<'T> -> 'T voption + +/// +/// Returns the index of the first element that satisfies the predicate, or ValueNone. +/// +/// The function to test the elements. +/// The input indexed list. +/// The index of the first element that satisfies the predicate, or ValueNone. +/// +/// O(n). The function wraps . +/// +[] +val tryFindIndex : predicate : ('T -> bool) -> list : IndexedList<'T> -> int voption + +/// +/// Returns the index of the last element that satisfies the predicate, or ValueNone. +/// +/// The function to test the elements. +/// The input indexed list. +/// The index of the last element that satisfies the predicate, or ValueNone. +/// +/// O(n). The function wraps . +/// +[] +val tryFindIndexBack : predicate : ('T -> bool) -> list : IndexedList<'T> -> int voption + +/// +/// Returns the index of the last element of a range that satisfies the predicate, or ValueNone. The search walks +/// backward from the index of the last element of the range. +/// +/// The index of the last element of the range, where the search starts. +/// The number of elements of the range, which ends at . +/// The function to test the elements. +/// The input indexed list. +/// The index in the indexed list, not in the range, of the last element of the range that satisfies the predicate, or ValueNone. +/// +/// Thrown when or is negative, exceeds the +/// length, or a range of elements does not lie in the indexed list: its first element, at +/// startIndex - count + 1, is before the start, or its last element, at , is at or after +/// the length. +/// +/// +/// +/// O(log n + count). A count of 0 is an empty range, which has no element that satisfies the predicate, for any start +/// index. The function wraps +/// . +/// +/// +/// That member does not check a negative count and answers with the index of an element for a start index at or after +/// the length. The function throws for both (ADR 0001, D14). +/// +/// +[] +val tryFindIndexBackRange : startIndex : int -> count : int -> predicate : ('T -> bool) -> list : IndexedList<'T> -> int voption + +/// +/// Returns the index of the first element of a range that satisfies the predicate, or ValueNone. +/// +/// The index of the first element of the range. +/// The number of elements of the range. +/// The function to test the elements. +/// The input indexed list. +/// The index in the indexed list, not in the range, of the first element of the range that satisfies the predicate, or ValueNone. +/// +/// Thrown when or is negative, or the range extends beyond the end +/// of the indexed list. +/// +/// +/// O(log n + count). The function wraps +/// . +/// +[] +val tryFindIndexRange : startIndex : int -> count : int -> predicate : ('T -> bool) -> list : IndexedList<'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 indexed list. +/// The first chosen value, or ValueNone. +/// +/// O(n). +/// +[] +val tryPick : chooser : ('T -> 'U voption) -> list : IndexedList<'T> -> 'U voption + +// ---- Transformation ---- + +/// +/// Returns an indexed list with a value added after the last element. +/// +/// The value to add. +/// The input indexed list. +/// An indexed list of the elements and the value. +/// +/// O(log n). The function wraps . +/// +[] +val add : value : 'T -> list : IndexedList<'T> -> IndexedList<'T> + +/// +/// Returns every element of the first indexed list paired with every element of the second one. +/// +/// The first indexed list. +/// The second indexed list. +/// The pairs, in the order of the first indexed list and then of the second one. +/// +/// O(n × m). +/// +[] +val allPairs : list1 : IndexedList<'T1> -> list2 : IndexedList<'T2> -> IndexedList + +/// +/// Returns an indexed list of the elements of the first indexed list followed by the elements of the second one. +/// +/// The first indexed list. +/// The second indexed list. +/// The concatenation. +/// +/// O(m log(n + m)). When the first indexed list is empty, the function returns the second one. The function wraps +/// . +/// +[] +val append : list1 : IndexedList<'T> -> list2 : IndexedList<'T> -> IndexedList<'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 indexed list. +/// The chosen values. +/// +/// O(n). +/// +[] +val choose : chooser : ('T -> 'U voption) -> list : IndexedList<'T> -> IndexedList<'U> + +/// Divides the indexed list into chunks of a given size. +/// The maximum size of a chunk. +/// The input indexed list. +/// The chunks; the last one can be smaller. +/// Thrown when is not positive. +/// +/// O(n). The chunk is the indexed list itself when it is as long as the indexed list. +/// +[] +val chunkBySize : chunkSize : int -> list : IndexedList<'T> -> IndexedList> + +/// +/// Concatenates the indexed lists that a function returns for the elements. +/// +/// The function that returns an indexed list for each element. +/// The input indexed list. +/// The concatenation of the returned indexed lists. +/// +/// O(n + the total length of the returned indexed lists that have fewer than 16 elements), and O(log m) for each of the +/// longer ones, where m is the length of the result: the elements of a returned indexed list of at least 16 elements are +/// not copied, the result shares its nodes. +/// +[] +val collect : mapping : ('T -> IndexedList<'U>) -> list : IndexedList<'T> -> IndexedList<'U> + +/// Concatenates a sequence of indexed lists. +/// The indexed lists. +/// The concatenation. +/// Thrown when is or a default flat list, or one of its indexed lists is . +/// +/// O(the total length of the indexed lists that have fewer than 16 elements), and O(log m) for each of the longer ones, +/// where m is the length of the result: the elements of an indexed list of at least 16 elements are not copied, the result +/// shares its nodes. +/// +[] +val concat : lists : seq> -> IndexedList<'T> + +/// +/// Returns an indexed list with a value added before the first element. +/// +/// The value to add. +/// The input indexed list. +/// An indexed list of the value and the elements. +/// +/// O(log n). The function wraps . +/// +[] +val cons : value : 'T -> list : IndexedList<'T> -> IndexedList<'T> + +/// +/// Returns the elements that satisfy the predicate. +/// +/// The function to test the elements. +/// The input indexed list. +/// The elements that satisfy the predicate, in their order. +/// +/// O(n). The function wraps . +/// +[] +val filter : predicate : ('T -> bool) -> list : IndexedList<'T> -> IndexedList<'T> + +/// Returns every element with its index. +/// The input indexed list. +/// The pairs of index and element. +/// O(n). +[] +val indexed : list : IndexedList<'T> -> IndexedList + +/// +/// Returns an indexed 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 indexed list. +/// The indexed list with the value. +/// Thrown when the index is negative or greater than the length. +/// +/// O(log n). The function wraps . +/// +[] +val insertAt : index : int -> value : 'T -> source : IndexedList<'T> -> IndexedList<'T> + +/// +/// Returns an indexed 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 indexed list. +/// The indexed list with the values. +/// Thrown when the index is negative or greater than the length. +/// Thrown when is or a default flat list. +/// +/// O(m log(n + m)). The function wraps +/// . +/// +[] +val insertManyAt : index : int -> values : seq<'T> -> source : IndexedList<'T> -> IndexedList<'T> + +/// +/// Returns the results of a function applied to each element. +/// +/// The function to transform the elements, called in the order of the elements. +/// The input indexed list. +/// The results, in the order of the elements. +/// +/// O(n). The function wraps . +/// +[] +val map : mapping : ('T -> 'U) -> list : IndexedList<'T> -> IndexedList<'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 indexed list. +/// The second indexed list. +/// The results. +/// Thrown when the indexed lists have different lengths. +/// +/// O(n). +/// +[] +val map2 : mapping : ('T1 -> 'T2 -> 'U) -> list1 : IndexedList<'T1> -> list2 : IndexedList<'T2> -> IndexedList<'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 indexed list. +/// The second indexed list. +/// The third indexed list. +/// The results. +/// Thrown when the indexed lists have different lengths. +/// +/// O(n). +/// +[] +val map3 : + mapping : ('T1 -> 'T2 -> 'T3 -> 'U) -> + list1 : IndexedList<'T1> -> + list2 : IndexedList<'T2> -> + list3 : IndexedList<'T3> -> + IndexedList<'U> + +/// +/// Returns the results of a function applied to each element and its index. +/// +/// The function to transform the elements. +/// The input indexed list. +/// The results. +/// +/// O(n). +/// +[] +val mapi : mapping : (int -> 'T -> 'U) -> list : IndexedList<'T> -> IndexedList<'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 indexed list. +/// The second indexed list. +/// The results. +/// Thrown when the indexed lists have different lengths. +/// +/// O(n). +/// +[] +val mapi2 : mapping : (int -> 'T1 -> 'T2 -> 'U) -> list1 : IndexedList<'T1> -> list2 : IndexedList<'T2> -> IndexedList<'U> + +/// Returns every element with its successor. +/// The input indexed list. +/// The pairs of neighbours; empty for fewer than two elements. +/// O(n). +[] +val pairwise : list : IndexedList<'T> -> IndexedList + +/// +/// 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 indexed list. +/// The permuted indexed list. +/// Thrown when the function does not compute a permutation. +/// +/// O(n). +/// +[] +val permute : indexMap : (int -> int) -> list : IndexedList<'T> -> IndexedList<'T> + +/// +/// Returns the indexed list without the first element that equals a value, in +/// . +/// +/// The value to remove. +/// The input indexed list. +/// The indexed 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 +/// ; +/// to remove the elements that satisfy a predicate, use +/// +/// with the negated predicate. +/// +/// +[] +val remove : item : 'T -> list : IndexedList<'T> -> IndexedList<'T> + +/// +/// Returns an indexed list without the element at an index. +/// +/// The index of the removed element. +/// The input indexed list. +/// The indexed list without the element. +/// Thrown when the index is out of range. +/// +/// O(log n). The function wraps . +/// +[] +val removeAt : index : int -> source : IndexedList<'T> -> IndexedList<'T> + +/// +/// Returns the indexed list without one element for each of the given values, in +/// . +/// +/// The values to remove. +/// The input indexed list. +/// The indexed 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 +/// . +/// +/// +/// To remove every element that equals one of the values, and the duplicates of the other elements, use +/// . +/// +/// +[] +val removeMany : items : seq<'T> -> list : IndexedList<'T> -> IndexedList<'T> + +/// +/// Returns an indexed list without a range of elements. +/// +/// The index of the first removed element. +/// +/// The number of removed elements. A count that is not positive removes nothing, and so does a count whose sum with the +/// index exceeds , as in +/// . +/// +/// The input indexed list. +/// The indexed list without the range. +/// Thrown when the index is negative or the range ends beyond the indexed list. +/// +/// O(count × log n). The function wraps +/// . +/// +[] +val removeManyAt : index : int -> count : int -> source : IndexedList<'T> -> IndexedList<'T> + +/// +/// Returns the indexed list without one element for each of the given values, in a given equality comparer. +/// +/// The equality comparer. +/// The values to remove. +/// The input indexed list. +/// The indexed 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 the comparer finds equal to 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 : IndexedList<'T> -> IndexedList<'T> + +/// +/// Returns the indexed list without the first element that equals a value in a given equality comparer. +/// +/// The equality comparer. +/// The value to remove. +/// The input indexed list. +/// The indexed 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 : IndexedList<'T> -> IndexedList<'T> + +/// +/// Returns the indexed list with the first element that equals a value replaced by another value, in +/// . +/// +/// The value to replace. +/// The new value. +/// The input indexed list. +/// The indexed list with the new value in place of the element. +/// Thrown when no element equals . +/// +/// +/// O(n). The function wraps . +/// +/// +/// To replace every element that equals a value, use +/// ; +/// to replace the element at an index, use +/// . +/// +/// +[] +val replace : oldValue : 'T -> newValue : 'T -> list : IndexedList<'T> -> IndexedList<'T> + +/// +/// Returns the indexed list with the first element that equals a value in a given equality comparer replaced by another +/// value. +/// +/// The equality comparer. +/// The value to replace. +/// The new value. +/// The input indexed list. +/// The indexed list with the new value in place of the element. +/// +/// Thrown when the comparer finds no element equal to . +/// +/// +/// O(n). The function wraps +/// . +/// +[] +val replaceWith : comparer : IEqualityComparer<'T> -> oldValue : 'T -> newValue : 'T -> list : IndexedList<'T> -> IndexedList<'T> + +/// +/// Returns the elements in reverse order. +/// +/// The input indexed list. +/// The reversed indexed list. +/// +/// O(n). The function copies the elements to an array, reverses it with +/// and builds a new tree with +/// . +/// At 100 000 integers, this takes about 3 ms where +/// takes about 23 ms; the copy of the elements is +/// the only extra allocation. +/// +[] +val rev : list : IndexedList<'T> -> IndexedList<'T> + +/// +/// Returns the indexed list with the elements of a range in reverse order. +/// +/// The index of the first element of the range. +/// The number of elements of the range. +/// The input indexed list. +/// The indexed list with the reversed range; the input itself when the range has at most one element. +/// +/// Thrown when or is negative, or the range extends beyond the end of +/// the indexed list. +/// +/// +/// O(count × log n). The elements outside the range keep their places. The function wraps +/// . +/// +[] +val revRange : index : int -> count : int -> list : IndexedList<'T> -> IndexedList<'T> + +/// +/// Returns the transpose of a sequence of indexed lists of the same length. +/// +/// The indexed lists. +/// An indexed list whose element i holds the element i of every input indexed list. +/// Thrown when is or a default flat list, or one of its indexed lists is . +/// Thrown when the indexed lists have different lengths. +/// +/// O(the total length). +/// +[] +val transpose : lists : seq> -> IndexedList> + +/// Splits an indexed list of pairs into two indexed lists. +/// The input indexed list. +/// The first components and the second components. +/// O(n). +[] +val unzip : list : IndexedList -> struct (IndexedList<'T1> * IndexedList<'T2>) + +/// Splits an indexed list of triples into three indexed lists. +/// The input indexed list. +/// The first, the second and the third components. +/// O(n). +[] +val unzip3 : list : IndexedList -> struct (IndexedList<'T1> * IndexedList<'T2> * IndexedList<'T3>) + +/// +/// Returns an indexed list with the element at an index replaced by a value. +/// +/// The index of the replaced element. +/// The new value. +/// The input indexed list. +/// The indexed list with the new value. +/// Thrown when the index is out of range. +/// +/// O(log n). The function wraps . +/// +[] +val updateAt : index : int -> value : 'T -> source : IndexedList<'T> -> IndexedList<'T> + +/// +/// Returns the elements that satisfy the predicate. +/// +/// The function to test the elements. +/// The input indexed list. +/// The elements that satisfy the predicate, in their order. +/// +/// O(n). A synonym of . +/// +[] +val where : predicate : ('T -> bool) -> list : IndexedList<'T> -> IndexedList<'T> + +/// +/// Returns the windows of a given size that slide over the indexed list. +/// +/// The size of a window. +/// The input indexed list. +/// The windows; empty when the indexed list is shorter than a window. +/// Thrown when is not positive. +/// +/// O(n × windowSize). The window is the indexed list itself when it is as long as the indexed list. +/// +[] +val windowed : windowSize : int -> list : IndexedList<'T> -> IndexedList> + +/// Combines two indexed lists into an indexed list of pairs. +/// The first indexed list. +/// The second indexed list. +/// The pairs of elements at the same index. +/// Thrown when the indexed lists have different lengths. +/// O(n). +[] +val zip : list1 : IndexedList<'T1> -> list2 : IndexedList<'T2> -> IndexedList + +/// +/// Combines three indexed lists into an indexed list of triples. +/// +/// The first indexed list. +/// The second indexed list. +/// The third indexed list. +/// The triples of elements at the same index. +/// Thrown when the indexed lists have different lengths. +/// +/// O(n). +/// +[] +val zip3 : + list1 : IndexedList<'T1> -> list2 : IndexedList<'T2> -> list3 : IndexedList<'T3> -> IndexedList + +// ---- Folding ---- + +/// Returns the average of the elements. +/// The input indexed list. +/// The average. +/// Thrown when the indexed list is empty. +/// O(n). +[] +val inline average : + list : IndexedList< ^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 indexed list. +/// The average of the results. +/// Thrown when the indexed list is empty. +/// +/// O(n). +/// +[] +val inline averageBy : + projection : ('T -> ^U) -> list : IndexedList<'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 indexed lists element by element with a comparer. +/// +/// The function that compares two elements. +/// The first indexed list. +/// The second indexed list. +/// +/// The first non-zero result of the comparer, or the comparison of the lengths when every common element compares equal. +/// +/// +/// O(n). The function walks the two indexed lists together and stops at the first pair of elements that the comparer +/// does not consider equal. +/// +[] +val compareWith : comparer : ('T -> 'T -> int) -> list1 : IndexedList<'T> -> list2 : IndexedList<'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 indexed list. +/// The final state. +/// +/// O(n). +/// +[] +val fold : folder : ('State -> 'T -> 'State) -> state : 'State -> list : IndexedList<'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 indexed list. +/// The second indexed list. +/// The final state. +/// Thrown when the indexed lists have different lengths. +/// +/// O(n). +/// +[] +val fold2 : + folder : ('State -> 'T1 -> 'T2 -> 'State) -> state : 'State -> list1 : IndexedList<'T1> -> list2 : IndexedList<'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 indexed list. +/// The initial state. +/// The final state. +/// +/// O(n). +/// +[] +val foldBack : folder : ('T -> 'State -> 'State) -> list : IndexedList<'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 indexed list. +/// The second indexed list. +/// The initial state. +/// The final state. +/// Thrown when the indexed lists have different lengths. +/// +/// O(n). +/// +[] +val foldBack2 : + folder : ('T1 -> 'T2 -> 'State -> 'State) -> list1 : IndexedList<'T1> -> list2 : IndexedList<'T2> -> state : 'State -> 'State + +/// +/// Applies an action to each element. +/// +/// The action. +/// The input indexed list. +/// +/// O(n). +/// +[] +val iter : action : ('T -> unit) -> list : IndexedList<'T> -> unit + +/// +/// Applies an action to each pair of elements at the same index. +/// +/// The action. +/// The first indexed list. +/// The second indexed list. +/// Thrown when the indexed lists have different lengths. +/// +/// O(n). +/// +[] +val iter2 : action : ('T1 -> 'T2 -> unit) -> list1 : IndexedList<'T1> -> list2 : IndexedList<'T2> -> unit + +/// Applies an action to each element and its index. +/// The action. +/// The input indexed list. +/// O(n). +[] +val iteri : action : (int -> 'T -> unit) -> list : IndexedList<'T> -> unit + +/// +/// Applies an action to the index and the pair of elements at that index. +/// +/// The action. +/// The first indexed list. +/// The second indexed list. +/// Thrown when the indexed lists have different lengths. +/// +/// O(n). +/// +[] +val iteri2 : action : (int -> 'T1 -> 'T2 -> unit) -> list1 : IndexedList<'T1> -> list2 : IndexedList<'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 indexed list. +/// The results and the final state. +/// +/// O(n). +/// +[] +val mapFold : + mapping : ('State -> 'T -> struct ('Result * 'State)) -> + state : 'State -> + list : IndexedList<'T> -> + struct (IndexedList<'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 indexed 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)) -> + list : IndexedList<'T> -> + state : 'State -> + struct (IndexedList<'Result> * 'State) + +/// Returns the greatest element, in F# generic comparison. +/// The input indexed list. +/// The greatest element. +/// Thrown when the indexed list is empty. +/// +/// O(n). The function is inline, as +/// is, so that the comparison is compiled for the type of the elements and boxes nothing. +/// +[] +val inline max : list : IndexedList<'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 indexed list. +/// The first element with the greatest result. +/// Thrown when the indexed list is empty. +/// +/// O(n). The function is inline, as +/// is, so that the comparison is compiled for the type of the results and boxes nothing. +/// +[] +val inline maxBy : projection : ('T -> 'U) -> list : IndexedList<'T> -> 'T when 'U : comparison + +/// Returns the smallest element, in F# generic comparison. +/// The input indexed list. +/// The smallest element. +/// Thrown when the indexed list is empty. +/// +/// O(n). The function is inline, as +/// is, so that the comparison is compiled for the type of the elements and boxes nothing. +/// +[] +val inline min : list : IndexedList<'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 indexed list. +/// The first element with the smallest result. +/// Thrown when the indexed list is empty. +/// +/// O(n). The function is inline, as +/// is, so that the comparison is compiled for the type of the results and boxes nothing. +/// +[] +val inline minBy : projection : ('T -> 'U) -> list : IndexedList<'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 indexed list. +/// The combined value. +/// Thrown when the indexed list is empty. +/// +/// O(n). +/// +[] +val reduce : reduction : ('T -> 'T -> 'T) -> list : IndexedList<'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 indexed list. +/// The combined value. +/// Thrown when the indexed list is empty. +/// +/// O(n). +/// +[] +val reduceBack : reduction : ('T -> 'T -> 'T) -> list : IndexedList<'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 indexed list. +/// The states; one more than the elements. +/// +/// O(n). +/// +[] +val scan : folder : ('State -> 'T -> 'State) -> state : 'State -> list : IndexedList<'T> -> IndexedList<'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 indexed list. +/// The initial state. +/// The states; one more than the elements. +/// +/// O(n). +/// +[] +val scanBack : folder : ('T -> 'State -> 'State) -> list : IndexedList<'T> -> state : 'State -> IndexedList<'State> + +/// Returns the sum of the elements. +/// The input indexed list. +/// The sum; zero for an empty indexed list. +/// O(n). +[] +val inline sum : list : IndexedList< ^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 indexed list. +/// The sum of the results; zero for an empty indexed list. +/// +/// O(n). +/// +[] +val inline sumBy : + projection : ('T -> ^U) -> list : IndexedList<'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 indexed 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) -> list : IndexedList<'T> -> IndexedList when 'Key : equality + +/// +/// Returns the elements without duplicates, in F# structural equality. +/// +/// The input indexed list. +/// The first occurrence of each element, in their order. +/// +/// O(n). +/// +[] +val distinct : list : IndexedList<'T> -> IndexedList<'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 indexed list. +/// The first element of each key, in their order. +/// +/// O(n). The keys compare in F# structural equality. +/// +[] +val distinctBy : projection : ('T -> 'Key) -> list : IndexedList<'T> -> IndexedList<'T> when 'Key : equality + +/// +/// Returns the distinct elements that do not equal any of the excluded items. +/// +/// The items to remove. +/// The input indexed list. +/// The distinct remaining elements, in their order. +/// Thrown when is or a default flat list. +/// +/// O(n + m). An empty indexed list is returned without a look at the items to exclude. +/// +[] +val except : itemsToExclude : seq<'T> -> list : IndexedList<'T> -> IndexedList<'T> when 'T : equality + +/// Groups the elements by the key of a projection. +/// The function that returns the key of an element. +/// The input indexed 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) -> list : IndexedList<'T> -> IndexedList)> when 'Key : equality + +/// Splits the elements by a predicate. +/// The function to test the elements. +/// The input indexed list. +/// The elements that satisfy the predicate and the other ones, both in their order. +/// O(n). +[] +val partition : predicate : ('T -> bool) -> list : IndexedList<'T> -> struct (IndexedList<'T> * IndexedList<'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 indexed 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>) -> list : IndexedList<'T> -> struct (IndexedList<'T1> * IndexedList<'T2>) + +/// +/// Returns the indexed list without its first elements. +/// +/// The number of elements to skip; a negative count skips nothing. +/// The input indexed list. +/// The remaining elements. +/// Thrown when the count exceeds the length. +/// +/// O(log n + k), where k is the length of the result, which is a new tree, unless it is empty or the whole indexed list. +/// +[] +val skip : count : int -> list : IndexedList<'T> -> IndexedList<'T> + +/// +/// Returns the indexed list without its last elements. +/// +/// The number of elements to skip; a negative count skips nothing. +/// The input indexed list. +/// The remaining elements. +/// Thrown when the count exceeds the length. +/// +/// O(log n + k), where k is the length of the result, which is a new tree, unless it is empty or the whole indexed list. +/// The checks follow . +/// +[] +val skipLast : count : int -> list : IndexedList<'T> -> IndexedList<'T> + +/// +/// Returns the indexed list without its first elements that satisfy the predicate. +/// +/// The function to test the elements. +/// The input indexed list. +/// The elements from the first one that does not satisfy the predicate. +/// +/// O(n + k), where k is the length of the result. +/// +[] +val skipWhile : predicate : ('T -> bool) -> list : IndexedList<'T> -> IndexedList<'T> + +/// +/// Splits the indexed list at an index. +/// +/// The index of the first element of the second part. +/// The input indexed 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). Each part is a new tree, unless it is empty or the whole indexed list. +/// +[] +val splitAt : index : int -> list : IndexedList<'T> -> struct (IndexedList<'T> * IndexedList<'T>) + +/// +/// Splits the indexed list into at most a given number of parts of nearly equal length. +/// +/// The maximum number of parts. +/// The input indexed list. +/// The parts; the first ones are one element longer when the length does not divide evenly. +/// Thrown when is not positive. +/// +/// O(n). +/// +[] +val splitInto : count : int -> list : IndexedList<'T> -> IndexedList> + +/// +/// Returns the elements of a range of the indexed list. +/// +/// The input indexed 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 indexed list. +/// +/// +/// +/// O(log n + count). The result is a new tree, unless it is empty or the whole indexed list. A range that +/// +/// and then +/// +/// take builds two trees, and this function builds one: in a measurement, 1 000 elements from the middle of an indexed +/// list of 1 000 000 elements took 0.1 ms and allocated 52 KB, where skip followed by take took 46 ms and allocated 26 MB. +/// +/// +/// The function throws what +/// throws, as +/// does. +/// +/// +[] +val sub : list : IndexedList<'T> -> startIndex : int -> count : int -> IndexedList<'T> + +/// +/// Returns the first elements of the indexed list. +/// +/// The number of elements to take. +/// The input indexed list. +/// The first elements. +/// Thrown when the count is negative. +/// Thrown when the count exceeds the length. +/// +/// O(log n + count). The result is a new tree, unless it is empty or the whole indexed list. +/// +[] +val take : count : int -> list : IndexedList<'T> -> IndexedList<'T> + +/// +/// Returns the last elements of the indexed list. +/// +/// The number of elements to take. +/// The input indexed list. +/// The last elements. +/// Thrown when the count is negative. +/// Thrown when the count exceeds the length. +/// +/// O(log n + count). The result is a new tree, unless it is empty or the whole indexed list. The checks follow +/// . +/// +[] +val takeLast : count : int -> list : IndexedList<'T> -> IndexedList<'T> + +/// +/// Returns the first elements of the indexed list that satisfy the predicate. +/// +/// The function to test the elements. +/// The input indexed list. +/// The elements before the first one that does not satisfy the predicate. +/// +/// O(k), where k is the length of the result. +/// +[] +val takeWhile : predicate : ('T -> bool) -> list : IndexedList<'T> -> IndexedList<'T> + +/// +/// Returns at most a given number of first elements. +/// +/// The maximum number of elements; a count that is not positive returns an empty indexed list. +/// The input indexed list. +/// The first elements. +/// +/// O(log n + k), where k is the length of the result, which is a new tree, unless it is empty or the whole indexed list. +/// +[] +val truncate : count : int -> list : IndexedList<'T> -> IndexedList<'T> + +// ---- Sorting ---- + +/// +/// Sorts the elements in the order of F# generic comparison. +/// +/// The input indexed list. +/// The sorted indexed list. +/// +/// Thrown when the comparison of two elements throws, as in . +/// +/// +/// O(n log n). This is a stable sort, as : +/// equal elements keep their order. +/// +[] +val sort : list : IndexedList<'T> -> IndexedList<'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 indexed list. +/// The sorted indexed list. +/// +/// O(n log n). This is a stable sort: elements with equal keys keep their order. +/// +[] +val sortBy : projection : ('T -> 'Key) -> list : IndexedList<'T> -> IndexedList<'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 indexed list. +/// The sorted indexed list. +/// +/// Thrown when the projection or the comparison of two keys throws: the projection runs in the comparison, as in +/// . +/// +/// +/// O(n log n). This is a stable sort: elements with equal keys keep their order. +/// +[] +val sortByDescending : projection : ('T -> 'Key) -> list : IndexedList<'T> -> IndexedList<'T> when 'Key : comparison + +/// +/// Sorts the elements in descending order of F# generic comparison. +/// +/// The input indexed list. +/// The sorted indexed list. +/// +/// Thrown when the comparison of two elements throws, as in . +/// +/// +/// O(n log n). This is a stable sort: equal elements keep their order. +/// +[] +val sortDescending : list : IndexedList<'T> -> IndexedList<'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 indexed list. +/// The indexed list with the sorted range; the input itself when the range has at most one element. +/// +/// Thrown when or is negative, or the range extends beyond the end of +/// the indexed list. +/// +/// Thrown when the comparison of two elements throws. +/// +/// +/// O(count × log n). This is a stable sort: elements that compare equal keep their order. The elements outside the range +/// keep their places. +/// +/// +/// The function sorts a copy of the range with +/// +/// and puts it back with +/// and +/// , +/// not with the unstable , +/// which copies the whole indexed list: in a measurement, 10 000 elements of an indexed list of 1 000 000 elements took +/// 2.3 ms and allocated 1.6 MB, where that member took 68 ms and allocated 52 MB. +/// +/// +[] +val sortRange : index : int -> count : int -> list : IndexedList<'T> -> IndexedList<'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 indexed list. +/// The indexed list with the sorted range; the input itself when the range has at most one element. +/// +/// Thrown when or is negative, or the range extends beyond the end of +/// the indexed list. +/// +/// Thrown when the comparison function throws. +/// +/// O(count × log n). This is a stable sort: elements that compare equal keep their order. The elements outside the range +/// keep their places. The function sorts a copy of the range as +/// +/// does. +/// +[] +val sortRangeWith : comparison : ('T -> 'T -> int) -> index : int -> count : int -> list : IndexedList<'T> -> IndexedList<'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 indexed list. +/// The indexed list with the sorted range; the input itself when the range has at most one element. +/// +/// Thrown when or is negative, or the range extends beyond the end of +/// the indexed list. +/// +/// Thrown when the comparer throws. +/// +/// O(count × log n). This is a stable sort: elements that compare equal keep their order. The elements outside the range +/// keep their places. The function sorts a copy of the range as +/// +/// does. +/// +[] +val sortRangeWithComparer : comparer : IComparer<'T> -> index : int -> count : int -> list : IndexedList<'T> -> IndexedList<'T> + +/// +/// Sorts the elements with a comparison function. +/// +/// The function that compares two elements. +/// The input indexed list. +/// The sorted indexed list. +/// +/// Thrown when the comparer throws, as in +/// . +/// +/// +/// O(n log n). This is a stable sort: elements that compare equal keep their order. +/// +[] +val sortWith : comparer : ('T -> 'T -> int) -> list : IndexedList<'T> -> IndexedList<'T> + +/// +/// Sorts the elements with a comparer. +/// +/// The comparer. +/// The input indexed list. +/// The sorted indexed list. +/// +/// O(n log n). This is a stable sort: elements that compare equal keep their order. The function wraps +/// , +/// not the unstable . +/// +[] +val sortWithComparer : comparer : IComparer<'T> -> list : IndexedList<'T> -> IndexedList<'T> + +// ---- Random ---- + +/// +/// Returns a random element, chosen with . +/// +/// The input indexed list. +/// A random element. +/// Thrown when the indexed list is empty. +/// +/// O(log n). +/// +[] +val randomChoice : source : IndexedList<'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 indexed list. +/// A random element. +/// Thrown when the indexed list is empty. +/// Thrown when the randomizer returns a number outside [0, 1). +/// +/// O(log n). +/// +[] +val randomChoiceBy : randomizer : (unit -> float) -> source : IndexedList<'T> -> 'T + +/// +/// Returns a random element, chosen with a given random number generator. +/// +/// The random number generator. +/// The input indexed list. +/// A random element. +/// Thrown when is . +/// Thrown when the indexed list is empty. +/// +/// O(log n). +/// +[] +val randomChoiceWith : random : Random -> source : IndexedList<'T> -> 'T + +/// +/// Returns random elements, chosen with ; an element can repeat. +/// +/// The number of elements to choose. +/// The input indexed list. +/// The chosen elements. +/// Thrown when the count is negative, or positive for an empty indexed list. +/// +/// O(n + count). +/// +[] +val randomChoices : count : int -> source : IndexedList<'T> -> IndexedList<'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 indexed list. +/// The chosen elements. +/// Thrown when the count is negative, or positive for an empty indexed list. +/// Thrown when the randomizer returns a number outside [0, 1). +/// +/// O(n + count). +/// +[] +val randomChoicesBy : randomizer : (unit -> float) -> count : int -> source : IndexedList<'T> -> IndexedList<'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 indexed list. +/// The chosen elements. +/// Thrown when is . +/// Thrown when the count is negative, or positive for an empty indexed list. +/// +/// O(n + count). +/// +[] +val randomChoicesWith : random : Random -> count : int -> source : IndexedList<'T> -> IndexedList<'T> + +/// +/// Returns random elements at distinct indices, chosen with . +/// +/// The number of elements to choose. +/// The input indexed list. +/// The chosen elements. +/// Thrown when the count is negative or exceeds the length. +/// +/// O(n + count). +/// +[] +val randomSample : count : int -> source : IndexedList<'T> -> IndexedList<'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 indexed 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(n + count). +/// +[] +val randomSampleBy : randomizer : (unit -> float) -> count : int -> source : IndexedList<'T> -> IndexedList<'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 indexed list. +/// The chosen elements. +/// Thrown when is . +/// Thrown when the count is negative or exceeds the length. +/// +/// O(n + count). +/// +[] +val randomSampleWith : random : Random -> count : int -> source : IndexedList<'T> -> IndexedList<'T> + +/// +/// Returns the elements in a random order, chosen with . +/// +/// The input indexed list. +/// The shuffled indexed list. +/// +/// O(n). +/// +[] +val randomShuffle : source : IndexedList<'T> -> IndexedList<'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 indexed list. +/// The shuffled indexed list. +/// Thrown when the randomizer returns a number outside [0, 1). +/// +/// O(n). +/// +[] +val randomShuffleBy : randomizer : (unit -> float) -> source : IndexedList<'T> -> IndexedList<'T> + +/// +/// Returns the elements in a random order, chosen with a given random number generator. +/// +/// The random number generator. +/// The input indexed list. +/// The shuffled indexed list. +/// Thrown when is . +/// +/// O(n). +/// +[] +val randomShuffleWith : random : Random -> source : IndexedList<'T> -> IndexedList<'T> + +// ---- Conversion ---- + +/// +/// Copies a range of the indexed list into an array. +/// +/// The input indexed 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 an index or the count is negative, or a range is outside its indexed list or array. +/// +/// O(log n + count). The function wraps +/// +/// and reports the names of its own parameters. +/// +[] +val blit : source : IndexedList<'T> -> sourceIndex : int -> target : 'T array -> targetIndex : int -> count : int -> unit + +/// Returns a new array with the elements. +/// The input indexed list. +/// A copy of the elements. +/// O(n). +[] +val toArray : list : IndexedList<'T> -> 'T array + +/// Returns a list of the elements. +/// The input indexed list. +/// The list of the elements. +/// O(n). +[] +val toList : list : IndexedList<'T> -> 'T list + +/// Returns the indexed list as a sequence. +/// The input indexed list. +/// The sequence of the elements. +/// O(1). +[] +val toSeq : list : IndexedList<'T> -> seq<'T> diff --git a/src/FSharp.Collections.Immutable/IndexedList/IndexedListBuilder.fs b/src/FSharp.Collections.Immutable/IndexedList/IndexedListBuilder.fs new file mode 100644 index 00000000..938bcfda --- /dev/null +++ b/src/FSharp.Collections.Immutable/IndexedList/IndexedListBuilder.fs @@ -0,0 +1,235 @@ +[] +module FSharp.Collections.Immutable.IndexedListBuilder + +open System +open System.Collections.Generic +open System.Collections.Immutable +open System.Linq + +/// Replaces the elements of the builder with the same elements in the stable order of the comparer, as the sorting +/// functions of IndexedList order them; the Sort members of the builder are not stable. +let private sortStably (comparer : IComparer<'T>) (builder : IndexedList<'T>.Builder) = + if builder.Count > 1 then + let sorted = builder.Order(comparer).ToArray() + builder.Clear () + builder.AddRange sorted + + builder + +/// Replaces a range of the elements of the builder with the same elements in the stable order of the comparer, as +/// IndexedList.sortRangeWithComparer orders them; Sort(Int32, Int32, IComparer) of the builder is not stable. +let private sortRangeStably (comparer : IComparer<'T>) index count (builder : IndexedList<'T>.Builder) = + ListRanges.forward (nameof index) index (nameof count) count (nameof count) builder.Count + + // A range of at most one element needs no sorting, and the comparer does not run for it + if count > 1 then + // The range is copied into an array, as IndexedList.sortRangeWithComparer copies it (see there for the reason) + let items = Array.zeroCreate count + builder.CopyTo (index, items, 0, count) + let sorted = items.Order(comparer).ToArray() + builder.RemoveRange (index, count) + builder.InsertRange (index, sorted) + + builder + +// ---- Properties ---- + +[] +let length (builder : IndexedList<'T>.Builder) = builder.Count + +// ---- Access ---- + +[] +let item index (builder : IndexedList<'T>.Builder) = builder[index] + +// ---- Search ---- + +[] +let binarySearch (value : 'T) (builder : IndexedList<'T>.Builder) = builder.BinarySearch (value, DefaultComparer<'T>.Instance) + +[] +let binarySearchRangeWithComparer (comparer : IComparer<'T>) index count (value : 'T) (builder : IndexedList<'T>.Builder) = + ListRanges.search (nameof index) index (nameof count) count builder.Count + builder.BinarySearch (index, count, value, comparer) + +[] +let binarySearchWithComparer (comparer : IComparer<'T>) (value : 'T) (builder : IndexedList<'T>.Builder) = + builder.BinarySearch (value, comparer) + +[] +let contains (value : 'T) (builder : IndexedList<'T>.Builder) = builder.Contains value + +[] +let indexOf (value : 'T) (builder : IndexedList<'T>.Builder) = builder.IndexOf value + +[] +let indexOfRange startIndex count (value : 'T) (builder : IndexedList<'T>.Builder) = + ListRanges.forward (nameof startIndex) startIndex (nameof count) count (nameof count) builder.Count + builder.IndexOf (value, startIndex, count, EqualityComparer<'T>.Default) + +[] +let indexOfRangeWith (comparer : IEqualityComparer<'T>) startIndex count (value : 'T) (builder : IndexedList<'T>.Builder) = + ListRanges.forward (nameof startIndex) startIndex (nameof count) count (nameof count) builder.Count + builder.IndexOf (value, startIndex, count, comparer) + +[] +let lastIndexOf (value : 'T) (builder : IndexedList<'T>.Builder) = builder.LastIndexOf value + +[] +let lastIndexOfRange startIndex count (value : 'T) (builder : IndexedList<'T>.Builder) = + ListRanges.backward (nameof startIndex) startIndex (nameof count) count builder.Count + + // An empty range has no equal element; the member rejects a start index of Int32.MaxValue for it + if count = 0 then + -1 + else + builder.LastIndexOf (value, startIndex, count, EqualityComparer<'T>.Default) + +[] +let lastIndexOfRangeWith (comparer : IEqualityComparer<'T>) startIndex count (value : 'T) (builder : IndexedList<'T>.Builder) = + ListRanges.backward (nameof startIndex) startIndex (nameof count) count builder.Count + + if count = 0 then + -1 + else + builder.LastIndexOf (value, startIndex, count, comparer) + +// ---- Transformation ---- + +[] +let add (value : 'T) (builder : IndexedList<'T>.Builder) = + builder.Add value + builder + +[] +let addRange (values : seq<'T>) (builder : IndexedList<'T>.Builder) = + // The member would name a null sequence items, and it fails while it enumerates a default flat list (ADR 0001, D6) + checkNotNullOrDefaultSeq (nameof values) values + builder.AddRange values + builder + +[] +let clear (builder : IndexedList<'T>.Builder) = + builder.Clear () + builder + +[] +let insertAt index (value : 'T) (builder : IndexedList<'T>.Builder) = + builder.Insert (index, value) + builder + +[] +let insertManyAt index (values : seq<'T>) (builder : IndexedList<'T>.Builder) = + // The member 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 : IndexedList<'T>.Builder) = builder.Remove value + +[] +let removeAll (predicate : 'T -> bool) (builder : IndexedList<'T>.Builder) = + builder.RemoveAll (Predicate predicate) |> ignore + builder + +[] +let removeAt index (builder : IndexedList<'T>.Builder) = + builder.RemoveAt index + builder + +[] +let removeMany (items : seq<'T>) (builder : IndexedList<'T>.Builder) = + // The member names null items as this function does, but it fails while it enumerates a default flat list + checkNotNullOrDefaultSeq (nameof items) items + builder.RemoveRange items + builder + +[] +let removeManyAt (index : int) (count : int) (builder : IndexedList<'T>.Builder) = + builder.RemoveRange (index, count) + builder + +[] +let removeManyWith (comparer : IEqualityComparer<'T>) (items : seq<'T>) (builder : IndexedList<'T>.Builder) = + checkNotNullOrDefaultSeq (nameof items) items + builder.RemoveRange (items, comparer) + builder + +[] +let removeWith (comparer : IEqualityComparer<'T>) (value : 'T) (builder : IndexedList<'T>.Builder) = + builder.Remove (value, comparer) + +[] +let replace (oldValue : 'T) (newValue : 'T) (builder : IndexedList<'T>.Builder) = + builder.Replace (oldValue, newValue) + builder + +[] +let replaceWith (comparer : IEqualityComparer<'T>) (oldValue : 'T) (newValue : 'T) (builder : IndexedList<'T>.Builder) = + builder.Replace (oldValue, newValue, comparer) + builder + +[] +let rev (builder : IndexedList<'T>.Builder) = + builder.Reverse () + builder + +[] +let revRange index count (builder : IndexedList<'T>.Builder) = + // ImmutableList.Builder.Reverse(Int32, Int32) names the count for a range beyond the end + ListRanges.forward (nameof index) index (nameof count) count (nameof count) builder.Count + builder.Reverse (index, count) + builder + +[] +let updateAt index (value : 'T) (builder : IndexedList<'T>.Builder) = + builder[index] <- value + builder + +// ---- Sorting ---- + +[] +let sort (builder : IndexedList<'T>.Builder) = sortStably DefaultComparer<'T>.Instance builder + +[] +let sortRange index count (builder : IndexedList<'T>.Builder) = sortRangeStably DefaultComparer<'T>.Instance index count builder + +[] +let sortRangeWith (comparison : 'T -> 'T -> int) index count (builder : IndexedList<'T>.Builder) = + sortRangeStably (ComparisonIdentity.FromFunction comparison) index count builder + +[] +let sortRangeWithComparer (comparer : IComparer<'T>) index count (builder : IndexedList<'T>.Builder) = + sortRangeStably comparer index count builder + +[] +let sortWith (comparer : 'T -> 'T -> int) (builder : IndexedList<'T>.Builder) = + sortStably (ComparisonIdentity.FromFunction comparer) builder + +[] +let sortWithComparer (comparer : IComparer<'T>) (builder : IndexedList<'T>.Builder) = sortStably comparer builder + +// ---- Conversion ---- + +[] +let blit (source : IndexedList<'T>.Builder) sourceIndex (target : 'T array) targetIndex count = + checkNotNull (nameof target) target + ListRanges.blit + (nameof sourceIndex) + sourceIndex + (nameof count) + count + source.Count + (nameof targetIndex) + targetIndex + target.Length + source.CopyTo (sourceIndex, target, targetIndex, count) + +[] +let toArray (builder : IndexedList<'T>.Builder) = + let array = Array.zeroCreate builder.Count + builder.CopyTo array + array diff --git a/src/FSharp.Collections.Immutable/IndexedList/IndexedListBuilder.fsi b/src/FSharp.Collections.Immutable/IndexedList/IndexedListBuilder.fsi new file mode 100644 index 00000000..489bd99e --- /dev/null +++ b/src/FSharp.Collections.Immutable/IndexedList/IndexedListBuilder.fsi @@ -0,0 +1,580 @@ +/// +/// Functions for the builder of an indexed 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 an indexed list. A function that takes a sequence throws +/// for a sequence and for a default +/// that is given as a sequence, which would fail with +/// when the function enumerates it. +/// +[] +module FSharp.Collections.Immutable.IndexedListBuilder + +open System.Collections.Generic + +// ---- Properties ---- + +/// Returns the number of elements in the builder. +/// The builder. +/// The number of elements. +[] +val length : builder : IndexedList<'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 : IndexedList<'T>.Builder -> 'T + +// ---- Search ---- + +/// +/// Searches the sorted builder for a value with the binary search algorithm. +/// +/// The value to search for. +/// The builder, 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 as +/// +/// does, and wraps +/// . +/// +[] +val binarySearch : value : 'T -> builder : IndexedList<'T>.Builder -> int when 'T : comparison + +/// +/// Searches a range of a builder for a value with the binary search algorithm and a given comparer. +/// +/// The comparer, in whose order the builder is sorted. +/// The index of the first element of the range. +/// The number of elements of the range. +/// The value to search for. +/// The builder. +/// +/// 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 of the range is larger. +/// +/// +/// Thrown when or is negative. +/// +/// Thrown when the range extends beyond the end of the builder. +/// +/// +/// O(log n) comparisons. An exception that the comparer throws is not wrapped. The function wraps +/// . +/// +/// +/// That member does not check that the range lies in the builder, although its documentation says that it throws +/// . The function throws, as the documentation says (ADR 0001, D14). +/// +/// +[] +val binarySearchRangeWithComparer : + comparer : IComparer<'T> -> index : int -> count : int -> value : 'T -> builder : IndexedList<'T>.Builder -> int + +/// +/// Searches a sorted builder for a value with the binary search algorithm and a given comparer. +/// +/// The comparer, in whose order the builder is sorted. +/// The value to search for. +/// The builder. +/// +/// 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) comparisons. An exception that the comparer throws is not wrapped. The function wraps +/// . +/// +[] +val binarySearchWithComparer : comparer : IComparer<'T> -> value : 'T -> builder : IndexedList<'T>.Builder -> int + +/// +/// 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 : IndexedList<'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 : IndexedList<'T>.Builder -> int + +/// +/// Returns the index of the first element of a range that equals a value, 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, not in the range, of the first equal element of the range, or -1. +/// +/// Thrown when or is negative, or the range extends beyond the end +/// of the builder. +/// +/// +/// O(log n + count). The function wraps +/// . +/// +[] +val indexOfRange : startIndex : int -> count : int -> value : 'T -> builder : IndexedList<'T>.Builder -> int + +/// +/// Returns the index of the first element of a range that a given equality comparer finds equal to a value. +/// +/// The equality comparer. +/// 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, not in the range, of the first equal element of the range, or -1. +/// +/// Thrown when or is negative, or the range extends beyond the end +/// of the builder. +/// +/// +/// O(log n + count). The function wraps +/// . +/// +[] +val indexOfRangeWith : + comparer : IEqualityComparer<'T> -> startIndex : int -> count : int -> value : 'T -> builder : IndexedList<'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 : IndexedList<'T>.Builder -> int + +/// +/// Returns the index of the last element of a range that equals a value, in +/// . The search walks backward from the index of the +/// last element of the range. +/// +/// The index of the last element of the range, where the search starts. +/// The number of elements of the range, which ends at . +/// The value to find. +/// The builder. +/// The index in the builder, not in the range, of the last equal element of the range, or -1. +/// +/// Thrown when or is negative, exceeds the +/// length, or a range of elements does not lie in the builder: its first element, at startIndex - count + 1, is +/// before the start, or its last element, at , is at or after the length. +/// +/// +/// +/// O(log n + count). A count of 0 is an empty range, which has no equal element, for any start index. The function wraps +/// . +/// +/// +/// That member accepts a last element at or after the length and answers with an index that does not belong to the equal +/// element, and it throws a plain for a range that starts before the first +/// element. The function throws for both (ADR 0001, D14). +/// +/// +[] +val lastIndexOfRange : startIndex : int -> count : int -> value : 'T -> builder : IndexedList<'T>.Builder -> int + +/// +/// Returns the index of the last element of a range that a given equality comparer finds equal to a value. The search +/// walks backward from the index of the last element of the range. +/// +/// The equality comparer. +/// The index of the last element of the range, where the search starts. +/// The number of elements of the range, which ends at . +/// The value to find. +/// The builder. +/// The index in the builder, not in the range, of the last equal element of the range, or -1. +/// +/// Thrown when or is negative, exceeds the +/// length, or a range of elements does not lie in the builder: its first element, at startIndex - count + 1, is +/// before the start, or its last element, at , is at or after the length. +/// +/// +/// +/// O(log n + count). A count of 0 is an empty range, which has no equal element, for any start index. The function wraps +/// . +/// +/// +/// That member accepts a last element at or after the length and answers with an index that does not belong to the equal +/// element, and it throws a plain for a range that starts before the first +/// element. The function throws for both (ADR 0001, D14). +/// +/// +[] +val lastIndexOfRangeWith : + comparer : IEqualityComparer<'T> -> startIndex : int -> count : int -> value : 'T -> builder : IndexedList<'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 : IndexedList<'T>.Builder -> IndexedList<'T>.Builder + +/// Adds values after the last element. +/// The values to add. +/// The builder. +/// The same builder. +/// Thrown when is or a default flat list. +[] +val addRange : values : seq<'T> -> builder : IndexedList<'T>.Builder -> IndexedList<'T>.Builder + +/// Removes every element. +/// The builder. +/// The same builder. +[] +val clear : builder : IndexedList<'T>.Builder -> IndexedList<'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 : IndexedList<'T>.Builder -> IndexedList<'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. +/// +/// The function wraps +/// , +/// which checks the index before the values: an index out of range wins over a missing sequence. +/// +[] +val insertManyAt : index : int -> values : seq<'T> -> builder : IndexedList<'T>.Builder -> IndexedList<'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 : IndexedList<'T>.Builder -> bool + +/// +/// Removes the elements that satisfy a predicate. +/// +/// The function to test the elements. +/// The builder. +/// The same builder. +/// +/// O(n + k × log n) for k removed elements. The function wraps +/// , which returns the +/// number of removed elements; compare the lengths to find it. +/// +[] +val removeAll : predicate : ('T -> bool) -> builder : IndexedList<'T>.Builder -> IndexedList<'T>.Builder + +/// 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 : IndexedList<'T>.Builder -> IndexedList<'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 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> -> builder : IndexedList<'T>.Builder -> IndexedList<'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 or is negative, or the range extends beyond the end of +/// the builder. +/// +/// +/// O(count × log n). The function wraps +/// , which +/// throws for a negative count. The function of the indexed list removes nothing for a count that is not positive, as +/// +/// does. +/// +[] +val removeManyAt : index : int -> count : int -> builder : IndexedList<'T>.Builder -> IndexedList<'T>.Builder + +/// +/// Removes one element for each of the given values, in a given equality comparer. +/// +/// The equality comparer. +/// 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 element that the comparer finds equal to 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> -> builder : IndexedList<'T>.Builder -> IndexedList<'T>.Builder + +/// +/// Removes the first element that equals a value in a given equality comparer. +/// +/// The equality comparer. +/// The value to remove. +/// The builder. +/// when an element was removed. +/// +/// O(n). The function wraps +/// . +/// +[] +val removeWith : comparer : IEqualityComparer<'T> -> value : 'T -> builder : IndexedList<'T>.Builder -> bool + +/// +/// Replaces the first element that equals a value by another value, in +/// . +/// +/// The value to replace. +/// The new value. +/// The builder. +/// The same builder. +/// +/// Thrown when no element equals , as the member does. +/// +/// +/// O(n). The function wraps +/// . +/// +[] +val replace : oldValue : 'T -> newValue : 'T -> builder : IndexedList<'T>.Builder -> IndexedList<'T>.Builder + +/// +/// Replaces the first element that equals a value in a given equality comparer by another value. +/// +/// The equality comparer. +/// The value to replace. +/// The new value. +/// The builder. +/// The same builder. +/// +/// Thrown when the comparer finds no element equal to , as the member does. +/// +/// +/// O(n). The function wraps +/// . +/// +[] +val replaceWith : + comparer : IEqualityComparer<'T> -> + oldValue : 'T -> + newValue : 'T -> + builder : IndexedList<'T>.Builder -> + IndexedList<'T>.Builder + +/// Reverses the order of the elements. +/// The builder. +/// The same builder. +[] +val rev : builder : IndexedList<'T>.Builder -> IndexedList<'T>.Builder + +/// +/// Reverses the order of the elements of a range. +/// +/// 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 n). The elements outside the range keep their places. The function wraps +/// . +/// +[] +val revRange : index : int -> count : int -> builder : IndexedList<'T>.Builder -> IndexedList<'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 : IndexedList<'T>.Builder -> IndexedList<'T>.Builder + +// ---- Sorting ---- + +/// +/// Sorts the elements in the order of F# generic comparison. +/// +/// The builder. +/// The same builder. +/// +/// This is a stable sort, as . +/// +[] +val sort : builder : IndexedList<'T>.Builder -> IndexedList<'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. +/// +/// Thrown when the comparison of two elements throws. +/// +/// O(count × log n). This is a stable sort, as +/// : +/// elements that compare equal keep their order, and the elements outside the range keep their places. The function +/// does not wrap the unstable +/// . +/// +[] +val sortRange : index : int -> count : int -> builder : IndexedList<'T>.Builder -> IndexedList<'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. +/// +/// Thrown when the comparison function throws. +/// +/// O(count × log n). This is a stable sort: elements that compare equal keep their order, and the elements outside the +/// range keep their places. +/// +[] +val sortRangeWith : + comparison : ('T -> 'T -> int) -> index : int -> count : int -> builder : IndexedList<'T>.Builder -> IndexedList<'T>.Builder + +/// +/// 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 builder. +/// The same builder. +/// +/// Thrown when or is negative, or the range extends beyond the end of +/// the builder. +/// +/// Thrown when the comparer throws. +/// +/// O(count × log n). This is a stable sort: elements that compare equal keep their order, and the elements outside the +/// range keep their places. +/// +[] +val sortRangeWithComparer : + comparer : IComparer<'T> -> index : int -> count : int -> builder : IndexedList<'T>.Builder -> IndexedList<'T>.Builder + +/// +/// Sorts the elements with a comparison function. +/// +/// The function that compares two elements. +/// The builder. +/// The same builder. +/// +/// This is a stable sort: elements that compare equal keep their order. +/// +[] +val sortWith : comparer : ('T -> 'T -> int) -> builder : IndexedList<'T>.Builder -> IndexedList<'T>.Builder + +/// +/// Sorts the elements with a comparer. +/// +/// The comparer. +/// The builder. +/// The same builder. +/// +/// This is a stable sort: elements that compare equal keep their order. +/// +[] +val sortWithComparer : comparer : IComparer<'T> -> builder : IndexedList<'T>.Builder -> IndexedList<'T>.Builder + +// ---- Conversion ---- + +/// +/// Copies a range of the elements of the builder into an array. +/// +/// The builder. +/// The index of the first copied element in the builder. +/// The array to copy into. +/// The index in the array of the first copied element. +/// The number of elements to copy. +/// Thrown when is . +/// +/// Thrown when , or is negative, or +/// a range extends beyond the end of the builder or of the array. +/// +/// +/// O(log n + count). The function wraps +/// +/// and reports the names of its own parameters. +/// +[] +val blit : source : IndexedList<'T>.Builder -> sourceIndex : int -> target : 'T array -> targetIndex : int -> count : int -> unit + +/// Returns a new array with the elements of the builder. +/// The builder. +/// A copy of the elements. +[] +val toArray : builder : IndexedList<'T>.Builder -> 'T array diff --git a/src/FSharp.Collections.Immutable/IndexedList/IndexedListCE.fs b/src/FSharp.Collections.Immutable/IndexedList/IndexedListCE.fs new file mode 100644 index 00000000..1578a7b7 --- /dev/null +++ b/src/FSharp.Collections.Immutable/IndexedList/IndexedListCE.fs @@ -0,0 +1,78 @@ +namespace FSharp.Collections.Immutable + +open System +open System.Collections.Immutable + +type IndexedListBuffer<'T> = IndexedList<'T>.Builder -> unit + +[] +type IndexedListCE () = + + member inline _.Zero () : IndexedListBuffer<'T> = ignore + + member inline _.Yield (item : 'T) : IndexedListBuffer<'T> = fun builder -> builder.Add item + + member inline _.YieldFrom (items : seq<'T>) : IndexedListBuffer<'T> = + // A default flat list given as a sequence is a null input, as in IndexedList.ofSeq (ADR 0001, D6) + checkNotNullOrDefaultSeq (nameof items) items + fun builder -> builder.AddRange items + + member inline _.YieldFrom (items : 'T array) : IndexedListBuffer<'T> = fun builder -> builder.AddRange items + + member inline _.YieldFrom (items : IndexedList<'T>) : IndexedListBuffer<'T> = fun builder -> builder.AddRange items + + member inline _.YieldFrom (buffer : IndexedListBuffer<'T>) : IndexedListBuffer<'T> = buffer + + member inline _.Combine (first : IndexedListBuffer<'T>, second : IndexedListBuffer<'T>) : IndexedListBuffer<'T> = + fun builder -> + first builder + second builder + + member inline _.Delay ([] delayed : unit -> IndexedListBuffer<'T>) : IndexedListBuffer<'T> = + fun builder -> delayed () builder + + member inline _.For (source : seq<'U>, [] body : 'U -> IndexedListBuffer<'T>) : IndexedListBuffer<'T> = + // A default flat list is a sequence that fails when it is enumerated; the expression rejects it as yield! does + checkNotNullOrDefaultSeq (nameof source) source + + fun builder -> + for item in source do + body item builder + + member inline _.While ([] guard : unit -> bool, body : IndexedListBuffer<'T>) : IndexedListBuffer<'T> = + fun builder -> + while guard () do + body builder + + member inline _.TryWith + (body : IndexedListBuffer<'T>, [] handler : exn -> IndexedListBuffer<'T>) + : IndexedListBuffer<'T> + = + fun builder -> + try + body builder + with ex -> + handler ex builder + + member inline _.TryFinally + (body : IndexedListBuffer<'T>, [] compensation : unit -> unit) + : IndexedListBuffer<'T> + = + fun builder -> + try + body builder + finally + compensation () + + member inline _.Using + (resource : 'Resource, [] body : 'Resource -> IndexedListBuffer<'T>) + : IndexedListBuffer<'T> + = + fun builder -> + use resource = resource + body resource builder + + member _.Run (buffer : IndexedListBuffer<'T>) : IndexedList<'T> = + let builder = ImmutableList.CreateBuilder<'T>() + buffer builder + builder.ToImmutable () diff --git a/src/FSharp.Collections.Immutable/IndexedList/IndexedListCE.fsi b/src/FSharp.Collections.Immutable/IndexedList/IndexedListCE.fsi new file mode 100644 index 00000000..58f49ddf --- /dev/null +++ b/src/FSharp.Collections.Immutable/IndexedList/IndexedListCE.fsi @@ -0,0 +1,73 @@ +namespace FSharp.Collections.Immutable + +open System + +/// +/// The body of an indexedList { } expression: a function that adds the elements to a builder. +/// +type IndexedListBuffer<'T> = IndexedList<'T>.Builder -> unit + +/// +/// The builder of the indexedList { } computation expression. +/// +/// +/// The expression collects its elements in an and +/// returns its tree without a copy (ADR 0001, D7). +/// +[] +type IndexedListCE = + + /// Creates the builder of the expression. + new : unit -> IndexedListCE + + /// An expression without elements. + member inline Zero : unit -> IndexedListBuffer<'T> + + /// Adds an element. + member inline Yield : item : 'T -> IndexedListBuffer<'T> + + /// Adds the elements of a sequence. + /// Thrown when the sequence is or a default flat list. + member inline YieldFrom : items : seq<'T> -> IndexedListBuffer<'T> + + /// Adds the elements of an array. + /// Thrown when the array is . + member inline YieldFrom : items : 'T array -> IndexedListBuffer<'T> + + /// Adds the elements of an indexed list. + /// Thrown when the indexed list is . + member inline YieldFrom : items : IndexedList<'T> -> IndexedListBuffer<'T> + + /// Adds the elements of another expression body. + member inline YieldFrom : buffer : IndexedListBuffer<'T> -> IndexedListBuffer<'T> + + /// Adds the elements of two parts of the expression, in order. + member inline Combine : first : IndexedListBuffer<'T> * second : IndexedListBuffer<'T> -> IndexedListBuffer<'T> + + /// + /// Runs the body of the expression each time the elements are collected. + /// + member inline Delay : [] delayed : (unit -> IndexedListBuffer<'T>) -> IndexedListBuffer<'T> + + /// Adds the elements of the body for each item of a sequence. + /// Thrown when the sequence is or a default flat list. + member inline For : source : seq<'U> * [] body : ('U -> IndexedListBuffer<'T>) -> IndexedListBuffer<'T> + + /// Adds the elements of the body while the guard holds. + member inline While : [] guard : (unit -> bool) * body : IndexedListBuffer<'T> -> IndexedListBuffer<'T> + + /// Handles the exceptions of the body. + member inline TryWith : + body : IndexedListBuffer<'T> * [] handler : (exn -> IndexedListBuffer<'T>) -> IndexedListBuffer<'T> + + /// Runs the compensation after the body. + member inline TryFinally : + body : IndexedListBuffer<'T> * [] compensation : (unit -> unit) -> IndexedListBuffer<'T> + + /// Disposes a resource after the body. + member inline Using : + resource : 'Resource * [] body : ('Resource -> IndexedListBuffer<'T>) -> IndexedListBuffer<'T> + when 'Resource :> IDisposable | null + + /// Collects the elements of the expression. + member Run : buffer : IndexedListBuffer<'T> -> IndexedList<'T> diff --git a/src/FSharp.Collections.Immutable/IndexedList/IndexedListOption.fs b/src/FSharp.Collections.Immutable/IndexedList/IndexedListOption.fs new file mode 100644 index 00000000..63a37afd --- /dev/null +++ b/src/FSharp.Collections.Immutable/IndexedList/IndexedListOption.fs @@ -0,0 +1,101 @@ +[] +module FSharp.Collections.Immutable.Option.IndexedList + +open FSharp.Collections.Immutable + +// The functions delegate to the default module and convert its results, or the results of the callbacks that they pass to +// it, so that both modules have the same checks and exceptions. + +// ---- Creation ---- + +[] +let unfold (generator : 'State -> ('T * 'State) option) (state : 'State) = + let generateStruct state = + match generator state with + | Some (item, next) -> ValueSome (struct (item, next)) + | None -> ValueNone + + FSharp.Collections.Immutable.IndexedList.unfold generateStruct state + +// ---- Access ---- + +[] +let tryExactlyOne (list : IndexedList<'T>) = + FSharp.Collections.Immutable.IndexedList.tryExactlyOne list + |> ValueOption.toOption + +[] +let tryHead (list : IndexedList<'T>) = + FSharp.Collections.Immutable.IndexedList.tryHead list + |> ValueOption.toOption + +[] +let tryItem index (list : IndexedList<'T>) = + FSharp.Collections.Immutable.IndexedList.tryItem index list + |> ValueOption.toOption + +[] +let tryLast (list : IndexedList<'T>) = + FSharp.Collections.Immutable.IndexedList.tryLast list + |> ValueOption.toOption + +[] +let tryTail (list : IndexedList<'T>) = + FSharp.Collections.Immutable.IndexedList.tryTail list + |> ValueOption.toOption + +[] +let tryUncons (list : IndexedList<'T>) = + match FSharp.Collections.Immutable.IndexedList.tryUncons list with + | ValueSome (struct (head, tail)) -> Some (head, tail) + | ValueNone -> None + +[] +let (|Cons|_|) (list : IndexedList<'T>) = tryUncons list + +// ---- Search ---- + +[] +let pick (chooser : 'T -> 'U option) (list : IndexedList<'T>) = + FSharp.Collections.Immutable.IndexedList.pick (fun item -> chooser item |> ValueOption.ofOption) list + +[] +let tryFind predicate (list : IndexedList<'T>) = + FSharp.Collections.Immutable.IndexedList.tryFind predicate list + |> ValueOption.toOption + +[] +let tryFindBack predicate (list : IndexedList<'T>) = + FSharp.Collections.Immutable.IndexedList.tryFindBack predicate list + |> ValueOption.toOption + +[] +let tryFindIndex predicate (list : IndexedList<'T>) = + FSharp.Collections.Immutable.IndexedList.tryFindIndex predicate list + |> ValueOption.toOption + +[] +let tryFindIndexBack predicate (list : IndexedList<'T>) = + FSharp.Collections.Immutable.IndexedList.tryFindIndexBack predicate list + |> ValueOption.toOption + +[] +let tryFindIndexBackRange startIndex count predicate (list : IndexedList<'T>) = + FSharp.Collections.Immutable.IndexedList.tryFindIndexBackRange startIndex count predicate list + |> ValueOption.toOption + +[] +let tryFindIndexRange startIndex count predicate (list : IndexedList<'T>) = + FSharp.Collections.Immutable.IndexedList.tryFindIndexRange startIndex count predicate list + |> ValueOption.toOption + +[] +let tryPick (chooser : 'T -> 'U option) (list : IndexedList<'T>) = + FSharp.Collections.Immutable.IndexedList.tryPick (fun item -> chooser item |> ValueOption.ofOption) list + |> ValueOption.toOption + +// ---- Transformation ---- + +[] +let choose (chooser : 'T -> 'U option) (list : IndexedList<'T>) = + FSharp.Collections.Immutable.IndexedList.choose (fun item -> chooser item |> ValueOption.ofOption) list diff --git a/src/FSharp.Collections.Immutable/IndexedList/IndexedListOption.fsi b/src/FSharp.Collections.Immutable/IndexedList/IndexedListOption.fsi new file mode 100644 index 00000000..2a25aa3a --- /dev/null +++ b/src/FSharp.Collections.Immutable/IndexedList/IndexedListOption.fsi @@ -0,0 +1,240 @@ +/// +/// 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, and the checks and the +/// exceptions of the default function. +/// +[] +module FSharp.Collections.Immutable.Option.IndexedList + +open FSharp.Collections.Immutable + +// ---- Creation ---- + +/// +/// Creates an indexed 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 indexed list. +/// +/// O(n). +/// +[] +val unfold : generator : ('State -> ('T * 'State) option) -> state : 'State -> IndexedList<'T> + +// ---- Access ---- + +/// +/// Returns the only element of the indexed list, or None when it does not have exactly one element. +/// +/// The input indexed list. +/// The only element, or None. +/// +/// O(1). +/// +[] +val tryExactlyOne : list : IndexedList<'T> -> 'T option + +/// +/// Returns the first element of the indexed list, or None when it is empty. +/// +/// The input indexed list. +/// The first element, or None. +/// +/// O(log n). +/// +[] +val tryHead : list : IndexedList<'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 indexed list. +/// The element at the index, or None. +/// +/// O(log n). +/// +[] +val tryItem : index : int -> list : IndexedList<'T> -> 'T option + +/// +/// Returns the last element of the indexed list, or None when it is empty. +/// +/// The input indexed list. +/// The last element, or None. +/// +/// O(log n). +/// +[] +val tryLast : list : IndexedList<'T> -> 'T option + +/// +/// Returns the indexed list without its first element, or None when it is empty. +/// +/// The input indexed list. +/// The elements after the first one, or None. +/// +/// O(log n). +/// +[] +val tryTail : list : IndexedList<'T> -> IndexedList<'T> option + +/// +/// Returns the first element and the other elements, or None when the indexed list is empty. +/// +/// The input indexed list. +/// The first element and the elements after it, or None. +/// +/// O(log n). +/// +[] +val tryUncons : list : IndexedList<'T> -> ('T * IndexedList<'T>) option + +/// +/// Matches an indexed list with a first element, and returns that element and the other elements. +/// +/// The input indexed list. +/// The first element and the elements after it, or None for an empty indexed list. +/// +/// O(log n). +/// +[] +val (|Cons|_|) : list : IndexedList<'T> -> ('T * IndexedList<'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 indexed list. +/// The first chosen value. +/// Thrown when the chooser returns None for every element. +/// +/// O(n). +/// +[] +val pick : chooser : ('T -> 'U option) -> list : IndexedList<'T> -> 'U + +/// +/// Returns the first element that satisfies the predicate, or None. +/// +/// The function to test the elements. +/// The input indexed list. +/// The first element that satisfies the predicate, or None. +/// +/// O(n). +/// +[] +val tryFind : predicate : ('T -> bool) -> list : IndexedList<'T> -> 'T option + +/// +/// Returns the last element that satisfies the predicate, or None. +/// +/// The function to test the elements. +/// The input indexed list. +/// The last element that satisfies the predicate, or None. +/// +/// O(n). +/// +[] +val tryFindBack : predicate : ('T -> bool) -> list : IndexedList<'T> -> 'T option + +/// +/// Returns the index of the first element that satisfies the predicate, or None. +/// +/// The function to test the elements. +/// The input indexed list. +/// The index of the first element that satisfies the predicate, or None. +/// +/// O(n). +/// +[] +val tryFindIndex : predicate : ('T -> bool) -> list : IndexedList<'T> -> int option + +/// +/// Returns the index of the last element that satisfies the predicate, or None. +/// +/// The function to test the elements. +/// The input indexed list. +/// The index of the last element that satisfies the predicate, or None. +/// +/// O(n). +/// +[] +val tryFindIndexBack : predicate : ('T -> bool) -> list : IndexedList<'T> -> int option + +/// +/// Returns the index of the last element of a range that satisfies the predicate, or None. The search walks +/// backward from the index of the last element of the range. +/// +/// The index of the last element of the range, where the search starts. +/// The number of elements of the range, which ends at . +/// The function to test the elements. +/// The input indexed list. +/// +/// The index in the indexed list, not in the range, of the last element of the range that satisfies the predicate, or +/// None. +/// +/// +/// Thrown when the range does not lie in the indexed list, as in +/// . +/// +/// +/// O(log n + count). +/// +[] +val tryFindIndexBackRange : startIndex : int -> count : int -> predicate : ('T -> bool) -> list : IndexedList<'T> -> int option + +/// +/// Returns the index of the first element of a range that satisfies the predicate, or None. +/// +/// The index of the first element of the range. +/// The number of elements of the range. +/// The function to test the elements. +/// The input indexed list. +/// +/// The index in the indexed list, not in the range, of the first element of the range that satisfies the predicate, or +/// None. +/// +/// +/// Thrown when the range does not lie in the indexed list, as in +/// . +/// +/// +/// O(log n + count). +/// +[] +val tryFindIndexRange : startIndex : int -> count : int -> predicate : ('T -> bool) -> list : IndexedList<'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 indexed list. +/// The first chosen value, or None. +/// +/// O(n). +/// +[] +val tryPick : chooser : ('T -> 'U option) -> list : IndexedList<'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 indexed list. +/// The chosen values. +/// +/// O(n). +/// +[] +val choose : chooser : ('T -> 'U option) -> list : IndexedList<'T> -> IndexedList<'U> diff --git a/src/FSharp.Collections.Immutable/IndexedList/IndexedListTuple.fs b/src/FSharp.Collections.Immutable/IndexedList/IndexedListTuple.fs new file mode 100644 index 00000000..525a2f5e --- /dev/null +++ b/src/FSharp.Collections.Immutable/IndexedList/IndexedListTuple.fs @@ -0,0 +1,137 @@ +[] +module FSharp.Collections.Immutable.Tuple.IndexedList + +open System.Collections.Immutable +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. + +// ---- 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.IndexedList.unfold generateStruct state + +// ---- Access ---- + +[] +let tryUncons (list : IndexedList<'T>) = + match FSharp.Collections.Immutable.IndexedList.tryUncons list with + | ValueSome (struct (head, tail)) -> ValueSome (head, tail) + | ValueNone -> ValueNone + +[] +let uncons (list : IndexedList<'T>) = + let struct (head, tail) = FSharp.Collections.Immutable.IndexedList.uncons list + head, tail + +[] +[] +let (|Cons|_|) (list : IndexedList<'T>) = tryUncons list + +// ---- Transformation ---- + +[] +let allPairs (list1 : IndexedList<'T1>) (list2 : IndexedList<'T2>) = + Arrays.allPairs referencePair (Snapshot.ofSeq (nameof list1) list1) (Snapshot.ofSeq (nameof list2) list2) + |> ImmutableList.CreateRange + +[] +let indexed (list : IndexedList<'T>) = + Arrays.indexed referencePair (Snapshot.ofSeq (nameof list) list) + |> ImmutableList.CreateRange + +[] +let pairwise (list : IndexedList<'T>) = + Arrays.pairwise referencePair (Snapshot.ofSeq (nameof list) list) + |> ImmutableList.CreateRange + +[] +let unzip (list : IndexedList<'T1 * 'T2>) = + let struct (first, second) = + Arrays.unzip (fun (item1, item2) -> struct (item1, item2)) (Snapshot.ofSeq (nameof list) list) + + ImmutableList.CreateRange first, ImmutableList.CreateRange second + +[] +let unzip3 (list : IndexedList<'T1 * 'T2 * 'T3>) = + let struct (first, second, third) = + Arrays.unzip3 (fun (item1, item2, item3) -> struct (item1, item2, item3)) (Snapshot.ofSeq (nameof list) list) + + ImmutableList.CreateRange first, ImmutableList.CreateRange second, ImmutableList.CreateRange third + +[] +let zip (list1 : IndexedList<'T1>) (list2 : IndexedList<'T2>) = + checkNotNull (nameof list1) list1 + checkNotNull (nameof list2) list2 + ListChecks.sameLengths (nameof list1) list1.Count (nameof list2) list2.Count + + Arrays.zip referencePair (Snapshot.ofSeq (nameof list1) list1) (Snapshot.ofSeq (nameof list2) list2) + |> ImmutableList.CreateRange + +[] +let zip3 (list1 : IndexedList<'T1>) (list2 : IndexedList<'T2>) (list3 : IndexedList<'T3>) = + checkNotNull (nameof list1) list1 + checkNotNull (nameof list2) list2 + checkNotNull (nameof list3) list3 + ListChecks.zip3 (nameof list1) list1.Count (nameof list2) list2.Count (nameof list3) list3.Count + + Arrays.zip3 + referenceTriple + (Snapshot.ofSeq (nameof list1) list1) + (Snapshot.ofSeq (nameof list2) list2) + (Snapshot.ofSeq (nameof list3) list3) + |> ImmutableList.CreateRange + +// ---- Folding ---- + +[] +let mapFold (mapping : 'State -> 'T -> 'Result * 'State) (state : 'State) (list : IndexedList<'T>) = + let mapStruct state item = + let result, next = mapping state item + struct (result, next) + + let struct (results, final) = FSharp.Collections.Immutable.IndexedList.mapFold mapStruct state list + results, final + +[] +let mapFoldBack (mapping : 'T -> 'State -> 'Result * 'State) (list : IndexedList<'T>) (state : 'State) = + let mapStruct item state = + let result, next = mapping item state + struct (result, next) + + let struct (results, final) = FSharp.Collections.Immutable.IndexedList.mapFoldBack mapStruct list state + results, final + +// ---- Partitioning and grouping ---- + +[] +let countBy (projection : 'T -> 'Key) (list : IndexedList<'T>) = + Arrays.countBy referencePair projection (Snapshot.ofSeq (nameof list) list) + |> ImmutableList.CreateRange + +[] +let groupBy (projection : 'T -> 'Key) (list : IndexedList<'T>) = + Arrays.groupBy (fun key items -> key, ImmutableList.CreateRange items) projection (Snapshot.ofSeq (nameof list) list) + |> ImmutableList.CreateRange + +[] +let partition predicate (list : IndexedList<'T>) = + let struct (accepted, rejected) = FSharp.Collections.Immutable.IndexedList.partition predicate list + accepted, rejected + +[] +let partitionWith (partitioner : 'T -> Choice<'T1, 'T2>) (list : IndexedList<'T>) = + let struct (first, second) = FSharp.Collections.Immutable.IndexedList.partitionWith partitioner list + first, second + +[] +let splitAt index (list : IndexedList<'T>) = + let struct (before, after) = FSharp.Collections.Immutable.IndexedList.splitAt index list + before, after diff --git a/src/FSharp.Collections.Immutable/IndexedList/IndexedListTuple.fsi b/src/FSharp.Collections.Immutable/IndexedList/IndexedListTuple.fsi new file mode 100644 index 00000000..0624b518 --- /dev/null +++ b/src/FSharp.Collections.Immutable/IndexedList/IndexedListTuple.fsi @@ -0,0 +1,210 @@ +/// +/// 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 +/// . Each function has the checks and the exceptions of the default +/// function. +/// +[] +module FSharp.Collections.Immutable.Tuple.IndexedList + +open FSharp.Collections.Immutable + +// ---- Creation ---- + +/// +/// Creates an indexed 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 indexed list. +/// +/// O(n). +/// +[] +val unfold : generator : ('State -> ('T * 'State) voption) -> state : 'State -> IndexedList<'T> + +// ---- Access ---- + +/// +/// Returns the first element and the other elements, or ValueNone when the indexed list is empty. +/// +/// The input indexed list. +/// The first element and the elements after it, or ValueNone. +/// +/// O(log n). +/// +[] +val tryUncons : list : IndexedList<'T> -> ('T * IndexedList<'T>) voption + +/// Returns the first element and the other elements. +/// The input indexed list. +/// The first element and the elements after it. +/// Thrown when the indexed list is empty. +/// O(log n). +[] +val uncons : list : IndexedList<'T> -> 'T * IndexedList<'T> + +/// +/// Matches an indexed list with a first element, and returns that element and the other elements. +/// +/// The input indexed list. +/// The first element and the elements after it, or ValueNone for an empty indexed list. +/// +/// O(log n). A struct partial pattern, so that a match allocates no option. +/// +[] +[] +val (|Cons|_|) : list : IndexedList<'T> -> ('T * IndexedList<'T>) voption + +// ---- Transformation ---- + +/// +/// Returns every element of the first indexed list paired with every element of the second one. +/// +/// The first indexed list. +/// The second indexed list. +/// The pairs, in the order of the first indexed list and then of the second one. +/// +/// O(n × m). +/// +[] +val allPairs : list1 : IndexedList<'T1> -> list2 : IndexedList<'T2> -> IndexedList<'T1 * 'T2> + +/// Returns every element with its index. +/// The input indexed list. +/// The pairs of index and element. +/// O(n). +[] +val indexed : list : IndexedList<'T> -> IndexedList + +/// Returns every element with its successor. +/// The input indexed list. +/// The pairs of neighbours; empty for fewer than two elements. +/// O(n). +[] +val pairwise : list : IndexedList<'T> -> IndexedList<'T * 'T> + +/// Splits an indexed list of pairs into two indexed lists. +/// The input indexed list. +/// The first components and the second components. +/// O(n). +[] +val unzip : list : IndexedList<'T1 * 'T2> -> IndexedList<'T1> * IndexedList<'T2> + +/// Splits an indexed list of triples into three indexed lists. +/// The input indexed list. +/// The first, the second and the third components. +/// O(n). +[] +val unzip3 : list : IndexedList<'T1 * 'T2 * 'T3> -> IndexedList<'T1> * IndexedList<'T2> * IndexedList<'T3> + +/// Combines two indexed lists into an indexed list of pairs. +/// The first indexed list. +/// The second indexed list. +/// The pairs of elements at the same index. +/// Thrown when the indexed lists have different lengths. +/// O(n). +[] +val zip : list1 : IndexedList<'T1> -> list2 : IndexedList<'T2> -> IndexedList<'T1 * 'T2> + +/// +/// Combines three indexed lists into an indexed list of triples. +/// +/// The first indexed list. +/// The second indexed list. +/// The third indexed list. +/// The triples of elements at the same index. +/// Thrown when the indexed lists have different lengths. +/// +/// O(n). +/// +[] +val zip3 : list1 : IndexedList<'T1> -> list2 : IndexedList<'T2> -> list3 : IndexedList<'T3> -> IndexedList<'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 indexed list. +/// The results and the final state. +/// +/// O(n). +/// +[] +val mapFold : + mapping : ('State -> 'T -> 'Result * 'State) -> state : 'State -> list : IndexedList<'T> -> IndexedList<'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 indexed 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) -> list : IndexedList<'T> -> state : 'State -> IndexedList<'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 indexed 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) -> list : IndexedList<'T> -> IndexedList<'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 indexed 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) -> list : IndexedList<'T> -> IndexedList<'Key * IndexedList<'T>> when 'Key : equality + +/// Splits the elements by a predicate. +/// The function to test the elements. +/// The input indexed list. +/// The elements that satisfy the predicate and the other ones, both in their order. +/// O(n). +[] +val partition : predicate : ('T -> bool) -> list : IndexedList<'T> -> IndexedList<'T> * IndexedList<'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 indexed 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>) -> list : IndexedList<'T> -> IndexedList<'T1> * IndexedList<'T2> + +/// +/// Splits the indexed list at an index. +/// +/// The index of the first element of the second part. +/// The input indexed 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 log n). Each part is a new tree, unless it is empty or the whole indexed list. +/// +[] +val splitAt : index : int -> list : IndexedList<'T> -> IndexedList<'T> * IndexedList<'T> diff --git a/src/FSharp.Collections.Immutable/IndexedList/IndexedListTypes.fs b/src/FSharp.Collections.Immutable/IndexedList/IndexedListTypes.fs new file mode 100644 index 00000000..4db18456 --- /dev/null +++ b/src/FSharp.Collections.Immutable/IndexedList/IndexedListTypes.fs @@ -0,0 +1,23 @@ +namespace FSharp.Collections.Immutable + +open System.Collections.Immutable + +// The abbreviations have their own file because the modules of the folder are root modules, which cannot declare a type +// of the namespace; a file of abbreviations needs no signature file (ADR 0001, D2). + +/// +/// An immutable list, . +/// +/// +/// The list is a balanced binary tree: reading, inserting, replacing or removing the element at an index takes O(log n). +/// +type IndexedList<'T> = ImmutableList<'T> + +/// +/// An immutable list of any implementation, . +/// +/// +/// and +/// implement the interface. +/// +type IIndexedList<'T> = IImmutableList<'T> diff --git a/src/FSharp.Collections.Immutable/IndexedSeq.fs b/src/FSharp.Collections.Immutable/IndexedSeq.fs deleted file mode 100644 index 7d388b4d..00000000 --- a/src/FSharp.Collections.Immutable/IndexedSeq.fs +++ /dev/null @@ -1,17 +0,0 @@ -namespace FSharp.Collections.Immutable - -type IIndexedSeq<'T> = System.Collections.Generic.IReadOnlyList<'T> - -module IndexedSeq = - - let check (seq : IIndexedSeq<_>) = checkNotNull (nameof seq) seq - - let item index seq = - check seq - seq.[index] - - let length seq = - check seq - seq.Count - -module ReadOnlyList = IndexedSeq From 0ed798533631aa2dd9791e8e420addc066c1748c Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 13:21:27 +0200 Subject: [PATCH 2/4] test: contracts of `IndexedList` and `IIndexedList`, and the stable sorting contract `StableSortContract` checks that `sort`, `sortBy`, `sortByDescending`, `sortDescending`, `sortWith` and `sortWithComparer` keep the order of equal elements, as the `List` functions do. The pairs of `Inputs.pairs` show it for the sorts by key; `sort` and `sortDescending` run on `KeyedItem`, whose comparison looks at a key only. The sequence-like, creation, index-based and stable sorting contracts run for `IndexedList`, and all of them but the creation contract for `IIndexedList`. Further tests cover null inputs, shared trees, `cons`, `uncons` and the patterns, `binarySearch`, `blit`, the order in which the functions of two lists call their callbacks before they throw, the complete messages of the `List` exceptions, the implementation that `IIndexedList` keeps, the builder functions, `indexedList { }` and the `Option` and `Tuple` namespaces. Two shared contracts now serve the modules that mirror `List` as well. `ofArray` of a null array is compared with `Array.toList`, because `List.ofArray` reads a null array without a check. `removeManyAt` of a count that is not positive is compared with what the mirrored function returns or throws, because `List.removeManyAt` removes nothing where `Array.removeManyAt` copies by the count. Co-Authored-By: Claude Opus 5.5 --- .../Contracts/AllocationAssert.fs | 37 + .../Contracts/CostContract.fs | 221 ++++ .../Contracts/IndexedContract.fs | 114 ++- .../Contracts/Inputs.fs | 12 + .../Contracts/SeqCreationContract.fs | 16 +- .../SeqLike/SeqLikePartitioningContract.fs | 18 + .../SeqLike/SeqLikeTransformationContract.fs | 71 ++ .../Contracts/StableSortContract.fs | 351 +++++++ .../FSharp.Collections.Immutable.Tests.fsproj | 17 + .../IndexedList/IIndexedListOps.fs | 242 +++++ .../IndexedList/IIndexedListOptionTests.fs | 194 ++++ .../IndexedList/IIndexedListTests.fs | 688 +++++++++++++ .../IndexedList/IIndexedListTupleTests.fs | 294 ++++++ .../IndexedList/IndexedListBuilderTests.fs | 951 ++++++++++++++++++ .../IndexedListComputationExpressionTests.fs | 236 +++++ .../IndexedList/IndexedListContractTests.fs | 59 ++ .../IndexedList/IndexedListOps.fs | 266 +++++ .../IndexedList/IndexedListOptionTests.fs | 266 +++++ .../IndexedList/IndexedListRangeTests.fs | 465 +++++++++ .../IndexedList/IndexedListTests.fs | 911 +++++++++++++++++ .../IndexedList/IndexedListTupleTests.fs | 316 ++++++ .../IndexedList/ListMemberCases.fs | 371 +++++++ .../IndexedList/ListMembersContract.fs | 366 +++++++ 23 files changed, 6477 insertions(+), 5 deletions(-) create mode 100644 tests/FSharp.Collections.Immutable.Tests/Contracts/AllocationAssert.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Contracts/CostContract.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Contracts/StableSortContract.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/IndexedList/IIndexedListOps.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/IndexedList/IIndexedListOptionTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/IndexedList/IIndexedListTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/IndexedList/IIndexedListTupleTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListBuilderTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListComputationExpressionTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListContractTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListOps.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListOptionTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListRangeTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListTupleTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/IndexedList/ListMemberCases.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/IndexedList/ListMembersContract.fs diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/AllocationAssert.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/AllocationAssert.fs new file mode 100644 index 00000000..433199f0 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/AllocationAssert.fs @@ -0,0 +1,37 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// +/// Measures what an action allocates, for the tests that bound the cost of a function. +/// +/// +/// The bytes are those that the current thread allocates, as +/// counts them, so the tests that run in parallel do not +/// disturb each other. A limit should leave room for the closures and the enumerators of the action and stay far below +/// what the slow implementation allocates, so that it tells the two apart in a Debug build as well. +/// +[] +type AllocationAssert = + + /// + /// The bytes that an action allocates on the current thread when it runs a second time, after the first run has + /// compiled the code. + /// + /// The action to measure. + /// The number of allocated bytes. + static member AllocatedBy (action : unit -> unit) : int64 = + action () + let before = GC.GetAllocatedBytesForCurrentThread () + action () + GC.GetAllocatedBytesForCurrentThread () - before + + /// + /// Asserts that an action allocates fewer bytes than a limit. + /// + /// The number of bytes that the action must stay below. + /// The action to measure. + /// What the action had to do, as the message of the failure. + static member LessThan (limit : int64, action : unit -> unit, message : string) = + Assert.IsLessThan (limit, AllocationAssert.AllocatedBy action, message) diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/CostContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/CostContract.fs new file mode 100644 index 00000000..9bc8f093 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/CostContract.fs @@ -0,0 +1,221 @@ +namespace FSharp.Collections.Immutable.Tests + +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// +/// The functions of a module whose cost the tests of +/// bound, over integers. +/// +type CostOps<'C> = { + /// The name of the module, for the messages of the assertions. + Name : string + /// Creates a collection that enumerates the given elements in their order. + OfSeq : seq -> 'C + /// The greatest element. + Max : 'C -> int + /// The first element with the greatest result of the projection. + MaxBy : (int -> int) -> 'C -> int + /// The smallest element. + Min : 'C -> int + /// The first element with the smallest result of the projection. + MinBy : (int -> int) -> 'C -> int + /// Whether any pair of elements at the same index satisfies the predicate. + Exists2 : (int -> int -> bool) -> 'C -> 'C -> bool + /// Whether all pairs of elements at the same index satisfy the predicate. + Forall2 : (int -> int -> bool) -> 'C -> 'C -> bool + /// The comparison of two collections element by element with a comparer. + CompareWith : (int -> int -> int) -> 'C -> 'C -> int + /// The number of elements of the concatenation of the collections. + ConcatLength : seq<'C> -> int + /// The number of elements of the concatenation of the collections that a function returns for the elements. + CollectLength : (int -> 'C) -> 'C -> int +} + +/// +/// The bounds on what the functions of a module allocate for large collections: the functions that compare or search +/// must not box the elements, the ones that can decide before the end must not copy the collections, and the ones that +/// concatenate long indexed lists must share their nodes instead of copying their elements. +/// +/// +/// Each limit is a tenth of what the implementation that it excludes allocates, or less. The tests create the +/// collections before they measure, and measure the second run of a function (see +/// ). +/// +[] +type CostContract<'C> (ops : CostOps<'C>) = + + /// The integers of the comparisons. A comparison that boxes them allocates two boxes of 24 bytes for each of the + /// 10 000, 480 KB; the function itself allocates a few hundred bytes. + static let comparedIntegers = Array.init 10_000 (fun index -> (index * 7919) % 10_007) + + static let boxingLimit = 100_000L + + /// Whether the optimizer of the F# compiler is on. The inline functions are compiled at the call site, and the + /// optimizer is what compiles the comparison of two integers into a comparison of integers: with the optimizer off, as + /// in a Debug build, it is a call of the generic operator, which boxes them, as it is for the inline functions of + /// FSharp.Core. +#if DEBUG + static let optimized = false +#else + static let optimized = true +#endif + + + /// The length of the collections that a function reads a few elements of: a copy of two of them is 800 KB. + static let walkedLength = 100_000 + + static let copyLimit = 50_000L + + /// The collections that a concatenation joins: 100 of 1000 elements, for which a copy allocates 5.8 MB. + static let joinedCount = 100 + + static let joinedLength = 1_000 + + static let joinLimit = 1_000_000L + + /// Asserts that a comparison of 10 000 integers boxes none of them; the test is inconclusive when the optimizer is off. + member private _.AssertNoBoxes (action : unit -> unit, message : string) = + if optimized then + AllocationAssert.LessThan (boxingLimit, action, message) + else + Assert.Inconclusive "The comparison of integers boxes them while the optimizer of the compiler is off" + + [] + member this.``max allocates no box for an integer in a comparison`` () = + let collection = ops.OfSeq comparedIntegers + + Assert.AreEqual(Array.max comparedIntegers, ops.Max collection, $"%s{ops.Name}.max must return the greatest element") + + this.AssertNoBoxes ( + (fun () -> ops.Max collection |> ignore), + $"%s{ops.Name}.max of 10 000 integers must not box them in the comparisons" + ) + + [] + member this.``maxBy allocates no box for a key in a comparison`` () = + let collection = ops.OfSeq comparedIntegers + + Assert.AreEqual( + Array.maxBy (fun item -> -item) comparedIntegers, + ops.MaxBy (fun item -> -item) collection, + $"%s{ops.Name}.maxBy must return the first element with the greatest result" + ) + + this.AssertNoBoxes ( + (fun () -> ops.MaxBy (fun item -> -item) collection |> ignore), + $"%s{ops.Name}.maxBy of 10 000 integers must not box the keys in the comparisons" + ) + + [] + member this.``min allocates no box for an integer in a comparison`` () = + let collection = ops.OfSeq comparedIntegers + + Assert.AreEqual(Array.min comparedIntegers, ops.Min collection, $"%s{ops.Name}.min must return the smallest element") + + this.AssertNoBoxes ( + (fun () -> ops.Min collection |> ignore), + $"%s{ops.Name}.min of 10 000 integers must not box them in the comparisons" + ) + + [] + member this.``minBy allocates no box for a key in a comparison`` () = + let collection = ops.OfSeq comparedIntegers + + Assert.AreEqual( + Array.minBy (fun item -> -item) comparedIntegers, + ops.MinBy (fun item -> -item) collection, + $"%s{ops.Name}.minBy must return the first element with the smallest result" + ) + + this.AssertNoBoxes ( + (fun () -> ops.MinBy (fun item -> -item) collection |> ignore), + $"%s{ops.Name}.minBy of 10 000 integers must not box the keys in the comparisons" + ) + + [] + member _.``exists2 stops at the first pair that satisfies the predicate`` () = + let first = ops.OfSeq (Array.init walkedLength id) + let second = ops.OfSeq (Array.init walkedLength id) + + Assert.IsTrue ( + ops.Exists2 (fun left right -> left = right) first second, + $"%s{ops.Name}.exists2 must find the first pair" + ) + + AllocationAssert.LessThan ( + copyLimit, + (fun () -> + ops.Exists2 (fun left right -> left = right) first second + |> ignore + ), + $"%s{ops.Name}.exists2 must not copy the collections when the first pair satisfies the predicate" + ) + + [] + member _.``forall2 stops at the first pair that does not satisfy the predicate`` () = + let first = ops.OfSeq (Array.init walkedLength id) + let second = ops.OfSeq (Array.init walkedLength id) + + Assert.IsFalse ( + ops.Forall2 (fun left right -> left <> right) first second, + $"%s{ops.Name}.forall2 must find that the first pair does not satisfy the predicate" + ) + + AllocationAssert.LessThan ( + copyLimit, + (fun () -> + ops.Forall2 (fun left right -> left <> right) first second + |> ignore + ), + $"%s{ops.Name}.forall2 must not copy the collections when the first pair does not satisfy the predicate" + ) + + [] + member _.``compareWith stops at the first pair that the comparer finds different`` () = + let first = ops.OfSeq (Array.init walkedLength (fun index -> index + 1)) + let second = ops.OfSeq (Array.init walkedLength id) + + Assert.AreEqual( + 1, + ops.CompareWith compare first second, + $"%s{ops.Name}.compareWith must return the first difference" + ) + + AllocationAssert.LessThan ( + copyLimit, + (fun () -> ops.CompareWith compare first second |> ignore), + $"%s{ops.Name}.compareWith must not copy the collections when the first pair differs" + ) + + [] + member _.``concat joins long collections instead of copying their elements`` () = + let collections = Array.init joinedCount (fun _ -> ops.OfSeq (Array.init joinedLength id)) + + Assert.AreEqual( + joinedCount * joinedLength, + ops.ConcatLength collections, + $"%s{ops.Name}.concat must keep every element" + ) + + AllocationAssert.LessThan ( + joinLimit, + (fun () -> ops.ConcatLength collections |> ignore), + $"%s{ops.Name}.concat of 100 collections of 1000 elements must not copy their elements" + ) + + [] + member _.``collect joins the long collections that the function returns instead of copying their elements`` () = + let collection = ops.OfSeq (Array.init joinedLength id) + let source = ops.OfSeq (Array.init joinedCount id) + + Assert.AreEqual( + joinedCount * joinedLength, + ops.CollectLength (fun _ -> collection) source, + $"%s{ops.Name}.collect must keep every element" + ) + + AllocationAssert.LessThan ( + joinLimit, + (fun () -> ops.CollectLength (fun _ -> collection) source |> ignore), + $"%s{ops.Name}.collect of 100 collections of 1000 elements must not copy their elements" + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/IndexedContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/IndexedContract.fs index 46f0d8d2..96ba7421 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/IndexedContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/IndexedContract.fs @@ -1,12 +1,14 @@ namespace FSharp.Collections.Immutable.Tests +open System 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). +/// The contract of the index-based functions of , +/// and +/// (ADR 0001, Appendix A). /// /// /// The tests run on the input with several elements; the indices of their rows are computed from its length. @@ -22,6 +24,13 @@ type IndexedContract<'C> (ops : IndexedOps<'C>) = member _.GetHashCode item = abs item } + /// The values of a kind that insertManyAt rejects: a null sequence, or a default flat list given as a sequence. + static let invalidValues (kind : string) : seq = + if kind = "null sequence" then + Unchecked.defaultof> + else + Inputs.DefaultFlatListAsSeq() + /// The collection of the input with several elements and the elements that it enumerates. let several () = let collection = ops.OfSeq Inputs.Several @@ -59,6 +68,19 @@ type IndexedContract<'C> (ops : IndexedOps<'C>) = struct (index, values) } + /// The sequences that insertManyAt rejects, by kind: a null sequence and a default flat list given as a sequence. + static member InvalidValueKinds = seq { + "null sequence" + "default flat list" + } + + /// Each index to insert at that is out of range with each kind of the values that insertManyAt rejects. + static member InsertionIndicesOutOfRangeAndInvalidValues = seq { + for index in IndexedContract<'C>.InsertionIndicesOutOfRange do + for kind in IndexedContract<'C>.InvalidValueKinds do + struct (index, kind) + } + /// The indices to remove at: the first element, one inside and the last one. static member RemovalIndices = seq { 0 @@ -74,12 +96,32 @@ type IndexedContract<'C> (ops : IndexedOps<'C>) = 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. + /// The ranges that are out of range, as an index and a count: before the first element and beyond the last one. static member RemovalRangesOutOfRange = seq { struct (-1, 1) struct (Inputs.Several.Length - 1, 2) + } + + /// The ranges with a count that is not positive, as an index and a count. `List` removes nothing for them while the + /// index plus the count stays within the list, and throws otherwise; `Array` copies by the count and throws or returns + /// what the copies give. + static member NonPositiveCountRanges = seq { + struct (0, 0) struct (0, -1) + struct (3, -2) + struct (10, -5) + struct (20, -5) + } + + /// The ranges whose end, the index plus the count, overflows `Int32.MaxValue`, as an index and a count, and the count + /// that reaches `Int32.MaxValue` from the first element. `List` walks the first index + count elements: a sum that + /// wraps around to a negative number removes nothing, and a sum that stays positive and passes the end throws; + /// `Array` throws for both. + static member OverflowingRanges = seq { + struct (1, Int32.MaxValue) + struct (Inputs.Several.Length - 1, Int32.MaxValue) + struct (Int32.MaxValue, 1) + struct (0, Int32.MaxValue) } /// The indices of the first and the last element. @@ -230,6 +272,31 @@ type IndexedContract<'C> (ops : IndexedOps<'C>) = "insertManyAt at an index out of range must throw" ) + [.InvalidValueKinds)>] + member _.``insertManyAt rejects null values and a default flat list given as the values`` (kind : string) = + let struct (collection, _) = several () + + let ex = + Assert.ThrowsExactly( + Action (fun () -> ops.InsertManyAt 0 (invalidValues kind) collection |> ignore), + $"%s{ops.Name}.insertManyAt must reject a %s{kind}" + ) + + Assert.AreEqual("values", ex.ParamName, $"%s{ops.Name}.insertManyAt must name the values for a %s{kind}") + + [.InsertionIndicesOutOfRangeAndInvalidValues)>] + member this.``insertManyAt checks the index before the values as FSharp.Core does`` (index : int, kind : string) = + let struct (collection, elements) = several () + let values = invalidValues kind + + // The oracles check the index first and never enumerate the default flat list + this.ThrowsLikeFSharpCore ( + (fun () -> Array.insertManyAt index values elements), + (fun () -> List.insertManyAt index values (List.ofArray elements)), + (fun () -> ops.InsertManyAt index values collection), + $"insertManyAt at an index out of range with a %s{kind} must throw for the index" + ) + [.RemovalIndices)>] member _.``removeAt removes the element at the index`` (index : int) = let struct (collection, elements) = several () @@ -272,6 +339,45 @@ type IndexedContract<'C> (ops : IndexedOps<'C>) = "removeManyAt of a range out of range must throw" ) + [.NonPositiveCountRanges)>] + member this.``removeManyAt of a count that is not positive does what FSharp.Core does`` (index : int, count : int) = + this.RemovesManyAtLikeFSharpCore (index, count) + + [.OverflowingRanges)>] + member this.``removeManyAt of a range whose end overflows does what FSharp.Core does`` (index : int, count : int) = + this.RemovesManyAtLikeFSharpCore (index, count) + + /// Asserts that removeManyAt returns what the mirrored FSharp.Core function returns for the range, or throws what it + /// throws. + member private this.RemovesManyAtLikeFSharpCore (index : int, count : int) = + let struct (collection, elements) = several () + let arrayOracle () = Array.removeManyAt index count elements + + let listOracle () = + List.removeManyAt index count (List.ofArray elements) + |> List.toArray + + let outcome = + try + Ok (this.Mirrored (arrayOracle, listOracle)) + with ex -> + Error ex + + match outcome with + | Ok expected -> + CollectionAssert.AreEqual ( + expected, + ops.ToArray (ops.RemoveManyAt index count collection), + $"%s{ops.Name}.removeManyAt must return what FSharp.Core returns" + ) + | Error _ -> + this.ThrowsLikeFSharpCore ( + arrayOracle, + listOracle, + (fun () -> ops.RemoveManyAt index count collection), + "removeManyAt must throw what FSharp.Core throws" + ) + [.FirstAndLastIndices)>] member _.``updateAt replaces the element at the index`` (index : int) = let struct (collection, elements) = several () diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/Inputs.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/Inputs.fs index bc0809f0..54022968 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/Inputs.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/Inputs.fs @@ -1,5 +1,7 @@ namespace FSharp.Collections.Immutable.Tests +open FSharp.Collections.Immutable + /// The inputs that the contract tests run the functions on. /// Expectations are computed with FSharp.Core over the order in which the collection enumerates its elements (ADR 0001, D8). /// Every property builds new arrays on each read: MSTest hands the arrays of a row source to the tests as they are, so an @@ -45,3 +47,13 @@ type Inputs = struct (0, 'e') struct (2, 'f') |] + + /// Pairs like `Pairs` for the tests of a stable sort: 20 of them, with the keys 0, 1, 2, 0, 1, … and the tags 'a' to 't'. + /// The sort of .NET (`Array.Sort`, behind `ImmutableList.Sort`) orders up to 16 elements by insertion, which keeps the + /// order of equal keys, so only a longer input tells a stable sort from an unstable one; this one is reordered by it + /// ascending and descending. + static member ManyPairs = [| for index in 0..19 -> struct (index % 3, char (int 'a' + index)) |] + + /// A default flat list given as a sequence: the input that a function rejects as it rejects a null sequence, because + /// the sequence fails with `InvalidOperationException` as soon as the function enumerates it (ADR 0001, D6). + static member DefaultFlatListAsSeq<'T> () : seq<'T> = Unchecked.defaultof> :> seq<'T> diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqCreationContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqCreationContract.fs index 1169acd3..f82a41d9 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqCreationContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqCreationContract.fs @@ -47,9 +47,11 @@ type SeqCreationContract<'C> (ops : SeqCreationOps<'C>) = member this.``ofArray throws for a null array as FSharp.Core does`` () = let source = Unchecked.defaultof + // List.ofArray reads a null array without a check and fails with NullReferenceException, while a null input throws + // ArgumentNullException (ADR 0001, D6), as Array.toList does for the same conversion this.ThrowsLikeFSharpCore ( (fun () -> Array.copy source), - (fun () -> List.ofArray source), + (fun () -> Array.toList source), (fun () -> ops.OfArray source), "ofArray of a null array must throw" ) @@ -101,6 +103,18 @@ type SeqCreationContract<'C> (ops : SeqCreationOps<'C>) = "ofSeq of a null sequence must throw" ) + [] + member _.``ofSeq rejects a default flat list given as a sequence as it rejects a null sequence`` () = + let nullSource = Unchecked.defaultof> + + // The default flat list is a boxed struct, not null: it fails with InvalidOperationException when it is enumerated + ExceptionAssert.Same ( + id, + (fun () -> ops.OfSeq nullSource |> ignore), + (fun () -> ops.OfSeq (Inputs.DefaultFlatListAsSeq()) |> ignore), + $"%s{ops.Name}.ofSeq of a default flat list must throw what it throws for a null sequence" + ) + [)>] 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") diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikePartitioningContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikePartitioningContract.fs index ffc03149..48ad1537 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikePartitioningContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikePartitioningContract.fs @@ -99,6 +99,24 @@ type SeqLikePartitioningContract<'C> (ops : SeqOps<'C>) = "except of null items must throw" ) + [)>] + member this.``except rejects a default flat list given as the items to exclude as null items`` (input : int array) = + let struct (collection, _) = this.Create input + + // FSharp.Core checks the items to exclude before it looks at the input, which an empty input does not even read + ExceptionAssert.Same ( + id, + (fun () -> + ops.Except Unchecked.defaultof> collection + |> ignore + ), + (fun () -> + ops.Except (Inputs.DefaultFlatListAsSeq()) collection + |> ignore + ), + $"%s{ops.Name}.except of a default flat list must throw what it throws for null items" + ) + [)>] 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 diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeTransformationContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeTransformationContract.fs index d970ba11..05f41334 100644 --- a/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeTransformationContract.fs +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/SeqLike/SeqLikeTransformationContract.fs @@ -1,5 +1,6 @@ namespace FSharp.Collections.Immutable.Tests +open System open FSharp.Collections.Immutable open Microsoft.VisualStudio.TestTools.UnitTesting @@ -16,6 +17,20 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = triples |> Array.map (fun (first, second, third) -> struct (first, second, third)) + /// The functions of the module that take a sequence of collections, by name, applied to a sequence. + let sequenceFunctions : Map -> unit> = + Map.ofArray [| + "concat", (fun collections -> ops.Concat collections |> ignore) + "transpose", (fun collections -> ops.Transpose collections |> ignore) + |] + + /// The names of the functions that take a sequence of collections, as the rows of the tests of the sequences that they + /// reject. + static member SequenceFunctions = seq { + "concat" + "transpose" + } + /// Every input with every input, for the functions of two collections of any lengths. static member InputPairs = seq { for first in Inputs.All do @@ -110,6 +125,62 @@ type SeqLikeTransformationContract<'C> (ops : SeqOps<'C>) = Assert.IsEmpty (ops.ToArray (ops.Concat Array.empty), $"%s{ops.Name}.concat of no collections must be empty") + [] + member _.``collect and concat keep the order of the elements across short and long collections`` () = + // The lengths cross 16 in both directions: IndexedList and IIndexedList copy a shorter collection and join a longer + // indexed list to the result as it is, and the order must not depend on which of the two happens. The collections + // come twice, so that the same long indexed list is joined twice. + let lengths = [| 0; 1; 15; 16; 17; 3; 40; 2; 100; 0; 16; 5 |] + + let elements = + lengths + |> Array.map (fun length -> Array.init length (fun index -> length * 1000 + index)) + + let collections = elements |> Array.map (fun items -> ops.OfSeq items) + let twice = Array.append elements elements + + CollectionAssert.AreEqual ( + Array.concat twice, + ops.ToArray (ops.Concat (Array.append collections collections)), + $"%s{ops.Name}.concat must keep the order of the elements across short and long collections" + ) + + let indexes = ops.OfSeq (Array.init (2 * lengths.Length) id) + + CollectionAssert.AreEqual ( + Array.concat twice, + ops.ToArray (ops.Collect (fun index -> collections[index % lengths.Length]) indexes), + $"%s{ops.Name}.collect must keep the order of the elements across short and long collections" + ) + + [.SequenceFunctions)>] + member this.``functions of a sequence of collections reject a null sequence and name the parameter of FSharp.Core`` + (name : string) + = + // Array names the sequence "arrays" and List names it "lists". List.concat does not check it and fails with + // NullReferenceException, while a null input throws ArgumentNullException (ADR 0001, D6), as Array.concat does + let paramName = ops.ParamName (this.Mirrored ((fun () -> "arrays"), (fun () -> "lists"))) + + let ex = + Assert.ThrowsExactly( + Action (fun () -> sequenceFunctions[name] Unchecked.defaultof>), + $"%s{ops.Name}.%s{name} must reject a null sequence" + ) + + Assert.AreEqual(paramName, ex.ParamName, $"%s{ops.Name}.%s{name} must name the sequence") + + [.SequenceFunctions)>] + member _.``functions of a sequence of collections reject a default flat list given as the sequence as a null sequence`` + (name : string) + = + // The default flat list is a boxed struct, not null: it fails with InvalidOperationException when it is enumerated + ExceptionAssert.Same ( + id, + (fun () -> sequenceFunctions[name] Unchecked.defaultof>), + (fun () -> sequenceFunctions[name](Inputs.DefaultFlatListAsSeq<'C>())), + $"%s{ops.Name}.%s{name} of a default flat list must throw what it throws for a null sequence" + ) + [)>] member this.``filter keeps the elements that satisfy the predicate`` (input : int array) = let struct (collection, elements) = this.Create input diff --git a/tests/FSharp.Collections.Immutable.Tests/Contracts/StableSortContract.fs b/tests/FSharp.Collections.Immutable.Tests/Contracts/StableSortContract.fs new file mode 100644 index 00000000..431eca0e --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Contracts/StableSortContract.fs @@ -0,0 +1,351 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open System.Collections.Generic +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// +/// An element whose equality and comparison look at its key only. +/// +/// +/// Elements with the same key and different tags compare equal, so a sort that compares whole elements, such as +/// sort, shows on them whether it keeps the order of equal elements. An element with the key +/// throws when it is compared, for the tests +/// of a sort whose comparison throws. +/// +[] +type KeyedItem = { + /// The key that the equality and the comparison look at. + Key : int + /// The tag that tells the elements with the same key apart. + Tag : char +} with + + /// The key of the elements whose comparison throws. + static member UncomparableKey = Int32.MinValue + + /// The keyed items of the pairs of , in their order: + /// the first component of a pair is the key, and the second one is the tag. + static member OfManyPairs () : KeyedItem array = + Inputs.ManyPairs + |> Array.map (fun struct (key, tag) -> { Key = key; Tag = tag }) + + /// The keys and the tags of keyed items, which tell apart the items that compare equal. + static member KeysAndTags (items : seq) : struct (int * char) array = + items + |> Seq.map (fun item -> struct (item.Key, item.Tag)) + |> Seq.toArray + + override this.Equals other = + match other with + | :? KeyedItem as other -> this.Key = other.Key + | _ -> false + + override this.GetHashCode () = this.Key.GetHashCode () + + // F# generic comparison calls the non-generic interface + interface IComparable with + member this.CompareTo other = + match other with + | :? KeyedItem as other when + this.Key = KeyedItem.UncomparableKey + || other.Key = KeyedItem.UncomparableKey + -> + raise (NotSupportedException "A keyed item with this key cannot be compared") + | :? KeyedItem as other -> compare this.Key other.Key + | _ -> invalidArg (nameof other) "A keyed item compares with keyed items only" + +/// +/// The directions of the comparisons of the sorting tests, 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 comparison of the case. +/// +[] +type SortDirection = + /// The order of the comparison. + | Ascending + /// The reverse order of the comparison. + | Descending + + /// The comparison in this direction. + member this.Apply (comparison : 'T -> 'T -> int) : 'T -> 'T -> int = + match this with + | SortDirection.Ascending -> comparison + | SortDirection.Descending -> fun left right -> comparison right left + +/// +/// The sorting functions of a module that sorts stably, for the contract tests. +/// +/// +/// The elements are the pairs of , more than an unstable +/// sort keeps in order: the functions sort them by the key in the first component, and the tag in the second component +/// shows the order of the elements with equal keys. +/// and +/// compare whole elements and pairs +/// never compare equal, so they take elements instead, and the +/// instance creates and reads the collection itself, as +/// does for its pairs. The same holds for the two +/// functions that sort integers: +/// the tests of the allocation of the descending sorts need elements that a comparison boxes. +/// +type StableSortOps<'C> = { + /// The name of the module, for the messages of the assertions. + Name : 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. + ToArray : 'C -> struct (int * char) array + + /// Sorts a collection of the given keyed items. + Sort : KeyedItem array -> KeyedItem array + SortBy : (struct (int * char) -> int) -> 'C -> 'C + SortByDescending : (struct (int * char) -> int) -> 'C -> 'C + /// Sorts a collection of the given keyed items in descending order. + SortDescending : KeyedItem array -> KeyedItem array + SortWith : (struct (int * char) -> struct (int * char) -> int) -> 'C -> 'C + SortWithComparer : IComparer -> 'C -> 'C + + /// Sorts a collection of the given integers with sortDescending. + SortIntegersDescending : int array -> int array + /// Sorts a collection of the given integers with sortByDescending, by the integers themselves. + SortIntegersByDescending : int array -> int array +} + +/// +/// The contract of the modules that sort stably, as does: +/// , +/// , Queue, IQueue, Stack and IStack +/// (ADR 0001, D5). +/// +/// +/// The expectations come from the stable sorts of over the +/// elements in the order in which the collection enumerates them: the sorts of +/// are not stable. +/// sorts unstably, as +/// does, and has no such contract. +/// +[] +type StableSortContract<'C> (ops : StableSortOps<'C>) = + + static let key (struct (key : int, _ : char)) = key + + static let compareKeys (left : struct (int * char)) (right : struct (int * char)) = compare (key left) (key right) + + + /// The keyed items of the pairs of the input, one of which throws when it is compared. + static let uncomparableItems () = + let items = KeyedItem.OfManyPairs () + items[items.Length / 2] <- { Key = KeyedItem.UncomparableKey; Tag = '!' } + items + + /// A comparison function that throws. + static let throwingComparer (_ : struct (int * char)) (_ : struct (int * char)) : int = + raise (NotSupportedException "The comparison fails") + + /// A projection that throws. + static let throwingProjection (_ : struct (int * char)) : int = raise (NotSupportedException "The projection fails") + + /// 10 000 integers with many duplicates, in a fixed pseudo-random order. + static let manyIntegers () = + let random = Random 42 + Array.init 10_000 (fun _ -> random.Next 1000) + + /// What a sort of 10 000 integers may allocate: the arrays of the sort and the tree of the result need about 1.2 MB, + /// and the boxes of the integers, which a comparison allocates when it goes through the generic operator, 6.8 MB more. + static let allocationLimit = 3_000_000L + + + /// The collection of the input pairs and the elements that it enumerates. + let pairs () = + let collection = ops.OfSeq Inputs.ManyPairs + struct (collection, ops.ToArray collection) + + /// Both directions of the comparison, as the rows of the tests that sort both ways. + static member Directions = seq { + SortDirection.Ascending + SortDirection.Descending + } + + [] + member _.``sort keeps the order of the elements that compare equal`` () = + let items = KeyedItem.OfManyPairs () + + CollectionAssert.AreEqual ( + List.sort (List.ofArray items) + |> List.toArray + |> KeyedItem.KeysAndTags, + ops.Sort items |> KeyedItem.KeysAndTags, + $"%s{ops.Name}.sort must keep the order of equal elements, as List.sort does" + ) + + [] + member _.``sortBy keeps the order of the elements with equal keys`` () = + let struct (collection, elements) = pairs () + + CollectionAssert.AreEqual ( + List.sortBy key (List.ofArray elements) |> List.toArray, + ops.ToArray (ops.SortBy key collection), + $"%s{ops.Name}.sortBy must keep the order of equal keys, as List.sortBy does" + ) + + [] + member _.``sortByDescending keeps the order of the elements with equal keys`` () = + let struct (collection, elements) = pairs () + + CollectionAssert.AreEqual ( + List.sortByDescending key (List.ofArray elements) + |> List.toArray, + ops.ToArray (ops.SortByDescending key collection), + $"%s{ops.Name}.sortByDescending must keep the order of equal keys, as List.sortByDescending does" + ) + + [] + member _.``sortDescending keeps the order of the elements that compare equal`` () = + let items = KeyedItem.OfManyPairs () + + CollectionAssert.AreEqual ( + List.sortDescending (List.ofArray items) + |> List.toArray + |> KeyedItem.KeysAndTags, + ops.SortDescending items |> KeyedItem.KeysAndTags, + $"%s{ops.Name}.sortDescending must keep the order of equal elements, as List.sortDescending does" + ) + + [.Directions)>] + member _.``sortWith keeps the order of the elements that compare equal`` (direction : SortDirection) = + let struct (collection, elements) = pairs () + let comparer = direction.Apply compareKeys + + CollectionAssert.AreEqual ( + List.sortWith comparer (List.ofArray elements) + |> List.toArray, + ops.ToArray (ops.SortWith comparer collection), + $"%s{ops.Name}.sortWith must keep the order of equal elements, as List.sortWith does" + ) + + [] + member _.``sortWithComparer keeps the order of the elements that compare equal`` () = + let struct (collection, elements) = pairs () + + CollectionAssert.AreEqual ( + List.sortWith compareKeys (List.ofArray elements) + |> List.toArray, + ops.ToArray ( + ops.SortWithComparer (Comparer.Create (fun left right -> compareKeys left right)) collection + ), + $"%s{ops.Name}.sortWithComparer must keep the order of equal elements, as List.sortWith does" + ) + + // List sorts in Array.Sort, which wraps the exception of a comparison in an InvalidOperationException; a projection + // that List calls before it sorts throws as it is + + [] + member _.``sort throws what List.sort throws when a comparison throws`` () = + let items = uncomparableItems () + + ExceptionAssert.Same ( + id, + (fun () -> List.sort (List.ofArray items) |> ignore), + (fun () -> ops.Sort items |> ignore), + $"%s{ops.Name}.sort must throw what List.sort throws when a comparison throws" + ) + + [] + member _.``sortBy throws what List.sortBy throws when the projection throws`` () = + let struct (collection, elements) = pairs () + + ExceptionAssert.Same ( + id, + (fun () -> + List.sortBy throwingProjection (List.ofArray elements) + |> ignore + ), + (fun () -> ops.SortBy throwingProjection collection |> ignore), + $"%s{ops.Name}.sortBy must throw what List.sortBy throws when the projection throws" + ) + + [] + member _.``sortByDescending throws what List.sortByDescending throws when the projection throws`` () = + let struct (collection, elements) = pairs () + + ExceptionAssert.Same ( + id, + (fun () -> + List.sortByDescending throwingProjection (List.ofArray elements) + |> ignore + ), + (fun () -> ops.SortByDescending throwingProjection collection |> ignore), + $"%s{ops.Name}.sortByDescending must throw what List.sortByDescending throws when the projection throws" + ) + + [] + member _.``sortDescending throws what List.sortDescending throws when a comparison throws`` () = + let items = uncomparableItems () + + ExceptionAssert.Same ( + id, + (fun () -> List.sortDescending (List.ofArray items) |> ignore), + (fun () -> ops.SortDescending items |> ignore), + $"%s{ops.Name}.sortDescending must throw what List.sortDescending throws when a comparison throws" + ) + + [] + member _.``sortWith throws what List.sortWith throws when the comparer throws`` () = + let struct (collection, elements) = pairs () + + ExceptionAssert.Same ( + id, + (fun () -> + List.sortWith throwingComparer (List.ofArray elements) + |> ignore + ), + (fun () -> ops.SortWith throwingComparer collection |> ignore), + $"%s{ops.Name}.sortWith must throw what List.sortWith throws when the comparer throws" + ) + + // The projection of sortByDescending runs in the comparisons, twice for each of them, as it does in + // List.sortByDescending. A sort that computed the keys first would call it once for each element. + + [] + member _.``sortByDescending calls the projection in the comparisons as List.sortByDescending does`` () = + let struct (collection, elements) = pairs () + + let record (calls : ResizeArray) (pair : struct (int * char)) = + calls.Add pair + key pair + + let expected = ResizeArray () + List.sortByDescending (record expected) (List.ofArray elements) + |> ignore + let actual = ResizeArray () + ops.SortByDescending (record actual) collection |> ignore + + CollectionAssert.AreEqual ( + expected.ToArray (), + actual.ToArray (), + $"%s{ops.Name}.sortByDescending must call the projection in the comparisons, as List.sortByDescending does" + ) + + // Seq.sortDescending and Seq.sortByDescending are inline: in a generic function they compare with the generic + // operator, which boxes the integers in every comparison and allocates 7.5 MB for 10 000 of them + + [] + member _.``sortDescending allocates no box for an integer in a comparison`` () = + let items = manyIntegers () + AllocationAssert.LessThan ( + allocationLimit, + (fun () -> ops.SortIntegersDescending items |> ignore), + $"%s{ops.Name}.sortDescending of 10 000 integers must not box them in the comparisons" + ) + + [] + member _.``sortByDescending allocates no box for a key in a comparison`` () = + let items = manyIntegers () + AllocationAssert.LessThan ( + allocationLimit, + (fun () -> ops.SortIntegersByDescending items |> ignore), + $"%s{ops.Name}.sortByDescending of 10 000 integers must not box the keys in the comparisons" + ) 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 ff9cc792..12eeec14 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj +++ b/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj @@ -37,6 +37,7 @@ + @@ -49,6 +50,8 @@ + + @@ -63,6 +66,20 @@ + + + + + + + + + + + + + + diff --git a/tests/FSharp.Collections.Immutable.Tests/IndexedList/IIndexedListOps.fs b/tests/FSharp.Collections.Immutable.Tests/IndexedList/IIndexedListOps.fs new file mode 100644 index 00000000..3c353224 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/IndexedList/IIndexedListOps.fs @@ -0,0 +1,242 @@ +/// The functions of the `IIndexedList` module for the contract tests, over indexed lists seen through the interface. +[] +module FSharp.Collections.Immutable.Tests.IIndexedListOps + +open FSharp.Collections.Immutable + +/// An indexed list of the elements, seen through the interface. +let private ofSeq (source : seq<'T>) : IIndexedList<'T> = IndexedList.ofSeq source + +// The functions of IIndexedList return indexed lists of indexed lists and pairs of indexed lists as the ones of IndexedList +let private toArrays = IndexedListOps.toArrays + +let private toArrayPair pair = IndexedListOps.toArrayPair pair + +/// The functions of the sequence-like facet. The functions that change the element type return an indexed list, which +/// the instance sees through the interface again where the record expects the collection of the module. +let seqLike : SeqOps> = { + Name = nameof IIndexedList + Mirror = ListMirror + ParamName = id + OfSeq = ofSeq + ToArray = IIndexedList.toArray + + IsEmpty = IIndexedList.isEmpty + Length = IIndexedList.length + + ExactlyOne = IIndexedList.exactlyOne + Head = IIndexedList.head + Last = IIndexedList.last + Tail = IIndexedList.tail + TryExactlyOne = IIndexedList.tryExactlyOne + TryHead = IIndexedList.tryHead + TryLast = IIndexedList.tryLast + TryTail = IIndexedList.tryTail + + Contains = IIndexedList.contains + Exists = IIndexedList.exists + Exists2 = IIndexedList.exists2 + Find = IIndexedList.find + FindBack = IIndexedList.findBack + FindIndex = IIndexedList.findIndex + FindIndexBack = IIndexedList.findIndexBack + Forall = IIndexedList.forall + Forall2 = IIndexedList.forall2 + Pick = IIndexedList.pick + TryFind = IIndexedList.tryFind + TryFindBack = IIndexedList.tryFindBack + TryFindIndex = IIndexedList.tryFindIndex + TryFindIndexBack = IIndexedList.tryFindIndexBack + TryPick = IIndexedList.tryPick + + AllPairs = fun list1 list2 -> IIndexedList.allPairs list1 list2 |> IndexedList.toArray + Append = IIndexedList.append + Choose = fun chooser list -> IIndexedList.choose chooser list + ChunkBySize = fun chunkSize list -> IIndexedList.chunkBySize chunkSize list |> toArrays + Collect = fun mapping list -> IIndexedList.collect mapping list + Concat = fun lists -> IIndexedList.concat lists + Filter = IIndexedList.filter + Indexed = IIndexedList.indexed >> IndexedList.toArray + Map = fun mapping list -> IIndexedList.map mapping list + Map2 = fun mapping list1 list2 -> IIndexedList.map2 mapping list1 list2 + Map3 = fun mapping list1 list2 list3 -> IIndexedList.map3 mapping list1 list2 list3 + Mapi = fun mapping list -> IIndexedList.mapi mapping list + Mapi2 = fun mapping list1 list2 -> IIndexedList.mapi2 mapping list1 list2 + Pairwise = IIndexedList.pairwise >> IndexedList.toArray + Permute = IIndexedList.permute + Rev = IIndexedList.rev + Transpose = fun lists -> IIndexedList.transpose lists |> toArrays + Unzip = fun pairs -> IIndexedList.unzip (ofSeq pairs) |> toArrayPair + Unzip3 = + fun triples -> + let struct (first, second, third) = IIndexedList.unzip3 (ofSeq triples) + struct (IndexedList.toArray first, IndexedList.toArray second, IndexedList.toArray third) + Where = IIndexedList.where + Windowed = fun windowSize list -> IIndexedList.windowed windowSize list |> toArrays + Zip = fun list1 list2 -> IIndexedList.zip list1 list2 |> IndexedList.toArray + Zip3 = fun list1 list2 list3 -> IIndexedList.zip3 list1 list2 list3 |> IndexedList.toArray + + Average = fun items -> IIndexedList.average (ofSeq items) + AverageBy = IIndexedList.averageBy + CompareWith = IIndexedList.compareWith + Fold = IIndexedList.fold + Fold2 = IIndexedList.fold2 + FoldBack = IIndexedList.foldBack + FoldBack2 = IIndexedList.foldBack2 + Iter = IIndexedList.iter + Iter2 = IIndexedList.iter2 + Iteri = IIndexedList.iteri + Iteri2 = IIndexedList.iteri2 + MapFold = + fun mapping state list -> + let struct (results, final) = IIndexedList.mapFold mapping state list + struct (IndexedList.toArray results, final) + MapFoldBack = + fun mapping list state -> + let struct (results, final) = IIndexedList.mapFoldBack mapping list state + struct (IndexedList.toArray results, final) + Max = IIndexedList.max + MaxBy = IIndexedList.maxBy + Min = IIndexedList.min + MinBy = IIndexedList.minBy + Reduce = IIndexedList.reduce + ReduceBack = IIndexedList.reduceBack + Scan = fun folder state list -> IIndexedList.scan folder state list |> IndexedList.toArray + ScanBack = + fun folder list state -> + IIndexedList.scanBack folder list state + |> IndexedList.toArray + Sum = IIndexedList.sum + SumBy = IIndexedList.sumBy + + CountBy = fun projection list -> IIndexedList.countBy projection list |> IndexedList.toArray + Distinct = IIndexedList.distinct + DistinctBy = IIndexedList.distinctBy + Except = IIndexedList.except + GroupBy = + fun projection list -> + IIndexedList.groupBy projection list + |> Seq.map (fun struct (key, items) -> struct (key, IndexedList.toArray items)) + |> Seq.toArray + Partition = IIndexedList.partition + PartitionWith = fun partitioner list -> IIndexedList.partitionWith partitioner list |> toArrayPair + Skip = IIndexedList.skip + SkipLast = IIndexedList.skipLast + SkipWhile = IIndexedList.skipWhile + SplitAt = IIndexedList.splitAt + SplitInto = fun count list -> IIndexedList.splitInto count list |> toArrays + Take = IIndexedList.take + TakeLast = IIndexedList.takeLast + TakeWhile = IIndexedList.takeWhile + Truncate = IIndexedList.truncate + + Sort = IIndexedList.sort + SortBy = IIndexedList.sortBy + SortByDescending = IIndexedList.sortByDescending + SortDescending = IIndexedList.sortDescending + SortWith = IIndexedList.sortWith + SortWithComparer = IIndexedList.sortWithComparer + + RandomChoice = IIndexedList.randomChoice + RandomChoiceBy = IIndexedList.randomChoiceBy + RandomChoiceWith = IIndexedList.randomChoiceWith + RandomChoices = IIndexedList.randomChoices + RandomChoicesBy = IIndexedList.randomChoicesBy + RandomChoicesWith = IIndexedList.randomChoicesWith + RandomSample = IIndexedList.randomSample + RandomSampleBy = IIndexedList.randomSampleBy + RandomSampleWith = IIndexedList.randomSampleWith + RandomShuffle = IIndexedList.randomShuffle + RandomShuffleBy = IIndexedList.randomShuffleBy + RandomShuffleWith = IIndexedList.randomShuffleWith + + ToList = IIndexedList.toList + ToSeq = IIndexedList.toSeq +} + +/// The functions of the index-based facet. +let indexed : IndexedOps> = { + Name = nameof IIndexedList + Mirror = ListMirror + ParamName = id + OfSeq = ofSeq + ToArray = IIndexedList.toArray + + Item = IIndexedList.item + TryItem = IIndexedList.tryItem + + IndexOf = IIndexedList.indexOf + IndexOfWith = IIndexedList.indexOfWith + LastIndexOf = IIndexedList.lastIndexOf + LastIndexOfWith = IIndexedList.lastIndexOfWith + + Add = IIndexedList.add + InsertAt = IIndexedList.insertAt + InsertManyAt = IIndexedList.insertManyAt + RemoveAt = IIndexedList.removeAt + RemoveManyAt = IIndexedList.removeManyAt + UpdateAt = IIndexedList.updateAt +} + +/// The functions of the facet of the members of `IImmutableList`, over strings. +let listMembers : ListMembersOps> = { + Name = nameof IIndexedList + OfSeq = ofSeq + ToArray = IIndexedList.toArray + + IndexOfRange = IIndexedList.indexOfRange + IndexOfRangeWith = IIndexedList.indexOfRangeWith + LastIndexOfRange = IIndexedList.lastIndexOfRange + LastIndexOfRangeWith = IIndexedList.lastIndexOfRangeWith + + Remove = IIndexedList.remove + RemoveMany = IIndexedList.removeMany + RemoveManyWith = IIndexedList.removeManyWith + RemoveWith = IIndexedList.removeWith + Replace = IIndexedList.replace + ReplaceWith = IIndexedList.replaceWith +} + +/// The functions of the stable sorting facet. +let stableSort : StableSortOps> = { + Name = nameof IIndexedList + OfSeq = ofSeq + ToArray = IIndexedList.toArray + + Sort = fun items -> ofSeq items |> IIndexedList.sort |> IIndexedList.toArray + SortBy = IIndexedList.sortBy + SortByDescending = IIndexedList.sortByDescending + SortDescending = + fun items -> + ofSeq items + |> IIndexedList.sortDescending + |> IIndexedList.toArray + SortWith = IIndexedList.sortWith + SortWithComparer = IIndexedList.sortWithComparer + + SortIntegersDescending = + fun items -> + ofSeq items + |> IIndexedList.sortDescending + |> IIndexedList.toArray + SortIntegersByDescending = + fun items -> + ofSeq items + |> IIndexedList.sortByDescending id + |> IIndexedList.toArray +} + +/// The functions of the facet whose cost the tests bound. +let cost : CostOps> = { + Name = nameof IIndexedList + OfSeq = ofSeq + Max = IIndexedList.max + MaxBy = IIndexedList.maxBy + Min = IIndexedList.min + MinBy = IIndexedList.minBy + Exists2 = IIndexedList.exists2 + Forall2 = IIndexedList.forall2 + CompareWith = IIndexedList.compareWith + ConcatLength = fun lists -> IIndexedList.concat lists |> IndexedList.length + CollectLength = fun mapping list -> IIndexedList.collect mapping list |> IndexedList.length +} diff --git a/tests/FSharp.Collections.Immutable.Tests/IndexedList/IIndexedListOptionTests.fs b/tests/FSharp.Collections.Immutable.Tests/IndexedList/IIndexedListOptionTests.fs new file mode 100644 index 00000000..88d15bdf --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/IndexedList/IIndexedListOptionTests.fs @@ -0,0 +1,194 @@ +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 `IIndexedList` module of the `Option` namespace. +[] +type IIndexedListOptionTests () = + + static let list : IIndexedList = IndexedList.ofArray Inputs.Several + + static let empty : IIndexedList = IndexedList.empty + + static let negatedIfNegative (item : int) = if item < 0 then Some (-item) else None + + [] + member _.``tryExactlyOne returns an option`` () = + let single : IIndexedList = IndexedList.singleton 5 + let found : int option = IIndexedList.tryExactlyOne single + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IIndexedList.tryExactlyOne single, + found, + "Option.IIndexedList.tryExactlyOne must match the default after conversion" + ) + + [] + member _.``tryHead returns an option`` () = + let found : int option = IIndexedList.tryHead list + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IIndexedList.tryHead list, + found, + "Option.IIndexedList.tryHead must match the default" + ) + + [] + member _.``tryItem returns an option`` (index : int) = + let found : int option = IIndexedList.tryItem index list + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IIndexedList.tryItem index list, + found, + "Option.IIndexedList.tryItem must match the default" + ) + + [] + member _.``tryLast returns an option`` () = + let found : int option = IIndexedList.tryLast list + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IIndexedList.tryLast list, + found, + "Option.IIndexedList.tryLast must match the default" + ) + + [] + member _.``tryTail returns an option`` () = + let found : IIndexedList option = IIndexedList.tryTail list + + // The equality of an option compares the elements of an array that it holds + Assert.AreEqual( + FSharp.Collections.Immutable.IIndexedList.tryTail list + |> ValueOption.map IIndexedList.toArray + |> ValueOption.toOption, + found |> Option.map IIndexedList.toArray, + "Option.IIndexedList.tryTail must match the default" + ) + + [] + member _.``tryUncons returns an option of a reference tuple`` () = + let found : (int * IIndexedList) option = IIndexedList.tryUncons list + + // A tuple compares an array by reference, so the first element and the other elements are compared apart + let head, tail = + Assert.WantSome (found, "Option.IIndexedList.tryUncons must return the elements of a list with elements") + + Assert.AreEqual(Array.head Inputs.Several, head, "Option.IIndexedList.tryUncons must return the first element") + + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + IIndexedList.toArray tail, + "Option.IIndexedList.tryUncons must return the other elements" + ) + + Assert.IsTrue ((IIndexedList.tryUncons empty).IsNone, "Option.IIndexedList.tryUncons must return None for an empty list") + + [] + member _.``Cons is a partial pattern of options`` () = + let pattern : IIndexedList -> (int * IIndexedList) option = IIndexedList.(|Cons|_|) + + match list with + | IIndexedList.Cons (head, tail) -> + Assert.AreEqual(Array.head Inputs.Several, head, "Option.IIndexedList.Cons must match the first element") + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + IIndexedList.toArray tail, + "Option.IIndexedList.Cons must match the other elements" + ) + | _ -> Assert.Fail "Option.IIndexedList.Cons must match a list with elements" + + Assert.IsTrue ((pattern empty).IsNone, "Option.IIndexedList.Cons must not match an empty list") + + [] + member _.``pick takes a chooser of options`` () = + let picked : int = IIndexedList.pick negatedIfNegative list + + Assert.AreEqual( + Array.pick negatedIfNegative Inputs.Several, + picked, + "Option.IIndexedList.pick must pick as List.pick does" + ) + + [] + member _.``tryFind returns an option`` () = + let found : int option = IIndexedList.tryFind (fun item -> item > 6) list + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IIndexedList.tryFind (fun item -> item > 6) list, + found, + "Option.IIndexedList.tryFind must match the default" + ) + + [] + member _.``tryFindBack returns an option`` () = + let found : int option = IIndexedList.tryFindBack (fun item -> item > 6) list + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IIndexedList.tryFindBack (fun item -> item > 6) list, + found, + "Option.IIndexedList.tryFindBack must match the default" + ) + + [] + member _.``tryFindIndex returns an option`` () = + let found : int option = IIndexedList.tryFindIndex (fun item -> item > 6) list + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IIndexedList.tryFindIndex (fun item -> item > 6) list, + found, + "Option.IIndexedList.tryFindIndex must match the default" + ) + + [] + member _.``tryFindIndexBack returns an option`` () = + let found : int option = IIndexedList.tryFindIndexBack (fun item -> item > 6) list + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IIndexedList.tryFindIndexBack (fun item -> item > 6) list, + found, + "Option.IIndexedList.tryFindIndexBack must match the default" + ) + + [] + member _.``tryPick takes a chooser of options and returns an option`` () = + let found : int option = IIndexedList.tryPick negatedIfNegative list + + Assert.AreEqual( + Array.tryPick negatedIfNegative Inputs.Several, + found, + "Option.IIndexedList.tryPick must pick as List.tryPick does" + ) + + [] + member _.``choose takes a chooser of options`` () = + let chosen : IndexedList = IIndexedList.choose negatedIfNegative list + + CollectionAssert.AreEqual ( + Array.choose negatedIfNegative Inputs.Several, + IndexedList.toArray chosen, + "Option.IIndexedList.choose must choose as List.choose does" + ) + + [] + member _.``the functions without an option keep resolving to the default module`` () = + let filtered : IIndexedList = IIndexedList.filter (fun item -> item > 0) list + let zipped : IndexedList = IIndexedList.zip list list + + CollectionAssert.AreEqual ( + Array.filter (fun item -> item > 0) Inputs.Several, + IIndexedList.toArray filtered, + "IIndexedList.filter must resolve to the default module" + ) + + CollectionAssert.AreEqual ( + Array.zip Inputs.Several Inputs.Several, + IndexedList.toArray zipped, + "IIndexedList.zip must resolve to the Tuple module" + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/IndexedList/IIndexedListTests.fs b/tests/FSharp.Collections.Immutable.Tests/IndexedList/IIndexedListTests.fs new file mode 100644 index 00000000..464efd97 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/IndexedList/IIndexedListTests.fs @@ -0,0 +1,688 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open System.Collections +open System.Collections.Generic +open System.Collections.Immutable +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// +/// An implementation of that is not an indexed list, to tell +/// whether a function keeps the implementation of its input. +/// +/// +/// Every member that returns a list returns a tagged list. A tagged list that treats null values as no values, where the +/// members of throw, shows whether a function rejects them +/// itself. +/// +type TaggedList<'T> (items : ImmutableList<'T>, nullValuesAreNothing : bool) = + + static let wrap (items : ImmutableList<'T>) = TaggedList items :> IIndexedList<'T> + + new (items : ImmutableList<'T>) = TaggedList (items, false) + + interface IImmutableList<'T> with + member _.Add value = wrap (items.Add value) + member _.AddRange values = wrap (items.AddRange values) + member _.Clear () = wrap (items.Clear ()) + member _.IndexOf (item, index, count, comparer) = items.IndexOf (item, index, count, comparer) + member _.Insert (index, element) = wrap (items.Insert (index, element)) + member _.InsertRange (index, values) = wrap (items.InsertRange (index, values)) + member _.LastIndexOf (item, index, count, comparer) = items.LastIndexOf (item, index, count, comparer) + member _.Remove (value, comparer) = wrap (items.Remove (value, comparer)) + member _.RemoveAll predicate = wrap (items.RemoveAll predicate) + member _.RemoveAt index = wrap (items.RemoveAt index) + + member _.RemoveRange (values : IEnumerable<'T>, comparer : IEqualityComparer<'T> | null) = + let values = + if nullValuesAreNothing && obj.ReferenceEquals (values, null) then + Seq.empty + else + values + wrap (items.RemoveRange (values, comparer)) + + member _.RemoveRange (index : int, count : int) = wrap (items.RemoveRange (index, count)) + member _.Replace (oldValue, newValue, comparer) = wrap (items.Replace (oldValue, newValue, comparer)) + member _.SetItem (index, value) = wrap (items.SetItem (index, value)) + + interface IReadOnlyList<'T> with + member _.Item + with get index = items[index] + + interface IReadOnlyCollection<'T> with + member _.Count = items.Count + + interface IEnumerable<'T> with + member _.GetEnumerator () = (items :> IEnumerable<'T>).GetEnumerator() + + interface IEnumerable with + member _.GetEnumerator () = (items :> IEnumerable).GetEnumerator() + +/// The behaviour of `IIndexedList` that the shared contracts do not cover: the implementation of the results, null +/// inputs and the patterns. +[] +type IIndexedListTests () = + + static let nullList = Unchecked.defaultof> + + static let several () : IIndexedList = IndexedList.ofArray Inputs.Several + + static let tagged () : IIndexedList = TaggedList (ImmutableList.CreateRange Inputs.Several) + + /// A tagged list that accepts null values, so that only the function can reject them. + static let lenient () : IIndexedList = TaggedList (ImmutableList.CreateRange Inputs.Several, true) + + // Each call draws from a new generator with the same seed, so that both implementations get the same choices + static let random () = Random 42 + + static let randomizer () = + let random = Random 42 + fun () -> random.NextDouble () + + static let first (struct (first : IIndexedList, _ : IIndexedList)) = first + + static let second (struct (_ : IIndexedList, second : IIndexedList)) = second + + /// The functions whose result has the element type of their input, by name, as functions of the input. The rows of + /// the test are the names: a function has no readable name in a row. + static let sameElementType : Map -> IIndexedList> = + Map.ofArray [| + nameof IIndexedList.add, IIndexedList.add 99 + nameof IIndexedList.append, (fun list -> IIndexedList.append list (IndexedList.ofArray [| 1; 2 |])) + nameof IIndexedList.cons, IIndexedList.cons 99 + nameof IIndexedList.filter, IIndexedList.filter (fun item -> item > 0) + nameof IIndexedList.insertAt, IIndexedList.insertAt 3 99 + nameof IIndexedList.insertManyAt, IIndexedList.insertManyAt 3 [| 98; 99 |] + nameof IIndexedList.permute, IIndexedList.permute (fun index -> (index + 3) % 8) + nameof IIndexedList.removeAt, IIndexedList.removeAt 3 + nameof IIndexedList.remove, IIndexedList.remove 5 + nameof IIndexedList.removeMany, IIndexedList.removeMany [| 5; 8 |] + nameof IIndexedList.removeManyAt, IIndexedList.removeManyAt 2 3 + nameof IIndexedList.removeManyWith, IIndexedList.removeManyWith EqualityComparer.Default [| 5; 8 |] + nameof IIndexedList.removeWith, IIndexedList.removeWith EqualityComparer.Default 5 + nameof IIndexedList.replace, IIndexedList.replace 5 99 + nameof IIndexedList.replaceWith, IIndexedList.replaceWith EqualityComparer.Default 5 99 + nameof IIndexedList.rev, IIndexedList.rev + nameof IIndexedList.updateAt, IIndexedList.updateAt 3 99 + nameof IIndexedList.where, IIndexedList.where (fun item -> item > 0) + nameof IIndexedList.distinct, IIndexedList.distinct + nameof IIndexedList.distinctBy, IIndexedList.distinctBy abs + nameof IIndexedList.except, IIndexedList.except [| 5 |] + $"%s{nameof IIndexedList.partition} (accepted)", IIndexedList.partition (fun item -> item > 0) >> first + $"%s{nameof IIndexedList.partition} (rejected)", IIndexedList.partition (fun item -> item > 0) >> second + nameof IIndexedList.skip, IIndexedList.skip 3 + nameof IIndexedList.skipLast, IIndexedList.skipLast 3 + nameof IIndexedList.skipWhile, IIndexedList.skipWhile (fun item -> item > 0) + $"%s{nameof IIndexedList.splitAt} (first part)", IIndexedList.splitAt 3 >> first + $"%s{nameof IIndexedList.splitAt} (second part)", IIndexedList.splitAt 3 >> second + nameof IIndexedList.take, IIndexedList.take 3 + nameof IIndexedList.takeLast, IIndexedList.takeLast 3 + nameof IIndexedList.takeWhile, IIndexedList.takeWhile (fun item -> item > 0) + nameof IIndexedList.truncate, IIndexedList.truncate 3 + nameof IIndexedList.sort, IIndexedList.sort + nameof IIndexedList.sortBy, IIndexedList.sortBy abs + nameof IIndexedList.sortByDescending, IIndexedList.sortByDescending abs + nameof IIndexedList.sortDescending, IIndexedList.sortDescending + nameof IIndexedList.sortWith, IIndexedList.sortWith compare + nameof IIndexedList.sortWithComparer, IIndexedList.sortWithComparer Comparer.Default + nameof IIndexedList.randomChoicesWith, (fun list -> IIndexedList.randomChoicesWith (random ()) 3 list) + nameof IIndexedList.randomChoicesBy, (fun list -> IIndexedList.randomChoicesBy (randomizer ()) 3 list) + nameof IIndexedList.randomSampleWith, (fun list -> IIndexedList.randomSampleWith (random ()) 3 list) + nameof IIndexedList.randomSampleBy, (fun list -> IIndexedList.randomSampleBy (randomizer ()) 3 list) + nameof IIndexedList.randomShuffleWith, (fun list -> IIndexedList.randomShuffleWith (random ()) list) + nameof IIndexedList.randomShuffleBy, (fun list -> IIndexedList.randomShuffleBy (randomizer ()) list) + nameof IIndexedList.tail, IIndexedList.tail + nameof IIndexedList.tryTail, + (fun list -> + Assert.WantValueSome ( + IIndexedList.tryTail list, + "IIndexedList.tryTail must return the tail of a list with elements" + ) + ) + (nameof IIndexedList.uncons, + fun list -> + let struct (_, tail) = IIndexedList.uncons list + tail) + nameof IIndexedList.clear, IIndexedList.clear + |] + + /// The functions that take a list, by name, with the parameter that names the null list and the function on a null + /// list. + static let nullListRejections = + let pairs = Unchecked.defaultof> + let triples = Unchecked.defaultof> + let floats = Unchecked.defaultof> + + [| + struct (nameof IIndexedList.isEmpty, "list", (fun () -> IIndexedList.isEmpty nullList |> ignore)) + struct (nameof IIndexedList.length, "list", (fun () -> IIndexedList.length nullList |> ignore)) + struct (nameof IIndexedList.exactlyOne, "list", (fun () -> IIndexedList.exactlyOne nullList |> ignore)) + struct (nameof IIndexedList.head, "list", (fun () -> IIndexedList.head nullList |> ignore)) + struct (nameof IIndexedList.item, "list", (fun () -> IIndexedList.item 0 nullList |> ignore)) + struct (nameof IIndexedList.last, "list", (fun () -> IIndexedList.last nullList |> ignore)) + struct (nameof IIndexedList.tail, "list", (fun () -> IIndexedList.tail nullList |> ignore)) + struct (nameof IIndexedList.tryExactlyOne, "list", (fun () -> IIndexedList.tryExactlyOne nullList |> ignore)) + struct (nameof IIndexedList.tryHead, "list", (fun () -> IIndexedList.tryHead nullList |> ignore)) + struct (nameof IIndexedList.tryItem, "list", (fun () -> IIndexedList.tryItem 0 nullList |> ignore)) + struct (nameof IIndexedList.tryLast, "list", (fun () -> IIndexedList.tryLast nullList |> ignore)) + struct (nameof IIndexedList.tryTail, "list", (fun () -> IIndexedList.tryTail nullList |> ignore)) + struct (nameof IIndexedList.tryUncons, "list", (fun () -> IIndexedList.tryUncons nullList |> ignore)) + struct (nameof IIndexedList.uncons, "list", (fun () -> IIndexedList.uncons nullList |> ignore)) + struct (nameof IIndexedList.(|Cons|_|), "list", (fun () -> IIndexedList.(|Cons|_|) nullList |> ignore)) + struct (nameof IIndexedList.(|Nil|_|), "list", (fun () -> IIndexedList.(|Nil|_|) nullList |> ignore)) + struct (nameof IIndexedList.contains, "source", (fun () -> IIndexedList.contains 1 nullList |> ignore)) + struct (nameof IIndexedList.exists, "list", (fun () -> IIndexedList.exists ((=) 1) nullList |> ignore)) + struct (nameof IIndexedList.exists2, "list1", (fun () -> IIndexedList.exists2 (=) nullList (several ()) |> ignore)) + struct (nameof IIndexedList.find, "list", (fun () -> IIndexedList.find ((=) 1) nullList |> ignore)) + struct (nameof IIndexedList.findBack, "list", (fun () -> IIndexedList.findBack ((=) 1) nullList |> ignore)) + struct (nameof IIndexedList.findIndex, "list", (fun () -> IIndexedList.findIndex ((=) 1) nullList |> ignore)) + struct (nameof IIndexedList.findIndexBack, "list", (fun () -> IIndexedList.findIndexBack ((=) 1) nullList |> ignore)) + struct (nameof IIndexedList.forall, "list", (fun () -> IIndexedList.forall ((=) 1) nullList |> ignore)) + struct (nameof IIndexedList.forall2, "list2", (fun () -> IIndexedList.forall2 (=) (several ()) nullList |> ignore)) + struct (nameof IIndexedList.indexOf, "list", (fun () -> IIndexedList.indexOf 1 nullList |> ignore)) + struct (nameof IIndexedList.indexOfRange, "list", (fun () -> IIndexedList.indexOfRange 0 0 1 nullList |> ignore)) + struct (nameof IIndexedList.indexOfRangeWith, + "list", + (fun () -> + IIndexedList.indexOfRangeWith EqualityComparer.Default 0 0 1 nullList + |> ignore + )) + struct (nameof IIndexedList.indexOfWith, + "list", + (fun () -> + IIndexedList.indexOfWith EqualityComparer.Default 1 nullList + |> ignore + )) + struct (nameof IIndexedList.lastIndexOf, "list", (fun () -> IIndexedList.lastIndexOf 1 nullList |> ignore)) + struct (nameof IIndexedList.lastIndexOfRange, + "list", + (fun () -> IIndexedList.lastIndexOfRange 0 0 1 nullList |> ignore)) + struct (nameof IIndexedList.lastIndexOfRangeWith, + "list", + (fun () -> + IIndexedList.lastIndexOfRangeWith EqualityComparer.Default 0 0 1 nullList + |> ignore + )) + struct (nameof IIndexedList.lastIndexOfWith, + "list", + (fun () -> + IIndexedList.lastIndexOfWith EqualityComparer.Default 1 nullList + |> ignore + )) + struct (nameof IIndexedList.pick, "list", (fun () -> IIndexedList.pick ValueSome nullList |> ignore)) + struct (nameof IIndexedList.tryFind, "list", (fun () -> IIndexedList.tryFind ((=) 1) nullList |> ignore)) + struct (nameof IIndexedList.tryFindBack, "list", (fun () -> IIndexedList.tryFindBack ((=) 1) nullList |> ignore)) + struct (nameof IIndexedList.tryFindIndex, "list", (fun () -> IIndexedList.tryFindIndex ((=) 1) nullList |> ignore)) + struct (nameof IIndexedList.tryFindIndexBack, + "list", + (fun () -> IIndexedList.tryFindIndexBack ((=) 1) nullList |> ignore)) + struct (nameof IIndexedList.tryPick, "list", (fun () -> IIndexedList.tryPick ValueSome nullList |> ignore)) + struct (nameof IIndexedList.add, "list", (fun () -> IIndexedList.add 1 nullList |> ignore)) + struct (nameof IIndexedList.allPairs, "list2", (fun () -> IIndexedList.allPairs (several ()) nullList |> ignore)) + struct (nameof IIndexedList.append, "list1", (fun () -> IIndexedList.append nullList (several ()) |> ignore)) + struct (nameof IIndexedList.choose, "list", (fun () -> IIndexedList.choose ValueSome nullList |> ignore)) + struct (nameof IIndexedList.chunkBySize, "list", (fun () -> IIndexedList.chunkBySize 2 nullList |> ignore)) + struct (nameof IIndexedList.collect, + "list", + (fun () -> + IIndexedList.collect (fun _ -> several ()) nullList + |> ignore + )) + struct (nameof IIndexedList.concat, "lists", (fun () -> IIndexedList.concat [| several (); nullList |] |> ignore)) + struct (nameof IIndexedList.cons, "list", (fun () -> IIndexedList.cons 1 nullList |> ignore)) + struct (nameof IIndexedList.filter, "list", (fun () -> IIndexedList.filter ((=) 1) nullList |> ignore)) + struct (nameof IIndexedList.indexed, "list", (fun () -> IIndexedList.indexed nullList |> ignore)) + struct (nameof IIndexedList.insertAt, "source", (fun () -> IIndexedList.insertAt 0 1 nullList |> ignore)) + struct (nameof IIndexedList.insertManyAt, "source", (fun () -> IIndexedList.insertManyAt 0 [| 1 |] nullList |> ignore)) + struct (nameof IIndexedList.map, "list", (fun () -> IIndexedList.map id nullList |> ignore)) + struct (nameof IIndexedList.map2, "list1", (fun () -> IIndexedList.map2 (+) nullList (several ()) |> ignore)) + struct (nameof IIndexedList.map3, + "list2", + (fun () -> + IIndexedList.map3 (fun a b c -> a + b + c) (several ()) nullList (several ()) + |> ignore + )) + struct (nameof IIndexedList.mapi, "list", (fun () -> IIndexedList.mapi (+) nullList |> ignore)) + struct (nameof IIndexedList.mapi2, + "list2", + (fun () -> + IIndexedList.mapi2 (fun index a b -> index + a + b) (several ()) nullList + |> ignore + )) + struct (nameof IIndexedList.pairwise, "list", (fun () -> IIndexedList.pairwise nullList |> ignore)) + struct (nameof IIndexedList.permute, "list", (fun () -> IIndexedList.permute id nullList |> ignore)) + struct (nameof IIndexedList.remove, "list", (fun () -> IIndexedList.remove 1 nullList |> ignore)) + struct (nameof IIndexedList.removeAt, "source", (fun () -> IIndexedList.removeAt 0 nullList |> ignore)) + struct (nameof IIndexedList.removeMany, "list", (fun () -> IIndexedList.removeMany [| 1 |] nullList |> ignore)) + struct (nameof IIndexedList.removeManyAt, "source", (fun () -> IIndexedList.removeManyAt 0 1 nullList |> ignore)) + struct (nameof IIndexedList.removeManyWith, + "list", + (fun () -> + IIndexedList.removeManyWith EqualityComparer.Default [| 1 |] nullList + |> ignore + )) + struct (nameof IIndexedList.removeWith, + "list", + (fun () -> + IIndexedList.removeWith EqualityComparer.Default 1 nullList + |> ignore + )) + struct (nameof IIndexedList.replace, "list", (fun () -> IIndexedList.replace 1 2 nullList |> ignore)) + struct (nameof IIndexedList.replaceWith, + "list", + (fun () -> + IIndexedList.replaceWith EqualityComparer.Default 1 2 nullList + |> ignore + )) + struct (nameof IIndexedList.rev, "list", (fun () -> IIndexedList.rev nullList |> ignore)) + struct (nameof IIndexedList.transpose, "lists", (fun () -> IIndexedList.transpose [| nullList |] |> ignore)) + struct (nameof IIndexedList.unzip, "list", (fun () -> IIndexedList.unzip pairs |> ignore)) + struct (nameof IIndexedList.unzip3, "list", (fun () -> IIndexedList.unzip3 triples |> ignore)) + struct (nameof IIndexedList.updateAt, "source", (fun () -> IIndexedList.updateAt 0 1 nullList |> ignore)) + struct (nameof IIndexedList.where, "list", (fun () -> IIndexedList.where ((=) 1) nullList |> ignore)) + struct (nameof IIndexedList.windowed, "list", (fun () -> IIndexedList.windowed 2 nullList |> ignore)) + struct (nameof IIndexedList.zip, "list2", (fun () -> IIndexedList.zip (several ()) nullList |> ignore)) + struct (nameof IIndexedList.zip3, + "list3", + (fun () -> + IIndexedList.zip3 (several ()) (several ()) nullList + |> ignore + )) + struct (nameof IIndexedList.average, "list", (fun () -> IIndexedList.average floats |> ignore)) + struct (nameof IIndexedList.averageBy, "list", (fun () -> IIndexedList.averageBy float nullList |> ignore)) + struct (nameof IIndexedList.compareWith, + "list1", + (fun () -> + IIndexedList.compareWith compare nullList (several ()) + |> ignore + )) + struct (nameof IIndexedList.fold, "list", (fun () -> IIndexedList.fold (+) 0 nullList |> ignore)) + struct (nameof IIndexedList.fold2, + "list1", + (fun () -> + IIndexedList.fold2 (fun state a b -> state + a + b) 0 nullList (several ()) + |> ignore + )) + struct (nameof IIndexedList.foldBack, "list", (fun () -> IIndexedList.foldBack (+) nullList 0 |> ignore)) + struct (nameof IIndexedList.foldBack2, + "list2", + (fun () -> + IIndexedList.foldBack2 (fun a b state -> state + a + b) (several ()) nullList 0 + |> ignore + )) + struct (nameof IIndexedList.iter, "list", (fun () -> IIndexedList.iter ignore nullList)) + struct (nameof IIndexedList.iter2, "list1", (fun () -> IIndexedList.iter2 (fun _ _ -> ()) nullList (several ()))) + struct (nameof IIndexedList.iteri, "list", (fun () -> IIndexedList.iteri (fun _ _ -> ()) nullList)) + struct (nameof IIndexedList.iteri2, "list2", (fun () -> IIndexedList.iteri2 (fun _ _ _ -> ()) (several ()) nullList)) + struct (nameof IIndexedList.mapFold, + "list", + (fun () -> + IIndexedList.mapFold (fun state item -> struct (item, state)) 0 nullList + |> ignore + )) + struct (nameof IIndexedList.mapFoldBack, + "list", + (fun () -> + IIndexedList.mapFoldBack (fun item state -> struct (item, state)) nullList 0 + |> ignore + )) + struct (nameof IIndexedList.max, "list", (fun () -> IIndexedList.max nullList |> ignore)) + struct (nameof IIndexedList.maxBy, "list", (fun () -> IIndexedList.maxBy id nullList |> ignore)) + struct (nameof IIndexedList.min, "list", (fun () -> IIndexedList.min nullList |> ignore)) + struct (nameof IIndexedList.minBy, "list", (fun () -> IIndexedList.minBy id nullList |> ignore)) + struct (nameof IIndexedList.reduce, "list", (fun () -> IIndexedList.reduce (+) nullList |> ignore)) + struct (nameof IIndexedList.reduceBack, "list", (fun () -> IIndexedList.reduceBack (+) nullList |> ignore)) + struct (nameof IIndexedList.scan, "list", (fun () -> IIndexedList.scan (+) 0 nullList |> ignore)) + struct (nameof IIndexedList.scanBack, "list", (fun () -> IIndexedList.scanBack (+) nullList 0 |> ignore)) + struct (nameof IIndexedList.sum, "list", (fun () -> IIndexedList.sum nullList |> ignore)) + struct (nameof IIndexedList.sumBy, "list", (fun () -> IIndexedList.sumBy id nullList |> ignore)) + struct (nameof IIndexedList.countBy, "list", (fun () -> IIndexedList.countBy id nullList |> ignore)) + struct (nameof IIndexedList.distinct, "list", (fun () -> IIndexedList.distinct nullList |> ignore)) + struct (nameof IIndexedList.distinctBy, "list", (fun () -> IIndexedList.distinctBy id nullList |> ignore)) + struct (nameof IIndexedList.except, "list", (fun () -> IIndexedList.except [| 1 |] nullList |> ignore)) + struct (nameof IIndexedList.groupBy, "list", (fun () -> IIndexedList.groupBy id nullList |> ignore)) + struct (nameof IIndexedList.partition, "list", (fun () -> IIndexedList.partition ((=) 1) nullList |> ignore)) + struct (nameof IIndexedList.partitionWith, + "list", + (fun () -> IIndexedList.partitionWith Choice1Of2 nullList |> ignore)) + struct (nameof IIndexedList.skip, "list", (fun () -> IIndexedList.skip 0 nullList |> ignore)) + struct (nameof IIndexedList.skipLast, "list", (fun () -> IIndexedList.skipLast 0 nullList |> ignore)) + struct (nameof IIndexedList.skipWhile, "list", (fun () -> IIndexedList.skipWhile ((=) 1) nullList |> ignore)) + struct (nameof IIndexedList.splitAt, "list", (fun () -> IIndexedList.splitAt 0 nullList |> ignore)) + struct (nameof IIndexedList.splitInto, "list", (fun () -> IIndexedList.splitInto 2 nullList |> ignore)) + struct (nameof IIndexedList.take, "list", (fun () -> IIndexedList.take 0 nullList |> ignore)) + struct (nameof IIndexedList.takeLast, "list", (fun () -> IIndexedList.takeLast 0 nullList |> ignore)) + struct (nameof IIndexedList.takeWhile, "list", (fun () -> IIndexedList.takeWhile ((=) 1) nullList |> ignore)) + struct (nameof IIndexedList.truncate, "list", (fun () -> IIndexedList.truncate 1 nullList |> ignore)) + struct (nameof IIndexedList.sort, "list", (fun () -> IIndexedList.sort nullList |> ignore)) + struct (nameof IIndexedList.sortBy, "list", (fun () -> IIndexedList.sortBy id nullList |> ignore)) + struct (nameof IIndexedList.sortByDescending, "list", (fun () -> IIndexedList.sortByDescending id nullList |> ignore)) + struct (nameof IIndexedList.sortDescending, "list", (fun () -> IIndexedList.sortDescending nullList |> ignore)) + struct (nameof IIndexedList.sortWith, "list", (fun () -> IIndexedList.sortWith compare nullList |> ignore)) + struct (nameof IIndexedList.sortWithComparer, + "list", + (fun () -> + IIndexedList.sortWithComparer Comparer.Default nullList + |> ignore + )) + struct (nameof IIndexedList.randomChoice, "source", (fun () -> IIndexedList.randomChoice nullList |> ignore)) + struct (nameof IIndexedList.randomChoiceBy, + "source", + (fun () -> + IIndexedList.randomChoiceBy (fun () -> 0.5) nullList + |> ignore + )) + struct (nameof IIndexedList.randomChoiceWith, + "source", + (fun () -> IIndexedList.randomChoiceWith (Random 1) nullList |> ignore)) + struct (nameof IIndexedList.randomChoices, "source", (fun () -> IIndexedList.randomChoices 1 nullList |> ignore)) + struct (nameof IIndexedList.randomChoicesBy, + "source", + (fun () -> + IIndexedList.randomChoicesBy (fun () -> 0.5) 1 nullList + |> ignore + )) + struct (nameof IIndexedList.randomChoicesWith, + "source", + (fun () -> + IIndexedList.randomChoicesWith (Random 1) 1 nullList + |> ignore + )) + struct (nameof IIndexedList.randomSample, "source", (fun () -> IIndexedList.randomSample 1 nullList |> ignore)) + struct (nameof IIndexedList.randomSampleBy, + "source", + (fun () -> + IIndexedList.randomSampleBy (fun () -> 0.5) 1 nullList + |> ignore + )) + struct (nameof IIndexedList.randomSampleWith, + "source", + (fun () -> + IIndexedList.randomSampleWith (Random 1) 1 nullList + |> ignore + )) + struct (nameof IIndexedList.randomShuffle, "source", (fun () -> IIndexedList.randomShuffle nullList |> ignore)) + struct (nameof IIndexedList.randomShuffleBy, + "source", + (fun () -> + IIndexedList.randomShuffleBy (fun () -> 0.5) nullList + |> ignore + )) + struct (nameof IIndexedList.randomShuffleWith, + "source", + (fun () -> IIndexedList.randomShuffleWith (Random 1) nullList |> ignore)) + struct (nameof IIndexedList.toArray, "list", (fun () -> IIndexedList.toArray nullList |> ignore)) + struct (nameof IIndexedList.toList, "list", (fun () -> IIndexedList.toList nullList |> ignore)) + struct (nameof IIndexedList.toSeq, "list", (fun () -> IIndexedList.toSeq nullList |> ignore)) + struct (nameof IIndexedList.clear, "list", (fun () -> IIndexedList.clear nullList |> ignore)) + |] + |> Array.map (fun struct (name, paramName, action) -> name, struct (paramName, action)) + |> Map.ofArray + + /// The functions that take the values to remove, by name, on null values and on a list that would accept them. + static let nullItemRejections : Map unit> = + let items = Unchecked.defaultof> + + Map.ofArray [| + nameof IIndexedList.removeMany, (fun () -> IIndexedList.removeMany items (lenient ()) |> ignore) + (nameof IIndexedList.removeManyWith, + fun () -> + IIndexedList.removeManyWith EqualityComparer.Default items (lenient ()) + |> ignore) + |] + + /// 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. The shared contracts cover a null sequence and a default flat list given as a + /// sequence of integers; the rows are a default flat list of strings given as a sequence of objects, which a type test + /// for the flat list of objects does not see, the order of the checks, and a default flat list as an element of a + /// sequence of lists: it implements the interface, so it can be one. A default flat list converts to a sequence + /// without a check and fails when the sequence is enumerated, so the other inputs are valid. + static let sequenceRejections : Map unit))> = + let widenedItems = FlatListOracles.defaultStringsAsObjects + let objects = IndexedList.ofArray [| "a" :> obj |] :> IIndexedList + let defaultItems = Inputs.DefaultFlatListAsSeq() + let defaultList = Unchecked.defaultof> :> IIndexedList + + Map.ofArray [| + "concat (default flat list as an element)", + struct ("lists", (fun () -> IIndexedList.concat [| several (); defaultList |] |> ignore)) + "transpose (default flat list as an element)", + struct ("lists", + (fun () -> + IIndexedList.transpose [| several (); defaultList |] + |> ignore + )) + "insertManyAt (default flat list of strings as objects)", + struct ("values", (fun () -> IIndexedList.insertManyAt 0 widenedItems objects |> ignore)) + // List.insertManyAt checks the source before the values + "insertManyAt (null list and default flat list)", + struct ("source", (fun () -> IIndexedList.insertManyAt 0 defaultItems nullList |> ignore)) + "except (default flat list of strings as objects)", + struct ("itemsToExclude", (fun () -> IIndexedList.except widenedItems objects |> ignore)) + // List.except checks the items to exclude before the list + "except (default flat list and null list)", + struct ("itemsToExclude", (fun () -> IIndexedList.except defaultItems nullList |> ignore)) + "removeMany (default flat list of strings as objects)", + struct ("items", (fun () -> IIndexedList.removeMany widenedItems objects |> ignore)) + "removeManyWith (default flat list of strings as objects)", + struct ("items", + (fun () -> + IIndexedList.removeManyWith EqualityComparer.Default widenedItems objects + |> ignore + )) + |] + + /// The strings of the searches in a range, in an implementation that is not an indexed list. + static let taggedNames () : IIndexedList = TaggedList (ImmutableList.CreateRange ListNames.Several) + + /// The names of the functions whose result has the element type of their input, as the rows of their test. + static member SameElementType = sameElementType.Keys + + /// The names of the functions that take the values to remove, as the rows of the test of null values. + static member NullItemRejections = nullItemRejections.Keys + + /// The names of the rows of the test of the sequences that the functions reject besides the ones of the contracts. + static member SequenceRejections = sequenceRejections.Keys + + /// The names of the functions that take a list, as the rows of their test. + static member NullListRejections = nullListRejections.Keys + + [] + member _.``functions with the element type of their input keep its implementation`` (name : string) = + let apply = sameElementType[name] + let result = apply (tagged ()) + Assert.IsInstanceOfType>(result, $"IIndexedList.%s{name} must keep the implementation of its input") + |> ignore + + CollectionAssert.AreEqual ( + IIndexedList.toArray (apply (several ())), + IIndexedList.toArray result, + $"IIndexedList.%s{name} must return the same elements for every implementation" + ) + + [] + member _.``functions that combine lists or change the element type return an indexed list of the elements`` () = + let list = tagged () + + CollectionAssert.AreEqual ( + Array.map string Inputs.Several, + IndexedList.toArray (IIndexedList.map string list), + "IIndexedList.map must map the elements of any implementation into an indexed list" + ) + + let concatenated = IIndexedList.concat [| list; several () |] + + CollectionAssert.AreEqual ( + Array.append Inputs.Several Inputs.Several, + IndexedList.toArray concatenated, + "IIndexedList.concat must concatenate lists of different implementations into an indexed list" + ) + + [] + member _.``functions reject a null list`` (name : string) = + let struct (paramName, action) = nullListRejections[name] + FlatListAssert.RejectsNull ($"IIndexedList.%s{name}", paramName, action) + + [] + member _.``functions that take a sequence reject a default flat list as an element or as a sequence of objects and check their inputs in order`` + (name : string) + = + let struct (paramName, actual) = sequenceRejections[name] + FlatListAssert.RejectsNull ($"IIndexedList.%s{name}", paramName, actual) + + [] + member _.``concat and transpose take a flat list that is not default as a list`` () = + let flat = FlatList.ofArray [| 1; 2 |] :> IIndexedList + + CollectionAssert.AreEqual ( + [| 1; 2; 1; 2 |], + IndexedList.toArray (IIndexedList.concat [| flat; flat |]), + "IIndexedList.concat must take the elements of a flat list that is not default" + ) + + let columns = IIndexedList.transpose [| flat; flat |] + + CollectionAssert.AreEqual ( + [| 1; 1 |], + IndexedList.toArray columns[0], + "IIndexedList.transpose must take the elements of a flat list that is not default" + ) + + [] + 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 = IndexedList.ofArray (asObjects [| "c" |]) :> IIndexedList + let removable = IndexedList.ofArray (asObjects [| "a"; "c"; "b" |]) :> IIndexedList + + CollectionAssert.AreEqual ( + asObjects [| "a"; "b"; "c" |], + IIndexedList.toArray (IIndexedList.insertManyAt 0 widened objects), + "IIndexedList.insertManyAt must take the elements of a flat list of strings as objects" + ) + + CollectionAssert.AreEqual ( + asObjects [| "c" |], + IIndexedList.toArray (IIndexedList.except widened objects), + "IIndexedList.except must take the elements of a flat list of strings as objects" + ) + + CollectionAssert.AreEqual ( + asObjects [| "c" |], + IIndexedList.toArray (IIndexedList.removeMany widened removable), + "IIndexedList.removeMany must take the elements of a flat list of strings as objects" + ) + + CollectionAssert.AreEqual ( + asObjects [| "c" |], + IIndexedList.toArray (IIndexedList.removeManyWith EqualityComparer.Default widened removable), + "IIndexedList.removeManyWith must take the elements of a flat list of strings as objects" + ) + + [] + member _.``removeMany and removeManyWith reject null items with the name of the parameter, also for a list that accepts them`` + (name : string) + = + FlatListAssert.RejectsNull ($"IIndexedList.%s{name}", "items", nullItemRejections[name]) + + [] + member _.``indexOfRange and indexOfRangeWith search the range of any implementation`` (startIndex : int, count : int) = + let list = taggedNames () + + Assert.AreEqual( + FlatListOracles.firstInRange (=) startIndex count "a" ListNames.Several, + IIndexedList.indexOfRange startIndex count "a" list, + "IIndexedList.indexOfRange must search the range of an implementation that is not an indexed list" + ) + + Assert.AreEqual( + FlatListOracles.firstInRange FlatListOracles.equalIgnoringCase startIndex count "A" ListNames.Several, + IIndexedList.indexOfRangeWith StringComparer.OrdinalIgnoreCase startIndex count "A" list, + "IIndexedList.indexOfRangeWith must search the range of an implementation that is not an indexed list" + ) + + [] + member _.``lastIndexOfRange and lastIndexOfRangeWith search the range of any implementation`` + (startIndex : int, count : int) + = + let list = taggedNames () + + Assert.AreEqual( + ListOracles.lastIndexOfRange (=) startIndex count "a" ListNames.Several, + IIndexedList.lastIndexOfRange startIndex count "a" list, + "IIndexedList.lastIndexOfRange must search the range of an implementation that is not an indexed list" + ) + + Assert.AreEqual( + ListOracles.lastIndexOfRange FlatListOracles.equalIgnoringCase startIndex count "A" ListNames.Several, + IIndexedList.lastIndexOfRangeWith StringComparer.OrdinalIgnoreCase startIndex count "A" list, + "IIndexedList.lastIndexOfRangeWith must search the range of an implementation that is not an indexed list" + ) + + [] + member _.``uncons throws for an empty list as List.head does`` () = + ExceptionAssert.Same ( + id, + (fun () -> List.head List.empty |> ignore), + (fun () -> IIndexedList.uncons (IndexedList.empty) |> ignore), + "IIndexedList.uncons of an empty list must throw as List.head does" + ) + + [] + member _.``cons adds the value before the first element`` () = + CollectionAssert.AreEqual ( + Array.append [| 99 |] Inputs.Several, + IIndexedList.toArray (IIndexedList.cons 99 (several ())), + "IIndexedList.cons must add the value before the first element" + ) + + [] + member _.``Cons matches the first element and the other elements`` () = + let rec sum (list : IIndexedList) = + match list with + | IIndexedList.Cons (struct (head, tail)) -> head + sum tail + | _ -> 0 + + Assert.AreEqual(Array.sum Inputs.Several, sum (tagged ()), "IIndexedList.Cons must match every element in turn") + + match tagged () with + | IIndexedList.Cons (struct (_, tail)) -> + Assert.IsInstanceOfType>(tail, "IIndexedList.Cons must keep the implementation of the tail") + |> ignore + | _ -> Assert.Fail "IIndexedList.Cons must match a list with elements" + + [] + member _.``Nil matches an empty list only`` () = + let describe (list : IIndexedList) = + match list with + | IIndexedList.Nil -> "empty" + | IIndexedList.Cons (struct (head, _)) -> $"starts with %d{head}" + | _ -> "unreachable" + + Assert.AreEqual("empty", describe (IIndexedList.clear (tagged ())), "IIndexedList.Nil must match an empty list") + Assert.AreEqual("starts with 5", describe (several ()), "IIndexedList.Nil must not match a list with elements") + + [] + member _.``contains compares in F# structural equality, indexOf in the default equality`` () = + let arrays : IIndexedList = IndexedList.singleton [| 1; 2 |] + + Assert.IsTrue ( + IIndexedList.contains [| 1; 2 |] arrays, + "IIndexedList.contains must compare arrays by their elements, as List.contains does" + ) + + Assert.AreEqual(-1, IIndexedList.indexOf [| 1; 2 |] arrays, "IIndexedList.indexOf must compare arrays by reference") + +/// The sequence-like contract for `IIndexedList` over an implementation that is not an indexed list. The functions that +/// take a shortcut for indexed lists go the general way here, and must give the same results. +[] +type IIndexedListTaggedSeqLikeTests () = + inherit + SeqLikeContract> ( + { + IIndexedListOps.seqLike with + Name = "IIndexedList of a tagged list" + OfSeq = fun items -> TaggedList (ImmutableList.CreateRange items) :> IIndexedList + } + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/IndexedList/IIndexedListTupleTests.fs b/tests/FSharp.Collections.Immutable.Tests/IndexedList/IIndexedListTupleTests.fs new file mode 100644 index 00000000..891cab0b --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/IndexedList/IIndexedListTupleTests.fs @@ -0,0 +1,294 @@ +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 `IIndexedList` module of the `Tuple` namespace. +[] +type IIndexedListTupleTests () = + + static let list : IIndexedList = IndexedList.ofArray Inputs.Several + + static let other : IIndexedList = IndexedList.ofArray Inputs.Other + + static let third : IIndexedList = IndexedList.ofArray Inputs.Third + + [] + member _.``tryUncons returns a value option of a reference tuple`` () = + let found : (int * IIndexedList) voption = IIndexedList.tryUncons list + + // A tuple compares an array by reference, so the first element and the other elements are compared apart + let head, tail = + Assert.WantValueSome (found, "Tuple.IIndexedList.tryUncons must return the elements of a list with elements") + + Assert.AreEqual(Array.head Inputs.Several, head, "Tuple.IIndexedList.tryUncons must return the first element") + + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + IIndexedList.toArray tail, + "Tuple.IIndexedList.tryUncons must return the other elements" + ) + + [] + member _.``uncons returns a reference tuple`` () = + let (head : int), (tail : IIndexedList) = IIndexedList.uncons list + Assert.AreEqual(Array.head Inputs.Several, head, "Tuple.IIndexedList.uncons must return the first element") + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + IIndexedList.toArray tail, + "Tuple.IIndexedList.uncons must return the other elements" + ) + + [] + member _.``Cons is a struct partial pattern of reference tuples`` () = + let pattern : IIndexedList -> (int * IIndexedList) voption = IIndexedList.(|Cons|_|) + + match list with + | IIndexedList.Cons (head, tail) -> + Assert.AreEqual(Array.head Inputs.Several, head, "Tuple.IIndexedList.Cons must match the first element") + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + IIndexedList.toArray tail, + "Tuple.IIndexedList.Cons must match the other elements" + ) + | _ -> Assert.Fail "Tuple.IIndexedList.Cons must match a list with elements" + + Assert.IsTrue ((pattern (IIndexedList.clear list)).IsNone, "Tuple.IIndexedList.Cons must not match an empty list") + + [] + member _.``allPairs returns reference tuples`` () = + let pairs : IndexedList = IIndexedList.allPairs list other + + CollectionAssert.AreEqual ( + Array.allPairs Inputs.Several Inputs.Other, + IndexedList.toArray pairs, + "Tuple.IIndexedList.allPairs must pair as List.allPairs does" + ) + + [] + member _.``indexed returns reference tuples`` () = + let pairs : IndexedList = IIndexedList.indexed list + + CollectionAssert.AreEqual ( + Array.indexed Inputs.Several, + IndexedList.toArray pairs, + "Tuple.IIndexedList.indexed must index as List.indexed does" + ) + + [] + member _.``pairwise returns reference tuples`` () = + let pairs : IndexedList = IIndexedList.pairwise list + + CollectionAssert.AreEqual ( + Array.pairwise Inputs.Several, + IndexedList.toArray pairs, + "Tuple.IIndexedList.pairwise must pair as List.pairwise does" + ) + + [] + member _.``unzip takes and returns reference tuples`` () = + let pairs : IIndexedList = IIndexedList.zip list other + let (first : IndexedList), (second : IndexedList) = IIndexedList.unzip pairs + CollectionAssert.AreEqual ( + Inputs.Several, + IndexedList.toArray first, + "Tuple.IIndexedList.unzip must return the first components" + ) + CollectionAssert.AreEqual ( + Inputs.Other, + IndexedList.toArray second, + "Tuple.IIndexedList.unzip must return the second components" + ) + + [] + member _.``unzip3 takes and returns reference tuples`` () = + let triples : IIndexedList = IIndexedList.zip3 list other third + let (first : IndexedList), (second : IndexedList), (last : IndexedList) = + IIndexedList.unzip3 triples + CollectionAssert.AreEqual ( + Inputs.Several, + IndexedList.toArray first, + "Tuple.IIndexedList.unzip3 must return the first components" + ) + CollectionAssert.AreEqual ( + Inputs.Other, + IndexedList.toArray second, + "Tuple.IIndexedList.unzip3 must return the second components" + ) + CollectionAssert.AreEqual ( + Inputs.Third, + IndexedList.toArray last, + "Tuple.IIndexedList.unzip3 must return the third components" + ) + + [] + member _.``zip returns reference tuples`` () = + let pairs : IndexedList = IIndexedList.zip list other + + CollectionAssert.AreEqual ( + Array.zip Inputs.Several Inputs.Other, + IndexedList.toArray pairs, + "Tuple.IIndexedList.zip must zip as List.zip does" + ) + + [] + member _.``zip and zip3 throw for different lengths as the defaults do`` () = + let short : IIndexedList = IndexedList.singleton 1 + + ExceptionAssert.Same ( + id, + (fun () -> + FSharp.Collections.Immutable.IIndexedList.zip list short + |> ignore + ), + (fun () -> IIndexedList.zip list short |> ignore), + "Tuple.IIndexedList.zip must throw as the default zip does" + ) + + ExceptionAssert.Same ( + id, + (fun () -> + FSharp.Collections.Immutable.IIndexedList.zip3 list short other + |> ignore + ), + (fun () -> IIndexedList.zip3 list short other |> ignore), + "Tuple.IIndexedList.zip3 must throw as the default zip3 does" + ) + + [] + member _.``zip3 returns reference tuples`` () = + let triples : IndexedList = IIndexedList.zip3 list other third + + CollectionAssert.AreEqual ( + Array.zip3 Inputs.Several Inputs.Other Inputs.Third, + IndexedList.toArray triples, + "Tuple.IIndexedList.zip3 must zip as List.zip3 does" + ) + + [] + member _.``mapFold takes and returns reference tuples`` () = + let (results : IndexedList), (state : int) = + IIndexedList.mapFold (fun state item -> item * state, state + 1) 1 list + + let expectedResults, expectedState = + Array.mapFold (fun state item -> item * state, state + 1) 1 Inputs.Several + + CollectionAssert.AreEqual ( + expectedResults, + IndexedList.toArray results, + "Tuple.IIndexedList.mapFold must map as List.mapFold does" + ) + Assert.AreEqual(expectedState, state, "Tuple.IIndexedList.mapFold must thread the state as List.mapFold does") + + [] + member _.``mapFoldBack takes and returns reference tuples`` () = + let (results : IndexedList), (state : int) = + IIndexedList.mapFoldBack (fun item state -> item * state, state + 1) list 1 + + let expectedResults, expectedState = + Array.mapFoldBack (fun item state -> item * state, state + 1) Inputs.Several 1 + + CollectionAssert.AreEqual ( + expectedResults, + IndexedList.toArray results, + "Tuple.IIndexedList.mapFoldBack must map as List.mapFoldBack does" + ) + + Assert.AreEqual( + expectedState, + state, + "Tuple.IIndexedList.mapFoldBack must thread the state as List.mapFoldBack does" + ) + + [] + member _.``countBy returns reference tuples`` () = + let counts : IndexedList = IIndexedList.countBy (fun item -> item % 3) list + + CollectionAssert.AreEqual ( + Array.countBy (fun item -> item % 3) Inputs.Several, + IndexedList.toArray counts, + "Tuple.IIndexedList.countBy must count as List.countBy does" + ) + + [] + member _.``groupBy returns reference tuples`` () = + let groups : IndexedList> = IIndexedList.groupBy (fun item -> item % 3) list + 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 |> Seq.map fst |> Seq.toArray, + "Tuple.IIndexedList.groupBy must return the keys as List.groupBy does" + ) + + CollectionAssert.AreEqual ( + Array.map snd expected, + groups + |> Seq.map (fun (_, items) -> IndexedList.toArray items) + |> Seq.toArray, + "Tuple.IIndexedList.groupBy must group the elements as List.groupBy does" + ) + + [] + member _.``partition returns a reference tuple`` () = + let (accepted : IIndexedList), (rejected : IIndexedList) = + IIndexedList.partition (fun item -> item > 0) list + + let expectedAccepted, expectedRejected = Array.partition (fun item -> item > 0) Inputs.Several + CollectionAssert.AreEqual ( + expectedAccepted, + IIndexedList.toArray accepted, + "Tuple.IIndexedList.partition must return the accepted elements" + ) + CollectionAssert.AreEqual ( + expectedRejected, + IIndexedList.toArray rejected, + "Tuple.IIndexedList.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 : IndexedList), (second : IndexedList) = + IIndexedList.partitionWith partitioner list + let expectedFirst, expectedSecond = Array.partitionWith partitioner Inputs.Several + CollectionAssert.AreEqual ( + expectedFirst, + IndexedList.toArray first, + "Tuple.IIndexedList.partitionWith must return the first choices" + ) + CollectionAssert.AreEqual ( + expectedSecond, + IndexedList.toArray second, + "Tuple.IIndexedList.partitionWith must return the second choices" + ) + + [] + member _.``splitAt returns a reference tuple`` () = + let (before : IIndexedList), (after : IIndexedList) = IIndexedList.splitAt 3 list + let expectedBefore, expectedAfter = Array.splitAt 3 Inputs.Several + CollectionAssert.AreEqual ( + expectedBefore, + IIndexedList.toArray before, + "Tuple.IIndexedList.splitAt must return the first part" + ) + CollectionAssert.AreEqual ( + expectedAfter, + IIndexedList.toArray after, + "Tuple.IIndexedList.splitAt must return the second part" + ) + + [] + member _.``the functions without a tuple keep resolving to the default module`` () = + let found : int voption = IIndexedList.tryFind (fun item -> item > 6) list + Assert.AreEqual(ValueSome 8, found, "IIndexedList.tryFind must resolve to the default module") diff --git a/tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListBuilderTests.fs b/tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListBuilderTests.fs new file mode 100644 index 00000000..7a980b71 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListBuilderTests.fs @@ -0,0 +1,951 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open System.Collections.Generic +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// The builder functions of `IndexedList` and the `IndexedListBuilder` module. +[] +type IndexedListBuilderTests () = + + /// A builder that holds the given elements. + static let builderOf (elements : 'T array) = + let builder = IndexedList.builder<'T>() + builder.AddRange elements + builder + + /// A builder that holds the elements of the input with several elements. + static let filled () = builderOf Inputs.Several + + /// A builder that holds the strings of the removal, the replacement and the searches. + static let named () = builderOf ListNames.Several + + /// An equality comparer that finds strings equal when the case is ignored. + static let ignoringCaseEquality : IEqualityComparer = StringComparer.OrdinalIgnoreCase + + /// The functions that search forward in a range of the strings, by name. + static let forwardSearches : Map int -> string -> IndexedList.Builder -> int> = + Map.ofArray [| + nameof IndexedListBuilder.indexOfRange, IndexedListBuilder.indexOfRange + nameof IndexedListBuilder.indexOfRangeWith, IndexedListBuilder.indexOfRangeWith ignoringCaseEquality + |] + + /// The functions that search backward in a range of the strings, by name. + static let backwardSearches : Map int -> string -> IndexedList.Builder -> int> = + Map.ofArray [| + nameof IndexedListBuilder.lastIndexOfRange, IndexedListBuilder.lastIndexOfRange + nameof IndexedListBuilder.lastIndexOfRangeWith, IndexedListBuilder.lastIndexOfRangeWith ignoringCaseEquality + |] + + /// 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; the stability has its own tests. + static let rangeSorts + : Map int -> IndexedList.Builder -> IndexedList.Builder) * (int array -> int array))> = + Map.ofArray [| + nameof IndexedListBuilder.sortRange, struct (IndexedListBuilder.sortRange, ListOracles.stableSort compare) + (nameof IndexedListBuilder.sortRangeWith, + struct (IndexedListBuilder.sortRangeWith FlatListOracles.descending, + ListOracles.stableSort FlatListOracles.descending)) + (nameof IndexedListBuilder.sortRangeWithComparer, + struct (IndexedListBuilder.sortRangeWithComparer (Comparer.Create(Comparison FlatListOracles.byAbsolute)), + ListOracles.stableSort FlatListOracles.byAbsolute)) + |] + + /// 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. A default flat list converts to a sequence without a check and fails + /// when the sequence is enumerated, so the other inputs are valid. The flat list of strings is a sequence of objects, + /// which a type test for the flat list of objects does not see. + static let sequenceRejections : Map unit))> = + let nullValues = Unchecked.defaultof> + let defaultValues = Inputs.DefaultFlatListAsSeq() + let widenedValues = FlatListOracles.defaultStringsAsObjects + let objects () = IndexedList.builder() + let comparer = EqualityComparer.Default + + Map.ofArray [| + "addRange (null sequence)", + struct ("values", (fun () -> IndexedListBuilder.addRange nullValues (filled ()) |> ignore)) + "addRange (default flat list)", + struct ("values", + (fun () -> + IndexedListBuilder.addRange defaultValues (filled ()) + |> ignore + )) + "addRange (default flat list of strings as objects)", + struct ("values", + (fun () -> + IndexedListBuilder.addRange widenedValues (objects ()) + |> ignore + )) + "insertManyAt (null sequence)", + struct ("values", + (fun () -> + IndexedListBuilder.insertManyAt 0 nullValues (filled ()) + |> ignore + )) + "insertManyAt (default flat list)", + struct ("values", + (fun () -> + IndexedListBuilder.insertManyAt 0 defaultValues (filled ()) + |> ignore + )) + "insertManyAt (default flat list of strings as objects)", + struct ("values", + (fun () -> + IndexedListBuilder.insertManyAt 0 widenedValues (objects ()) + |> ignore + )) + "removeMany (null sequence)", + struct ("items", + (fun () -> + IndexedListBuilder.removeMany nullValues (filled ()) + |> ignore + )) + "removeMany (default flat list)", + struct ("items", + (fun () -> + IndexedListBuilder.removeMany defaultValues (filled ()) + |> ignore + )) + "removeMany (default flat list of strings as objects)", + struct ("items", + (fun () -> + IndexedListBuilder.removeMany widenedValues (objects ()) + |> ignore + )) + "removeManyWith (null sequence)", + struct ("items", + (fun () -> + IndexedListBuilder.removeManyWith comparer nullValues (filled ()) + |> ignore + )) + "removeManyWith (default flat list)", + struct ("items", + (fun () -> + IndexedListBuilder.removeManyWith comparer defaultValues (filled ()) + |> ignore + )) + "removeManyWith (default flat list of strings as objects)", + struct ("items", + (fun () -> + IndexedListBuilder.removeManyWith EqualityComparer.Default widenedValues (objects ()) + |> ignore + )) + |] + + /// The functions that change a builder, by name, on a builder; each must return the builder. + static let chainedFunctions : Map.Builder -> IndexedList.Builder> = + Map.ofArray [| + nameof IndexedListBuilder.insertManyAt, IndexedListBuilder.insertManyAt 2 [| 98; 99 |] + nameof IndexedListBuilder.removeAll, IndexedListBuilder.removeAll (fun item -> item < 0) + nameof IndexedListBuilder.removeMany, IndexedListBuilder.removeMany [| 5; 8 |] + nameof IndexedListBuilder.removeManyAt, IndexedListBuilder.removeManyAt 1 2 + nameof IndexedListBuilder.removeManyWith, IndexedListBuilder.removeManyWith EqualityComparer.Default [| 5; 8 |] + nameof IndexedListBuilder.replace, IndexedListBuilder.replace 5 99 + nameof IndexedListBuilder.replaceWith, IndexedListBuilder.replaceWith EqualityComparer.Default 5 99 + nameof IndexedListBuilder.revRange, IndexedListBuilder.revRange 1 4 + nameof IndexedListBuilder.sortRange, IndexedListBuilder.sortRange 1 4 + nameof IndexedListBuilder.sortRangeWith, IndexedListBuilder.sortRangeWith compare 1 4 + nameof IndexedListBuilder.sortRangeWithComparer, IndexedListBuilder.sortRangeWithComparer Comparer.Default 1 4 + |] + + /// The names of the rows of the test of the sequences that the functions reject. + static member SequenceRejections = sequenceRejections.Keys + + /// The names of the functions that change a builder, as the rows of the test of the result. + static member ChainedFunctions = chainedFunctions.Keys + + /// The names of the functions that sort a range, as the rows of the tests that tell them apart. + static member RangeSorts = rangeSorts.Keys + + /// The ranges that each function that sorts a range rejects. + static member SortRangesOutOfRange = seq { + for name in rangeSorts.Keys do + for struct (index, count, fault) in ListMemberCases.RangeFaults do + struct (name, index, count, fault) + } + + /// The ranges that removeManyAt rejects, with the parameter that the BCL names: it names the index for an index + /// beyond the end of the builder, and the count for a count that does not fit. + static member RemovalRangesOutOfRange = seq { + struct (-1, 0, "index") + struct (0, -1, "count") + struct (5, 4, "count") + struct (8, 1, "count") + struct (9, 0, "index") + struct (Int32.MaxValue, 1, "index") + struct (1, Int32.MaxValue, "count") + } + + [] + member _.``build returns the elements that the action adds`` () = + let list = IndexedList.build (fun builder -> builder.AddRange Inputs.Several) + CollectionAssert.AreEqual (Inputs.Several, IndexedList.toArray list, "IndexedList.build must return the added elements") + + [] + member _.``builder starts empty`` () = + let builder = IndexedList.builder() + Assert.AreEqual(0, builder.Count, "IndexedList.builder must start without elements") + + [] + member _.``ofBuilder returns the contents and keeps the builder usable`` () = + let builder = filled () + let list = IndexedList.ofBuilder builder + builder.Add 99 + CollectionAssert.AreEqual (Inputs.Several, IndexedList.toArray list, "IndexedList.ofBuilder must not see later changes") + Assert.AreEqual(Inputs.Several.Length + 1, builder.Count, "IndexedList.ofBuilder must keep the builder usable") + + [] + member _.``ofBuilder throws for a null builder`` () = + FlatListAssert.RejectsNull ( + "IndexedList.ofBuilder", + "builder", + (fun () -> + IndexedList.ofBuilder (Unchecked.defaultof.Builder>) + |> ignore + ) + ) + + [] + member _.``toBuilder starts with the elements and leaves the indexed list unchanged`` () = + let list = IndexedList.ofArray Inputs.Several + let builder = IndexedList.toBuilder list + builder.Add 99 + + CollectionAssert.AreEqual ( + Array.append Inputs.Several [| 99 |], + Seq.toArray builder, + "IndexedList.toBuilder must hold the elements" + ) + + CollectionAssert.AreEqual ( + Inputs.Several, + IndexedList.toArray list, + "IndexedList.toBuilder must not change the indexed list" + ) + + [] + member _.``update applies the action to a builder over the elements`` () = + let list = IndexedList.ofArray Inputs.Several + let updated = IndexedList.update (fun builder -> builder.RemoveAt 0) list + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + IndexedList.toArray updated, + "IndexedList.update must apply the action" + ) + CollectionAssert.AreEqual (Inputs.Several, IndexedList.toArray list, "IndexedList.update must not change the input") + + [] + member _.``add, addRange and insertAt return the same builder`` () = + let builder = IndexedList.builder() + + let result = + builder + |> IndexedListBuilder.add 1 + |> IndexedListBuilder.addRange [| 2; 3 |] + |> IndexedListBuilder.insertAt 0 0 + + Assert.AreSame (builder, result, "The functions that change a builder must return it") + CollectionAssert.AreEqual ([| 0; 1; 2; 3 |], Seq.toArray builder, "add, addRange and insertAt must change the builder") + + [] + member _.``length counts the elements`` () = + Assert.AreEqual( + Inputs.Several.Length, + IndexedListBuilder.length (filled ()), + "IndexedListBuilder.length must count the elements" + ) + + [] + member _.``clear removes every element`` () = + let builder = filled () |> IndexedListBuilder.clear + Assert.AreEqual(0, IndexedListBuilder.length builder, "IndexedListBuilder.clear must remove every element") + + [] + member _.``contains, indexOf and lastIndexOf search the builder`` () = + let builder = filled () + Assert.IsTrue (IndexedListBuilder.contains 8 builder, "IndexedListBuilder.contains must find 8") + Assert.AreEqual(1, IndexedListBuilder.indexOf -3 builder, "IndexedListBuilder.indexOf must return the first index") + Assert.AreEqual( + 5, + IndexedListBuilder.lastIndexOf -3 builder, + "IndexedListBuilder.lastIndexOf must return the last index" + ) + Assert.AreEqual(-1, IndexedListBuilder.indexOf 1000 builder, "IndexedListBuilder.indexOf must return -1 when absent") + + [] + member _.``item and updateAt read and replace an element`` () = + let builder = filled () |> IndexedListBuilder.updateAt 2 99 + Assert.AreEqual(99, IndexedListBuilder.item 2 builder, "IndexedListBuilder.updateAt must replace the element") + + [] + member _.``item throws for an index out of range`` () = + Assert.ThrowsExactly( + Action (fun () -> IndexedListBuilder.item 8 (filled ()) |> ignore), + "IndexedListBuilder.item must reject an index out of range as the builder does" + ) + |> ignore + + [] + member _.``remove and removeAt remove elements`` () = + let builder = filled () + Assert.IsTrue (IndexedListBuilder.remove -3 builder, "IndexedListBuilder.remove must report a removed element") + Assert.IsFalse (IndexedListBuilder.remove 1000 builder, "IndexedListBuilder.remove must report a missing element") + IndexedListBuilder.removeAt 0 builder |> ignore + 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 () |> IndexedListBuilder.rev + CollectionAssert.AreEqual ( + Array.rev Inputs.Several, + Seq.toArray builder, + "IndexedListBuilder.rev must reverse the elements" + ) + + [] + member _.``sort, sortWith and sortWithComparer order the builder`` () = + CollectionAssert.AreEqual ( + Array.sort Inputs.Several, + Seq.toArray (filled () |> IndexedListBuilder.sort), + "IndexedListBuilder.sort must sort ascending" + ) + + CollectionAssert.AreEqual ( + Array.sortDescending Inputs.Several, + Seq.toArray ( + filled () + |> IndexedListBuilder.sortWith (fun left right -> compare right left) + ), + "IndexedListBuilder.sortWith must use the comparison function" + ) + + CollectionAssert.AreEqual ( + Array.sortDescending Inputs.Several, + Seq.toArray ( + filled () + |> IndexedListBuilder.sortWithComparer (Comparer.Create (fun left right -> compare right left)) + ), + "IndexedListBuilder.sortWithComparer must use the comparer" + ) + + [] + member _.``the sorts of the builder are stable`` () = + // The sorts of Array are not stable, so the expectations come from the stable sorts of List + let key (struct (key : int, _ : char)) = key + let compareKeys left right = compare (key left) (key right) + + let builder () = + let builder = IndexedList.builder() + builder.AddRange Inputs.ManyPairs + builder + + CollectionAssert.AreEqual ( + List.sortWith compareKeys (List.ofArray Inputs.ManyPairs) + |> List.toArray, + Seq.toArray (builder () |> IndexedListBuilder.sortWith compareKeys), + "IndexedListBuilder.sortWith must keep the order of equal elements, as List.sortWith does" + ) + + CollectionAssert.AreEqual ( + List.sortWith compareKeys (List.ofArray Inputs.ManyPairs) + |> List.toArray, + Seq.toArray ( + builder () + |> IndexedListBuilder.sortWithComparer ( + Comparer.Create (fun left right -> compareKeys left right) + ) + ), + "IndexedListBuilder.sortWithComparer must keep the order of equal elements, as List.sortWith does" + ) + + let keyed () = builderOf (KeyedItem.OfManyPairs ()) + + CollectionAssert.AreEqual ( + List.sort (List.ofSeq (keyed ())) |> KeyedItem.KeysAndTags, + keyed () |> IndexedListBuilder.sort |> KeyedItem.KeysAndTags, + "IndexedListBuilder.sort must keep the order of equal elements, as List.sort does" + ) + + [] + member _.``toArray copies the elements`` () = + CollectionAssert.AreEqual ( + Inputs.Several, + IndexedListBuilder.toArray (filled ()), + "IndexedListBuilder.toArray must copy the elements" + ) + + // ---- the functions that change a builder return it ---- + + [] + member _.``the functions that change a builder return it`` (name : string) = + let builder = filled () + + Assert.AreSame ( + builder, + chainedFunctions[name] builder, + $"IndexedListBuilder.%s{name} must return the builder, so that the calls chain" + ) + + [] + 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, action) = sequenceRejections[name] + FlatListAssert.RejectsNull ($"IndexedListBuilder.%s{name}", paramName, action) + + [] + member _.``insertManyAt rejects an index out of range before the values as the BCL does`` (index : int, kind : string) = + // The builder has 8 elements, so the indices -1 and 9 are out of range. The member checks the index before it reads + // the values, and the default flat list is as good as a null sequence for it + let values = + if kind = "null sequence" then + Unchecked.defaultof> + else + Inputs.DefaultFlatListAsSeq() + + let ex = + Assert.ThrowsExactly( + Action (fun () -> + IndexedListBuilder.insertManyAt index values (filled ()) + |> ignore + ), + $"IndexedListBuilder.insertManyAt must check the index before a %s{kind}" + ) + + Assert.AreEqual("index", ex.ParamName, "IndexedListBuilder.insertManyAt must name the index") + + [] + 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 = IndexedList.builder() + + // Add the elements, insert them again after the first one, then remove one element for each of them + builder + |> IndexedListBuilder.addRange widened + |> IndexedListBuilder.insertManyAt 1 widened + |> IndexedListBuilder.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" + ) + + IndexedListBuilder.removeManyWith EqualityComparer.Default widened builder + |> ignore + + Assert.IsEmpty (Seq.toArray builder, "removeManyWith must take the elements of a flat list of strings as objects") + + // ---- insertManyAt, removeManyAt and removeAll ---- + + [] + member _.``insertManyAt inserts the values at the index`` (index : int) = + CollectionAssert.AreEqual ( + Array.insertManyAt index [| 98; 99 |] Inputs.Several, + Seq.toArray ( + filled () + |> IndexedListBuilder.insertManyAt index [| 98; 99 |] + ), + "IndexedListBuilder.insertManyAt must insert the values at the index" + ) + + [] + member _.``insertManyAt throws for an index out of range`` (index : int) = + let ex = + Assert.ThrowsExactly( + Action (fun () -> + IndexedListBuilder.insertManyAt index [| 98 |] (filled ()) + |> ignore + ), + "IndexedListBuilder.insertManyAt must reject an index out of range as the builder does" + ) + + Assert.AreEqual("index", ex.ParamName, "IndexedListBuilder.insertManyAt must name the index") + + [)>] + member _.``removeManyAt removes the elements of the range`` (input : int array, index : int, count : int) = + CollectionAssert.AreEqual ( + ListOracles.changeRange (fun _ -> [||]) input index count, + Seq.toArray ( + builderOf input + |> IndexedListBuilder.removeManyAt index count + ), + "IndexedListBuilder.removeManyAt must remove the elements of the range" + ) + + [] + member _.``removeManyAt throws for a range out of range as the builder does`` (index : int, count : int, paramName : string) = + let ex = + Assert.ThrowsExactly( + Action (fun () -> + IndexedListBuilder.removeManyAt index count (filled ()) + |> ignore + ), + "IndexedListBuilder.removeManyAt must reject a range out of range as the builder does" + ) + + Assert.AreEqual( + paramName, + ex.ParamName, + "IndexedListBuilder.removeManyAt must name the parameter that the BCL names" + ) + + [)>] + member _.``removeAll removes the elements that satisfy the predicate`` (input : int array) = + CollectionAssert.AreEqual ( + Array.filter (fun item -> item % 2 <> 0) input, + Seq.toArray ( + builderOf input + |> IndexedListBuilder.removeAll (fun item -> item % 2 = 0) + ), + "IndexedListBuilder.removeAll must remove the elements that satisfy the predicate" + ) + + // ---- remove, removeWith, removeMany and removeManyWith ---- + + [)>] + member _.``remove removes the first equal element and tells whether it removed one`` (input : string array, value : string) = + let builder = builderOf input + let removed = IndexedListBuilder.remove value builder + + Assert.AreEqual( + Array.contains value input, + removed, + "IndexedListBuilder.remove must tell whether it removed an element" + ) + + CollectionAssert.AreEqual ( + FlatListOracles.removeFirst (=) value input, + Seq.toArray builder, + "IndexedListBuilder.remove must remove the first equal element" + ) + + [)>] + member _.``removeWith removes the first element that the comparer finds equal and tells whether it removed one`` + (input : string array, value : string) + = + let builder = builderOf input + let removed = IndexedListBuilder.removeWith ignoringCaseEquality value builder + + Assert.AreEqual( + Array.exists (FlatListOracles.equalIgnoringCase value) input, + removed, + "IndexedListBuilder.removeWith must tell whether it removed an element" + ) + + CollectionAssert.AreEqual ( + FlatListOracles.removeFirst FlatListOracles.equalIgnoringCase value input, + Seq.toArray builder, + "IndexedListBuilder.removeWith must remove the first element that the comparer finds equal" + ) + + [)>] + member _.``removeMany removes one element for each value`` (input : string array, items : string array) = + CollectionAssert.AreEqual ( + ListOracles.removeEach (=) items input, + Seq.toArray (builderOf input |> IndexedListBuilder.removeMany items), + "IndexedListBuilder.removeMany must remove one element for each value" + ) + + [)>] + member _.``removeManyWith removes one element for each value that the comparer finds equal`` + (input : string array, items : string array) + = + CollectionAssert.AreEqual ( + ListOracles.removeEach FlatListOracles.equalIgnoringCase items input, + Seq.toArray ( + builderOf input + |> IndexedListBuilder.removeManyWith ignoringCaseEquality items + ), + "IndexedListBuilder.removeManyWith must remove one element for each value that the comparer finds equal" + ) + + // ---- replace and replaceWith ---- + + [)>] + member _.``replace replaces the first equal element`` (input : string array, oldValue : string) = + CollectionAssert.AreEqual ( + Assert.WantValueSome ( + FlatListOracles.replaceFirst (=) oldValue "X" input, + "The oracle must find the old value, which the rows take from the elements" + ), + Seq.toArray (builderOf input |> IndexedListBuilder.replace oldValue "X"), + "IndexedListBuilder.replace must replace the first equal element" + ) + + [)>] + member _.``replaceWith replaces the first element that the comparer finds equal`` (input : string array, oldValue : string) = + CollectionAssert.AreEqual ( + Assert.WantValueSome ( + FlatListOracles.replaceFirst FlatListOracles.equalIgnoringCase oldValue "X" input, + "The oracle must find the old value, which the rows take from the elements" + ), + Seq.toArray ( + builderOf input + |> IndexedListBuilder.replaceWith ignoringCaseEquality oldValue "X" + ), + "IndexedListBuilder.replaceWith must replace the first element that the comparer finds equal" + ) + + [)>] + member _.``replace throws ArgumentException naming the old value when no element equals it, as the builder does`` + (input : string array, oldValue : string) + = + let builder = builderOf input + + let ex = + Assert.ThrowsExactly( + Action (fun () -> IndexedListBuilder.replace oldValue "X" builder |> ignore), + "IndexedListBuilder.replace must throw ArgumentException for a value that no element equals" + ) + + Assert.AreEqual("oldValue", ex.ParamName, "IndexedListBuilder.replace must name the old value") + CollectionAssert.AreEqual ( + input, + Seq.toArray builder, + "IndexedListBuilder.replace must not change the builder when it throws" + ) + + [)>] + member _.``replaceWith throws ArgumentException naming the old value when the comparer finds no element equal`` + (input : string array, oldValue : string) + = + let ex = + Assert.ThrowsExactly( + Action (fun () -> + IndexedListBuilder.replaceWith ignoringCaseEquality oldValue "X" (builderOf input) + |> ignore + ), + "IndexedListBuilder.replaceWith must throw ArgumentException for a value that the comparer finds in no element" + ) + + Assert.AreEqual("oldValue", ex.ParamName, "IndexedListBuilder.replaceWith must name the old value") + + // ---- revRange ---- + + [)>] + member _.``revRange reverses the elements of the range and keeps the other elements`` + (input : int array, index : int, count : int) + = + CollectionAssert.AreEqual ( + ListOracles.changeRange Array.rev input index count, + Seq.toArray (builderOf input |> IndexedListBuilder.revRange index count), + "IndexedListBuilder.revRange must reverse the range and keep the other elements" + ) + + [)>] + member _.``revRange throws for a range out of range naming the parameter that the BCL names`` + (index : int, count : int, fault : RangeFault) + = + let ex = + Assert.ThrowsExactly( + Action (fun () -> + IndexedListBuilder.revRange index count (filled ()) + |> ignore + ), + "IndexedListBuilder.revRange must throw ArgumentOutOfRangeException for a range out of range" + ) + + // ImmutableList.Builder.Reverse(Int32, Int32) names the count for a range beyond the end + Assert.AreEqual( + fault.ParamName ("index", "count"), + ex.ParamName, + "IndexedListBuilder.revRange must name the parameter that the BCL names" + ) + + // ---- 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] + + CollectionAssert.AreEqual ( + ListOracles.changeRange sortPart input index count, + Seq.toArray (sortRange index count (builderOf input)), + $"IndexedListBuilder.%s{name} must sort the range and keep the other elements" + ) + + [] + member _.``functions that sort a range throw for a range out of range naming the parameter that the BCL names`` + (name : string, index : int, count : int, fault : RangeFault) + = + let struct (sortRange, _) = rangeSorts[name] + + let ex = + Assert.ThrowsExactly( + Action (fun () -> sortRange index count (filled ()) |> ignore), + $"IndexedListBuilder.%s{name} must throw ArgumentOutOfRangeException for a range out of range" + ) + + Assert.AreEqual( + fault.ParamName ("index", "count"), + ex.ParamName, + $"IndexedListBuilder.%s{name} must name the parameter that the BCL names" + ) + + [] + member _.``functions that sort a range do not run the comparison for a range of at most one element`` (name : string) = + let sorts : Map int -> IndexedList.Builder -> IndexedList.Builder> = + let fail _ _ : int = failwith "The comparison must not run" + + Map.ofArray [| + nameof IndexedListBuilder.sortRange, IndexedListBuilder.sortRange + nameof IndexedListBuilder.sortRangeWith, IndexedListBuilder.sortRangeWith fail + nameof IndexedListBuilder.sortRangeWithComparer, + IndexedListBuilder.sortRangeWithComparer (Comparer.Create(Comparison fail)) + |] + + // A range of one element and an empty one compare nothing + CollectionAssert.AreEqual ( + Inputs.Several, + Seq.toArray (sorts[name] 2 1 (filled ())), + $"IndexedListBuilder.%s{name} must not compare the one element of a range" + ) + + CollectionAssert.AreEqual ( + Inputs.Several, + Seq.toArray (sorts[name] 5 0 (filled ())), + $"IndexedListBuilder.%s{name} must not compare the elements of an empty range" + ) + + [)>] + member _.``sortRange keeps the order of the elements that compare equal`` (index : int, count : int) = + CollectionAssert.AreEqual ( + ListOracles.sortedKeyedRange index count, + KeyedItem.KeysAndTags ( + builderOf (KeyedItem.OfManyPairs ()) + |> IndexedListBuilder.sortRange index count + ), + "IndexedListBuilder.sortRange must keep the order of equal elements, as List.sort does" + ) + + [)>] + member _.``sortRangeWith keeps the order of the elements that compare equal`` + (direction : SortDirection, index : int, count : int) + = + let comparison = ListOracles.compareKeys direction + + CollectionAssert.AreEqual ( + ListOracles.sortedPairRange comparison index count, + Seq.toArray ( + builderOf Inputs.ManyPairs + |> IndexedListBuilder.sortRangeWith comparison index count + ), + "IndexedListBuilder.sortRangeWith must keep the order of equal elements, as List.sortWith does" + ) + + [)>] + member _.``sortRangeWithComparer keeps the order of the elements that compare equal`` + (direction : SortDirection, index : int, count : int) + = + let comparison = ListOracles.compareKeys direction + let comparer = Comparer.Create (fun left right -> comparison left right) + + CollectionAssert.AreEqual ( + ListOracles.sortedPairRange comparison index count, + Seq.toArray ( + builderOf Inputs.ManyPairs + |> IndexedListBuilder.sortRangeWithComparer comparer index count + ), + "IndexedListBuilder.sortRangeWithComparer must keep the order of equal elements, as List.sortWith does" + ) + + // ---- indexOfRange, indexOfRangeWith, lastIndexOfRange and lastIndexOfRangeWith ---- + + [)>] + member _.``indexOfRange and indexOfRangeWith return the index of the first equal element of the range or -1`` + (startIndex : int, count : int, value : string) + = + Assert.AreEqual( + FlatListOracles.firstInRange (=) startIndex count value ListNames.Several, + IndexedListBuilder.indexOfRange startIndex count value (named ()), + "IndexedListBuilder.indexOfRange must return the index of the first equal element of the range, or -1" + ) + + Assert.AreEqual( + FlatListOracles.firstInRange FlatListOracles.equalIgnoringCase startIndex count value ListNames.Several, + IndexedListBuilder.indexOfRangeWith ignoringCaseEquality startIndex count value (named ()), + "IndexedListBuilder.indexOfRangeWith must compare with the equality comparer" + ) + + [)>] + member _.``lastIndexOfRange and lastIndexOfRangeWith return the index of the last equal element of the range or -1`` + (startIndex : int, count : int, value : string) + = + Assert.AreEqual( + ListOracles.lastIndexOfRange (=) startIndex count value ListNames.Several, + IndexedListBuilder.lastIndexOfRange startIndex count value (named ()), + "IndexedListBuilder.lastIndexOfRange must return the index of the last equal element of the range, or -1" + ) + + Assert.AreEqual( + ListOracles.lastIndexOfRange FlatListOracles.equalIgnoringCase startIndex count value ListNames.Several, + IndexedListBuilder.lastIndexOfRangeWith ignoringCaseEquality startIndex count value (named ()), + "IndexedListBuilder.lastIndexOfRangeWith must compare with the equality comparer" + ) + + [)>] + member _.``the searches forward in a range throw for a range out of range`` + (name : string, startIndex : int, count : int, fault : RangeFault) + = + let ex = + Assert.ThrowsExactly( + Action (fun () -> + forwardSearches[name] startIndex count "a" (named ()) + |> ignore + ), + $"IndexedListBuilder.%s{name} must throw ArgumentOutOfRangeException for a range out of range" + ) + + Assert.AreEqual( + fault.ParamName ("startIndex", "count"), + ex.ParamName, + $"IndexedListBuilder.%s{name} must name the offending parameter" + ) + + [)>] + member _.``the searches backward in a range throw for a range out of range`` + (name : string, startIndex : int, count : int, fault : RangeFault) + = + let ex = + Assert.ThrowsExactly( + Action (fun () -> + backwardSearches[name] startIndex count "a" (named ()) + |> ignore + ), + $"IndexedListBuilder.%s{name} must throw ArgumentOutOfRangeException for a range out of range" + ) + + Assert.AreEqual( + fault.ParamName ("startIndex", "count"), + ex.ParamName, + $"IndexedListBuilder.%s{name} must name the offending parameter" + ) + + // ---- binarySearch, binarySearchWithComparer and binarySearchRangeWithComparer ---- + + [] + member _.``binarySearch returns what Array.BinarySearch returns`` (value : int) = + Assert.AreEqual( + Array.BinarySearch (SortedInputs.Ascending, value), + IndexedListBuilder.binarySearch value (builderOf SortedInputs.Ascending), + "IndexedListBuilder.binarySearch must search as Array.BinarySearch does" + ) + + [] + member _.``binarySearch uses the order of sort, which is ordinal for strings`` (index : int) = + // In the order of the characters, and not in the order of a culture, "A" and "B" come before "a" and "b" + let strings = [| "A"; "B"; "a"; "b" |] + + Assert.AreEqual( + index, + IndexedListBuilder.binarySearch strings[index] (builderOf strings), + "IndexedListBuilder.binarySearch must find every element of a builder sorted ordinally at its index" + ) + [)>] + member _.``binarySearchWithComparer searches in the order of the comparer`` (value : string) = + let comparer = StringComparer.OrdinalIgnoreCase :> IComparer + + Assert.AreEqual( + Array.BinarySearch (SortedInputs.Names, value, comparer), + IndexedListBuilder.binarySearchWithComparer comparer value (builderOf SortedInputs.Names), + "IndexedListBuilder.binarySearchWithComparer must search in the order of the comparer" + ) + + [)>] + member _.``binarySearchRangeWithComparer searches the range in the order of the comparer`` + (index : int, count : int, value : int) + = + let descending = Array.rev SortedInputs.Ascending + + Assert.AreEqual( + Array.BinarySearch (descending, index, count, value, SortedInputs.DescendingComparer), + IndexedListBuilder.binarySearchRangeWithComparer + SortedInputs.DescendingComparer + index + count + value + (builderOf descending), + "IndexedListBuilder.binarySearchRangeWithComparer must search the range in the order of the comparer" + ) + + [)>] + member _.``binarySearchRangeWithComparer throws for a range out of range`` (index : int, count : int, fault : RangeFault) = + let action () = + IndexedListBuilder.binarySearchRangeWithComparer + Comparer.Default + index + count + 7 + (builderOf SortedInputs.Ascending) + |> ignore + + match fault with + | RangeFault.Beyond -> + // The BCL does not check this and its documentation promises ArgumentException (ADR 0001, D14) + let ex = + Assert.ThrowsExactly( + Action action, + "IndexedListBuilder.binarySearchRangeWithComparer must throw ArgumentException for a range beyond the end" + ) + + Assert.AreEqual( + "count", + ex.ParamName, + "IndexedListBuilder.binarySearchRangeWithComparer must name the count" + ) + | _ -> + let ex = + Assert.ThrowsExactly( + Action action, + "IndexedListBuilder.binarySearchRangeWithComparer must throw ArgumentOutOfRangeException for a negative number" + ) + + Assert.AreEqual( + fault.ParamName ("index", "count"), + ex.ParamName, + "IndexedListBuilder.binarySearchRangeWithComparer must name the parameter that the BCL names" + ) + + // ---- blit ---- + + [] + member _.``blit copies a range into an array`` () = + let target = Array.zeroCreate 5 + IndexedListBuilder.blit (filled ()) 2 target 1 3 + + CollectionAssert.AreEqual ( + [| 0; 8; 5; 0; 0 |], + target, + "IndexedListBuilder.blit must copy three elements from index 2 to index 1" + ) + + [)>] + member _.``blit throws for a range out of range with the names of its parameters`` + (sourceIndex : int, targetIndex : int, count : int, paramName : string) + = + let ex = + Assert.ThrowsExactly( + Action (fun () -> IndexedListBuilder.blit (filled ()) sourceIndex (Array.zeroCreate 5) targetIndex count), + "IndexedListBuilder.blit must reject a range out of range" + ) + + Assert.AreEqual(paramName, ex.ParamName, $"IndexedListBuilder.blit must name %s{paramName}") + + [] + member _.``blit throws for a null target with the name of the parameter`` () = + FlatListAssert.RejectsNull ( + "IndexedListBuilder.blit", + "target", + (fun () -> IndexedListBuilder.blit (filled ()) 0 (Unchecked.defaultof) 0 0) + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListComputationExpressionTests.fs b/tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListComputationExpressionTests.fs new file mode 100644 index 00000000..1c9f6c70 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListComputationExpressionTests.fs @@ -0,0 +1,236 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// The `indexedList { }` computation expression. +[] +type IndexedListComputationExpressionTests () = + + /// The expressions that give yield! a null input, by the shape of the input. The rows of the test are the names: a + /// function has no readable name in a row. + static let nullYields : Map IndexedList> = + Map.ofArray [| + "sequence", (fun () -> indexedList { yield! Unchecked.defaultof> }) + "array", (fun () -> indexedList { yield! Unchecked.defaultof }) + "indexed list", (fun () -> indexedList { yield! Unchecked.defaultof> }) + |] + + /// The sequences that yield! and for reject, by the construct and the input, with the parameter that names the input and + /// the expression. A default flat list converts to a sequence without a check and fails when the sequence is + /// enumerated, and the flat list of strings is a sequence of objects, which a type test for the flat list of objects + /// does not see. + static let sequenceRejections : Map unit))> = + let nullSequence = Unchecked.defaultof> + let defaultSequence = Inputs.DefaultFlatListAsSeq() + let widenedSequence = FlatListOracles.defaultStringsAsObjects + + Map.ofArray [| + "yield! (null sequence)", struct ("items", (fun () -> indexedList { yield! nullSequence } |> ignore)) + "yield! (default flat list)", struct ("items", (fun () -> indexedList { yield! defaultSequence } |> ignore)) + "yield! (default flat list of strings as objects)", + struct ("items", (fun () -> indexedList { yield! widenedSequence } |> ignore)) + "for (null sequence)", + struct ("source", + (fun () -> + indexedList { + for item in nullSequence do + yield item + } + |> ignore + )) + "for (default flat list)", + struct ("source", + (fun () -> + indexedList { + for item in defaultSequence do + yield item + } + |> ignore + )) + "for (default flat list of strings as objects)", + struct ("source", + (fun () -> + indexedList { + for item in widenedSequence do + yield item + } + |> ignore + )) + |] + + /// The shapes of the null inputs of yield!, as the rows of their test. + static member NullYields = nullYields.Keys + + /// The constructs and the inputs that take a sequence, as the rows of the test of the sequences that they reject. + static member SequenceRejections = sequenceRejections.Keys + + [] + member _.``an expression without elements is empty`` () = + let list : IndexedList = indexedList { () } + Assert.IsTrue (IndexedList.isEmpty list, "indexedList { () } must be empty") + + [] + member _.``yield adds the elements in order`` () = + let list = indexedList { + yield 1 + yield 2 + yield 3 + } + + CollectionAssert.AreEqual ([| 1; 2; 3 |], IndexedList.toArray list, "indexedList must keep the order of yield") + + [] + member _.``implicit yields add the elements in order`` () = + let list = indexedList { + 1 + 2 + 3 + } + + CollectionAssert.AreEqual ([| 1; 2; 3 |], IndexedList.toArray list, "indexedList must keep the order of implicit yields") + + [] + member _.``yield! adds a sequence, an array, an indexed list and another expression`` () = + let inner = indexedList { yield 7 } + + let list = indexedList { + yield! [ 1; 2 ] + yield! [| 3; 4 |] + yield! IndexedList.ofArray [| 5; 6 |] + yield! inner + } + + CollectionAssert.AreEqual ( + [| 1; 2; 3; 4; 5; 6; 7 |], + IndexedList.toArray list, + "indexedList must add the elements of yield!" + ) + + [] + member _.``yield! rejects a null input with the name of its parameter`` (shape : string) = + FlatListAssert.RejectsNull ( + $"indexedList with a null %s{shape} in yield!", + "items", + (fun () -> nullYields[shape]() |> ignore) + ) + + [] + member _.``yield! and for reject a null sequence, a default flat list and a default flat list of strings`` (name : string) = + let struct (paramName, expression) = sequenceRejections[name] + FlatListAssert.RejectsNull ($"indexedList with %s{name}", paramName, expression) + + [] + 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 list = indexedList { + yield! widened + + for item in widened do + yield item + } + + CollectionAssert.AreEqual ( + [| "a" :> obj; "b" :> obj; "a" :> obj; "b" :> obj |], + IndexedList.toArray list, + "indexedList must take the elements of a flat list of strings as objects in yield! and for" + ) + + [] + member _.``for adds the elements of the body for each item`` () = + let list = indexedList { + for item in 1..3 do + yield item * 10 + } + + CollectionAssert.AreEqual ( + [| 10; 20; 30 |], + IndexedList.toArray list, + "indexedList must run the body of for for each item" + ) + + [] + member _.``while adds the elements while the guard holds`` () = + let mutable count = 0 + + let list = indexedList { + while count < 3 do + count <- count + 1 + yield count + } + + CollectionAssert.AreEqual ( + [| 1; 2; 3 |], + IndexedList.toArray list, + "indexedList must run the body of while while the guard holds" + ) + + [] + member _.``if without else and following elements combine`` () = + let list = indexedList { + if false then + yield 0 + + yield 1 + + if true then + yield 2 + } + + CollectionAssert.AreEqual ( + [| 1; 2 |], + IndexedList.toArray list, + "indexedList must combine conditional and plain elements" + ) + + [] + member _.``try with adds the elements of the handler after an exception`` () = + let list = indexedList { + try + yield 1 + failwith "fails" + with _ -> + yield 2 + } + + CollectionAssert.AreEqual ( + [| 1; 2 |], + IndexedList.toArray list, + "indexedList must keep the elements before the exception and add the handler's" + ) + + [] + member _.``try finally runs the compensation`` () = + let mutable compensated = false + + let list = indexedList { + try + yield 1 + finally + compensated <- true + } + + Assert.IsTrue (compensated, "indexedList must run the compensation of try finally") + CollectionAssert.AreEqual ( + [| 1 |], + IndexedList.toArray list, + "indexedList must keep the elements of the body of try finally" + ) + + [] + member _.``use disposes the resource after the body`` () = + let mutable disposed = false + + let list = indexedList { + use _resource = { + new IDisposable with + member _.Dispose () = disposed <- true + } + + yield 1 + } + + Assert.IsTrue (disposed, "indexedList must dispose the resource of use") + CollectionAssert.AreEqual ([| 1 |], IndexedList.toArray list, "indexedList must keep the elements of the body of use") diff --git a/tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListContractTests.fs b/tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListContractTests.fs new file mode 100644 index 00000000..95550bd8 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListContractTests.fs @@ -0,0 +1,59 @@ +namespace FSharp.Collections.Immutable.Tests + +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// The sequence-like contract for `IndexedList`. +[] +type IndexedListSeqLikeTests () = + inherit SeqLikeContract> (IndexedListOps.seqLike) + +/// The creation contract for `IndexedList`. +[] +type IndexedListCreationTests () = + inherit SeqCreationContract> (IndexedListOps.creation) + +/// The index-based contract for `IndexedList`. +[] +type IndexedListIndexedTests () = + inherit IndexedContract> (IndexedListOps.indexed) + +/// The contract of the functions that wrap members of `IImmutableList`, for `IndexedList`. +[] +type IndexedListMembersTests () = + inherit ListMembersContract> (IndexedListOps.listMembers) + +/// The stable sorting contract for `IndexedList`. +[] +type IndexedListStableSortTests () = + inherit StableSortContract> (IndexedListOps.stableSort) + +/// The sequence-like contract for `IIndexedList`. +[] +type IIndexedListSeqLikeTests () = + inherit SeqLikeContract> (IIndexedListOps.seqLike) + +/// The index-based contract for `IIndexedList`. +[] +type IIndexedListIndexedTests () = + inherit IndexedContract> (IIndexedListOps.indexed) + +/// The contract of the functions that wrap members of `IImmutableList`, for `IIndexedList`. +[] +type IIndexedListMembersTests () = + inherit ListMembersContract> (IIndexedListOps.listMembers) + +/// The stable sorting contract for `IIndexedList`. +[] +type IIndexedListStableSortTests () = + inherit StableSortContract> (IIndexedListOps.stableSort) + +/// The bounds on what `IndexedList` allocates for large collections. +[] +type IndexedListCostTests () = + inherit CostContract> (IndexedListOps.cost) + +/// The bounds on what `IIndexedList` allocates for large indexed lists. +[] +type IIndexedListCostTests () = + inherit CostContract> (IIndexedListOps.cost) diff --git a/tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListOps.fs b/tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListOps.fs new file mode 100644 index 00000000..e7b09c7a --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListOps.fs @@ -0,0 +1,266 @@ +/// The functions of the `IndexedList` module for the contract tests. +[] +module FSharp.Collections.Immutable.Tests.IndexedListOps + +open System +open FSharp.Collections.Immutable + +/// The elements of the indexed lists of an indexed list, as arrays. The functions of IIndexedList return indexed lists +/// of indexed lists as well, so the Ops of IIndexedList uses it too. +let toArrays (lists : IndexedList>) = lists |> Seq.map IndexedList.toArray |> Seq.toArray + +/// The elements of the two indexed lists of a pair, as a pair of arrays; the Ops of IIndexedList uses it too. +let toArrayPair (struct (first : IndexedList<'T1>, second : IndexedList<'T2>)) = + struct (IndexedList.toArray first, IndexedList.toArray second) + +/// The functions of the sequence-like facet. `IndexedList` names its parameters as the `List` module does, so its +/// exceptions name the parameters that the `List` functions name. +let seqLike : SeqOps> = { + Name = nameof IndexedList + Mirror = ListMirror + ParamName = id + OfSeq = IndexedList.ofSeq + ToArray = IndexedList.toArray + + IsEmpty = IndexedList.isEmpty + Length = IndexedList.length + + ExactlyOne = IndexedList.exactlyOne + Head = IndexedList.head + Last = IndexedList.last + Tail = IndexedList.tail + TryExactlyOne = IndexedList.tryExactlyOne + TryHead = IndexedList.tryHead + TryLast = IndexedList.tryLast + TryTail = IndexedList.tryTail + + Contains = IndexedList.contains + Exists = IndexedList.exists + Exists2 = IndexedList.exists2 + Find = IndexedList.find + FindBack = IndexedList.findBack + FindIndex = IndexedList.findIndex + FindIndexBack = IndexedList.findIndexBack + Forall = IndexedList.forall + Forall2 = IndexedList.forall2 + Pick = IndexedList.pick + TryFind = IndexedList.tryFind + TryFindBack = IndexedList.tryFindBack + TryFindIndex = IndexedList.tryFindIndex + TryFindIndexBack = IndexedList.tryFindIndexBack + TryPick = IndexedList.tryPick + + AllPairs = fun list1 list2 -> IndexedList.allPairs list1 list2 |> IndexedList.toArray + Append = IndexedList.append + Choose = IndexedList.choose + ChunkBySize = fun chunkSize list -> IndexedList.chunkBySize chunkSize list |> toArrays + Collect = IndexedList.collect + Concat = IndexedList.concat + Filter = IndexedList.filter + Indexed = IndexedList.indexed >> IndexedList.toArray + Map = IndexedList.map + Map2 = IndexedList.map2 + Map3 = IndexedList.map3 + Mapi = IndexedList.mapi + Mapi2 = IndexedList.mapi2 + Pairwise = IndexedList.pairwise >> IndexedList.toArray + Permute = IndexedList.permute + Rev = IndexedList.rev + Transpose = fun lists -> IndexedList.transpose lists |> toArrays + Unzip = fun pairs -> IndexedList.unzip (IndexedList.ofArray pairs) |> toArrayPair + Unzip3 = + fun triples -> + let struct (first, second, third) = IndexedList.unzip3 (IndexedList.ofArray triples) + struct (IndexedList.toArray first, IndexedList.toArray second, IndexedList.toArray third) + Where = IndexedList.where + Windowed = fun windowSize list -> IndexedList.windowed windowSize list |> toArrays + Zip = fun list1 list2 -> IndexedList.zip list1 list2 |> IndexedList.toArray + Zip3 = fun list1 list2 list3 -> IndexedList.zip3 list1 list2 list3 |> IndexedList.toArray + + Average = fun items -> IndexedList.average (IndexedList.ofArray items) + AverageBy = IndexedList.averageBy + CompareWith = IndexedList.compareWith + Fold = IndexedList.fold + Fold2 = IndexedList.fold2 + FoldBack = IndexedList.foldBack + FoldBack2 = IndexedList.foldBack2 + Iter = IndexedList.iter + Iter2 = IndexedList.iter2 + Iteri = IndexedList.iteri + Iteri2 = IndexedList.iteri2 + MapFold = + fun mapping state list -> + let struct (results, final) = IndexedList.mapFold mapping state list + struct (IndexedList.toArray results, final) + MapFoldBack = + fun mapping list state -> + let struct (results, final) = IndexedList.mapFoldBack mapping list state + struct (IndexedList.toArray results, final) + Max = IndexedList.max + MaxBy = IndexedList.maxBy + Min = IndexedList.min + MinBy = IndexedList.minBy + Reduce = IndexedList.reduce + ReduceBack = IndexedList.reduceBack + Scan = fun folder state list -> IndexedList.scan folder state list |> IndexedList.toArray + ScanBack = + fun folder list state -> + IndexedList.scanBack folder list state + |> IndexedList.toArray + Sum = IndexedList.sum + SumBy = IndexedList.sumBy + + CountBy = fun projection list -> IndexedList.countBy projection list |> IndexedList.toArray + Distinct = IndexedList.distinct + DistinctBy = IndexedList.distinctBy + Except = IndexedList.except + GroupBy = + fun projection list -> + IndexedList.groupBy projection list + |> Seq.map (fun struct (key, items) -> struct (key, IndexedList.toArray items)) + |> Seq.toArray + Partition = IndexedList.partition + PartitionWith = fun partitioner list -> IndexedList.partitionWith partitioner list |> toArrayPair + Skip = IndexedList.skip + SkipLast = IndexedList.skipLast + SkipWhile = IndexedList.skipWhile + SplitAt = IndexedList.splitAt + SplitInto = fun count list -> IndexedList.splitInto count list |> toArrays + Take = IndexedList.take + TakeLast = IndexedList.takeLast + TakeWhile = IndexedList.takeWhile + Truncate = IndexedList.truncate + + Sort = IndexedList.sort + SortBy = IndexedList.sortBy + SortByDescending = IndexedList.sortByDescending + SortDescending = IndexedList.sortDescending + SortWith = IndexedList.sortWith + SortWithComparer = IndexedList.sortWithComparer + + RandomChoice = IndexedList.randomChoice + RandomChoiceBy = IndexedList.randomChoiceBy + RandomChoiceWith = IndexedList.randomChoiceWith + RandomChoices = IndexedList.randomChoices + RandomChoicesBy = IndexedList.randomChoicesBy + RandomChoicesWith = IndexedList.randomChoicesWith + RandomSample = IndexedList.randomSample + RandomSampleBy = IndexedList.randomSampleBy + RandomSampleWith = IndexedList.randomSampleWith + RandomShuffle = IndexedList.randomShuffle + RandomShuffleBy = IndexedList.randomShuffleBy + RandomShuffleWith = IndexedList.randomShuffleWith + + ToList = IndexedList.toList + ToSeq = IndexedList.toSeq +} + +/// The functions of the creation facet. +let creation : SeqCreationOps> = { + Name = nameof IndexedList + Mirror = ListMirror + ParamName = id + ToArray = IndexedList.toArray + + Empty = fun () -> IndexedList.empty + Init = IndexedList.init + OfArray = IndexedList.ofArray + OfList = IndexedList.ofList + OfReadOnlySpan = fun array -> IndexedList.ofReadOnlySpan (ReadOnlySpan array) + OfSeq = IndexedList.ofSeq + OfSpan = fun array -> IndexedList.ofSpan (Span array) + Replicate = IndexedList.replicate + Singleton = IndexedList.singleton + Unfold = IndexedList.unfold +} + +/// The functions of the index-based facet. +let indexed : IndexedOps> = { + Name = nameof IndexedList + Mirror = ListMirror + ParamName = id + OfSeq = IndexedList.ofSeq + ToArray = IndexedList.toArray + + Item = IndexedList.item + TryItem = IndexedList.tryItem + + IndexOf = IndexedList.indexOf + IndexOfWith = IndexedList.indexOfWith + LastIndexOf = IndexedList.lastIndexOf + LastIndexOfWith = IndexedList.lastIndexOfWith + + Add = IndexedList.add + InsertAt = IndexedList.insertAt + InsertManyAt = IndexedList.insertManyAt + RemoveAt = IndexedList.removeAt + RemoveManyAt = IndexedList.removeManyAt + UpdateAt = IndexedList.updateAt +} + +/// The functions of the facet of the members of `IImmutableList`, over strings. +let listMembers : ListMembersOps> = { + Name = nameof IndexedList + OfSeq = IndexedList.ofSeq + ToArray = IndexedList.toArray + + IndexOfRange = IndexedList.indexOfRange + IndexOfRangeWith = IndexedList.indexOfRangeWith + LastIndexOfRange = IndexedList.lastIndexOfRange + LastIndexOfRangeWith = IndexedList.lastIndexOfRangeWith + + Remove = IndexedList.remove + RemoveMany = IndexedList.removeMany + RemoveManyWith = IndexedList.removeManyWith + RemoveWith = IndexedList.removeWith + Replace = IndexedList.replace + ReplaceWith = IndexedList.replaceWith +} + +/// The functions of the stable sorting facet. +let stableSort : StableSortOps> = { + Name = nameof IndexedList + OfSeq = IndexedList.ofSeq + ToArray = IndexedList.toArray + + Sort = + fun items -> + IndexedList.ofArray items + |> IndexedList.sort + |> IndexedList.toArray + SortBy = IndexedList.sortBy + SortByDescending = IndexedList.sortByDescending + SortDescending = + fun items -> + IndexedList.ofArray items + |> IndexedList.sortDescending + |> IndexedList.toArray + SortWith = IndexedList.sortWith + SortWithComparer = IndexedList.sortWithComparer + + SortIntegersDescending = + fun items -> + IndexedList.ofArray items + |> IndexedList.sortDescending + |> IndexedList.toArray + SortIntegersByDescending = + fun items -> + IndexedList.ofArray items + |> IndexedList.sortByDescending id + |> IndexedList.toArray +} + +/// The functions of the facet whose cost the tests bound. +let cost : CostOps> = { + Name = nameof IndexedList + OfSeq = IndexedList.ofSeq + Max = IndexedList.max + MaxBy = IndexedList.maxBy + Min = IndexedList.min + MinBy = IndexedList.minBy + Exists2 = IndexedList.exists2 + Forall2 = IndexedList.forall2 + CompareWith = IndexedList.compareWith + ConcatLength = fun lists -> IndexedList.concat lists |> IndexedList.length + CollectLength = fun mapping list -> IndexedList.collect mapping list |> IndexedList.length +} diff --git a/tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListOptionTests.fs b/tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListOptionTests.fs new file mode 100644 index 00000000..25d89af5 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListOptionTests.fs @@ -0,0 +1,266 @@ +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 `IndexedList` module of the `Option` namespace. +[] +type IndexedListOptionTests () = + + static let list = IndexedList.ofArray 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 : IndexedList = IndexedList.unfold generator 3 + + CollectionAssert.AreEqual ( + Array.unfold generator 3, + IndexedList.toArray generated, + "Option.IndexedList.unfold must generate as List.unfold does" + ) + + [] + member _.``tryExactlyOne returns an option`` () = + let found : int option = IndexedList.tryExactlyOne (IndexedList.singleton 5) + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IndexedList.tryExactlyOne (IndexedList.singleton 5), + found, + "Option.IndexedList.tryExactlyOne must match the default after conversion" + ) + + [] + member _.``tryHead returns an option`` () = + let found : int option = IndexedList.tryHead list + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IndexedList.tryHead list, + found, + "Option.IndexedList.tryHead must match the default" + ) + + [] + member _.``tryItem returns an option`` (index : int) = + let found : int option = IndexedList.tryItem index list + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IndexedList.tryItem index list, + found, + "Option.IndexedList.tryItem must match the default" + ) + + [] + member _.``tryLast returns an option`` () = + let found : int option = IndexedList.tryLast list + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IndexedList.tryLast list, + found, + "Option.IndexedList.tryLast must match the default" + ) + + [] + member _.``tryTail returns an option`` () = + let found : IndexedList option = IndexedList.tryTail list + + // The equality of an option compares the elements of an array that it holds + Assert.AreEqual( + FSharp.Collections.Immutable.IndexedList.tryTail list + |> ValueOption.map IndexedList.toArray + |> ValueOption.toOption, + found |> Option.map IndexedList.toArray, + "Option.IndexedList.tryTail must match the default" + ) + + [] + member _.``tryUncons returns an option of a reference tuple`` () = + let found : (int * IndexedList) option = IndexedList.tryUncons list + + // A tuple compares an array by reference, so the first element and the other elements are compared apart + let head, tail = + Assert.WantSome (found, "Option.IndexedList.tryUncons must return the elements of a list with elements") + + Assert.AreEqual(Array.head Inputs.Several, head, "Option.IndexedList.tryUncons must return the first element") + + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + IndexedList.toArray tail, + "Option.IndexedList.tryUncons must return the other elements" + ) + + Assert.AreEqual<(int * IndexedList) option>( + None, + IndexedList.tryUncons IndexedList.empty, + "Option.IndexedList.tryUncons must return None for an empty list" + ) + + [] + member _.``Cons is a partial pattern of options`` () = + let pattern : IndexedList -> (int * IndexedList) option = IndexedList.(|Cons|_|) + + match list with + | IndexedList.Cons (head, tail) -> + Assert.AreEqual(Array.head Inputs.Several, head, "Option.IndexedList.Cons must match the first element") + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + IndexedList.toArray tail, + "Option.IndexedList.Cons must match the other elements" + ) + | _ -> Assert.Fail "Option.IndexedList.Cons must match a list with elements" + + Assert.IsTrue ((pattern IndexedList.empty).IsNone, "Option.IndexedList.Cons must not match an empty list") + + [] + member _.``pick takes a chooser of options`` () = + let picked : int = IndexedList.pick negatedIfNegative list + + Assert.AreEqual( + Array.pick negatedIfNegative Inputs.Several, + picked, + "Option.IndexedList.pick must pick as List.pick does" + ) + + [] + member _.``pick throws as List.pick does`` () = + ExceptionAssert.Same ( + id, + (fun () -> + List.pick (fun _ -> None) (List.ofArray Inputs.Several) + |> ignore + ), + (fun () -> IndexedList.pick (fun _ -> None) list |> ignore), + "Option.IndexedList.pick without a choice must throw as List.pick does" + ) + + [] + member _.``tryFind returns an option`` () = + let found : int option = IndexedList.tryFind (fun item -> item > 6) list + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IndexedList.tryFind (fun item -> item > 6) list, + found, + "Option.IndexedList.tryFind must match the default" + ) + + [] + member _.``tryFindBack returns an option`` () = + let found : int option = IndexedList.tryFindBack (fun item -> item > 6) list + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IndexedList.tryFindBack (fun item -> item > 6) list, + found, + "Option.IndexedList.tryFindBack must match the default" + ) + + [] + member _.``tryFindIndex returns an option`` () = + let found : int option = IndexedList.tryFindIndex (fun item -> item > 6) list + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IndexedList.tryFindIndex (fun item -> item > 6) list, + found, + "Option.IndexedList.tryFindIndex must match the default" + ) + + [] + member _.``tryFindIndexBack returns an option`` () = + let found : int option = IndexedList.tryFindIndexBack (fun item -> item > 6) list + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IndexedList.tryFindIndexBack (fun item -> item > 6) list, + found, + "Option.IndexedList.tryFindIndexBack must match the default" + ) + + [] + member _.``tryFindIndexBackRange returns an option`` (startIndex : int, count : int) = + let found : int option = IndexedList.tryFindIndexBackRange startIndex count (fun item -> item < 0) list + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IndexedList.tryFindIndexBackRange startIndex count (fun item -> item < 0) list, + found, + "Option.IndexedList.tryFindIndexBackRange must match the default" + ) + + [] + member _.``tryFindIndexRange returns an option`` (startIndex : int, count : int) = + let found : int option = IndexedList.tryFindIndexRange startIndex count (fun item -> item < 0) list + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IndexedList.tryFindIndexRange startIndex count (fun item -> item < 0) list, + found, + "Option.IndexedList.tryFindIndexRange must match the default" + ) + + [] + member _.``tryFindIndexRange and tryFindIndexBackRange throw for a range out of range as the default functions do`` () = + ExceptionAssert.Same ( + id, + (fun () -> + FSharp.Collections.Immutable.IndexedList.tryFindIndexRange 5 4 (fun _ -> true) list + |> ignore + ), + (fun () -> + IndexedList.tryFindIndexRange 5 4 (fun _ -> true) list + |> ignore + ), + "Option.IndexedList.tryFindIndexRange must throw what the default function throws" + ) + + ExceptionAssert.Same ( + id, + (fun () -> + FSharp.Collections.Immutable.IndexedList.tryFindIndexBackRange 2 4 (fun _ -> true) list + |> ignore + ), + (fun () -> + IndexedList.tryFindIndexBackRange 2 4 (fun _ -> true) list + |> ignore + ), + "Option.IndexedList.tryFindIndexBackRange must throw what the default function throws" + ) + + [] + member _.``tryPick takes a chooser of options and returns an option`` () = + let found : int option = IndexedList.tryPick negatedIfNegative list + + Assert.AreEqual( + Array.tryPick negatedIfNegative Inputs.Several, + found, + "Option.IndexedList.tryPick must pick as List.tryPick does" + ) + + [] + member _.``choose takes a chooser of options`` () = + let chosen : IndexedList = IndexedList.choose negatedIfNegative list + + CollectionAssert.AreEqual ( + Array.choose negatedIfNegative Inputs.Several, + IndexedList.toArray chosen, + "Option.IndexedList.choose must choose as List.choose does" + ) + + [] + member _.``the functions without an option keep resolving to the default module`` () = + let mapped : IndexedList = IndexedList.map (fun item -> item * 2) list + let zipped : IndexedList = IndexedList.zip list list + + CollectionAssert.AreEqual ( + Array.map (fun item -> item * 2) Inputs.Several, + IndexedList.toArray mapped, + "IndexedList.map must resolve to the default module" + ) + + CollectionAssert.AreEqual ( + Array.zip Inputs.Several Inputs.Several, + IndexedList.toArray zipped, + "IndexedList.zip must resolve to the Tuple module" + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListRangeTests.fs b/tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListRangeTests.fs new file mode 100644 index 00000000..0c320528 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListRangeTests.fs @@ -0,0 +1,465 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open System.Collections.Generic +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// +/// The functions of that work on a range of the elements, and its +/// binary searches with a comparer. +/// +/// +/// The rows of the ranges of the input are the ones of , +/// the functions of which have the same names and the same ranges. The removal, the replacement and the searches in a +/// range of the shared contract are in . +/// +[] +type IndexedListRangeTests () = + + static let several () = IndexedList.ofArray Inputs.Several + + /// 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; the stability has its own tests. + static let rangeSorts : Map int -> IndexedList -> IndexedList) * (int array -> int array))> = + Map.ofArray [| + nameof IndexedList.sortRange, struct (IndexedList.sortRange, ListOracles.stableSort compare) + (nameof IndexedList.sortRangeWith, + struct (IndexedList.sortRangeWith FlatListOracles.descending, ListOracles.stableSort FlatListOracles.descending)) + (nameof IndexedList.sortRangeWithComparer, + struct (IndexedList.sortRangeWithComparer (Comparer.Create(Comparison FlatListOracles.byAbsolute)), + ListOracles.stableSort FlatListOracles.byAbsolute)) + |] + + + /// The ranges of the sorts of pairs, which an unstable sort of .NET reorders: more than 16 elements. + static member StableRanges = seq { + struct (0, 20) + struct (1, 18) + struct (3, 17) + } + + /// Both directions of the comparison with each range of the sorts of pairs. + static member StableRangesAndDirections = seq { + for direction in [| SortDirection.Ascending; SortDirection.Descending |] do + for struct (index, count) in IndexedListRangeTests.StableRanges do + struct (direction, index, count) + } + + /// The ranges that each function that sorts a range rejects. + static member SortRangesOutOfRange = seq { + for name in rangeSorts.Keys do + for struct (index, count, fault) in ListMemberCases.RangeFaults do + struct (name, index, count, fault) + } + + /// The names of the functions that sort a range, as the rows of the tests that tell them apart. + static member RangeSorts = rangeSorts.Keys + // ---- 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, + IndexedList.toArray (IndexedList.sub (IndexedList.ofArray input) startIndex count), + "IndexedList.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 ( + id, + (fun () -> Array.sub input startIndex count |> ignore), + (fun () -> + IndexedList.sub (IndexedList.ofArray input) startIndex count + |> ignore + ), + "IndexedList.sub must throw as Array.sub does for a range out of range" + ) + + [] + member _.``sub throws for a range whose end overflows`` (startIndex : int, count : int) = + let ex = + Assert.ThrowsExactly( + Action (fun () -> IndexedList.sub (several ()) startIndex count |> ignore), + "IndexedList.sub must reject a range whose end overflows Int32.MaxValue" + ) + + Assert.AreEqual("count", ex.ParamName, "IndexedList.sub must name the count for a range beyond the end") + + [] + member _.``sub returns the empty indexed list for an empty range`` (startIndex : int) = + Assert.AreSame ( + IndexedList.empty, + IndexedList.sub (several ()) startIndex 0, + "IndexedList.sub of an empty range must return the empty indexed list" + ) + + [] + member _.``sub reports the length of the indexed list in the message for a range beyond the end`` () = + let ex = + Assert.ThrowsExactly( + Action (fun () -> IndexedList.sub (several ()) 5 4 |> ignore), + "IndexedList.sub must reject a range beyond the end" + ) + + Assert.Contains ("list.Length = 8", ex.Message, "The message of IndexedList.sub must name the length of the indexed list") + + // ---- revRange ---- + + [)>] + member _.``revRange reverses the elements of the range and keeps the other elements`` + (input : int array, index : int, count : int) + = + CollectionAssert.AreEqual ( + ListOracles.changeRange Array.rev input index count, + IndexedList.toArray (IndexedList.revRange index count (IndexedList.ofArray input)), + "IndexedList.revRange must reverse the range and keep the other elements" + ) + + [)>] + member _.``revRange throws for a range out of range naming the parameter that the BCL names`` + (index : int, count : int, fault : RangeFault) + = + let ex = + Assert.ThrowsExactly( + Action (fun () -> IndexedList.revRange index count (several ()) |> ignore), + "IndexedList.revRange must throw ArgumentOutOfRangeException for a range out of range" + ) + + // ImmutableList.Reverse(Int32, Int32) names the index for a range beyond the end + Assert.AreEqual( + fault.ParamName ("index", "index"), + ex.ParamName, + "IndexedList.revRange must name the parameter that the BCL names" + ) + + [] + member _.``revRange returns the input itself for a range of at most one element`` (index : int, count : int) = + let list = several () + + Assert.AreSame ( + list, + IndexedList.revRange index count list, + "IndexedList.revRange must not copy a range that has nothing to reverse" + ) + + // ---- 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] + + CollectionAssert.AreEqual ( + ListOracles.changeRange sortPart input index count, + IndexedList.toArray (sortRange index count (IndexedList.ofArray input)), + $"IndexedList.%s{name} must sort the range and keep the other elements" + ) + + [] + member _.``functions that sort a range throw for a range out of range naming the parameter that the BCL names`` + (name : string, index : int, count : int, fault : RangeFault) + = + let struct (sortRange, _) = rangeSorts[name] + + let ex = + Assert.ThrowsExactly( + Action (fun () -> sortRange index count (several ()) |> ignore), + $"IndexedList.%s{name} must throw ArgumentOutOfRangeException for a range out of range" + ) + + Assert.AreEqual( + fault.ParamName ("index", "count"), + ex.ParamName, + $"IndexedList.%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 list = several () + + Assert.AreSame (list, sortRange index count list, $"IndexedList.%s{name} must not copy a range that needs no sorting") + + [] + member _.``functions that sort a range do not run the comparison for a range of at most one element`` (name : string) = + let sorts : Map int -> IndexedList -> IndexedList> = + let fail _ _ : int = failwith "The comparison must not run" + + Map.ofArray [| + nameof IndexedList.sortRange, IndexedList.sortRange + nameof IndexedList.sortRangeWith, IndexedList.sortRangeWith fail + nameof IndexedList.sortRangeWithComparer, + IndexedList.sortRangeWithComparer (Comparer.Create(Comparison fail)) + |] + + // A range of one element and an empty one compare nothing + let list = several () + Assert.AreSame (list, sorts[name] 2 1 list, $"IndexedList.%s{name} must not compare the one element of a range") + Assert.AreSame (list, sorts[name] 5 0 list, $"IndexedList.%s{name} must not compare the elements of an empty range") + + [] + member _.``sortRange orders strings ordinally, as F# generic comparison does`` () = + let list = IndexedList.ofArray [| "b"; "A"; "a"; "B"; "z" |] + + CollectionAssert.AreEqual ( + [| "A"; "B"; "a"; "b"; "z" |], + IndexedList.toArray (IndexedList.sortRange 0 4 list), + "IndexedList.sortRange must sort strings ordinally" + ) + + [] + member _.``sortRange keeps the order of the elements that compare equal`` (index : int, count : int) = + // The items compare by their keys only, so the tags show the order of equal items + let items = KeyedItem.OfManyPairs () + + CollectionAssert.AreEqual ( + ListOracles.sortedKeyedRange index count, + KeyedItem.KeysAndTags (IndexedList.toArray (IndexedList.sortRange index count (IndexedList.ofArray items))), + "IndexedList.sortRange must keep the order of equal elements, as List.sort does" + ) + + [] + member _.``sortRangeWith keeps the order of the elements that compare equal`` + (direction : SortDirection, index : int, count : int) + = + let comparison = ListOracles.compareKeys direction + + CollectionAssert.AreEqual ( + ListOracles.sortedPairRange comparison index count, + IndexedList.toArray (IndexedList.sortRangeWith comparison index count (IndexedList.ofArray Inputs.ManyPairs)), + "IndexedList.sortRangeWith must keep the order of equal elements, as List.sortWith does" + ) + + [] + member _.``sortRangeWithComparer keeps the order of the elements that compare equal`` + (direction : SortDirection, index : int, count : int) + = + let comparison = ListOracles.compareKeys direction + let comparer = Comparer.Create (fun left right -> comparison left right) + + CollectionAssert.AreEqual ( + ListOracles.sortedPairRange comparison index count, + IndexedList.toArray (IndexedList.sortRangeWithComparer comparer index count (IndexedList.ofArray Inputs.ManyPairs)), + "IndexedList.sortRangeWithComparer must keep the order of equal elements, as List.sortWith does" + ) + + [] + member _.``sortRangeWith wraps the exception of the comparison in an InvalidOperationException`` () = + let ex = + Assert.ThrowsExactly( + Action (fun () -> + IndexedList.sortRangeWith (fun _ _ -> raise (NotSupportedException "The comparison fails")) 1 5 (several ()) + |> ignore + ), + "IndexedList.sortRangeWith must wrap the exception of the comparison, as Array.Sort does" + ) + + Assert.IsInstanceOfType( + ex.InnerException, + "The exception of the comparison must be the inner exception" + ) + |> ignore + + // ---- binarySearchWithComparer, binarySearchRange and binarySearchRangeWithComparer ---- + + [)>] + member _.``binarySearchWithComparer searches in the order of the comparer`` (value : string) = + let comparer = StringComparer.OrdinalIgnoreCase :> IComparer + + Assert.AreEqual( + Array.BinarySearch (SortedInputs.Names, value, comparer), + IndexedList.binarySearchWithComparer comparer value (IndexedList.ofArray SortedInputs.Names), + "IndexedList.binarySearchWithComparer must search in the order of the comparer" + ) + + [] + member _.``binarySearchWithComparer finds in the order of the comparer what the order of F# generic comparison loses`` () = + let list = IndexedList.ofArray SortedInputs.Names + + Assert.AreEqual( + 1, + IndexedList.binarySearchWithComparer StringComparer.OrdinalIgnoreCase "b" list, + "IndexedList.binarySearchWithComparer must find b equal to B" + ) + + Assert.IsLessThan ( + 0, + IndexedList.binarySearch "b" list, + "IndexedList.binarySearch compares ordinally and must not find b in strings sorted when the case is ignored" + ) + + [)>] + member _.``binarySearchRange searches the range as Array.BinarySearch does`` (index : int, count : int, value : int) = + Assert.AreEqual( + Array.BinarySearch (SortedInputs.Ascending, index, count, value), + IndexedList.binarySearchRange index count value (IndexedList.ofArray SortedInputs.Ascending), + "IndexedList.binarySearchRange must search the range as Array.BinarySearch does" + ) + + [)>] + member _.``binarySearchRangeWithComparer searches the range in the order of the comparer`` + (index : int, count : int, value : int) + = + let descending = Array.rev SortedInputs.Ascending + + Assert.AreEqual( + Array.BinarySearch (descending, index, count, value, SortedInputs.DescendingComparer), + IndexedList.binarySearchRangeWithComparer + SortedInputs.DescendingComparer + index + count + value + (IndexedList.ofArray descending), + "IndexedList.binarySearchRangeWithComparer must search the range in the order of the comparer" + ) + + [)>] + member _.``the binary searches of a range throw for a range out of range`` (index : int, count : int, fault : RangeFault) = + let list = IndexedList.ofArray SortedInputs.Ascending + + let actions : (string * (unit -> unit)) array = [| + nameof IndexedList.binarySearchRange, (fun () -> IndexedList.binarySearchRange index count 7 list |> ignore) + (nameof IndexedList.binarySearchRangeWithComparer, + (fun () -> + IndexedList.binarySearchRangeWithComparer Comparer.Default index count 7 list + |> ignore + )) + |] + + for name, action in actions do + match fault with + | RangeFault.Beyond -> + // The BCL does not check this and its documentation promises ArgumentException (ADR 0001, D14) + let ex = + Assert.ThrowsExactly( + Action action, + $"IndexedList.%s{name} must throw ArgumentException for a range beyond the end" + ) + + Assert.AreEqual("count", ex.ParamName, $"IndexedList.%s{name} must name the count") + | _ -> + let ex = + Assert.ThrowsExactly( + Action action, + $"IndexedList.%s{name} must throw ArgumentOutOfRangeException for a negative number" + ) + + Assert.AreEqual( + fault.ParamName ("index", "count"), + ex.ParamName, + $"IndexedList.%s{name} must name the parameter that the BCL names" + ) + + [] + member _.``the binary searches do not wrap the exception of the comparer`` () = + let comparer = Comparer.Create (fun _ _ -> raise (NotSupportedException "The comparison fails")) + let list = IndexedList.ofArray SortedInputs.Ascending + + Assert.ThrowsExactly( + Action (fun () -> + IndexedList.binarySearchWithComparer comparer 7 list + |> ignore + ), + "IndexedList.binarySearchWithComparer must not wrap the exception of the comparer" + ) + |> ignore + + Assert.ThrowsExactly( + Action (fun () -> + IndexedList.binarySearchRangeWithComparer comparer 0 4 7 list + |> ignore + ), + "IndexedList.binarySearchRangeWithComparer must not wrap the exception of the comparer" + ) + |> ignore + + // ---- tryFindIndexRange and tryFindIndexBackRange ---- + + [)>] + member _.``tryFindIndexRange returns the index of the first element of the range that satisfies the predicate`` + (startIndex : int, count : int, value : string) + = + let expected = FlatListOracles.firstInRange (=) startIndex count value ListNames.Several + + Assert.AreEqual( + (if expected < 0 then ValueNone else ValueSome expected), + IndexedList.tryFindIndexRange startIndex count (fun item -> item = value) (IndexedList.ofArray ListNames.Several), + "IndexedList.tryFindIndexRange must return the index of the first element of the range that satisfies the predicate" + ) + + [)>] + member _.``tryFindIndexBackRange returns the index of the last element of the range that satisfies the predicate`` + (startIndex : int, count : int, value : string) + = + let expected = ListOracles.lastIndexOfRange (=) startIndex count value ListNames.Several + + Assert.AreEqual( + (if expected < 0 then ValueNone else ValueSome expected), + IndexedList.tryFindIndexBackRange startIndex count (fun item -> item = value) (IndexedList.ofArray ListNames.Several), + "IndexedList.tryFindIndexBackRange must return the index of the last element of the range that satisfies the predicate" + ) + + [)>] + member _.``tryFindIndexRange throws for a range out of range`` (startIndex : int, count : int, fault : RangeFault) = + let ex = + Assert.ThrowsExactly( + Action (fun () -> + IndexedList.tryFindIndexRange startIndex count (fun _ -> true) (several ()) + |> ignore + ), + "IndexedList.tryFindIndexRange must throw ArgumentOutOfRangeException for a range out of range" + ) + + Assert.AreEqual( + fault.ParamName ("startIndex", "count"), + ex.ParamName, + "IndexedList.tryFindIndexRange must name the offending parameter" + ) + + [)>] + member _.``tryFindIndexBackRange throws for a range out of range`` (startIndex : int, count : int, fault : RangeFault) = + let ex = + Assert.ThrowsExactly( + Action (fun () -> + IndexedList.tryFindIndexBackRange startIndex count (fun _ -> true) (several ()) + |> ignore + ), + "IndexedList.tryFindIndexBackRange must throw ArgumentOutOfRangeException for a range out of range" + ) + + Assert.AreEqual( + fault.ParamName ("startIndex", "count"), + ex.ParamName, + "IndexedList.tryFindIndexBackRange must name the offending parameter" + ) + + [] + member _.``tryFindIndexRange and tryFindIndexBackRange do not call the predicate outside the range`` () = + let visited = ResizeArray() + + let record item = + visited.Add item + false + + IndexedList.tryFindIndexRange 2 3 record (several ()) + |> ignore + CollectionAssert.AreEqual ( + [| 8; 5; 0 |], + visited.ToArray (), + "tryFindIndexRange must test the elements of the range in order" + ) + + visited.Clear () + IndexedList.tryFindIndexBackRange 4 3 record (several ()) + |> ignore + CollectionAssert.AreEqual ( + [| 0; 5; 8 |], + visited.ToArray (), + "tryFindIndexBackRange must test the elements of the range from its last element to its first" + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListTests.fs b/tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListTests.fs new file mode 100644 index 00000000..8d3645bf --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListTests.fs @@ -0,0 +1,911 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open System.Collections.Generic +open System.Globalization +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// The behaviour of `IndexedList` that the shared contracts do not cover: null inputs, shared trees, the extras and the +/// details of the semantics of the List module. +[] +type IndexedListTests () = + + static let nullList = Unchecked.defaultof> + + static let several () = IndexedList.ofArray Inputs.Several + + /// The exception of an action and its complete message, under the invariant culture, in which FSharp.Core writes the + /// English messages that this library copies. + static let thrown (action : unit -> unit) (message : string) = + let culture = CultureInfo.CurrentUICulture + CultureInfo.CurrentUICulture <- CultureInfo.InvariantCulture + + try + Assert.Throws(Action action, message) + finally + CultureInfo.CurrentUICulture <- culture + + /// The functions that take an indexed list, by name, with the parameter that names the null indexed list and the + /// function on a null indexed list. The rows of the test are the names: a function has no readable name in a row. + static let nullListRejections = + let pairs = Unchecked.defaultof> + let triples = Unchecked.defaultof> + let floats = Unchecked.defaultof> + + [| + struct (nameof IndexedList.toBuilder, "list", (fun () -> IndexedList.toBuilder nullList |> ignore)) + struct (nameof IndexedList.update, "list", (fun () -> IndexedList.update ignore nullList |> ignore)) + struct (nameof IndexedList.isEmpty, "list", (fun () -> IndexedList.isEmpty nullList |> ignore)) + struct (nameof IndexedList.length, "list", (fun () -> IndexedList.length nullList |> ignore)) + struct (nameof IndexedList.exactlyOne, "list", (fun () -> IndexedList.exactlyOne nullList |> ignore)) + struct (nameof IndexedList.head, "list", (fun () -> IndexedList.head nullList |> ignore)) + struct (nameof IndexedList.item, "list", (fun () -> IndexedList.item 0 nullList |> ignore)) + struct (nameof IndexedList.last, "list", (fun () -> IndexedList.last nullList |> ignore)) + struct (nameof IndexedList.tail, "list", (fun () -> IndexedList.tail nullList |> ignore)) + struct (nameof IndexedList.tryExactlyOne, "list", (fun () -> IndexedList.tryExactlyOne nullList |> ignore)) + struct (nameof IndexedList.tryHead, "list", (fun () -> IndexedList.tryHead nullList |> ignore)) + struct (nameof IndexedList.tryItem, "list", (fun () -> IndexedList.tryItem 0 nullList |> ignore)) + struct (nameof IndexedList.tryLast, "list", (fun () -> IndexedList.tryLast nullList |> ignore)) + struct (nameof IndexedList.tryTail, "list", (fun () -> IndexedList.tryTail nullList |> ignore)) + struct (nameof IndexedList.tryUncons, "list", (fun () -> IndexedList.tryUncons nullList |> ignore)) + struct (nameof IndexedList.uncons, "list", (fun () -> IndexedList.uncons nullList |> ignore)) + struct (nameof IndexedList.(|Cons|_|), "list", (fun () -> IndexedList.(|Cons|_|) nullList |> ignore)) + struct (nameof IndexedList.(|Nil|_|), "list", (fun () -> IndexedList.(|Nil|_|) nullList |> ignore)) + struct (nameof IndexedList.binarySearch, "list", (fun () -> IndexedList.binarySearch 1 nullList |> ignore)) + struct (nameof IndexedList.binarySearchRange, + "list", + (fun () -> IndexedList.binarySearchRange 0 0 1 nullList |> ignore)) + struct (nameof IndexedList.binarySearchRangeWithComparer, + "list", + (fun () -> + IndexedList.binarySearchRangeWithComparer Comparer.Default 0 0 1 nullList + |> ignore + )) + struct (nameof IndexedList.binarySearchWithComparer, + "list", + (fun () -> + IndexedList.binarySearchWithComparer Comparer.Default 1 nullList + |> ignore + )) + struct (nameof IndexedList.contains, "source", (fun () -> IndexedList.contains 1 nullList |> ignore)) + struct (nameof IndexedList.exists, "list", (fun () -> IndexedList.exists ((=) 1) nullList |> ignore)) + struct (nameof IndexedList.exists2, "list2", (fun () -> IndexedList.exists2 (=) (several ()) nullList |> ignore)) + struct (nameof IndexedList.find, "list", (fun () -> IndexedList.find ((=) 1) nullList |> ignore)) + struct (nameof IndexedList.findBack, "list", (fun () -> IndexedList.findBack ((=) 1) nullList |> ignore)) + struct (nameof IndexedList.findIndex, "list", (fun () -> IndexedList.findIndex ((=) 1) nullList |> ignore)) + struct (nameof IndexedList.findIndexBack, "list", (fun () -> IndexedList.findIndexBack ((=) 1) nullList |> ignore)) + struct (nameof IndexedList.forall, "list", (fun () -> IndexedList.forall ((=) 1) nullList |> ignore)) + struct (nameof IndexedList.forall2, "list1", (fun () -> IndexedList.forall2 (=) nullList (several ()) |> ignore)) + struct (nameof IndexedList.indexOf, "list", (fun () -> IndexedList.indexOf 1 nullList |> ignore)) + struct (nameof IndexedList.indexOfRange, "list", (fun () -> IndexedList.indexOfRange 0 0 1 nullList |> ignore)) + struct (nameof IndexedList.indexOfRangeWith, + "list", + (fun () -> + IndexedList.indexOfRangeWith EqualityComparer.Default 0 0 1 nullList + |> ignore + )) + struct (nameof IndexedList.indexOfWith, + "list", + (fun () -> + IndexedList.indexOfWith EqualityComparer.Default 1 nullList + |> ignore + )) + struct (nameof IndexedList.lastIndexOf, "list", (fun () -> IndexedList.lastIndexOf 1 nullList |> ignore)) + struct (nameof IndexedList.lastIndexOfRange, "list", (fun () -> IndexedList.lastIndexOfRange 0 0 1 nullList |> ignore)) + struct (nameof IndexedList.lastIndexOfRangeWith, + "list", + (fun () -> + IndexedList.lastIndexOfRangeWith EqualityComparer.Default 0 0 1 nullList + |> ignore + )) + struct (nameof IndexedList.lastIndexOfWith, + "list", + (fun () -> + IndexedList.lastIndexOfWith EqualityComparer.Default 1 nullList + |> ignore + )) + struct (nameof IndexedList.pick, "list", (fun () -> IndexedList.pick ValueSome nullList |> ignore)) + struct (nameof IndexedList.tryFind, "list", (fun () -> IndexedList.tryFind ((=) 1) nullList |> ignore)) + struct (nameof IndexedList.tryFindBack, "list", (fun () -> IndexedList.tryFindBack ((=) 1) nullList |> ignore)) + struct (nameof IndexedList.tryFindIndex, "list", (fun () -> IndexedList.tryFindIndex ((=) 1) nullList |> ignore)) + struct (nameof IndexedList.tryFindIndexBack, + "list", + (fun () -> IndexedList.tryFindIndexBack ((=) 1) nullList |> ignore)) + struct (nameof IndexedList.tryFindIndexBackRange, + "list", + (fun () -> + IndexedList.tryFindIndexBackRange 0 0 ((=) 1) nullList + |> ignore + )) + struct (nameof IndexedList.tryFindIndexRange, + "list", + (fun () -> IndexedList.tryFindIndexRange 0 0 ((=) 1) nullList |> ignore)) + struct (nameof IndexedList.tryPick, "list", (fun () -> IndexedList.tryPick ValueSome nullList |> ignore)) + struct (nameof IndexedList.add, "list", (fun () -> IndexedList.add 1 nullList |> ignore)) + struct (nameof IndexedList.allPairs, "list1", (fun () -> IndexedList.allPairs nullList (several ()) |> ignore)) + struct (nameof IndexedList.append, "list2", (fun () -> IndexedList.append (several ()) nullList |> ignore)) + struct (nameof IndexedList.choose, "list", (fun () -> IndexedList.choose ValueSome nullList |> ignore)) + struct (nameof IndexedList.chunkBySize, "list", (fun () -> IndexedList.chunkBySize 2 nullList |> ignore)) + struct (nameof IndexedList.collect, "list", (fun () -> IndexedList.collect IndexedList.singleton nullList |> ignore)) + struct (nameof IndexedList.concat, "lists", (fun () -> IndexedList.concat [| several (); nullList |] |> ignore)) + struct (nameof IndexedList.cons, "list", (fun () -> IndexedList.cons 1 nullList |> ignore)) + struct (nameof IndexedList.filter, "list", (fun () -> IndexedList.filter ((=) 1) nullList |> ignore)) + struct (nameof IndexedList.indexed, "list", (fun () -> IndexedList.indexed nullList |> ignore)) + struct (nameof IndexedList.insertAt, "source", (fun () -> IndexedList.insertAt 0 1 nullList |> ignore)) + struct (nameof IndexedList.insertManyAt, "source", (fun () -> IndexedList.insertManyAt 0 [| 1 |] nullList |> ignore)) + struct (nameof IndexedList.map, "list", (fun () -> IndexedList.map id nullList |> ignore)) + struct (nameof IndexedList.map2, "list2", (fun () -> IndexedList.map2 (+) (several ()) nullList |> ignore)) + struct (nameof IndexedList.map3, + "list3", + (fun () -> + IndexedList.map3 (fun a b c -> a + b + c) (several ()) (several ()) nullList + |> ignore + )) + struct (nameof IndexedList.mapi, "list", (fun () -> IndexedList.mapi (+) nullList |> ignore)) + struct (nameof IndexedList.mapi2, + "list1", + (fun () -> + IndexedList.mapi2 (fun index a b -> index + a + b) nullList (several ()) + |> ignore + )) + struct (nameof IndexedList.pairwise, "list", (fun () -> IndexedList.pairwise nullList |> ignore)) + struct (nameof IndexedList.permute, "list", (fun () -> IndexedList.permute id nullList |> ignore)) + struct (nameof IndexedList.remove, "list", (fun () -> IndexedList.remove 1 nullList |> ignore)) + struct (nameof IndexedList.removeAt, "source", (fun () -> IndexedList.removeAt 0 nullList |> ignore)) + struct (nameof IndexedList.removeMany, "list", (fun () -> IndexedList.removeMany [| 1 |] nullList |> ignore)) + struct (nameof IndexedList.removeManyAt, "source", (fun () -> IndexedList.removeManyAt 0 1 nullList |> ignore)) + struct (nameof IndexedList.removeManyWith, + "list", + (fun () -> + IndexedList.removeManyWith EqualityComparer.Default [| 1 |] nullList + |> ignore + )) + struct (nameof IndexedList.removeWith, + "list", + (fun () -> + IndexedList.removeWith EqualityComparer.Default 1 nullList + |> ignore + )) + struct (nameof IndexedList.replace, "list", (fun () -> IndexedList.replace 1 2 nullList |> ignore)) + struct (nameof IndexedList.replaceWith, + "list", + (fun () -> + IndexedList.replaceWith EqualityComparer.Default 1 2 nullList + |> ignore + )) + struct (nameof IndexedList.rev, "list", (fun () -> IndexedList.rev nullList |> ignore)) + struct (nameof IndexedList.revRange, "list", (fun () -> IndexedList.revRange 0 0 nullList |> ignore)) + struct (nameof IndexedList.transpose, "lists", (fun () -> IndexedList.transpose [| nullList |] |> ignore)) + struct (nameof IndexedList.unzip, "list", (fun () -> IndexedList.unzip pairs |> ignore)) + struct (nameof IndexedList.unzip3, "list", (fun () -> IndexedList.unzip3 triples |> ignore)) + struct (nameof IndexedList.updateAt, "source", (fun () -> IndexedList.updateAt 0 1 nullList |> ignore)) + struct (nameof IndexedList.where, "list", (fun () -> IndexedList.where ((=) 1) nullList |> ignore)) + struct (nameof IndexedList.windowed, "list", (fun () -> IndexedList.windowed 2 nullList |> ignore)) + struct (nameof IndexedList.zip, "list1", (fun () -> IndexedList.zip nullList (several ()) |> ignore)) + struct (nameof IndexedList.zip3, + "list2", + (fun () -> + IndexedList.zip3 (several ()) nullList (several ()) + |> ignore + )) + struct (nameof IndexedList.average, "list", (fun () -> IndexedList.average floats |> ignore)) + struct (nameof IndexedList.averageBy, "list", (fun () -> IndexedList.averageBy float nullList |> ignore)) + struct (nameof IndexedList.compareWith, + "list2", + (fun () -> + IndexedList.compareWith compare (several ()) nullList + |> ignore + )) + struct (nameof IndexedList.fold, "list", (fun () -> IndexedList.fold (+) 0 nullList |> ignore)) + struct (nameof IndexedList.fold2, + "list2", + (fun () -> + IndexedList.fold2 (fun state a b -> state + a + b) 0 (several ()) nullList + |> ignore + )) + struct (nameof IndexedList.foldBack, "list", (fun () -> IndexedList.foldBack (+) nullList 0 |> ignore)) + struct (nameof IndexedList.foldBack2, + "list1", + (fun () -> + IndexedList.foldBack2 (fun a b state -> state + a + b) nullList (several ()) 0 + |> ignore + )) + struct (nameof IndexedList.iter, "list", (fun () -> IndexedList.iter ignore nullList)) + struct (nameof IndexedList.iter2, "list2", (fun () -> IndexedList.iter2 (fun _ _ -> ()) (several ()) nullList)) + struct (nameof IndexedList.iteri, "list", (fun () -> IndexedList.iteri (fun _ _ -> ()) nullList)) + struct (nameof IndexedList.iteri2, "list1", (fun () -> IndexedList.iteri2 (fun _ _ _ -> ()) nullList (several ()))) + struct (nameof IndexedList.mapFold, + "list", + (fun () -> + IndexedList.mapFold (fun state item -> struct (item, state)) 0 nullList + |> ignore + )) + struct (nameof IndexedList.mapFoldBack, + "list", + (fun () -> + IndexedList.mapFoldBack (fun item state -> struct (item, state)) nullList 0 + |> ignore + )) + struct (nameof IndexedList.max, "list", (fun () -> IndexedList.max nullList |> ignore)) + struct (nameof IndexedList.maxBy, "list", (fun () -> IndexedList.maxBy id nullList |> ignore)) + struct (nameof IndexedList.min, "list", (fun () -> IndexedList.min nullList |> ignore)) + struct (nameof IndexedList.minBy, "list", (fun () -> IndexedList.minBy id nullList |> ignore)) + struct (nameof IndexedList.reduce, "list", (fun () -> IndexedList.reduce (+) nullList |> ignore)) + struct (nameof IndexedList.reduceBack, "list", (fun () -> IndexedList.reduceBack (+) nullList |> ignore)) + struct (nameof IndexedList.scan, "list", (fun () -> IndexedList.scan (+) 0 nullList |> ignore)) + struct (nameof IndexedList.scanBack, "list", (fun () -> IndexedList.scanBack (+) nullList 0 |> ignore)) + struct (nameof IndexedList.sum, "list", (fun () -> IndexedList.sum nullList |> ignore)) + struct (nameof IndexedList.sumBy, "list", (fun () -> IndexedList.sumBy id nullList |> ignore)) + struct (nameof IndexedList.countBy, "list", (fun () -> IndexedList.countBy id nullList |> ignore)) + struct (nameof IndexedList.distinct, "list", (fun () -> IndexedList.distinct nullList |> ignore)) + struct (nameof IndexedList.distinctBy, "list", (fun () -> IndexedList.distinctBy id nullList |> ignore)) + struct (nameof IndexedList.except, "list", (fun () -> IndexedList.except [| 1 |] nullList |> ignore)) + struct (nameof IndexedList.groupBy, "list", (fun () -> IndexedList.groupBy id nullList |> ignore)) + struct (nameof IndexedList.partition, "list", (fun () -> IndexedList.partition ((=) 1) nullList |> ignore)) + struct (nameof IndexedList.partitionWith, "list", (fun () -> IndexedList.partitionWith Choice1Of2 nullList |> ignore)) + struct (nameof IndexedList.skip, "list", (fun () -> IndexedList.skip 0 nullList |> ignore)) + struct (nameof IndexedList.skipLast, "list", (fun () -> IndexedList.skipLast 0 nullList |> ignore)) + struct (nameof IndexedList.skipWhile, "list", (fun () -> IndexedList.skipWhile ((=) 1) nullList |> ignore)) + struct (nameof IndexedList.splitAt, "list", (fun () -> IndexedList.splitAt 0 nullList |> ignore)) + struct (nameof IndexedList.splitInto, "list", (fun () -> IndexedList.splitInto 2 nullList |> ignore)) + struct (nameof IndexedList.sub, "list", (fun () -> IndexedList.sub nullList 0 0 |> ignore)) + struct (nameof IndexedList.take, "list", (fun () -> IndexedList.take 0 nullList |> ignore)) + struct (nameof IndexedList.takeLast, "list", (fun () -> IndexedList.takeLast 0 nullList |> ignore)) + struct (nameof IndexedList.takeWhile, "list", (fun () -> IndexedList.takeWhile ((=) 1) nullList |> ignore)) + struct (nameof IndexedList.truncate, "list", (fun () -> IndexedList.truncate 1 nullList |> ignore)) + struct (nameof IndexedList.sort, "list", (fun () -> IndexedList.sort nullList |> ignore)) + struct (nameof IndexedList.sortBy, "list", (fun () -> IndexedList.sortBy id nullList |> ignore)) + struct (nameof IndexedList.sortByDescending, "list", (fun () -> IndexedList.sortByDescending id nullList |> ignore)) + struct (nameof IndexedList.sortDescending, "list", (fun () -> IndexedList.sortDescending nullList |> ignore)) + struct (nameof IndexedList.sortRange, "list", (fun () -> IndexedList.sortRange 0 0 nullList |> ignore)) + struct (nameof IndexedList.sortRangeWith, "list", (fun () -> IndexedList.sortRangeWith compare 0 0 nullList |> ignore)) + struct (nameof IndexedList.sortRangeWithComparer, + "list", + (fun () -> + IndexedList.sortRangeWithComparer Comparer.Default 0 0 nullList + |> ignore + )) + struct (nameof IndexedList.sortWith, "list", (fun () -> IndexedList.sortWith compare nullList |> ignore)) + struct (nameof IndexedList.sortWithComparer, + "list", + (fun () -> + IndexedList.sortWithComparer Comparer.Default nullList + |> ignore + )) + struct (nameof IndexedList.randomChoice, "source", (fun () -> IndexedList.randomChoice nullList |> ignore)) + struct (nameof IndexedList.randomChoiceBy, + "source", + (fun () -> + IndexedList.randomChoiceBy (fun () -> 0.5) nullList + |> ignore + )) + struct (nameof IndexedList.randomChoiceWith, + "source", + (fun () -> IndexedList.randomChoiceWith (Random 1) nullList |> ignore)) + struct (nameof IndexedList.randomChoices, "source", (fun () -> IndexedList.randomChoices 1 nullList |> ignore)) + struct (nameof IndexedList.randomChoicesBy, + "source", + (fun () -> + IndexedList.randomChoicesBy (fun () -> 0.5) 1 nullList + |> ignore + )) + struct (nameof IndexedList.randomChoicesWith, + "source", + (fun () -> + IndexedList.randomChoicesWith (Random 1) 1 nullList + |> ignore + )) + struct (nameof IndexedList.randomSample, "source", (fun () -> IndexedList.randomSample 1 nullList |> ignore)) + struct (nameof IndexedList.randomSampleBy, + "source", + (fun () -> + IndexedList.randomSampleBy (fun () -> 0.5) 1 nullList + |> ignore + )) + struct (nameof IndexedList.randomSampleWith, + "source", + (fun () -> IndexedList.randomSampleWith (Random 1) 1 nullList |> ignore)) + struct (nameof IndexedList.randomShuffle, "source", (fun () -> IndexedList.randomShuffle nullList |> ignore)) + struct (nameof IndexedList.randomShuffleBy, + "source", + (fun () -> + IndexedList.randomShuffleBy (fun () -> 0.5) nullList + |> ignore + )) + struct (nameof IndexedList.randomShuffleWith, + "source", + (fun () -> IndexedList.randomShuffleWith (Random 1) nullList |> ignore)) + struct (nameof IndexedList.blit, "source", (fun () -> IndexedList.blit nullList 0 [| 0 |] 0 0)) + struct (nameof IndexedList.toArray, "list", (fun () -> IndexedList.toArray nullList |> ignore)) + struct (nameof IndexedList.toList, "list", (fun () -> IndexedList.toList nullList |> ignore)) + struct (nameof IndexedList.toSeq, "list", (fun () -> IndexedList.toSeq nullList |> ignore)) + |] + |> Array.map (fun struct (name, paramName, action) -> name, struct (paramName, action)) + |> Map.ofArray + + /// The functions that take another input that can be null, by name, with the name of that parameter and the function + /// on a null input. + static let otherNullRejections = + [| + struct (nameof IndexedList.ofArray, + "array", + (fun () -> + IndexedList.ofArray (Unchecked.defaultof) + |> ignore + )) + struct (nameof IndexedList.ofSeq, "source", (fun () -> IndexedList.ofSeq (Unchecked.defaultof>) |> ignore)) + struct (nameof IndexedList.ofBuilder, + "builder", + (fun () -> + IndexedList.ofBuilder (Unchecked.defaultof.Builder>) + |> ignore + )) + struct (nameof IndexedList.concat, + "lists", + (fun () -> + IndexedList.concat (Unchecked.defaultof>>) + |> ignore + )) + struct (nameof IndexedList.transpose, + "lists", + (fun () -> + IndexedList.transpose (Unchecked.defaultof>>) + |> ignore + )) + struct (nameof IndexedList.insertManyAt, + "values", + (fun () -> + IndexedList.insertManyAt 0 (Unchecked.defaultof>) (several ()) + |> ignore + )) + struct (nameof IndexedList.blit, + "target", + (fun () -> IndexedList.blit (several ()) 0 (Unchecked.defaultof) 0 0)) + struct (nameof IndexedList.removeMany, + "items", + (fun () -> + IndexedList.removeMany (Unchecked.defaultof>) (several ()) + |> ignore + )) + struct (nameof IndexedList.removeManyWith, + "items", + (fun () -> + IndexedList.removeManyWith EqualityComparer.Default (Unchecked.defaultof>) (several ()) + |> ignore + )) + |] + |> Array.map (fun struct (name, paramName, action) -> name, struct (paramName, action)) + |> Map.ofArray + + /// 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. The shared contracts cover a null sequence and a default flat list given as a + /// sequence of integers; the rows are a default flat list of strings given as a sequence of objects, which a type test + /// for the flat list of objects does not see, and the order of the checks. A default flat list converts to a sequence + /// without a check and fails when the sequence is enumerated, so the other inputs are valid. + static let sequenceRejections : Map unit))> = + // 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 = IndexedList.ofArray [| "a" :> obj |] + let defaultItems = Inputs.DefaultFlatListAsSeq() + let nullIndexedList = Unchecked.defaultof> + + Map.ofArray [| + "ofSeq (default flat list of strings as objects)", + struct ("source", (fun () -> IndexedList.ofSeq widenedItems |> ignore)) + "insertManyAt (default flat list of strings as objects)", + struct ("values", (fun () -> IndexedList.insertManyAt 0 widenedItems objects |> ignore)) + // List.insertManyAt checks the source before the values + "insertManyAt (null indexed list and default flat list)", + struct ("source", + (fun () -> + IndexedList.insertManyAt 0 defaultItems nullIndexedList + |> ignore + )) + "except (default flat list of strings as objects)", + struct ("itemsToExclude", (fun () -> IndexedList.except widenedItems objects |> ignore)) + // List.except checks the items to exclude before the list + "except (default flat list and null indexed list)", + struct ("itemsToExclude", (fun () -> IndexedList.except defaultItems nullIndexedList |> ignore)) + "removeMany (default flat list of strings as objects)", + struct ("items", (fun () -> IndexedList.removeMany widenedItems objects |> ignore)) + "removeManyWith (default flat list of strings as objects)", + struct ("items", + (fun () -> + IndexedList.removeManyWith EqualityComparer.Default widenedItems objects + |> ignore + )) + |] + + /// The functions that take a range, by name, on the whole range of the input with several elements. + static let wholeRanges : Map -> IndexedList> = + Map.ofArray [| + nameof IndexedList.take, IndexedList.take 8 + nameof IndexedList.takeLast, IndexedList.takeLast 8 + nameof IndexedList.takeWhile, IndexedList.takeWhile (fun _ -> true) + nameof IndexedList.truncate, IndexedList.truncate 100 + nameof IndexedList.skip, IndexedList.skip -1 + nameof IndexedList.skipLast, IndexedList.skipLast 0 + nameof IndexedList.skipWhile, IndexedList.skipWhile (fun _ -> false) + nameof IndexedList.removeManyAt, IndexedList.removeManyAt 3 0 + nameof IndexedList.sub, (fun list -> IndexedList.sub list 0 list.Count) + |] + + /// The sorting functions, by name, with a projection or a comparison function that must not run on one element. + static let singleElementSorts : Map -> IndexedList> = + Map.ofArray [| + nameof IndexedList.sort, IndexedList.sort + nameof IndexedList.sortBy, IndexedList.sortBy (fun _ -> failwith "The projection must not run") + (nameof IndexedList.sortByDescending, + IndexedList.sortByDescending (fun _ -> failwith "The projection must not run")) + nameof IndexedList.sortDescending, IndexedList.sortDescending + nameof IndexedList.sortWith, IndexedList.sortWith (fun _ _ -> failwith "The comparer must not run") + |] + + /// Strings whose ordinal order differs from their order in the current culture. + static let strings () = [| "b"; "A"; "a"; "B" |] + + + /// The functions of two lists, by name, with the List function that they mirror, as functions of the action that + /// records the pairs; both run on a shorter and a longer list. + static let commonPrefixWalks = + let short = [| 1; 2 |] + let long = [| 10; 20; 30 |] + + Map.ofArray [| + (nameof IndexedList.iter2, + struct ((fun (action : int -> int -> unit) -> List.iter2 action (List.ofArray short) (List.ofArray long)), + (fun (action : int -> int -> unit) -> + IndexedList.iter2 action (IndexedList.ofArray short) (IndexedList.ofArray long) + ))) + (nameof IndexedList.map2, + struct ((fun (action : int -> int -> unit) -> + List.map2 action (List.ofArray long) (List.ofArray short) + |> ignore + ), + (fun (action : int -> int -> unit) -> + IndexedList.map2 action (IndexedList.ofArray long) (IndexedList.ofArray short) + |> ignore + ))) + (nameof IndexedList.fold2, + struct ((fun (action : int -> int -> unit) -> + List.fold2 (fun () a b -> action a b) () (List.ofArray short) (List.ofArray long) + ), + (fun (action : int -> int -> unit) -> + IndexedList.fold2 (fun () a b -> action a b) () (IndexedList.ofArray short) (IndexedList.ofArray long) + ))) + (nameof IndexedList.foldBack2, + struct ((fun (action : int -> int -> unit) -> + List.foldBack2 (fun a b () -> action a b) (List.ofArray short) (List.ofArray long) () + ), + (fun (action : int -> int -> unit) -> + IndexedList.foldBack2 + (fun a b () -> action a b) + (IndexedList.ofArray short) + (IndexedList.ofArray long) + () + ))) + |] + + /// The functions whose exceptions List writes with more than one line, by name, with the List function on the same + /// input. The names tell apart two inputs of the same function. + static let completeMessages = + let one = [| 1 |] + let three = [| 1; 2; 3 |] + let four = [| 1; 2; 3; 4 |] + + Map.ofArray [| + (nameof IndexedList.zip3, + struct ((fun () -> + List.zip3 (List.ofArray three) (List.ofArray one) (List.ofArray four) + |> ignore + ), + (fun () -> + IndexedList.zip3 (IndexedList.ofArray three) (IndexedList.ofArray one) (IndexedList.ofArray four) + |> ignore + ))) + (nameof IndexedList.map3, + struct ((fun () -> + List.map3 (fun a b c -> a + b + c) (List.ofArray three) (List.ofArray one) (List.ofArray four) + |> ignore + ), + (fun () -> + IndexedList.map3 + (fun a b c -> a + b + c) + (IndexedList.ofArray three) + (IndexedList.ofArray one) + (IndexedList.ofArray four) + |> ignore + ))) + (nameof IndexedList.zip, + struct ((fun () -> List.zip (List.ofArray four) (List.ofArray one) |> ignore), + (fun () -> + IndexedList.zip (IndexedList.ofArray four) (IndexedList.ofArray one) + |> ignore + ))) + (nameof IndexedList.foldBack2, + struct ((fun () -> + List.foldBack2 (fun _ _ state -> state) (List.ofArray one) (List.ofArray four) 0 + |> ignore + ), + (fun () -> + IndexedList.foldBack2 (fun _ _ state -> state) (IndexedList.ofArray one) (IndexedList.ofArray four) 0 + |> ignore + ))) + ($"%s{nameof IndexedList.foldBack2} of an empty first list", + struct ((fun () -> + List.foldBack2 (fun _ _ state -> state) List.empty (List.ofArray four) 0 + |> ignore + ), + (fun () -> + IndexedList.foldBack2 (fun _ _ state -> state) IndexedList.empty (IndexedList.ofArray four) 0 + |> ignore + ))) + (nameof IndexedList.take, + struct ((fun () -> List.take 5 (List.ofArray three) |> ignore), + (fun () -> IndexedList.take 5 (IndexedList.ofArray three) |> ignore))) + (nameof IndexedList.skip, + struct ((fun () -> List.skip 5 (List.ofArray three) |> ignore), + (fun () -> IndexedList.skip 5 (IndexedList.ofArray three) |> ignore))) + (nameof IndexedList.splitAt, + struct ((fun () -> List.splitAt 5 (List.ofArray three) |> ignore), + (fun () -> IndexedList.splitAt 5 (IndexedList.ofArray three) |> ignore))) + (nameof IndexedList.init, struct ((fun () -> List.init -2 id |> ignore), (fun () -> IndexedList.init -2 id |> ignore))) + (nameof IndexedList.windowed, + struct ((fun () -> List.windowed 0 (List.ofArray three) |> ignore), + (fun () -> IndexedList.windowed 0 (IndexedList.ofArray three) |> ignore))) + (nameof IndexedList.transpose, + struct ((fun () -> + List.transpose [| List.ofArray four; List.ofArray three; List.ofArray four |] + |> ignore + ), + (fun () -> + IndexedList.transpose [| + IndexedList.ofArray four + IndexedList.ofArray three + IndexedList.ofArray four + |] + |> ignore + ))) + (nameof IndexedList.randomChoiceBy, + struct ((fun () -> + List.randomChoiceBy (fun () -> 1.5) (List.ofArray three) + |> ignore + ), + (fun () -> + IndexedList.randomChoiceBy (fun () -> 1.5) (IndexedList.ofArray three) + |> ignore + ))) + |] + + /// The names of the functions that take an indexed list, as the rows of their test. + static member NullListRejections = nullListRejections.Keys + + /// The names of the functions that take another input that can be null, as the rows of their test. + static member OtherNullRejections = otherNullRejections.Keys + + /// The names of the rows of the test of the sequences that the functions reject besides the ones of the contracts. + 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 + + /// The names of the sorting functions, as the rows of the test of one element. + static member SingleElementSorts = singleElementSorts.Keys + + /// Every index of the strings that binarySearch searches. + static member StringIndices = seq { 0 .. (strings ()).Length - 1 } + + + /// The names of the functions of two lists, as the rows of the test of the common prefix. + static member CommonPrefixWalks = commonPrefixWalks.Keys + + /// The names of the functions whose exceptions have several lines, as the rows of the test of the messages. + static member CompleteMessages = completeMessages.Keys + + [] + member _.``functions reject a null indexed list`` (name : string) = + let struct (paramName, action) = nullListRejections[name] + FlatListAssert.RejectsNull ($"IndexedList.%s{name}", paramName, action) + + [] + member _.``functions reject other null inputs with the names of their parameters`` (name : string) = + let struct (paramName, action) = otherNullRejections[name] + FlatListAssert.RejectsNull ($"IndexedList.%s{name}", paramName, action) + + [] + member _.``functions that take a sequence reject a default flat list of strings as objects and check their inputs in order`` + (name : string) + = + let struct (paramName, actual) = sequenceRejections[name] + FlatListAssert.RejectsNull ($"IndexedList.%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 = IndexedList.ofArray (asObjects [| "c" |]) + + CollectionAssert.AreEqual ( + asObjects [| "a"; "b" |], + IndexedList.toArray (IndexedList.ofSeq widened), + "IndexedList.ofSeq must take the elements of a flat list of strings as objects" + ) + + CollectionAssert.AreEqual ( + asObjects [| "a"; "b"; "c" |], + IndexedList.toArray (IndexedList.insertManyAt 0 widened objects), + "IndexedList.insertManyAt must take the elements of a flat list of strings as objects" + ) + + CollectionAssert.AreEqual ( + asObjects [| "c" |], + IndexedList.toArray (IndexedList.except widened objects), + "IndexedList.except must take the elements of a flat list of strings as objects" + ) + + CollectionAssert.AreEqual ( + asObjects [| "c" |], + IndexedList.toArray (IndexedList.removeMany widened (IndexedList.ofArray (asObjects [| "a"; "c"; "b" |]))), + "IndexedList.removeMany must take the elements of a flat list of strings as objects" + ) + + CollectionAssert.AreEqual ( + asObjects [| "c" |], + IndexedList.toArray ( + IndexedList.removeManyWith + EqualityComparer.Default + widened + (IndexedList.ofArray (asObjects [| "a"; "c"; "b" |])) + ), + "IndexedList.removeManyWith must take the elements of a flat list of strings as objects" + ) + + [] + member _.``ofSeq of an indexed list returns it without a copy`` () = + let list = several () + Assert.AreSame (list, IndexedList.ofSeq list, "IndexedList.ofSeq of an indexed list must return it") + + [] + member _.``range functions return the input itself for the whole range`` (name : string) = + let list = several () + Assert.AreSame (list, wholeRanges[name] list, $"IndexedList.%s{name} of the whole range must return the input") + + [] + member _.``sorts return a list of one element itself, as List does`` (name : string) = + let single = IndexedList.singleton 1 + Assert.AreSame (single, singleElementSorts[name] single, $"IndexedList.%s{name} of one element must return the input") + + [] + member _.``append returns the second indexed list when the first one is empty`` () = + let list = several () + Assert.AreSame ( + list, + IndexedList.append IndexedList.empty list, + "IndexedList.append of an empty first list must return the second one" + ) + + [] + member _.``cons adds the value before the first element`` () = + CollectionAssert.AreEqual ( + Array.append [| 99 |] Inputs.Several, + IndexedList.toArray (IndexedList.cons 99 (several ())), + "IndexedList.cons must add the value before the first element" + ) + + [] + member _.``uncons returns the first element and the other elements`` () = + let struct (head, tail) = IndexedList.uncons (several ()) + Assert.AreEqual(Array.head Inputs.Several, head, "IndexedList.uncons must return the first element") + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + IndexedList.toArray tail, + "IndexedList.uncons must return the other elements" + ) + + [] + member _.``uncons throws for an empty indexed list as List.head does`` () = + ExceptionAssert.Same ( + id, + (fun () -> List.head List.empty |> ignore), + (fun () -> IndexedList.uncons IndexedList.empty |> ignore), + "IndexedList.uncons of an empty list must throw as List.head does" + ) + + [] + member _.``tryUncons returns the first element and the other elements or nothing`` () = + let struct (head, tail) = + Assert.WantValueSome ( + IndexedList.tryUncons (several ()), + "IndexedList.tryUncons must return the elements of a list with elements" + ) + + Assert.AreEqual(Array.head Inputs.Several, head, "IndexedList.tryUncons must return the first element") + + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + IndexedList.toArray tail, + "IndexedList.tryUncons must return the other elements" + ) + + Assert.IsTrue ( + (IndexedList.tryUncons IndexedList.empty).IsNone, + "IndexedList.tryUncons of an empty list must return nothing" + ) + + [] + member _.``Cons matches the first element and the other elements`` () = + let rec sum (list : IndexedList) = + match list with + | IndexedList.Cons (struct (head, tail)) -> head + sum tail + | _ -> 0 + + Assert.AreEqual(Array.sum Inputs.Several, sum (several ()), "IndexedList.Cons must match every element in turn") + + match IndexedList.empty with + | IndexedList.Cons _ -> Assert.Fail "IndexedList.Cons must not match an empty list" + | _ -> () + + [] + member _.``Nil matches an empty indexed list only`` () = + let describe (list : IndexedList) = + match list with + | IndexedList.Nil -> "empty" + | IndexedList.Cons (struct (head, _)) -> $"starts with %d{head}" + | _ -> "unreachable" + + Assert.AreEqual("empty", describe IndexedList.empty, "IndexedList.Nil must match an empty list") + Assert.AreEqual("starts with 5", describe (several ()), "IndexedList.Nil must not match a list with elements") + + [] + member _.``binarySearch finds a value or the complement of its insertion point`` () = + let list = IndexedList.ofArray [| 1; 3; 5; 7 |] + Assert.AreEqual(2, IndexedList.binarySearch 5 list, "IndexedList.binarySearch must find 5 at index 2") + Assert.AreEqual( + ~~~2, + IndexedList.binarySearch 4 list, + "IndexedList.binarySearch must return the complement of the insertion point of 4" + ) + Assert.AreEqual( + ~~~4, + IndexedList.binarySearch 9 list, + "IndexedList.binarySearch must return the complement of the length for 9" + ) + + [] + member _.``binarySearch uses the order of sort, which is ordinal for strings`` (index : int) = + let list = IndexedList.ofArray (strings ()) |> IndexedList.sort + CollectionAssert.AreEqual ( + [| "A"; "B"; "a"; "b" |], + IndexedList.toArray list, + "IndexedList.sort must order strings ordinally" + ) + + Assert.AreEqual( + index, + IndexedList.binarySearch list[index] list, + "IndexedList.binarySearch must find every element of a sorted indexed list at its index" + ) + + [] + member _.``blit copies a range into an array`` () = + let target = Array.zeroCreate 5 + IndexedList.blit (several ()) 2 target 1 3 + CollectionAssert.AreEqual ( + [| 0; 8; 5; 0; 0 |], + target, + "IndexedList.blit must copy three elements from index 2 to index 1" + ) + + [)>] + member _.``blit throws for a range out of range with the names of its parameters`` + (sourceIndex : int, targetIndex : int, count : int, paramName : string) + = + let ex = + Assert.ThrowsExactly( + Action (fun () -> IndexedList.blit (several ()) sourceIndex (Array.zeroCreate 5) targetIndex count), + "IndexedList.blit must reject a range out of range" + ) + + Assert.AreEqual(paramName, ex.ParamName, $"IndexedList.blit must name %s{paramName}") + + [] + member _.``contains compares in F# structural equality, indexOf in the default equality`` () = + let arrays = IndexedList.singleton [| 1; 2 |] + Assert.IsTrue ( + IndexedList.contains [| 1; 2 |] arrays, + "IndexedList.contains must compare arrays by their elements, as List.contains does" + ) + Assert.AreEqual(-1, IndexedList.indexOf [| 1; 2 |] arrays, "IndexedList.indexOf must compare arrays by reference") + + let nan = IndexedList.singleton Double.NaN + Assert.AreEqual( + List.contains Double.NaN [ Double.NaN ], + IndexedList.contains Double.NaN nan, + "IndexedList.contains must compare NaN as List.contains does" + ) + + [] + member _.``map and mapi call the mapping in the order of the elements`` () = + let list = IndexedList.init 100 id + let visited = ResizeArray() + IndexedList.map visited.Add list |> ignore + CollectionAssert.AreEqual (Array.init 100 id, visited.ToArray (), "IndexedList.map must call the mapping in order") + + visited.Clear () + IndexedList.mapi (fun _ item -> visited.Add item) list + |> ignore + CollectionAssert.AreEqual (Array.init 100 id, visited.ToArray (), "IndexedList.mapi must call the mapping in order") + + [] + member _.``the functions of two lists walk the common prefix before they throw, as List does`` (name : string) = + let struct (oracle, actual) = commonPrefixWalks[name] + + let walk (run : (int -> int -> unit) -> unit) = + let visited = ResizeArray() + + try + run (fun a b -> visited.Add (struct (a, b))) + with :? ArgumentException -> + () + + visited.ToArray () + + CollectionAssert.AreEqual ( + walk oracle, + walk actual, + $"IndexedList.%s{name} must call its function for the pairs that List calls it for" + ) + + [] + member _.``exists2 and forall2 decide before they compare the lengths, as List does`` () = + let short = [| 1; 2 |] + let long = [| 1; 20; 30 |] + Assert.AreEqual( + List.exists2 (=) (List.ofArray short) (List.ofArray long), + IndexedList.exists2 (=) (IndexedList.ofArray short) (IndexedList.ofArray long), + "IndexedList.exists2 must return true for a match before the end of the shorter list" + ) + Assert.AreEqual( + List.forall2 (=) (List.ofArray short) (List.ofArray long), + IndexedList.forall2 (=) (IndexedList.ofArray short) (IndexedList.ofArray long), + "IndexedList.forall2 must return false for a mismatch before the end of the shorter list" + ) + + [] + member _.``except does not read the items to exclude for an empty indexed list, as List does`` () = + let items = seq { failwith "The items to exclude must not be read" } + Assert.IsEmpty (IndexedList.except items IndexedList.empty, "IndexedList.except of an empty list must be empty") + + [] + member _.``transpose names the list that ends first as List does`` () = + ExceptionAssert.Same ( + id, + (fun () -> List.transpose [| [ 1 ]; [ 1; 2 ] |] |> ignore), + (fun () -> + IndexedList.transpose [| IndexedList.ofArray [| 1 |]; IndexedList.ofArray [| 1; 2 |] |] + |> ignore + ), + "IndexedList.transpose of a shorter first list must throw as List.transpose does" + ) + + [] + member _.``splitAt of an empty indexed list throws as List.splitAt does`` () = + ExceptionAssert.Same ( + id, + (fun () -> List.splitAt 1 List.empty |> ignore), + (fun () -> IndexedList.splitAt 1 IndexedList.empty |> ignore), + "IndexedList.splitAt 1 of an empty list must throw as List.splitAt does" + ) + + [] + member _.``the exceptions have the complete messages of List`` (name : string) = + let struct (oracle, actual) = completeMessages[name] + let expected = thrown oracle $"List.%s{name} must throw" + let ex = thrown actual $"IndexedList.%s{name} must throw" + Assert.AreEqual( + expected.GetType (), + ex.GetType (), + $"IndexedList.%s{name} must throw the type of exception of List" + ) + Assert.AreEqual(expected.Message, ex.Message, $"IndexedList.%s{name} must throw the message of List") diff --git a/tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListTupleTests.fs b/tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListTupleTests.fs new file mode 100644 index 00000000..516defa0 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/IndexedList/IndexedListTupleTests.fs @@ -0,0 +1,316 @@ +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 `IndexedList` module of the `Tuple` namespace. +[] +type IndexedListTupleTests () = + + static let list = IndexedList.ofArray Inputs.Several + + static let other = IndexedList.ofArray Inputs.Other + + static let third = IndexedList.ofArray 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 : IndexedList = IndexedList.unfold generator 3 + CollectionAssert.AreEqual ( + [| 3; 2; 1 |], + IndexedList.toArray generated, + "Tuple.IndexedList.unfold must generate the elements" + ) + + [] + member _.``tryUncons returns a value option of a reference tuple`` () = + let found : (int * IndexedList) voption = IndexedList.tryUncons list + + // A tuple compares an array by reference, so the first element and the other elements are compared apart + let head, tail = + Assert.WantValueSome (found, "Tuple.IndexedList.tryUncons must return the elements of a list with elements") + + Assert.AreEqual(Array.head Inputs.Several, head, "Tuple.IndexedList.tryUncons must return the first element") + + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + IndexedList.toArray tail, + "Tuple.IndexedList.tryUncons must return the other elements" + ) + + [] + member _.``uncons returns a reference tuple`` () = + let (head : int), (tail : IndexedList) = IndexedList.uncons list + Assert.AreEqual(Array.head Inputs.Several, head, "Tuple.IndexedList.uncons must return the first element") + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + IndexedList.toArray tail, + "Tuple.IndexedList.uncons must return the other elements" + ) + + [] + member _.``uncons throws as the default does`` () = + ExceptionAssert.Same ( + id, + (fun () -> + FSharp.Collections.Immutable.IndexedList.uncons IndexedList.empty + |> ignore + ), + (fun () -> IndexedList.uncons IndexedList.empty |> ignore), + "Tuple.IndexedList.uncons must throw as the default uncons does" + ) + + [] + member _.``Cons is a struct partial pattern of reference tuples`` () = + let pattern : IndexedList -> (int * IndexedList) voption = IndexedList.(|Cons|_|) + + match list with + | IndexedList.Cons (head, tail) -> + Assert.AreEqual(Array.head Inputs.Several, head, "Tuple.IndexedList.Cons must match the first element") + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + IndexedList.toArray tail, + "Tuple.IndexedList.Cons must match the other elements" + ) + | _ -> Assert.Fail "Tuple.IndexedList.Cons must match a list with elements" + + Assert.IsTrue ((pattern IndexedList.empty).IsNone, "Tuple.IndexedList.Cons must not match an empty list") + + [] + member _.``allPairs returns reference tuples`` () = + let pairs : IndexedList = IndexedList.allPairs list other + + CollectionAssert.AreEqual ( + Array.allPairs Inputs.Several Inputs.Other, + IndexedList.toArray pairs, + "Tuple.IndexedList.allPairs must pair as List.allPairs does" + ) + + [] + member _.``indexed returns reference tuples`` () = + let pairs : IndexedList = IndexedList.indexed list + + CollectionAssert.AreEqual ( + Array.indexed Inputs.Several, + IndexedList.toArray pairs, + "Tuple.IndexedList.indexed must index as List.indexed does" + ) + + [] + member _.``pairwise returns reference tuples`` () = + let pairs : IndexedList = IndexedList.pairwise list + + CollectionAssert.AreEqual ( + Array.pairwise Inputs.Several, + IndexedList.toArray pairs, + "Tuple.IndexedList.pairwise must pair as List.pairwise does" + ) + + [] + member _.``unzip takes and returns reference tuples`` () = + let (first : IndexedList), (second : IndexedList) = + IndexedList.unzip (IndexedList.zip list other) + CollectionAssert.AreEqual ( + Inputs.Several, + IndexedList.toArray first, + "Tuple.IndexedList.unzip must return the first components" + ) + CollectionAssert.AreEqual ( + Inputs.Other, + IndexedList.toArray second, + "Tuple.IndexedList.unzip must return the second components" + ) + + [] + member _.``unzip3 takes and returns reference tuples`` () = + let (first : IndexedList), (second : IndexedList), (last : IndexedList) = + IndexedList.unzip3 (IndexedList.zip3 list other third) + + CollectionAssert.AreEqual ( + Inputs.Several, + IndexedList.toArray first, + "Tuple.IndexedList.unzip3 must return the first components" + ) + CollectionAssert.AreEqual ( + Inputs.Other, + IndexedList.toArray second, + "Tuple.IndexedList.unzip3 must return the second components" + ) + CollectionAssert.AreEqual ( + Inputs.Third, + IndexedList.toArray last, + "Tuple.IndexedList.unzip3 must return the third components" + ) + + [] + member _.``zip returns reference tuples`` () = + let pairs : IndexedList = IndexedList.zip list other + + CollectionAssert.AreEqual ( + Array.zip Inputs.Several Inputs.Other, + IndexedList.toArray pairs, + "Tuple.IndexedList.zip must zip as List.zip does" + ) + + [] + member _.``zip and zip3 throw for different lengths as the defaults do`` () = + let short = IndexedList.singleton 1 + + ExceptionAssert.Same ( + id, + (fun () -> + FSharp.Collections.Immutable.IndexedList.zip list short + |> ignore + ), + (fun () -> IndexedList.zip list short |> ignore), + "Tuple.IndexedList.zip must throw as the default zip does" + ) + + ExceptionAssert.Same ( + id, + (fun () -> + FSharp.Collections.Immutable.IndexedList.zip3 list short other + |> ignore + ), + (fun () -> IndexedList.zip3 list short other |> ignore), + "Tuple.IndexedList.zip3 must throw as the default zip3 does" + ) + + [] + member _.``zip3 returns reference tuples`` () = + let triples : IndexedList = IndexedList.zip3 list other third + + CollectionAssert.AreEqual ( + Array.zip3 Inputs.Several Inputs.Other Inputs.Third, + IndexedList.toArray triples, + "Tuple.IndexedList.zip3 must zip as List.zip3 does" + ) + + [] + member _.``mapFold takes and returns reference tuples`` () = + let (results : IndexedList), (state : int) = + IndexedList.mapFold (fun state item -> item * state, state + 1) 1 list + + let expectedResults, expectedState = + Array.mapFold (fun state item -> item * state, state + 1) 1 Inputs.Several + + CollectionAssert.AreEqual ( + expectedResults, + IndexedList.toArray results, + "Tuple.IndexedList.mapFold must map as List.mapFold does" + ) + Assert.AreEqual(expectedState, state, "Tuple.IndexedList.mapFold must thread the state as List.mapFold does") + + [] + member _.``mapFoldBack takes and returns reference tuples`` () = + let (results : IndexedList), (state : int) = + IndexedList.mapFoldBack (fun item state -> item * state, state + 1) list 1 + + let expectedResults, expectedState = + Array.mapFoldBack (fun item state -> item * state, state + 1) Inputs.Several 1 + + CollectionAssert.AreEqual ( + expectedResults, + IndexedList.toArray results, + "Tuple.IndexedList.mapFoldBack must map as List.mapFoldBack does" + ) + + Assert.AreEqual(expectedState, state, "Tuple.IndexedList.mapFoldBack must thread the state as List.mapFoldBack does") + + [] + member _.``countBy returns reference tuples`` () = + let counts : IndexedList = IndexedList.countBy (fun item -> item % 3) list + + CollectionAssert.AreEqual ( + Array.countBy (fun item -> item % 3) Inputs.Several, + IndexedList.toArray counts, + "Tuple.IndexedList.countBy must count as List.countBy does" + ) + + [] + member _.``groupBy returns reference tuples`` () = + let groups : IndexedList> = IndexedList.groupBy (fun item -> item % 3) list + 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 |> Seq.map fst |> Seq.toArray, + "Tuple.IndexedList.groupBy must return the keys as List.groupBy does" + ) + + CollectionAssert.AreEqual ( + Array.map snd expected, + groups + |> Seq.map (fun (_, items) -> IndexedList.toArray items) + |> Seq.toArray, + "Tuple.IndexedList.groupBy must group the elements as List.groupBy does" + ) + + [] + member _.``partition returns a reference tuple`` () = + let (accepted : IndexedList), (rejected : IndexedList) = + IndexedList.partition (fun item -> item > 0) list + let expectedAccepted, expectedRejected = Array.partition (fun item -> item > 0) Inputs.Several + CollectionAssert.AreEqual ( + expectedAccepted, + IndexedList.toArray accepted, + "Tuple.IndexedList.partition must return the accepted elements" + ) + CollectionAssert.AreEqual ( + expectedRejected, + IndexedList.toArray rejected, + "Tuple.IndexedList.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 : IndexedList), (second : IndexedList) = + IndexedList.partitionWith partitioner list + let expectedFirst, expectedSecond = Array.partitionWith partitioner Inputs.Several + CollectionAssert.AreEqual ( + expectedFirst, + IndexedList.toArray first, + "Tuple.IndexedList.partitionWith must return the first choices" + ) + CollectionAssert.AreEqual ( + expectedSecond, + IndexedList.toArray second, + "Tuple.IndexedList.partitionWith must return the second choices" + ) + + [] + member _.``splitAt returns a reference tuple`` () = + let (before : IndexedList), (after : IndexedList) = IndexedList.splitAt 3 list + let expectedBefore, expectedAfter = Array.splitAt 3 Inputs.Several + CollectionAssert.AreEqual ( + expectedBefore, + IndexedList.toArray before, + "Tuple.IndexedList.splitAt must return the first part" + ) + CollectionAssert.AreEqual ( + expectedAfter, + IndexedList.toArray after, + "Tuple.IndexedList.splitAt must return the second part" + ) + + [] + member _.``the functions without a tuple keep resolving to the default module`` () = + let found : int voption = IndexedList.tryFind (fun item -> item > 6) list + Assert.AreEqual(ValueSome 8, found, "IndexedList.tryFind must resolve to the default module") diff --git a/tests/FSharp.Collections.Immutable.Tests/IndexedList/ListMemberCases.fs b/tests/FSharp.Collections.Immutable.Tests/IndexedList/ListMemberCases.fs new file mode 100644 index 00000000..cadcf74b --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/IndexedList/ListMemberCases.fs @@ -0,0 +1,371 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open System.Collections.Generic + +/// +/// The strings that the tests of the functions which wrap members of +/// run on. +/// +/// +/// The strings tell the default equality from an equality that ignores the case: "b" and "B" are equal in the second +/// one only. Every property builds new arrays on each read, as +/// does. +/// +[] +type ListNames = + + /// No elements. + static member Empty : string array = [||] + + /// One element. + static member Singleton : string array = [| "b" |] + + /// Strings that repeat, in two cases: "a" at 0 and 4, "b" at 1 and 5, "B" at 2, "c" at 3, "A" at 6 and "d" at 7. When + /// the case is ignored, "a" is also at 6, and "b" at 2. + static member Several : string array = [| "a"; "b"; "B"; "c"; "a"; "b"; "A"; "d" |] + + /// Every input, as the rows of the tests that run on each one. + static member All = seq { + ListNames.Empty + ListNames.Singleton + ListNames.Several + } + +/// +/// The results that the functions which wrap members of must +/// give, computed on arrays with FSharp.Core. +/// +/// +/// The oracles that the flat list shares with the indexed list, such as the removal of the first equal element, the +/// replacement, the search in a range from the front, the descending order and the equality that ignores the case, are in +/// . Strings are equal in the default equality when +/// F# = finds them equal, which compares the characters. +/// +[] +module ListOracles = + + /// The elements before a range, the elements of the range and the elements after it. + let splitRange (elements : 'T array) index count = + struct (elements[.. index - 1], elements[index .. index + count - 1], elements[index + count ..]) + + /// The elements with the elements of a range replaced by the result of a function of them: the oracle of the functions + /// that reverse, sort or remove a range. + let changeRange (change : 'T array -> 'T array) (elements : 'T array) index count = + let struct (before, part, after) = splitRange elements index count + Array.concat [| before; change part; after |] + + /// A stable sort of the elements, as the sorts of List do it. + let stableSort (comparison : 'T -> 'T -> int) (elements : 'T array) = + List.sortWith comparison (List.ofArray elements) + |> List.toArray + + /// The comparison of the keys of pairs in a direction, which ignores the tags. + let compareKeys (direction : SortDirection) = + direction.Apply (fun (struct (left : int, _ : char)) (struct (right : int, _ : char)) -> compare left right) + + /// The keys and the tags of the keyed items of after + /// a stable sort of a range by key, as + /// sorts + /// them. The items compare by their keys only. + let sortedKeyedRange index count = + KeyedItem.KeysAndTags ( + changeRange (fun part -> List.sort (List.ofArray part) |> List.toArray) (KeyedItem.OfManyPairs ()) index count + ) + + /// The pairs of after a stable sort of a range with a + /// comparison, as + /// + /// sorts them. + let sortedPairRange (comparison : struct (int * char) -> struct (int * char) -> int) index count = + changeRange (stableSort comparison) Inputs.ManyPairs index count + + /// The elements without one element for each of the items. + let removeEach (equal : 'T -> 'T -> bool) (items : 'T array) (elements : 'T array) = + Array.fold (fun remaining item -> FlatListOracles.removeFirst equal item remaining) elements items + + /// The index of the last element of a range that the equality finds equal to the value, or -1: the oracle of the + /// searches from the back, which ends at the start index and has the count of elements. A range of no elements has no + /// element wherever it lies, also at , where the bounds of the range overflow in + /// . + let lastIndexOfRange (equal : 'T -> 'T -> bool) startIndex count (value : 'T) (elements : 'T array) = + match count with + | 0 -> -1 + | _ -> FlatListOracles.lastInRange equal startIndex count value elements + +/// +/// Why a function rejects a range of a collection, which tells the parameter that its exception names: the functions name +/// their parameters differently, and a row holds the fault, which every test turns into the name of its function. +/// +[] +type RangeFault = + /// The index of the first element, or of the last element of a backward search, is negative. + | FirstNegative + /// The count is negative. + | CountNegative + /// The range extends beyond the end of the collection, or the count of a backward search exceeds the length. + | Beyond + /// A backward search: the range starts before the first element. + | BeforeStart + /// A backward search: the last element of the range is at or after the end of the collection. + | AfterEnd + + /// The name of the parameter that the exception names: the name of the first index for the faults of the index, the + /// name of the count, and the name that the function gives a range beyond the end. + member this.ParamName (first : string, beyond : string) = + match this with + | FirstNegative + | BeforeStart + | AfterEnd -> first + | CountNegative -> "count" + | Beyond -> beyond + +/// +/// The sorted inputs of the binary searches. Every property builds new arrays on each read. +/// +[] +type SortedInputs = + + /// Eight integers in ascending order, without duplicates: a binary search of a duplicate may find any of its places. + static member Ascending : int array = [| 1; 3; 5; 7; 9; 11; 13; 15 |] + + /// Four strings in ascending order when the case is ignored. In the order of their characters, "B" and "D" come before + /// "a" and "c". + static member Names : string array = [| "a"; "B"; "c"; "D" |] + + /// A comparer for integers in descending order. + static member DescendingComparer : IComparer = Comparer.Create (fun left right -> compare right left) + +/// +/// The rows of the tests of the functions which wrap members of +/// , for the modules and the builder module of the indexed +/// list. +/// +/// +/// The inputs with several elements, and +/// , and the ascending integers of +/// have eight elements, so that the rows of the +/// ranges serve all of them. +/// +[] +type ListMemberCases = + + /// The names of the functions that search forward in a range, as the rows of the tests that tell them apart. + static member ForwardSearches = seq { + "indexOfRange" + "indexOfRangeWith" + } + + /// The names of the functions that search backward in a range. + static member BackwardSearches = seq { + "lastIndexOfRange" + "lastIndexOfRangeWith" + } + + /// The names of the functions that remove several values, as the rows of the tests that tell them apart. + static member Removals = seq { + "removeMany" + "removeManyWith" + } + + /// Each function that removes several values with each sequence of values that it rejects, a null sequence and a default + /// flat list given as a sequence, on a collection without elements and on one with elements: a function may return early + /// for an empty collection. + static member RejectedValues = seq { + for name in ListMemberCases.Removals do + for kind in [| "null sequence"; "default flat list" |] do + for input in [| "empty collection"; "collection with elements" |] do + struct (name, kind, input) + } + + /// Every input with each value to remove or to replace: the first of two equal elements, the only element, the last + /// element, a value that equals another in a different case, and a value that no input has. + static member Values = seq { + for input in ListNames.All do + for value in [| "a"; "b"; "B"; "A"; "c"; "d"; "z" |] do + struct (input, value) + } + + /// The values of that an element equals in the + /// default equality of strings. + static member PresentValues = seq { + for struct (input, value) in ListMemberCases.Values do + if Array.contains value input then + struct (input, value) + } + + /// The values of that an element equals when + /// the case is ignored. + static member PresentValuesIgnoringCase = seq { + for struct (input, value) in ListMemberCases.Values do + if Array.exists (FlatListOracles.equalIgnoringCase value) input then + struct (input, value) + } + + /// Every input with values that no element equals in the default equality of strings. + static member AbsentValues = seq { + struct (ListNames.Empty, "a") + struct (ListNames.Singleton, "a") + struct (ListNames.Singleton, "B") + struct (ListNames.Several, "z") + struct (ListNames.Several, "C") + struct (ListNames.Several, "D") + } + + /// Every input with values that no element equals when the case is ignored. + static member AbsentValuesIgnoringCase = seq { + struct (ListNames.Empty, "a") + struct (ListNames.Singleton, "a") + struct (ListNames.Several, "z") + struct (ListNames.Several, "e") + } + + /// Every input with the values to remove: none, one, a value as often as the input with several elements has it and + /// more often, the same value in two cases, and several values of which some are missing. + static member ValueSequences = seq { + for input in ListNames.All do + for items in + [| + [||] + [| "a" |] + [| "a"; "a" |] + [| "a"; "a"; "a" |] + [| "B"; "b" |] + [| "b"; "B"; "B" |] + [| "z" |] + [| "d"; "a"; "z"; "a" |] + |] do + struct (input, items) + } + + /// Every input with sequences of values that no element equals in the default equality of strings. + static member AbsentValueSequences = seq { + for input in ListNames.All do + for items in [| [||]; [| "z" |]; [| "z"; "C" |] |] do + struct (input, items) + } + + /// Every input with sequences of values that no element equals when the case is ignored. + static member AbsentValueSequencesIgnoringCase = seq { + for input in ListNames.All do + for items in [| [||]; [| "z" |]; [| "z"; "e" |] |] do + struct (input, items) + } + + /// Every range of the input with several elements, as the index of its first element and its length, with the values + /// to look for: one that occurs twice, one in two cases, and the last element. + static member ForwardRanges = seq { + let length = ListNames.Several.Length + + for startIndex in 0..length do + for count in 0 .. length - startIndex do + for value in [| "a"; "B"; "d" |] do + struct (startIndex, count, value) + } + + /// Every range of the input with several elements for a backward search, as the index of its last element and its + /// length, with the values to look for. A range of no elements lies anywhere, so the rows add the indices at and after + /// the end. + static member BackwardRanges = seq { + let length = ListNames.Several.Length + + for startIndex in 0 .. length - 1 do + for count in 0 .. startIndex + 1 do + for value in [| "a"; "B"; "d" |] do + struct (startIndex, count, value) + + for startIndex in [| length; length + 1; Int32.MaxValue |] do + for value in [| "a"; "B" |] do + struct (startIndex, 0, value) + } + + /// The ranges that the input with several elements does not have, as the index of the first element, the length and + /// the fault: negative numbers, a range beyond the end, and ranges whose end overflows `Int32.MaxValue`, which the + /// members of `ImmutableList` accept. + static member RangeFaults = seq { + let length = ListNames.Several.Length + + struct (-1, 0, RangeFault.FirstNegative) + struct (-1, 1, RangeFault.FirstNegative) + struct (-1, -1, RangeFault.FirstNegative) + struct (Int32.MinValue, 0, RangeFault.FirstNegative) + struct (0, -1, RangeFault.CountNegative) + struct (0, Int32.MinValue, RangeFault.CountNegative) + struct (0, length + 1, RangeFault.Beyond) + struct (length - 3, 4, RangeFault.Beyond) + struct (length, 1, RangeFault.Beyond) + struct (length + 1, 0, RangeFault.Beyond) + struct (Int32.MaxValue, 0, RangeFault.Beyond) + struct (Int32.MaxValue, 1, RangeFault.Beyond) + struct (1, Int32.MaxValue, RangeFault.Beyond) + } + + /// The ranges that the input with several elements does not have for a backward search, as the index of the last + /// element, the length and the fault: negative numbers, a count beyond the length, a range that starts before the + /// first element, and a last element at or after the end, which the members of `ImmutableList` accept. + static member BackwardRangeFaults = seq { + let length = ListNames.Several.Length + + struct (-1, 0, RangeFault.FirstNegative) + struct (-1, 1, RangeFault.FirstNegative) + struct (-1, -1, RangeFault.FirstNegative) + struct (Int32.MinValue, 0, RangeFault.FirstNegative) + struct (0, -1, RangeFault.CountNegative) + struct (0, Int32.MinValue, RangeFault.CountNegative) + struct (length - 1, length + 1, RangeFault.Beyond) + struct (0, 2, RangeFault.BeforeStart) + struct (3, 5, RangeFault.BeforeStart) + struct (length, 1, RangeFault.AfterEnd) + struct (length + 1, 2, RangeFault.AfterEnd) + struct (Int32.MaxValue, 1, RangeFault.AfterEnd) + struct (Int32.MaxValue, 2, RangeFault.AfterEnd) + } + + /// Every range of the ascending integers with the values to search for: present in some ranges and absent from others, + /// absent between two elements, before the first one and after the last one. + static member SearchRanges = seq { + let length = SortedInputs.Ascending.Length + + for index in 0..length do + for count in 0 .. length - index do + for value in [| 7; 4; 0; 99 |] do + struct (index, count, value) + } + + /// The strings to search for with a comparer that ignores the case: equal in both cases, between the elements, before + /// the first one and after the last one. + static member NameValues = seq { + "a" + "A" + "b" + "B" + "c" + "D" + "d" + "e" + "0" + } + + /// The copies that reject a range of a source of eight elements into a target of five elements, as the index in the + /// source, the index in the target, the count and the name of the parameter that the exception names. + static member BlitRejections = seq { + struct (-1, 0, 1, "sourceIndex") + struct (0, 0, -1, "count") + struct (6, 0, 3, "count") + struct (0, -1, 1, "targetIndex") + struct (0, 3, 3, "targetIndex") + } + + /// The ranges that the functions that search forward reject, for each of them. + static member ForwardRangesOutOfRange = seq { + for name in ListMemberCases.ForwardSearches do + for struct (startIndex, count, fault) in ListMemberCases.RangeFaults do + struct (name, startIndex, count, fault) + } + + /// The ranges that the functions that search backward reject, for each of them. + static member BackwardRangesOutOfRange = seq { + for name in ListMemberCases.BackwardSearches do + for struct (startIndex, count, fault) in ListMemberCases.BackwardRangeFaults do + struct (name, startIndex, count, fault) + } diff --git a/tests/FSharp.Collections.Immutable.Tests/IndexedList/ListMembersContract.fs b/tests/FSharp.Collections.Immutable.Tests/IndexedList/ListMembersContract.fs new file mode 100644 index 00000000..0f8c430c --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/IndexedList/ListMembersContract.fs @@ -0,0 +1,366 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// +/// The functions of a module that wraps members of , for the +/// contract tests, over elements. +/// +/// +/// The strings tell the default equality from an equality that ignores the case. The module and its interface module have +/// these functions, and the module of the builder works on a builder, which has its own tests. +/// +type ListMembersOps<'C> = { + /// The name of the module, for the messages of the assertions. + Name : 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. + ToArray : 'C -> string array + + IndexOfRange : int -> int -> string -> 'C -> int + IndexOfRangeWith : IEqualityComparer -> int -> int -> string -> 'C -> int + LastIndexOfRange : int -> int -> string -> 'C -> int + LastIndexOfRangeWith : IEqualityComparer -> int -> int -> string -> 'C -> int + + Remove : string -> 'C -> 'C + RemoveMany : seq -> 'C -> 'C + RemoveManyWith : IEqualityComparer -> seq -> 'C -> 'C + RemoveWith : IEqualityComparer -> string -> 'C -> 'C + Replace : string -> string -> 'C -> 'C + ReplaceWith : IEqualityComparer -> string -> string -> 'C -> 'C +} + +/// +/// The contract of the functions that wrap members of : the +/// removal and the replacement of a value, and the search in a range. +/// and +/// have them (ADR 0001, D14). +/// +/// +/// is not a list of this contract: it has no interface, and the tests +/// of its own removal, replacement and search are in its tests. +/// +[] +type ListMembersContract<'C> (ops : ListMembersOps<'C>) = + + /// An equality comparer that finds strings equal when the case is ignored. + static let ignoringCaseEquality : IEqualityComparer = StringComparer.OrdinalIgnoreCase + + /// The replacement value of the tests; no input has it. + static let replacement = "X" + + /// The functions that search forward in a range, by name. + let forwardSearches : Map int -> string -> 'C -> int> = + Map.ofArray [| + "indexOfRange", ops.IndexOfRange + "indexOfRangeWith", ops.IndexOfRangeWith ignoringCaseEquality + |] + + /// The functions that search backward in a range, by name. + let backwardSearches : Map int -> string -> 'C -> int> = + Map.ofArray [| + "lastIndexOfRange", ops.LastIndexOfRange + "lastIndexOfRangeWith", ops.LastIndexOfRangeWith ignoringCaseEquality + |] + + /// The functions that remove several values, by name. + let removals : Map -> 'C -> 'C> = + Map.ofArray [| "removeMany", ops.RemoveMany; "removeManyWith", ops.RemoveManyWith ignoringCaseEquality |] + + // ---- remove and removeWith ---- + + [)>] + member _.``remove removes the first equal element`` (input : string array, value : string) = + CollectionAssert.AreEqual ( + FlatListOracles.removeFirst (=) value input, + ops.ToArray (ops.Remove value (ops.OfSeq input)), + $"%s{ops.Name}.remove must remove the first equal element, in the default equality" + ) + + [)>] + member _.``remove returns the input itself when no element equals the value`` (input : string array, value : string) = + let collection = ops.OfSeq input + + Assert.AreSame ( + box collection, + box (ops.Remove value collection), + $"%s{ops.Name}.remove must return a collection that has no equal element as it is" + ) + + [)>] + member _.``removeWith removes the first element that the comparer finds equal`` (input : string array, value : string) = + CollectionAssert.AreEqual ( + FlatListOracles.removeFirst FlatListOracles.equalIgnoringCase value input, + ops.ToArray (ops.RemoveWith ignoringCaseEquality value (ops.OfSeq input)), + $"%s{ops.Name}.removeWith must remove the first element that the comparer finds equal" + ) + + [)>] + member _.``removeWith returns the input itself when the comparer finds no element equal`` + (input : string array, value : string) + = + let collection = ops.OfSeq input + + Assert.AreSame ( + box collection, + box (ops.RemoveWith ignoringCaseEquality value collection), + $"%s{ops.Name}.removeWith must return a collection that has no equal element as it is" + ) + + // ---- removeMany and removeManyWith ---- + + [)>] + member _.``removeMany removes one element for each value`` (input : string array, items : string array) = + CollectionAssert.AreEqual ( + ListOracles.removeEach (=) items input, + ops.ToArray (ops.RemoveMany items (ops.OfSeq input)), + $"%s{ops.Name}.removeMany must remove one element for each value" + ) + + [] + member _.``removeMany takes the values as any sequence`` () = + let collection = ops.OfSeq ListNames.Several + // The values "a", "B" and "a" remove the first "a", the only "B" and the second "a" + let expected = [| "b"; "c"; "b"; "A"; "d" |] + + CollectionAssert.AreEqual ( + expected, + ops.ToArray (ops.RemoveMany [| "a"; "B"; "a" |] collection), + $"%s{ops.Name}.removeMany must take the values as an array" + ) + + CollectionAssert.AreEqual ( + expected, + ops.ToArray (ops.RemoveMany [ "a"; "B"; "a" ] collection), + $"%s{ops.Name}.removeMany must take the values as a list" + ) + + CollectionAssert.AreEqual ( + expected, + ops.ToArray (ops.RemoveMany (Seq.map id [| "a"; "B"; "a" |]) collection), + $"%s{ops.Name}.removeMany must take the values as a lazy sequence" + ) + + [)>] + member _.``removeMany returns the input itself when no value is in it`` (input : string array, items : string array) = + let collection = ops.OfSeq input + + Assert.AreSame ( + box collection, + box (ops.RemoveMany items collection), + $"%s{ops.Name}.removeMany must return a collection that has none of the values as it is" + ) + + [)>] + member _.``removeManyWith removes one element for each value that the comparer finds equal`` + (input : string array, items : string array) + = + CollectionAssert.AreEqual ( + ListOracles.removeEach FlatListOracles.equalIgnoringCase items input, + ops.ToArray (ops.RemoveManyWith ignoringCaseEquality items (ops.OfSeq input)), + $"%s{ops.Name}.removeManyWith must remove one element for each value that the comparer finds equal" + ) + + [)>] + member _.``removeManyWith returns the input itself when the comparer finds none of the values`` + (input : string array, items : string array) + = + let collection = ops.OfSeq input + + Assert.AreSame ( + box collection, + box (ops.RemoveManyWith ignoringCaseEquality items collection), + $"%s{ops.Name}.removeManyWith must return a collection that has none of the values as it is" + ) + + [)>] + member _.``the removals of several values reject null items and a default flat list given as the items`` + (name : string, kind : string, input : string) + = + let items = + if kind = "null sequence" then + Unchecked.defaultof> + else + Inputs.DefaultFlatListAsSeq() + + let collection = + ops.OfSeq ( + if input = "empty collection" then + ListNames.Empty + else + ListNames.Several + ) + + // A default flat list is a boxed struct, not null: it fails with InvalidOperationException when it is enumerated + FlatListAssert.RejectsNull ( + $"%s{ops.Name}.%s{name} of a %s{kind} on a %s{input}", + "items", + (fun () -> removals[name] items collection |> ignore) + ) + + [)>] + member _.``the removals of several values check the collection before the items`` (name : string) = + // The member cannot be called on a null collection, so the function checks the collection before the items + FlatListAssert.RejectsNull ( + $"%s{ops.Name}.%s{name} of a null collection and a default flat list", + "list", + (fun () -> + removals[name] (Inputs.DefaultFlatListAsSeq()) Unchecked.defaultof<'C> + |> ignore + ) + ) + + // ---- replace and replaceWith ---- + + [)>] + member _.``replace replaces the first equal element`` (input : string array, oldValue : string) = + CollectionAssert.AreEqual ( + Assert.WantValueSome ( + FlatListOracles.replaceFirst (=) oldValue replacement input, + "The oracle must find the old value, which the rows take from the elements" + ), + ops.ToArray (ops.Replace oldValue replacement (ops.OfSeq input)), + $"%s{ops.Name}.replace must replace the first equal element, in the default equality" + ) + + [)>] + member _.``replaceWith replaces the first element that the comparer finds equal`` (input : string array, oldValue : string) = + CollectionAssert.AreEqual ( + Assert.WantValueSome ( + FlatListOracles.replaceFirst FlatListOracles.equalIgnoringCase oldValue replacement input, + "The oracle must find the old value, which the rows take from the elements" + ), + ops.ToArray (ops.ReplaceWith ignoringCaseEquality oldValue replacement (ops.OfSeq input)), + $"%s{ops.Name}.replaceWith must replace the first element that the comparer finds equal" + ) + + [)>] + member _.``replace throws ArgumentException naming the old value when no element equals it`` + (input : string array, oldValue : string) + = + let ex = + Assert.ThrowsExactly( + Action (fun () -> ops.Replace oldValue replacement (ops.OfSeq input) |> ignore), + $"%s{ops.Name}.replace must throw ArgumentException for a value that no element equals" + ) + + Assert.AreEqual("oldValue", ex.ParamName, $"%s{ops.Name}.replace must name the old value") + + [)>] + member _.``replaceWith throws ArgumentException naming the old value when the comparer finds no element equal`` + (input : string array, oldValue : string) + = + let ex = + Assert.ThrowsExactly( + Action (fun () -> + ops.ReplaceWith ignoringCaseEquality oldValue replacement (ops.OfSeq input) + |> ignore + ), + $"%s{ops.Name}.replaceWith must throw ArgumentException for a value that the comparer finds in no element" + ) + + Assert.AreEqual("oldValue", ex.ParamName, $"%s{ops.Name}.replaceWith must name the old value") + + // ---- indexOfRange, indexOfRangeWith, lastIndexOfRange and lastIndexOfRangeWith ---- + + [)>] + member _.``indexOfRange returns the index of the first equal element of the range or -1`` + (startIndex : int, count : int, value : string) + = + Assert.AreEqual( + FlatListOracles.firstInRange (=) startIndex count value ListNames.Several, + ops.IndexOfRange startIndex count value (ops.OfSeq ListNames.Several), + $"%s{ops.Name}.indexOfRange must return the index of the first equal element of the range, or -1" + ) + + [)>] + member _.``indexOfRangeWith compares with the equality comparer`` (startIndex : int, count : int, value : string) = + Assert.AreEqual( + FlatListOracles.firstInRange FlatListOracles.equalIgnoringCase startIndex count value ListNames.Several, + ops.IndexOfRangeWith ignoringCaseEquality startIndex count value (ops.OfSeq ListNames.Several), + $"%s{ops.Name}.indexOfRangeWith must compare with the equality comparer" + ) + + [)>] + member _.``lastIndexOfRange returns the index of the last equal element of the range or -1`` + (startIndex : int, count : int, value : string) + = + Assert.AreEqual( + ListOracles.lastIndexOfRange (=) startIndex count value ListNames.Several, + ops.LastIndexOfRange startIndex count value (ops.OfSeq ListNames.Several), + $"%s{ops.Name}.lastIndexOfRange must return the index of the last equal element of the range, or -1" + ) + + [)>] + member _.``lastIndexOfRangeWith compares with the equality comparer`` (startIndex : int, count : int, value : string) = + Assert.AreEqual( + ListOracles.lastIndexOfRange FlatListOracles.equalIgnoringCase startIndex count value ListNames.Several, + ops.LastIndexOfRangeWith ignoringCaseEquality startIndex count value (ops.OfSeq ListNames.Several), + $"%s{ops.Name}.lastIndexOfRangeWith must compare with the equality comparer" + ) + + [] + member _.``the searches in a range return -1 in an empty collection`` () = + let empty = ops.OfSeq ListNames.Empty + + Assert.AreEqual( + -1, + ops.IndexOfRange 0 0 "a" empty, + $"%s{ops.Name}.indexOfRange must return -1 in an empty collection" + ) + Assert.AreEqual( + -1, + ops.IndexOfRangeWith ignoringCaseEquality 0 0 "a" empty, + $"%s{ops.Name}.indexOfRangeWith must return -1 in an empty collection" + ) + Assert.AreEqual( + -1, + ops.LastIndexOfRange 0 0 "a" empty, + $"%s{ops.Name}.lastIndexOfRange must return -1 in an empty collection" + ) + Assert.AreEqual( + -1, + ops.LastIndexOfRangeWith ignoringCaseEquality 0 0 "a" empty, + $"%s{ops.Name}.lastIndexOfRangeWith must return -1 in an empty collection" + ) + + [)>] + member _.``the searches forward in a range throw for a range out of range`` + (name : string, startIndex : int, count : int, fault : RangeFault) + = + let ex = + Assert.ThrowsExactly( + Action (fun () -> + forwardSearches[name] startIndex count "a" (ops.OfSeq ListNames.Several) + |> ignore + ), + $"%s{ops.Name}.%s{name} must throw ArgumentOutOfRangeException for a range out of range" + ) + + Assert.AreEqual( + fault.ParamName ("startIndex", "count"), + ex.ParamName, + $"%s{ops.Name}.%s{name} must name the offending parameter" + ) + + [)>] + member _.``the searches backward in a range throw for a range out of range`` + (name : string, startIndex : int, count : int, fault : RangeFault) + = + let ex = + Assert.ThrowsExactly( + Action (fun () -> + backwardSearches[name] startIndex count "a" (ops.OfSeq ListNames.Several) + |> ignore + ), + $"%s{ops.Name}.%s{name} must throw ArgumentOutOfRangeException for a range out of range" + ) + + Assert.AreEqual( + fault.ParamName ("startIndex", "count"), + ex.ParamName, + $"%s{ops.Name}.%s{name} must name the offending parameter" + ) From 53b8a6bf8c002321de9704c741e3c6a7a81cbb81 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 13:21:34 +0200 Subject: [PATCH 3/4] docs: `IndexedList` and `IIndexedList` in the changelog and the collection table The changelog lists the new functions, the renamed `ImmutableList` module and functions, the breaking changes of the `List` semantics and the removed functions and modules. The collection tables name `IndexedList` with `indexedList { }` and `IIndexedList` instead of `ImmutableList` and `IIndexedSeq`, and the tutorial creates an `IndexedList`. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 100 +++++++++++++++++++++++++++ README.md | 4 +- docsSrc/Explanations/Background.md | 4 +- docsSrc/Tutorials/Getting_Started.md | 2 +- docsSrc/index.md | 4 +- 5 files changed, 107 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8373ad55..029a7817 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 semantics and exceptions. Its `tryFind`, `tryFindIndex` and `tryPick` return `voption`, `choose` and `tryPick` take functions that return `voption`, and `groupBy`, `partition`, `partitionWith` and `zip` return struct tuples; the `Parallel` modules of the `Option` and `Tuple` namespaces repeat them with options and reference tuples. +* `IndexedList` has every function of the FSharp.Core `List` module that applies to an immutable list, with its semantics, + parameter names and exceptions and with stable sorts, plus `binarySearch`, `binarySearchRange`, + `binarySearchRangeWithComparer`, `binarySearchWithComparer`, `blit`, `indexOf`, `indexOfRange`, `indexOfRangeWith`, + `indexOfWith`, `lastIndexOf`, `lastIndexOfRange`, `lastIndexOfRangeWith`, `lastIndexOfWith`, `ofReadOnlySpan`, `ofSpan`, + `removeMany`, `removeManyWith`, `revRange`, `skipLast`, `sortRange`, `sortRangeWith`, `sortRangeWithComparer`, + `sortWithComparer`, `sub`, `takeLast`, `tryFindIndexBackRange`, `tryFindIndexRange`, `tryUncons`, `uncons` and the struct + partial active patterns `Cons` and `Nil`. +* The functions of `IndexedList` that wrap a member of `ImmutableList<'T>` throw what the member throws, with the names of + their own parameters. `removeMany` removes one element for each value, in `EqualityComparer<'T>.Default`. The functions + that take a range check it before they call the member: the members accept a range whose end overflows `Int32.MaxValue`, + `BinarySearch` accepts a range beyond the end, and `LastIndexOf` and `FindLastIndex` accept a last element at or after + the end and answer with the index of another element. + A function that takes a sequence throws `ArgumentNullException` that names the parameter for a `null` sequence and for a + default `FlatList` given as a sequence, which the members would reject with `InvalidOperationException` while they + enumerate it; the `IIndexedList` and `IndexedListBuilder` modules and the `indexedList { }` expression do the same. +* `sortRange`, `sortRangeWith` and `sortRangeWithComparer` are stable, as the other sorts of `IndexedList` are, and do not + wrap the unstable `ImmutableList<'T>.Sort(index, count, comparer)`. +* The `IIndexedList` module has the functions of `IndexedList` for `IImmutableList<'T>`, without the creation and builder + functions, `binarySearch*`, `blit`, `revRange`, `sortRange*`, `sub` and `tryFindIndex*Range`, plus `clear`. Its functions + keep the implementation of the input list where the result has the element type of the input, and return an + `IndexedList` otherwise. `IIndexedList<'T>` is the abbreviation of `IImmutableList<'T>`, as `IndexedList<'T>` is the + abbreviation of `ImmutableList<'T>`. +* `max`, `maxBy`, `min` and `minBy` of `IndexedList` and `IIndexedList` are inline, as the ones of `List` are, so that the + comparison is compiled for the type of the elements and boxes nothing. `exists2`, `forall2` and `compareWith` stop at the + first pair that decides. `concat` and `collect` share the nodes of an indexed list of at least 16 elements instead of + copying its elements, and `skip`, `take`, `sub`, `splitAt`, `chunkBySize`, `windowed` and the range sorts read a range in + O(log n + count). +* The `FSharp.Collections.Immutable.Option` and `FSharp.Collections.Immutable.Tuple` namespaces have `IndexedList` and + `IIndexedList` modules that repeat the functions that use value options or struct tuples with options and reference + tuples. +* The `IndexedListBuilder` module works on the builder of an indexed list, and `indexedList { }` builds an indexed list. + The module removes and replaces values (`remove`, `removeWith`, `removeAll`, `removeMany`, `removeManyAt`, + `removeManyWith`, `replace`, `replaceWith`), inserts values (`insertManyAt`), reverses, sorts and searches ranges + (`revRange`, `sortRange`, `sortRangeWith`, `sortRangeWithComparer`, `indexOfRange`, `indexOfRangeWith`, + `lastIndexOfRange`, `lastIndexOfRangeWith`, `binarySearch`, `binarySearchWithComparer`, + `binarySearchRangeWithComparer`) and copies elements into an array (`blit`). `replace` throws `ArgumentException` for a + value that no element equals, as the builder of an indexed list does. ### Changed @@ -44,6 +81,49 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * `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. +* The `ImmutableList` module is `IndexedList`. It works on `ImmutableList<'T>` (`IndexedList<'T>`), where most functions of + 1.0.0 took any `IImmutableList<'T>` and the ones that change a list returned an `IImmutableList<'T>`; the functions for + `IImmutableList<'T>` are in the new `IIndexedList` module. 1.0.0 compiled the module as `ImmutableListModule`, with the + F# names of its functions, such as `isEmpty`, and `IndexedList` compiles them to PascalCase names, such as `IsEmpty`. +* `IndexedList.ofList` takes an F# list, where `ofList` took any sequence. The mapping of `collect` returns an indexed + list and `concat` takes indexed lists, where they took any sequences. `append` takes two indexed lists, where it took a + list and a sequence: `append list (ofSeq items)` or `insertManyAt (length list) items list` appends a sequence. +* `IndexedList`: `tryFind`, `tryFindIndex`, `tryHead`, `tryItem`, `tryLast` and `tryTail` return `voption`; `choose` and + `pick` take choosers that return `voption`; `splitAt` returns a struct tuple; and the generator of `unfold` returns a + `voption` of a struct tuple with the element before the state, as in `List.unfold`, where it returned an `option` of a + tuple with the state before the element. +* `IndexedList`: every function throws what the FSharp.Core `List` function of the same name throws, and names the + parameters of `List`, such as `source` in `ofSeq`, `itemsToExclude` in `except` and `list1` in `append`. `head`, `last` + and `tail` of an empty list, and `item` and `removeAt` for an index outside the list, throw `ArgumentException`, where + they threw `ArgumentOutOfRangeException`. `take` and `splitAt` throw `InvalidOperationException` for a count beyond the + length and `ArgumentException` for a negative count, and `skip` and `truncate` return the list and an empty list for a + negative count, where all four threw `ArgumentOutOfRangeException`. `forall2` throws `ArgumentException` for lists of + different lengths when every pair of the shorter list satisfies the predicate, where it returned `true`. `contains` and + `ofBuilder` throw `ArgumentNullException` for a `null` input, where they threw `NullReferenceException`. A default + `FlatList` given as a sequence to `ofSeq`, `concat` and `except` throws `ArgumentNullException`, where it threw + `InvalidOperationException` (`except` of an empty list returned). +* `IndexedList.skipWhile` and `takeWhile` do what their names say, as the functions of `List` do. In 1.0.0, `skipWhile` + returned the elements before the first one that fails the predicate, and `takeWhile` returned the elements from it on. +* `IndexedList.except` removes every element equal to an excluded item and the duplicates of the other elements, as + `List.except` does, where the 1.0.0 `except` removed one element for each item, as `removeMany` does now. +* `IndexedList.contains` compares in F# structural equality, where 1.0.0 compared in `EqualityComparer<'T>.Default`: + arrays with equal elements are equal, and `nan` is not equal to itself. `remove` and `replace`, and `indexOf`, + `lastIndexOf`, `indexOfRange` and `lastIndexOfRange`, which replace `index`, `lastIndex`, `indexRange` and + `lastIndexRange`, compare in `EqualityComparer<'T>.Default`, where 1.0.0 compared in F# structural equality + (`HashIdentity.Structural`), so elements that are arrays, or whose type defines `Equals` differently from F# equality, + compare differently. +* The functions that replace renamed functions of 1.0.0 differ from them in these cases. `insertAt`, `insertManyAt` and + `updateAt` throw `ArgumentException` for an index outside the list, where `insert`, `insertRange` and `withItem` threw + `ArgumentOutOfRangeException`. `removeManyAt` throws `ArgumentException` for a range that is not inside the list and + removes nothing for a negative count or a range whose end overflows `Int32.MaxValue`, where `removeRange` threw + `ArgumentOutOfRangeException`. `lastIndexOf` returns -1 for an empty list, where `lastIndex` threw + `ArgumentOutOfRangeException`. `lastIndexOfRange` throws `ArgumentOutOfRangeException` for a start index at or after the + end of the list, where `lastIndexRange` accepted it and could answer with an index that the list does not have. `rev` + and `revRange` throw `ArgumentNullException` for a `null` list, where `reverse` and `reverseRange` threw + `NullReferenceException`. +* `IndexedList.rev` builds the reversed list from a reversed copy of the elements. At 100 000 integers it takes about 3 ms + where `reverse`, which called `ImmutableList.Reverse`, took about 23 ms, and it allocates 5.2 MB where `reverse` + allocated 4.8 MB. ### Removed @@ -61,6 +141,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 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`. +* The functions of the `ImmutableList` module that `IndexedList` has under another name and with the same arguments: + `lastIndex` to `lastIndexOf`, `lastIndexWith` to `lastIndexOfWith`, `indexRange` to `indexOfRange`, `indexRangeWith` to + `indexOfRangeWith`, `lastIndexRange` to `lastIndexOfRange`, `lastIndexRangeWith` to `lastIndexOfRangeWith`, `insert` to + `insertAt`, `insertRange` to `insertManyAt`, `removeRange` to `removeManyAt`, `exceptWith` to `removeManyWith`, `reverse` + to `rev`, `reverseRange` to `revRange` and `withItem` to `updateAt`. Only `revRange index count list` differs: it takes + the index and the count as two arguments, where `reverseRange` took them as a tuple. +* `ImmutableList.index` and `indexWith`: use `IndexedList.indexOf` and `indexOfWith`. `ImmutableList.indexFrom`, + `indexFromWith`, `lastIndexFrom` and `lastIndexFromWith`: use `IndexedList.indexOfRange`, `indexOfRangeWith`, + `lastIndexOfRange` and `lastIndexOfRangeWith`. `indexFrom index item list` is + `indexOfRange index (length list - index) item list`, and `lastIndexFrom index item list` is + `lastIndexOfRange index (index + 1) item list`. `index`, `indexWith`, `indexFrom` and `indexFromWith` returned a function + that took the list a second time, as in `index item list list`; the functions that replace them return the index. +* `ImmutableList.clear`: use `IndexedList.empty`, or `IIndexedList.clear` to keep the implementation of an interface value. +* `ImmutableList.skipUntil` and `takeUntil`: use `IndexedList.skipWhile` and `takeWhile` with the negated predicate. They + returned each other's result in 1.0.0: `skipUntil p list` returned what `takeWhile (not << p) list` returns, and + `takeUntil p list` returned what `skipWhile (not << p) list` returns. +* `ImmutableList.filterFold`: it has no replacement. +* The `IndexedSeq` module and the `IIndexedSeq<'T>` abbreviation of `IReadOnlyList<'T>`: use `IReadOnlyList<'T>`, whose + `Count` and indexer replace `IndexedSeq.length` and `IndexedSeq.item`. `IndexedSeq.check` needs no replacement, because + every function of the list modules throws `ArgumentNullException` for a `null` list. ## [1.0.0] - 2020-02-23 diff --git a/README.md b/README.md index 950057e1..239ef746 100644 --- a/README.md +++ b/README.md @@ -13,14 +13,14 @@ Please also join the [F# Open Source Group](http://fsharp.github.com) | Module | .NET type | Computation expression | |---|---|---| | `FlatList` | `ImmutableArray<'T>` | `flatList { }` | -| `ImmutableList` | `ImmutableList<'T>` | – | +| `IndexedList` | `ImmutableList<'T>` | `indexedList { }` | +| `IIndexedList` | `IImmutableList<'T>` | – | | `Stack` | `ImmutableStack<'T>` | – | | `Queue` | `ImmutableQueue<'T>` | – | | `HashMap` | `ImmutableDictionary<'Key, 'Value>` | – | | `SortedMap` | `ImmutableSortedDictionary<'Key, 'Value>` | – | | `HashSet` | `ImmutableHashSet<'T>` | – | | `SortedSet` | `ImmutableSortedSet<'T>` | – | -| `IIndexedSeq` | `IReadOnlyList<'T>` | – | [Documentation](https://fsprojects.github.io/FSharp.Collections.Immutable/) diff --git a/docsSrc/Explanations/Background.md b/docsSrc/Explanations/Background.md index 822820fa..21f275db 100644 --- a/docsSrc/Explanations/Background.md +++ b/docsSrc/Explanations/Background.md @@ -27,14 +27,14 @@ While F# has its own built-in immutable collections, System.Collections.Immutabl | Module | .NET type | Computation expression | |---|---|---| | `FlatList` | `ImmutableArray<'T>` | `flatList { }` | -| `ImmutableList` | `ImmutableList<'T>` | – | +| `IndexedList` | `ImmutableList<'T>` | `indexedList { }` | +| `IIndexedList` | `IImmutableList<'T>` | – | | `Stack` | `ImmutableStack<'T>` | – | | `Queue` | `ImmutableQueue<'T>` | – | | `HashMap` | `ImmutableDictionary<'Key, 'Value>` | – | | `SortedMap` | `ImmutableSortedDictionary<'Key, 'Value>` | – | | `HashSet` | `ImmutableHashSet<'T>` | – | | `SortedSet` | `ImmutableSortedSet<'T>` | – | -| `IIndexedSeq` | `IReadOnlyList<'T>` | – | --- diff --git a/docsSrc/Tutorials/Getting_Started.md b/docsSrc/Tutorials/Getting_Started.md index eea423e8..4d82dde2 100644 --- a/docsSrc/Tutorials/Getting_Started.md +++ b/docsSrc/Tutorials/Getting_Started.md @@ -22,7 +22,7 @@ open FSharp.Collections.Immutable ``` F# // Create an immutable list -let list1 = ImmutableList.ofSeq [1; 2; 3] +let list1 = IndexedList.ofSeq [1; 2; 3] // Create an immutable hash set let set1 = HashSet.ofSeq ["a"; "b"] diff --git a/docsSrc/index.md b/docsSrc/index.md index 276ad8fe..8fd0c476 100644 --- a/docsSrc/index.md +++ b/docsSrc/index.md @@ -9,14 +9,14 @@ F# bindings for [System.Collections.Immutable](https://learn.microsoft.com/en-us | Module | .NET type | Computation expression | |---|---|---| | `FlatList` | `ImmutableArray<'T>` | `flatList { }` | -| `ImmutableList` | `ImmutableList<'T>` | – | +| `IndexedList` | `ImmutableList<'T>` | `indexedList { }` | +| `IIndexedList` | `IImmutableList<'T>` | – | | `Stack` | `ImmutableStack<'T>` | – | | `Queue` | `ImmutableQueue<'T>` | – | | `HashMap` | `ImmutableDictionary<'Key, 'Value>` | – | | `SortedMap` | `ImmutableSortedDictionary<'Key, 'Value>` | – | | `HashSet` | `ImmutableHashSet<'T>` | – | | `SortedSet` | `ImmutableSortedSet<'T>` | – | -| `IIndexedSeq` | `IReadOnlyList<'T>` | – | --- From 1c8661140bfd6041c7b9f47a9a25ccbb2f6b686f Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Wed, 21 Oct 2026 03:10:23 +0200 Subject: [PATCH 4/4] refactor: the `Tuple` and `Option` modules of `IndexedList` and `IIndexedList` call the default modules by their short names Each of these files opens `FSharp.Collections.Immutable`, and inside `FSharp.Collections.Immutable.Tuple.IndexedList` the name `IndexedList` reaches the default module, also for the functions that both modules define, so the full namespace in front of it added nothing. The compiled code is the same: the opcodes and the called methods of every method of both builds match one to one. Co-Authored-By: Claude Sonnet 5.5 --- .../IndexedList/IIndexedListOption.fs | 38 ++++++---------- .../IndexedList/IIndexedListTuple.fs | 14 +++--- .../IndexedList/IndexedListOption.fs | 44 +++++++------------ .../IndexedList/IndexedListTuple.fs | 16 +++---- 4 files changed, 44 insertions(+), 68 deletions(-) diff --git a/src/FSharp.Collections.Immutable/IndexedList/IIndexedListOption.fs b/src/FSharp.Collections.Immutable/IndexedList/IIndexedListOption.fs index 39097b46..4663782c 100644 --- a/src/FSharp.Collections.Immutable/IndexedList/IIndexedListOption.fs +++ b/src/FSharp.Collections.Immutable/IndexedList/IIndexedListOption.fs @@ -9,33 +9,23 @@ open FSharp.Collections.Immutable // ---- Access ---- [] -let tryExactlyOne (list : IIndexedList<'T>) = - FSharp.Collections.Immutable.IIndexedList.tryExactlyOne list - |> ValueOption.toOption +let tryExactlyOne (list : IIndexedList<'T>) = IIndexedList.tryExactlyOne list |> ValueOption.toOption [] -let tryHead (list : IIndexedList<'T>) = - FSharp.Collections.Immutable.IIndexedList.tryHead list - |> ValueOption.toOption +let tryHead (list : IIndexedList<'T>) = IIndexedList.tryHead list |> ValueOption.toOption [] -let tryItem index (list : IIndexedList<'T>) = - FSharp.Collections.Immutable.IIndexedList.tryItem index list - |> ValueOption.toOption +let tryItem index (list : IIndexedList<'T>) = IIndexedList.tryItem index list |> ValueOption.toOption [] -let tryLast (list : IIndexedList<'T>) = - FSharp.Collections.Immutable.IIndexedList.tryLast list - |> ValueOption.toOption +let tryLast (list : IIndexedList<'T>) = IIndexedList.tryLast list |> ValueOption.toOption [] -let tryTail (list : IIndexedList<'T>) = - FSharp.Collections.Immutable.IIndexedList.tryTail list - |> ValueOption.toOption +let tryTail (list : IIndexedList<'T>) = IIndexedList.tryTail list |> ValueOption.toOption [] let tryUncons (list : IIndexedList<'T>) = - match FSharp.Collections.Immutable.IIndexedList.tryUncons list with + match IIndexedList.tryUncons list with | ValueSome (struct (head, tail)) -> Some (head, tail) | ValueNone -> None @@ -46,35 +36,33 @@ let (|Cons|_|) (list : IIndexedList<'T>) = tryUncons list [] let pick (chooser : 'T -> 'U option) (list : IIndexedList<'T>) = - FSharp.Collections.Immutable.IIndexedList.pick (fun item -> chooser item |> ValueOption.ofOption) list + IIndexedList.pick (fun item -> chooser item |> ValueOption.ofOption) list [] -let tryFind predicate (list : IIndexedList<'T>) = - FSharp.Collections.Immutable.IIndexedList.tryFind predicate list - |> ValueOption.toOption +let tryFind predicate (list : IIndexedList<'T>) = IIndexedList.tryFind predicate list |> ValueOption.toOption [] let tryFindBack predicate (list : IIndexedList<'T>) = - FSharp.Collections.Immutable.IIndexedList.tryFindBack predicate list + IIndexedList.tryFindBack predicate list |> ValueOption.toOption [] let tryFindIndex predicate (list : IIndexedList<'T>) = - FSharp.Collections.Immutable.IIndexedList.tryFindIndex predicate list + IIndexedList.tryFindIndex predicate list |> ValueOption.toOption [] let tryFindIndexBack predicate (list : IIndexedList<'T>) = - FSharp.Collections.Immutable.IIndexedList.tryFindIndexBack predicate list + IIndexedList.tryFindIndexBack predicate list |> ValueOption.toOption [] let tryPick (chooser : 'T -> 'U option) (list : IIndexedList<'T>) = - FSharp.Collections.Immutable.IIndexedList.tryPick (fun item -> chooser item |> ValueOption.ofOption) list + IIndexedList.tryPick (fun item -> chooser item |> ValueOption.ofOption) list |> ValueOption.toOption // ---- Transformation ---- [] let choose (chooser : 'T -> 'U option) (list : IIndexedList<'T>) = - FSharp.Collections.Immutable.IIndexedList.choose (fun item -> chooser item |> ValueOption.ofOption) list + IIndexedList.choose (fun item -> chooser item |> ValueOption.ofOption) list diff --git a/src/FSharp.Collections.Immutable/IndexedList/IIndexedListTuple.fs b/src/FSharp.Collections.Immutable/IndexedList/IIndexedListTuple.fs index 5206919d..d05f05fa 100644 --- a/src/FSharp.Collections.Immutable/IndexedList/IIndexedListTuple.fs +++ b/src/FSharp.Collections.Immutable/IndexedList/IIndexedListTuple.fs @@ -11,13 +11,13 @@ open FSharp.Collections.Immutable [] let tryUncons (list : IIndexedList<'T>) = - match FSharp.Collections.Immutable.IIndexedList.tryUncons list with + match IIndexedList.tryUncons list with | ValueSome (struct (head, tail)) -> ValueSome (head, tail) | ValueNone -> ValueNone [] let uncons (list : IIndexedList<'T>) = - let struct (head, tail) = FSharp.Collections.Immutable.IIndexedList.uncons list + let struct (head, tail) = IIndexedList.uncons list head, tail [] @@ -86,7 +86,7 @@ let mapFold (mapping : 'State -> 'T -> 'Result * 'State) (state : 'State) (list let result, next = mapping state item struct (result, next) - let struct (results, final) = FSharp.Collections.Immutable.IIndexedList.mapFold mapStruct state list + let struct (results, final) = IIndexedList.mapFold mapStruct state list results, final [] @@ -95,7 +95,7 @@ let mapFoldBack (mapping : 'T -> 'State -> 'Result * 'State) (list : IIndexedLis let result, next = mapping item state struct (result, next) - let struct (results, final) = FSharp.Collections.Immutable.IIndexedList.mapFoldBack mapStruct list state + let struct (results, final) = IIndexedList.mapFoldBack mapStruct list state results, final // ---- Partitioning and grouping ---- @@ -112,15 +112,15 @@ let groupBy (projection : 'T -> 'Key) (list : IIndexedList<'T>) = [] let partition predicate (list : IIndexedList<'T>) = - let struct (accepted, rejected) = FSharp.Collections.Immutable.IIndexedList.partition predicate list + let struct (accepted, rejected) = IIndexedList.partition predicate list accepted, rejected [] let partitionWith (partitioner : 'T -> Choice<'T1, 'T2>) (list : IIndexedList<'T>) = - let struct (first, second) = FSharp.Collections.Immutable.IIndexedList.partitionWith partitioner list + let struct (first, second) = IIndexedList.partitionWith partitioner list first, second [] let splitAt index (list : IIndexedList<'T>) = - let struct (before, after) = FSharp.Collections.Immutable.IIndexedList.splitAt index list + let struct (before, after) = IIndexedList.splitAt index list before, after diff --git a/src/FSharp.Collections.Immutable/IndexedList/IndexedListOption.fs b/src/FSharp.Collections.Immutable/IndexedList/IndexedListOption.fs index 63a37afd..d1dac722 100644 --- a/src/FSharp.Collections.Immutable/IndexedList/IndexedListOption.fs +++ b/src/FSharp.Collections.Immutable/IndexedList/IndexedListOption.fs @@ -15,38 +15,28 @@ let unfold (generator : 'State -> ('T * 'State) option) (state : 'State) = | Some (item, next) -> ValueSome (struct (item, next)) | None -> ValueNone - FSharp.Collections.Immutable.IndexedList.unfold generateStruct state + IndexedList.unfold generateStruct state // ---- Access ---- [] -let tryExactlyOne (list : IndexedList<'T>) = - FSharp.Collections.Immutable.IndexedList.tryExactlyOne list - |> ValueOption.toOption +let tryExactlyOne (list : IndexedList<'T>) = IndexedList.tryExactlyOne list |> ValueOption.toOption [] -let tryHead (list : IndexedList<'T>) = - FSharp.Collections.Immutable.IndexedList.tryHead list - |> ValueOption.toOption +let tryHead (list : IndexedList<'T>) = IndexedList.tryHead list |> ValueOption.toOption [] -let tryItem index (list : IndexedList<'T>) = - FSharp.Collections.Immutable.IndexedList.tryItem index list - |> ValueOption.toOption +let tryItem index (list : IndexedList<'T>) = IndexedList.tryItem index list |> ValueOption.toOption [] -let tryLast (list : IndexedList<'T>) = - FSharp.Collections.Immutable.IndexedList.tryLast list - |> ValueOption.toOption +let tryLast (list : IndexedList<'T>) = IndexedList.tryLast list |> ValueOption.toOption [] -let tryTail (list : IndexedList<'T>) = - FSharp.Collections.Immutable.IndexedList.tryTail list - |> ValueOption.toOption +let tryTail (list : IndexedList<'T>) = IndexedList.tryTail list |> ValueOption.toOption [] let tryUncons (list : IndexedList<'T>) = - match FSharp.Collections.Immutable.IndexedList.tryUncons list with + match IndexedList.tryUncons list with | ValueSome (struct (head, tail)) -> Some (head, tail) | ValueNone -> None @@ -57,45 +47,43 @@ let (|Cons|_|) (list : IndexedList<'T>) = tryUncons list [] let pick (chooser : 'T -> 'U option) (list : IndexedList<'T>) = - FSharp.Collections.Immutable.IndexedList.pick (fun item -> chooser item |> ValueOption.ofOption) list + IndexedList.pick (fun item -> chooser item |> ValueOption.ofOption) list [] -let tryFind predicate (list : IndexedList<'T>) = - FSharp.Collections.Immutable.IndexedList.tryFind predicate list - |> ValueOption.toOption +let tryFind predicate (list : IndexedList<'T>) = IndexedList.tryFind predicate list |> ValueOption.toOption [] let tryFindBack predicate (list : IndexedList<'T>) = - FSharp.Collections.Immutable.IndexedList.tryFindBack predicate list + IndexedList.tryFindBack predicate list |> ValueOption.toOption [] let tryFindIndex predicate (list : IndexedList<'T>) = - FSharp.Collections.Immutable.IndexedList.tryFindIndex predicate list + IndexedList.tryFindIndex predicate list |> ValueOption.toOption [] let tryFindIndexBack predicate (list : IndexedList<'T>) = - FSharp.Collections.Immutable.IndexedList.tryFindIndexBack predicate list + IndexedList.tryFindIndexBack predicate list |> ValueOption.toOption [] let tryFindIndexBackRange startIndex count predicate (list : IndexedList<'T>) = - FSharp.Collections.Immutable.IndexedList.tryFindIndexBackRange startIndex count predicate list + IndexedList.tryFindIndexBackRange startIndex count predicate list |> ValueOption.toOption [] let tryFindIndexRange startIndex count predicate (list : IndexedList<'T>) = - FSharp.Collections.Immutable.IndexedList.tryFindIndexRange startIndex count predicate list + IndexedList.tryFindIndexRange startIndex count predicate list |> ValueOption.toOption [] let tryPick (chooser : 'T -> 'U option) (list : IndexedList<'T>) = - FSharp.Collections.Immutable.IndexedList.tryPick (fun item -> chooser item |> ValueOption.ofOption) list + IndexedList.tryPick (fun item -> chooser item |> ValueOption.ofOption) list |> ValueOption.toOption // ---- Transformation ---- [] let choose (chooser : 'T -> 'U option) (list : IndexedList<'T>) = - FSharp.Collections.Immutable.IndexedList.choose (fun item -> chooser item |> ValueOption.ofOption) list + IndexedList.choose (fun item -> chooser item |> ValueOption.ofOption) list diff --git a/src/FSharp.Collections.Immutable/IndexedList/IndexedListTuple.fs b/src/FSharp.Collections.Immutable/IndexedList/IndexedListTuple.fs index 525a2f5e..0a1e79ae 100644 --- a/src/FSharp.Collections.Immutable/IndexedList/IndexedListTuple.fs +++ b/src/FSharp.Collections.Immutable/IndexedList/IndexedListTuple.fs @@ -16,19 +16,19 @@ let unfold (generator : 'State -> ('T * 'State) voption) (state : 'State) = | ValueSome (item, next) -> ValueSome (struct (item, next)) | ValueNone -> ValueNone - FSharp.Collections.Immutable.IndexedList.unfold generateStruct state + IndexedList.unfold generateStruct state // ---- Access ---- [] let tryUncons (list : IndexedList<'T>) = - match FSharp.Collections.Immutable.IndexedList.tryUncons list with + match IndexedList.tryUncons list with | ValueSome (struct (head, tail)) -> ValueSome (head, tail) | ValueNone -> ValueNone [] let uncons (list : IndexedList<'T>) = - let struct (head, tail) = FSharp.Collections.Immutable.IndexedList.uncons list + let struct (head, tail) = IndexedList.uncons list head, tail [] @@ -97,7 +97,7 @@ let mapFold (mapping : 'State -> 'T -> 'Result * 'State) (state : 'State) (list let result, next = mapping state item struct (result, next) - let struct (results, final) = FSharp.Collections.Immutable.IndexedList.mapFold mapStruct state list + let struct (results, final) = IndexedList.mapFold mapStruct state list results, final [] @@ -106,7 +106,7 @@ let mapFoldBack (mapping : 'T -> 'State -> 'Result * 'State) (list : IndexedList let result, next = mapping item state struct (result, next) - let struct (results, final) = FSharp.Collections.Immutable.IndexedList.mapFoldBack mapStruct list state + let struct (results, final) = IndexedList.mapFoldBack mapStruct list state results, final // ---- Partitioning and grouping ---- @@ -123,15 +123,15 @@ let groupBy (projection : 'T -> 'Key) (list : IndexedList<'T>) = [] let partition predicate (list : IndexedList<'T>) = - let struct (accepted, rejected) = FSharp.Collections.Immutable.IndexedList.partition predicate list + let struct (accepted, rejected) = IndexedList.partition predicate list accepted, rejected [] let partitionWith (partitioner : 'T -> Choice<'T1, 'T2>) (list : IndexedList<'T>) = - let struct (first, second) = FSharp.Collections.Immutable.IndexedList.partitionWith partitioner list + let struct (first, second) = IndexedList.partitionWith partitioner list first, second [] let splitAt index (list : IndexedList<'T>) = - let struct (before, after) = FSharp.Collections.Immutable.IndexedList.splitAt index list + let struct (before, after) = IndexedList.splitAt index list before, after