diff --git a/CHANGELOG.md b/CHANGELOG.md index 029a7817..ce315795 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `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. +* `Stack` has every function of the FSharp.Core `List` module that applies to an immutable stack, with its semantics and + exceptions and with stable sorts, plus `consMany`, `ofReadOnlySpan`, `ofSpan`, `skipLast`, `sortWithComparer`, + `takeLast`, `tryTail`, `tryUncons` and `uncons`. A stack enumerates its elements from the top, and every function + treats it as a list in that order; `rev` puts the last element on top. Its functions name their stacks `stack`, + `stack1` and `stacks` where `List` names its lists, and the exceptions report these names. `consMany`, `uncons`, + `tryHead`, `tryTail` and `tryUncons` compile to `PushMany`, `PeekPop`, `TryPeek`, `TryPop` and `TryPeekPop`, in the + `IStack`, `Option` and `Tuple` modules too. 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 would fail with + `InvalidOperationException` while the function enumerates it; the `IStack` module and the `stack { }` expression do the + same. +* The `IStack` module has the functions of `Stack` for `IImmutableStack<'T>`, without the creation functions, plus + `clear`. Its functions keep the implementation of the input stack where the result has the element type of the input, + and return a `Stack` otherwise. `append` pushes the first stack onto the second one, which receives the elements, so + its result keeps the implementation and the nodes of the second stack, as `List.append` shares its second list. +* The `FSharp.Collections.Immutable.Option` and `FSharp.Collections.Immutable.Tuple` namespaces have `Stack` and `IStack` + modules that repeat the functions that use value options or struct tuples with options and reference tuples. +* `stack { }` builds a stack whose first element is on top. ### Changed @@ -124,6 +141,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * `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. +* `Stack`: `ofSeq` and `ofArray` put the first element on top, so that the stack enumerates the elements in the order of + the input. They wrapped `ImmutableStack.CreateRange`, which puts the last element on top. A `null` input throws + `ArgumentNullException` for `source` in `ofSeq` and for `array` in `ofArray`, where 1.0.0 named both parameters `items`. + A default `FlatList` given as a sequence to `ofSeq` throws the same exception as a `null` sequence, where it threw + `InvalidOperationException`. +* `Stack`: `cons`, `head` and `tail` take and return `ImmutableStack<'T>`, where they took and returned + `IImmutableStack<'T>`; the `IStack` module has them for the interface. They compile to `Push`, `Peek` and `Pop`, where + 1.0.0 compiled them under their F# names. +* `Stack`: `head` and `tail` of an empty stack throw the `ArgumentException` of `List.head` and `List.tail`, where they + threw the `InvalidOperationException` of `ImmutableStack`. +* `Stack`: the complete active pattern `Cons | Nil`, which matched a reference tuple, is replaced by the struct partial + active patterns `Cons`, which matches a struct tuple, and `Nil`. ### Removed @@ -161,6 +190,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * 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. +* `Stack.push`, `peek` and `pop`: use `cons`, `head` and `uncons`. `pop` returned the top and the stack below it as a + reference tuple; `uncons` returns a struct tuple. ## [1.0.0] - 2020-02-23 diff --git a/README.md b/README.md index 239ef746..a11bc426 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,8 @@ Please also join the [F# Open Source Group](http://fsharp.github.com) | `FlatList` | `ImmutableArray<'T>` | `flatList { }` | | `IndexedList` | `ImmutableList<'T>` | `indexedList { }` | | `IIndexedList` | `IImmutableList<'T>` | – | -| `Stack` | `ImmutableStack<'T>` | – | +| `Stack` | `ImmutableStack<'T>` | `stack { }` | +| `IStack` | `IImmutableStack<'T>` | – | | `Queue` | `ImmutableQueue<'T>` | – | | `HashMap` | `ImmutableDictionary<'Key, 'Value>` | – | | `SortedMap` | `ImmutableSortedDictionary<'Key, 'Value>` | – | diff --git a/docsSrc/Explanations/Background.md b/docsSrc/Explanations/Background.md index 21f275db..880dbc42 100644 --- a/docsSrc/Explanations/Background.md +++ b/docsSrc/Explanations/Background.md @@ -29,7 +29,8 @@ While F# has its own built-in immutable collections, System.Collections.Immutabl | `FlatList` | `ImmutableArray<'T>` | `flatList { }` | | `IndexedList` | `ImmutableList<'T>` | `indexedList { }` | | `IIndexedList` | `IImmutableList<'T>` | – | -| `Stack` | `ImmutableStack<'T>` | – | +| `Stack` | `ImmutableStack<'T>` | `stack { }` | +| `IStack` | `IImmutableStack<'T>` | – | | `Queue` | `ImmutableQueue<'T>` | – | | `HashMap` | `ImmutableDictionary<'Key, 'Value>` | – | | `SortedMap` | `ImmutableSortedDictionary<'Key, 'Value>` | – | diff --git a/docsSrc/index.md b/docsSrc/index.md index 8fd0c476..7ab3147d 100644 --- a/docsSrc/index.md +++ b/docsSrc/index.md @@ -11,7 +11,8 @@ F# bindings for [System.Collections.Immutable](https://learn.microsoft.com/en-us | `FlatList` | `ImmutableArray<'T>` | `flatList { }` | | `IndexedList` | `ImmutableList<'T>` | `indexedList { }` | | `IIndexedList` | `IImmutableList<'T>` | – | -| `Stack` | `ImmutableStack<'T>` | – | +| `Stack` | `ImmutableStack<'T>` | `stack { }` | +| `IStack` | `IImmutableStack<'T>` | – | | `Queue` | `ImmutableQueue<'T>` | – | | `HashMap` | `ImmutableDictionary<'Key, 'Value>` | – | | `SortedMap` | `ImmutableSortedDictionary<'Key, 'Value>` | – | diff --git a/src/FSharp.Collections.Immutable/ComputationExpressions.fs b/src/FSharp.Collections.Immutable/ComputationExpressions.fs index 972f77ec..4ad50f4a 100644 --- a/src/FSharp.Collections.Immutable/ComputationExpressions.fs +++ b/src/FSharp.Collections.Immutable/ComputationExpressions.fs @@ -6,3 +6,5 @@ module FSharp.Collections.Immutable.ComputationExpressions let flatList = FlatListCE () let indexedList = IndexedListCE () + +let stack = StackCE () diff --git a/src/FSharp.Collections.Immutable/ComputationExpressions.fsi b/src/FSharp.Collections.Immutable/ComputationExpressions.fsi index e588c81b..dbfa0803 100644 --- a/src/FSharp.Collections.Immutable/ComputationExpressions.fsi +++ b/src/FSharp.Collections.Immutable/ComputationExpressions.fsi @@ -33,3 +33,19 @@ val flatList : FlatListCE /// Evaluates to an indexed list of 0, 1, 4, 9, 100 and 200. /// val indexedList : IndexedListCE + +/// +/// Builds a stack, with the first element of the expression on top. +/// +/// +/// +/// let squares = stack { +/// yield 0 +/// for i in 1..3 do +/// yield i * i +/// yield! [ 100; 200 ] +/// } +/// +/// Evaluates to a stack of 0, 1, 4, 9, 100 and 200, with 0 on top. +/// +val stack : StackCE diff --git a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj index f66316ea..73aa6463 100644 --- a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj +++ b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj @@ -40,6 +40,7 @@ + @@ -68,7 +69,21 @@ - + + + + + + + + + + + + + + + diff --git a/src/FSharp.Collections.Immutable/Helper/StackConstruction.fs b/src/FSharp.Collections.Immutable/Helper/StackConstruction.fs new file mode 100644 index 00000000..90fdf63d --- /dev/null +++ b/src/FSharp.Collections.Immutable/Helper/StackConstruction.fs @@ -0,0 +1,46 @@ +/// +/// The construction of the stacks that the stack modules and the stack { } expression return. +/// +/// +/// +/// pushes the elements in order and leaves the last element on top. The stack modules keep the first element on top +/// instead, so that a stack enumerates its elements in the order in which it was created (ADR 0001, D5). The module serves +/// the stack modules, their Tuple counterparts and the computation expression, so that they build their stacks with +/// one implementation. +/// +module internal FSharp.Collections.Immutable.StackConstruction + +open System.Collections.Immutable + +/// +/// Pushes the elements of an array onto a stack, from the last element to the first one. +/// +/// The elements; neither checked for nor kept. +/// +/// The stack to push the elements onto, an or an +/// . +/// +/// A stack of the elements of the array, the first one on top, followed by the elements of the stack. +/// +/// O(m), where m is the length of the array. The result shares the nodes of the stack and keeps its implementation. +/// +let inline pushArray (items : 'T array) (stack : ^Stack) : ^Stack when ^Stack : (member Push : 'T -> ^Stack) = + // The constraint serves the concrete stack and the interface with one loop: each Push returns the type it is called + // on, so the result keeps the implementation of the stack + let mutable result = stack + + // Pushing from the last element leaves the first one on top, so that the result enumerates the array in its order + for index = items.Length - 1 downto 0 do + result <- (^Stack : (member Push : 'T -> ^Stack) (result, items[index])) + + result + +/// +/// A stack whose top is the first element of an array, which enumerates the elements in the order of the array. +/// +/// The elements; neither checked for nor kept. +/// The stack of the elements. +/// +/// O(n). The function pushes the elements onto the empty stack from the last one to the first one. +/// +let ofArrayUnchecked (array : 'T array) : ImmutableStack<'T> = pushArray array ImmutableStack<'T>.Empty diff --git a/src/FSharp.Collections.Immutable/Stack.fs b/src/FSharp.Collections.Immutable/Stack.fs deleted file mode 100644 index 774ad6b5..00000000 --- a/src/FSharp.Collections.Immutable/Stack.fs +++ /dev/null @@ -1,52 +0,0 @@ -namespace FSharp.Collections.Immutable - -type IStack<'T> = System.Collections.Immutable.IImmutableStack<'T> - -type Stack<'T> = System.Collections.Immutable.ImmutableStack<'T> - -[] -module Stack = - type internal StackFactory = System.Collections.Immutable.ImmutableStack - - let inline internal check (stack : IStack<_>) = checkNotNull (nameof stack) stack - - let inline empty<'T> = StackFactory.Create<'T>() - - let inline ofSeq source = StackFactory.CreateRange source - - let inline ofArray (array : 'T[]) : Stack<'T> = ofSeq array - - let inline toSeq (stack : IStack<_>) = stack :> seq<_> - - let push head stack : IStack<'T> = - check stack - stack.Push head - - let cons head stack = push head stack - - let peek stack = - check stack - stack.Peek () - - let head stack = peek stack - - let tail stack : IStack<_> = - check stack - stack.Pop () - - let pop stack = - check stack - stack.Peek (), tail stack - - let (|Cons|Nil|) stack = - check stack - - if stack.IsEmpty then - Nil - else - Cons (stack.Peek (), stack.Pop ()) - -///////////// -module ImmutableStack = Stack diff --git a/src/FSharp.Collections.Immutable/Stack/IStack.fs b/src/FSharp.Collections.Immutable/Stack/IStack.fs new file mode 100644 index 00000000..ec72f51e --- /dev/null +++ b/src/FSharp.Collections.Immutable/Stack/IStack.fs @@ -0,0 +1,991 @@ +[] +module FSharp.Collections.Immutable.IStack + +// System.Collections.Generic stays closed: its Stack<'T> would shadow the Stack<'T> abbreviation of this library +open System +open System.Collections.Immutable +open System.Linq +open FSharp.Collections.Immutable.StackConstruction + +// The interface has the members Clear, Push, Peek and Pop of the stacks. A result with the element type of the input keeps +// the implementation of the input: it comes from Pop, or from Clear followed by Push from the last element of a snapshot to +// the first one. A result of another element type is an ImmutableStack in the same order. The checks of ListChecks throw +// what the List functions throw, with the names of the parameters of this module (ADR 0001, D5 and D6); Lengths.stackUpTo +// counts a stack only as far as a check needs it. + +/// A stack of the implementation of the input with the given elements, the first one on top. +let private rebuild (stack : IStack<'T>) (items : 'T array) = pushArray items (stack.Clear ()) + +/// The stack below the first elements; the caller checks that the stack has them. +let private dropTop (count : int) (stack : IStack<'T>) = + let mutable node = stack + + for _ in 1..count do + node <- node.Pop () + + node + +/// The first elements in an array and the stack below them; the caller checks that the stack has them. +let private splitTop (count : int) (stack : IStack<'T>) = + let top = Array.zeroCreate count + let mutable node = stack + + for index = 0 to count - 1 do + top[index] <- node.Peek () + node <- node.Pop () + + struct (top, node) + +/// The index and the first element that satisfies the predicate, or ValueNone. +let private firstMatch (predicate : 'T -> bool) (stack : IStack<'T>) = + use enumerator = stack.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; List searches a copy from the end too. +let private lastMatch (predicate : 'T -> bool) (stack : IStack<'T>) = + let array = Snapshot.ofSeq (nameof stack) stack + 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) (stack : IStack<'T>) = + use enumerator = stack.GetEnumerator () + let mutable result = ValueNone + + while result.IsNone && enumerator.MoveNext () do + result <- chooser enumerator.Current + + result + +// ---- Properties ---- + +[] +let isEmpty (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + stack.IsEmpty + +[] +let length (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + // The interface does not tell the length + Lengths.stackUpTo Int32.MaxValue stack + +// ---- Access ---- + +[] +let exactlyOne (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + // List.exactlyOne reports its input under the name "source"; two elements tell a longer stack apart + ListChecks.exactlyOne "source" (Lengths.stackUpTo 2 stack) + stack.Peek () + +[] +let head (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + ListChecks.notEmpty (nameof stack) (Lengths.stackUpTo 1 stack) + stack.Peek () + +[] +let last (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + ListChecks.notEmpty (nameof stack) (Lengths.stackUpTo 1 stack) + Enumerable.Last stack + +[] +let tail (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + ListChecks.notEmpty (nameof stack) (Lengths.stackUpTo 1 stack) + stack.Pop () + +[] +let tryExactlyOne (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + + if Lengths.stackUpTo 2 stack = 1 then + ValueSome (stack.Peek ()) + else + ValueNone + +[] +let tryHead (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + if stack.IsEmpty then + ValueNone + else + ValueSome (stack.Peek ()) + +[] +let tryLast (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + + if stack.IsEmpty then + ValueNone + else + ValueSome (Enumerable.Last stack) + +[] +let tryTail (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + if stack.IsEmpty then + ValueNone + else + ValueSome (stack.Pop ()) + +[] +let tryUncons (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + + if stack.IsEmpty then + ValueNone + else + ValueSome (struct (stack.Peek (), stack.Pop ())) + +[] +let uncons (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + // An empty stack throws what List.head throws + ListChecks.notEmpty (nameof stack) (Lengths.stackUpTo 1 stack) + struct (stack.Peek (), stack.Pop ()) + +[] +[] +let (|Cons|_|) (stack : IStack<'T>) = tryUncons stack + +[] +let (|Nil|_|) (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + stack.IsEmpty + +// ---- Search ---- + +[] +let contains (value : 'T) (source : IStack<'T>) = + checkNotNull (nameof source) source + // F# equality with the value on the left, as List.contains compares + (firstMatch (fun item -> value = item) source).IsSome + +[] +let exists (predicate : 'T -> bool) (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + (firstMatch predicate stack).IsSome + +[] +let exists2 predicate (stack1 : IStack<'T1>) (stack2 : IStack<'T2>) = + ListArrays.exists2 (nameof stack2) predicate (Snapshot.ofSeq (nameof stack1) stack1) (Snapshot.ofSeq (nameof stack2) stack2) + +[] +let find (predicate : 'T -> bool) (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + + match firstMatch predicate stack with + | ValueSome (struct (_, item)) -> item + | ValueNone -> Raise.indexNotFound () + +[] +let findBack (predicate : 'T -> bool) (stack : IStack<'T>) = + match lastMatch predicate stack with + | ValueSome (struct (_, item)) -> item + | ValueNone -> Raise.indexNotFound () + +[] +let findIndex (predicate : 'T -> bool) (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + + match firstMatch predicate stack with + | ValueSome (struct (index, _)) -> index + | ValueNone -> Raise.indexNotFound () + +[] +let findIndexBack (predicate : 'T -> bool) (stack : IStack<'T>) = + match lastMatch predicate stack with + | ValueSome (struct (index, _)) -> index + | ValueNone -> Raise.indexNotFound () + +[] +let forall (predicate : 'T -> bool) (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + (firstMatch (fun item -> not (predicate item)) stack).IsNone + +[] +let forall2 predicate (stack1 : IStack<'T1>) (stack2 : IStack<'T2>) = + ListArrays.forall2 + (nameof stack1) + (nameof stack2) + predicate + (Snapshot.ofSeq (nameof stack1) stack1) + (Snapshot.ofSeq (nameof stack2) stack2) + +[] +let pick (chooser : 'T -> 'U voption) (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + + match firstChoice chooser stack with + | ValueSome value -> value + | ValueNone -> Raise.indexNotFound () + +[] +let tryFind (predicate : 'T -> bool) (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + + match firstMatch predicate stack with + | ValueSome (struct (_, item)) -> ValueSome item + | ValueNone -> ValueNone + +[] +let tryFindBack (predicate : 'T -> bool) (stack : IStack<'T>) = + match lastMatch predicate stack with + | ValueSome (struct (_, item)) -> ValueSome item + | ValueNone -> ValueNone + +[] +let tryFindIndex (predicate : 'T -> bool) (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + + match firstMatch predicate stack with + | ValueSome (struct (index, _)) -> ValueSome index + | ValueNone -> ValueNone + +[] +let tryFindIndexBack (predicate : 'T -> bool) (stack : IStack<'T>) = + match lastMatch predicate stack with + | ValueSome (struct (index, _)) -> ValueSome index + | ValueNone -> ValueNone + +[] +let tryPick (chooser : 'T -> 'U voption) (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + firstChoice chooser stack + +// ---- Transformation ---- + +[] +let allPairs (stack1 : IStack<'T1>) (stack2 : IStack<'T2>) = + Arrays.allPairs structPair (Snapshot.ofSeq (nameof stack1) stack1) (Snapshot.ofSeq (nameof stack2) stack2) + |> ofArrayUnchecked + +[] +let append (stack1 : IStack<'T>) (stack2 : IStack<'T>) = + checkNotNull (nameof stack1) stack1 + checkNotNull (nameof stack2) stack2 + + if stack1.IsEmpty then + stack2 + else + // The elements of the first stack go on top of the second one, whose implementation and nodes the result keeps + pushArray (Snapshot.ofSeq (nameof stack1) stack1) stack2 + +[] +let choose (chooser : 'T -> 'U voption) (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + let chosen = ResizeArray<'U>() + + for item in stack do + match chooser item with + | ValueSome value -> chosen.Add value + | ValueNone -> () + + ofArrayUnchecked (chosen.ToArray ()) + +[] +let chunkBySize chunkSize (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + ListChecks.positive (nameof chunkSize) chunkSize + + Array.chunkBySize chunkSize (Snapshot.ofSeq (nameof stack) stack) + |> Array.map ofArrayUnchecked + |> ofArrayUnchecked + +[] +let collect (mapping : 'T -> IStack<'U>) (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + let collected = ResizeArray<'U>() + + for item in stack do + collected.AddRange (mapping item) + + ofArrayUnchecked (collected.ToArray ()) + +[] +let concat (stacks : seq>) = + checkNotNullOrDefaultSeq (nameof stacks) stacks + let concatenated = ResizeArray<'T>() + + for stack in stacks do + checkNotNull (nameof stacks) stack + concatenated.AddRange stack + + ofArrayUnchecked (concatenated.ToArray ()) + +[] +let cons (value : 'T) (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + stack.Push value + +[] +let consMany (values : seq<'T>) (stack : IStack<'T>) = + checkNotNullOrDefaultSeq (nameof values) values + checkNotNull (nameof stack) stack + pushArray (Snapshot.ofSeq (nameof values) values) stack + +[] +let filter (predicate : 'T -> bool) (stack : IStack<'T>) = + let array = Snapshot.ofSeq (nameof stack) stack + rebuild stack (Array.filter predicate array) + +[] +let indexed (stack : IStack<'T>) = + Arrays.indexed structPair (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let map (mapping : 'T -> 'U) (stack : IStack<'T>) = + Array.map mapping (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let map2 (mapping : 'T1 -> 'T2 -> 'U) (stack1 : IStack<'T1>) (stack2 : IStack<'T2>) = + ListArrays.map2 + (nameof stack1) + (nameof stack2) + mapping + (Snapshot.ofSeq (nameof stack1) stack1) + (Snapshot.ofSeq (nameof stack2) stack2) + |> ofArrayUnchecked + +[] +let map3 (mapping : 'T1 -> 'T2 -> 'T3 -> 'U) (stack1 : IStack<'T1>) (stack2 : IStack<'T2>) (stack3 : IStack<'T3>) = + ListArrays.map3 + (nameof stack1) + (nameof stack2) + (nameof stack3) + mapping + (Snapshot.ofSeq (nameof stack1) stack1) + (Snapshot.ofSeq (nameof stack2) stack2) + (Snapshot.ofSeq (nameof stack3) stack3) + |> ofArrayUnchecked + +[] +let mapi (mapping : int -> 'T -> 'U) (stack : IStack<'T>) = + Array.mapi mapping (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let mapi2 (mapping : int -> 'T1 -> 'T2 -> 'U) (stack1 : IStack<'T1>) (stack2 : IStack<'T2>) = + ListArrays.mapi2 + (nameof stack1) + (nameof stack2) + mapping + (Snapshot.ofSeq (nameof stack1) stack1) + (Snapshot.ofSeq (nameof stack2) stack2) + |> ofArrayUnchecked + +[] +let pairwise (stack : IStack<'T>) = + Arrays.pairwise structPair (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let permute indexMap (stack : IStack<'T>) = + // List.permute applies the algorithm of Array.permute to a copy of the list, with the same checks + let array = Snapshot.ofSeq (nameof stack) stack + rebuild stack (Array.permute indexMap array) + +[] +let rev (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + + if Lengths.stackUpTo 2 stack <= 1 then + stack + else + // Pushing the elements in their order leaves the last one on top + let mutable reversed = stack.Clear () + + for item in stack do + reversed <- reversed.Push item + + reversed + +[] +let transpose (stacks : seq>) = + checkNotNullOrDefaultSeq (nameof stacks) stacks + + let arrays = + stacks + |> Seq.map (fun stack -> Snapshot.ofSeq (nameof stacks) stack) + |> Seq.toArray + + // List.transpose names the lists "list.[0]", "list.[1]", …, which are the stacks "stack.[0]", "stack.[1]", … here; + // once their lengths agree, Array.transpose agrees with it + ListChecks.transpose "stack" (arrays |> Array.map _.Length) + + Array.transpose arrays + |> Array.map ofArrayUnchecked + |> ofArrayUnchecked + +[] +let unzip (stack : IStack) = + let struct (first, second) = Arrays.unzip id (Snapshot.ofSeq (nameof stack) stack) + struct (ofArrayUnchecked first, ofArrayUnchecked second) + +[] +let unzip3 (stack : IStack) = + let struct (first, second, third) = Arrays.unzip3 id (Snapshot.ofSeq (nameof stack) stack) + struct (ofArrayUnchecked first, ofArrayUnchecked second, ofArrayUnchecked third) + +[] +let where (predicate : 'T -> bool) (stack : IStack<'T>) = + let array = Snapshot.ofSeq (nameof stack) stack + rebuild stack (Array.filter predicate array) + +[] +let windowed windowSize (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + ListChecks.positive (nameof windowSize) windowSize + + Array.windowed windowSize (Snapshot.ofSeq (nameof stack) stack) + |> Array.map ofArrayUnchecked + |> ofArrayUnchecked + +[] +let zip (stack1 : IStack<'T1>) (stack2 : IStack<'T2>) = + let array1 = Snapshot.ofSeq (nameof stack1) stack1 + let array2 = Snapshot.ofSeq (nameof stack2) stack2 + ListChecks.sameLengths (nameof stack1) array1.Length (nameof stack2) array2.Length + Arrays.zip structPair array1 array2 |> ofArrayUnchecked + +[] +let zip3 (stack1 : IStack<'T1>) (stack2 : IStack<'T2>) (stack3 : IStack<'T3>) = + let array1 = Snapshot.ofSeq (nameof stack1) stack1 + let array2 = Snapshot.ofSeq (nameof stack2) stack2 + let array3 = Snapshot.ofSeq (nameof stack3) stack3 + ListChecks.zip3 (nameof stack1) array1.Length (nameof stack2) array2.Length (nameof stack3) array3.Length + + Arrays.zip3 structTriple array1 array2 array3 + |> ofArrayUnchecked + +// ---- Folding ---- + +// The inline functions can use public members only, so they check the stack themselves; the empty stack throws what +// List.average and List.averageBy throw, with the name "source" +[] +let inline average (stack : IStack< ^T >) : ^T = + if obj.ReferenceEquals (stack, null) then + nullArg (nameof stack) + + if stack.IsEmpty then + invalidArg "source" ErrorStrings.InputSequenceEmpty + + let mutable sum = LanguagePrimitives.GenericZero< ^T> + let mutable count = 0 + + for item in stack do + sum <- Checked.(+) sum item + count <- count + 1 + + LanguagePrimitives.DivideByInt< ^T> sum count + +[] +let inline averageBy ([] projection : 'T -> ^U) (stack : IStack<'T>) : ^U = + if obj.ReferenceEquals (stack, null) then + nullArg (nameof stack) + + if stack.IsEmpty then + invalidArg "source" ErrorStrings.InputSequenceEmpty + + let mutable sum = LanguagePrimitives.GenericZero< ^U> + let mutable count = 0 + + for item in stack do + sum <- Checked.(+) sum (projection item) + count <- count + 1 + + LanguagePrimitives.DivideByInt< ^U> sum count + +[] +let compareWith (comparer : 'T -> 'T -> int) (stack1 : IStack<'T>) (stack2 : IStack<'T>) = + // Array.compareWith compares as List.compareWith does: the first difference, then the lengths + Array.compareWith comparer (Snapshot.ofSeq (nameof stack1) stack1) (Snapshot.ofSeq (nameof stack2) stack2) + +[] +let fold (folder : 'State -> 'T -> 'State) (state : 'State) (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + let mutable state = state + + for item in stack do + state <- folder state item + + state + +[] +let fold2 (folder : 'State -> 'T1 -> 'T2 -> 'State) (state : 'State) (stack1 : IStack<'T1>) (stack2 : IStack<'T2>) = + ListArrays.fold2 + (nameof stack1) + (nameof stack2) + folder + state + (Snapshot.ofSeq (nameof stack1) stack1) + (Snapshot.ofSeq (nameof stack2) stack2) + +[] +let foldBack (folder : 'T -> 'State -> 'State) (stack : IStack<'T>) (state : 'State) = + Array.foldBack folder (Snapshot.ofSeq (nameof stack) stack) state + +[] +let foldBack2 (folder : 'T1 -> 'T2 -> 'State -> 'State) (stack1 : IStack<'T1>) (stack2 : IStack<'T2>) (state : 'State) = + let array1 = Snapshot.ofSeq (nameof stack1) stack1 + let array2 = Snapshot.ofSeq (nameof stack2) stack2 + // List.foldBack2 checks the lengths before it calls the folder + ListChecks.foldBack2 (nameof stack1) array1.Length (nameof stack2) array2.Length + Array.foldBack2 folder array1 array2 state + +[] +let iter (action : 'T -> unit) (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + + for item in stack do + action item + +[] +let iter2 (action : 'T1 -> 'T2 -> unit) (stack1 : IStack<'T1>) (stack2 : IStack<'T2>) = + ListArrays.iter2 + (nameof stack1) + (nameof stack2) + action + (Snapshot.ofSeq (nameof stack1) stack1) + (Snapshot.ofSeq (nameof stack2) stack2) + +[] +let iteri (action : int -> 'T -> unit) (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + let mutable index = 0 + + for item in stack do + action index item + index <- index + 1 + +[] +let iteri2 (action : int -> 'T1 -> 'T2 -> unit) (stack1 : IStack<'T1>) (stack2 : IStack<'T2>) = + ListArrays.iteri2 + (nameof stack1) + (nameof stack2) + action + (Snapshot.ofSeq (nameof stack1) stack1) + (Snapshot.ofSeq (nameof stack2) stack2) + +[] +let mapFold (mapping : 'State -> 'T -> struct ('Result * 'State)) (state : 'State) (stack : IStack<'T>) = + let source = Snapshot.ofSeq (nameof stack) stack + 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 (ofArrayUnchecked results, state) + +[] +let mapFoldBack (mapping : 'T -> 'State -> struct ('Result * 'State)) (stack : IStack<'T>) (state : 'State) = + let source = Snapshot.ofSeq (nameof stack) stack + 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 (ofArrayUnchecked results, state) + +// Seq.max and the others compare as the List functions do once the stack has elements, so the List checks come first + +[] +let max (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + ListChecks.notEmptySequence (nameof stack) (Lengths.stackUpTo 1 stack) + Seq.max stack + +[] +let maxBy (projection : 'T -> 'U) (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + ListChecks.notEmptySequence (nameof stack) (Lengths.stackUpTo 1 stack) + Seq.maxBy projection stack + +[] +let min (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + ListChecks.notEmptySequence (nameof stack) (Lengths.stackUpTo 1 stack) + Seq.min stack + +[] +let minBy (projection : 'T -> 'U) (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + ListChecks.notEmptySequence (nameof stack) (Lengths.stackUpTo 1 stack) + Seq.minBy projection stack + +[] +let reduce (reduction : 'T -> 'T -> 'T) (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + ListChecks.notEmpty (nameof stack) (Lengths.stackUpTo 1 stack) + Seq.reduce reduction stack + +[] +let reduceBack (reduction : 'T -> 'T -> 'T) (stack : IStack<'T>) = + let array = Snapshot.ofSeq (nameof stack) stack + ListChecks.notEmpty (nameof stack) array.Length + Array.reduceBack reduction array + +[] +let scan (folder : 'State -> 'T -> 'State) (state : 'State) (stack : IStack<'T>) = + Array.scan folder state (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let scanBack (folder : 'T -> 'State -> 'State) (stack : IStack<'T>) (state : 'State) = + Array.scanBack folder (Snapshot.ofSeq (nameof stack) stack) state + |> ofArrayUnchecked + +[] +let inline sum (stack : IStack< ^T >) : ^T = + if obj.ReferenceEquals (stack, null) then + nullArg (nameof stack) + + let mutable sum = LanguagePrimitives.GenericZero< ^T> + + for item in stack do + sum <- Checked.(+) sum item + + sum + +[] +let inline sumBy ([] projection : 'T -> ^U) (stack : IStack<'T>) : ^U = + if obj.ReferenceEquals (stack, null) then + nullArg (nameof stack) + + let mutable sum = LanguagePrimitives.GenericZero< ^U> + + for item in stack do + sum <- Checked.(+) sum (projection item) + + sum + +// ---- Partitioning and grouping ---- + +[] +let countBy (projection : 'T -> 'Key) (stack : IStack<'T>) = + Arrays.countBy structPair projection (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let distinct (stack : IStack<'T>) = + let array = Snapshot.ofSeq (nameof stack) stack + rebuild stack (Array.distinct array) + +[] +let distinctBy (projection : 'T -> 'Key) (stack : IStack<'T>) = + let array = Snapshot.ofSeq (nameof stack) stack + rebuild stack (Array.distinctBy projection array) + +[] +let except (itemsToExclude : seq<'T>) (stack : IStack<'T>) = + // List.except checks the items to exclude first and does not read them for an empty list + checkNotNullOrDefaultSeq (nameof itemsToExclude) itemsToExclude + checkNotNull (nameof stack) stack + + if stack.IsEmpty then + stack + else + rebuild stack (Array.except itemsToExclude (Snapshot.ofSeq (nameof stack) stack)) + +[] +let groupBy (projection : 'T -> 'Key) (stack : IStack<'T>) = + Arrays.groupBy (fun key items -> struct (key, ofArrayUnchecked items)) projection (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let partition (predicate : 'T -> bool) (stack : IStack<'T>) = + let accepted, rejected = Array.partition predicate (Snapshot.ofSeq (nameof stack) stack) + struct (rebuild stack accepted, rebuild stack rejected) + +[] +let partitionWith (partitioner : 'T -> Choice<'T1, 'T2>) (stack : IStack<'T>) = + let first, second = Array.partitionWith partitioner (Snapshot.ofSeq (nameof stack) stack) + struct (ofArrayUnchecked first, ofArrayUnchecked second) + +[] +let skip count (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + // A stack shorter than the count stops the counting at its length, which the exception reports + ListChecks.skip (nameof count) count (Lengths.stackUpTo count stack) + dropTop count stack + +[] +let skipLast count (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + + if count <= 0 then + stack + else + let length = Lengths.stackUpTo Int32.MaxValue stack + // The checks of List.skip + ListChecks.skip (nameof count) count length + let struct (top, _) = splitTop (length - count) stack + rebuild stack top + +[] +let skipWhile (predicate : 'T -> bool) (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + let mutable node = stack + + while not node.IsEmpty && predicate (node.Peek ()) do + node <- node.Pop () + + node + +[] +let splitAt index (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + // A stack shorter than the index stops the counting at its length, which the exception reports + ListChecks.splitAt (nameof index) index (Lengths.stackUpTo index stack) + let struct (top, rest) = splitTop index stack + struct ((if rest.IsEmpty then stack else rebuild stack top), rest) + +[] +let splitInto count (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + ListChecks.positive (nameof count) count + + Array.splitInto count (Snapshot.ofSeq (nameof stack) stack) + |> Array.map ofArrayUnchecked + |> ofArrayUnchecked + +[] +let take count (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + // A stack shorter than the count stops the counting at its length, which the exception reports + ListChecks.take (nameof count) count (Lengths.stackUpTo count stack) + let struct (top, rest) = splitTop count stack + if rest.IsEmpty then stack else rebuild stack top + +[] +let takeLast count (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + let length = Lengths.stackUpTo Int32.MaxValue stack + // The checks of List.take + ListChecks.take (nameof count) count length + dropTop (length - count) stack + +[] +let takeWhile (predicate : 'T -> bool) (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + let taken = ResizeArray<'T>() + let mutable node = stack + let mutable go = true + + while go && not node.IsEmpty do + let item = node.Peek () + + if predicate item then + taken.Add item + node <- node.Pop () + else + go <- false + + if node.IsEmpty then + stack + else + rebuild stack (taken.ToArray ()) + +[] +let truncate count (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + let struct (top, rest) = splitTop (Lengths.stackUpTo count stack) stack + if rest.IsEmpty then stack else rebuild stack top + +// ---- 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. The sorted elements go to an array, which +// the stack of the implementation of the input is pushed from. A stack of one element is returned without a call of the +// projection or the comparer, as List returns it. + +[] +let sort (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + + if Lengths.stackUpTo 2 stack <= 1 then + stack + else + rebuild stack (Enumerable.ToArray (Seq.sort stack)) + +[] +let sortBy (projection : 'T -> 'Key) (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + + if Lengths.stackUpTo 2 stack <= 1 then + stack + else + rebuild stack (stack.OrderBy(Func<'T, 'Key> projection, DefaultComparer<'Key>.Instance).ToArray()) + +[] +let sortByDescending (projection : 'T -> 'Key) (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + + if Lengths.stackUpTo 2 stack <= 1 then + stack + else + rebuild stack (Enumerable.ToArray (DescendingSorts.sortByDescending projection stack)) + +[] +let sortDescending (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + + if Lengths.stackUpTo 2 stack <= 1 then + stack + else + rebuild stack (Enumerable.ToArray (DescendingSorts.sortDescending stack)) + +[] +let sortWith (comparer : 'T -> 'T -> int) (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + + if Lengths.stackUpTo 2 stack <= 1 then + stack + else + rebuild stack (Enumerable.ToArray (Seq.sortWith comparer stack)) + +[] +let sortWithComparer (comparer : Collections.Generic.IComparer<'T>) (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + + if Lengths.stackUpTo 2 stack <= 1 then + stack + else + rebuild stack (stack.Order(comparer).ToArray()) + +// ---- Random ---- + +// The functions draw the random numbers as the List functions do, from a snapshot after the List checks: the Array +// functions throw other messages for an empty array + +[] +let randomChoice (source : IStack<'T>) = + let array = Snapshot.ofSeq (nameof source) source + ListChecks.notEmptySequence (nameof source) array.Length + array[Random.Shared.Next (0, array.Length)] + +[] +let randomChoiceBy (randomizer : unit -> float) (source : IStack<'T>) = + let array = Snapshot.ofSeq (nameof source) source + ListChecks.notEmptySequence (nameof source) array.Length + array[ListChecks.randomIndex (nameof randomizer) randomizer array.Length] + +[] +let randomChoiceWith (random : Random) (source : IStack<'T>) = + checkNotNull (nameof random) random + let array = Snapshot.ofSeq (nameof source) source + ListChecks.notEmptySequence (nameof source) array.Length + array[random.Next (0, array.Length)] + +[] +let randomChoices count (source : IStack<'T>) = + let array = Snapshot.ofSeq (nameof source) source + ListChecks.randomChoices (nameof count) count (nameof source) array.Length + rebuild source (Array.randomChoices count array) + +[] +let randomChoicesBy (randomizer : unit -> float) count (source : IStack<'T>) = + let array = Snapshot.ofSeq (nameof source) source + ListChecks.randomChoices (nameof count) count (nameof source) array.Length + rebuild source (Array.randomChoicesBy randomizer count array) + +[] +let randomChoicesWith (random : Random) count (source : IStack<'T>) = + checkNotNull (nameof random) random + let array = Snapshot.ofSeq (nameof source) source + ListChecks.randomChoices (nameof count) count (nameof source) array.Length + rebuild source (Array.randomChoicesWith random count array) + +[] +let randomSample count (source : IStack<'T>) = + let array = Snapshot.ofSeq (nameof source) source + ListChecks.randomSample (nameof count) count (nameof source) array.Length + rebuild source (Array.randomSample count array) + +[] +let randomSampleBy (randomizer : unit -> float) count (source : IStack<'T>) = + let array = Snapshot.ofSeq (nameof source) source + ListChecks.randomSample (nameof count) count (nameof source) array.Length + rebuild source (Array.randomSampleBy randomizer count array) + +[] +let randomSampleWith (random : Random) count (source : IStack<'T>) = + checkNotNull (nameof random) random + let array = Snapshot.ofSeq (nameof source) source + ListChecks.randomSample (nameof count) count (nameof source) array.Length + rebuild source (Array.randomSampleWith random count array) + +[] +let randomShuffle (source : IStack<'T>) = + let array = Snapshot.ofSeq (nameof source) source + Array.randomShuffleInPlace array + rebuild source array + +[] +let randomShuffleBy (randomizer : unit -> float) (source : IStack<'T>) = + let array = Snapshot.ofSeq (nameof source) source + Array.randomShuffleInPlaceBy randomizer array + rebuild source array + +[] +let randomShuffleWith (random : Random) (source : IStack<'T>) = + checkNotNull (nameof random) random + let array = Snapshot.ofSeq (nameof source) source + Array.randomShuffleInPlaceWith random array + rebuild source array + +// ---- Conversion ---- + +[] +let toArray (stack : IStack<'T>) = Snapshot.ofSeq (nameof stack) stack + +[] +let toList (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + List.ofSeq stack + +[] +let toSeq (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + stack :> seq<'T> + +// ---- Type-specific ---- + +[] +let clear (stack : IStack<'T>) = + checkNotNull (nameof stack) stack + stack.Clear () diff --git a/src/FSharp.Collections.Immutable/Stack/IStack.fsi b/src/FSharp.Collections.Immutable/Stack/IStack.fsi new file mode 100644 index 00000000..e6483af7 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Stack/IStack.fsi @@ -0,0 +1,1439 @@ +/// +/// Functions for , the interface of the immutable stacks. +/// +/// +/// +/// The module mirrors as +/// does: each function has the semantics, the argument checks and the +/// exceptions of the function with the same name in that module (ADR 0001), its parameters and exceptions have the names +/// of the stack, and its sorting functions are stable. A stack enumerates its elements from the top to the bottom, and +/// every function treats it as a list in that order. The module has no creation functions, because an interface cannot +/// create a stack. +/// +/// +/// 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 +/// from the last element to the first one, or +/// . A function that changes the element type, or that +/// combines several stacks into one, returns a , except +/// : +/// it pushes the first stack onto the second one, so its result has the implementation of the second stack. +/// +/// +/// Every function throws for a stack. 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.IStack + +// System.Collections.Generic stays closed: its Stack<'T> would shadow the Stack<'T> abbreviation of this library +open System +open System.Collections.Immutable + +// ---- Properties ---- + +/// +/// Returns whether the stack has no elements. +/// +/// The input stack. +/// when the stack is empty. +/// +/// O(1). The function wraps . +/// +[] +val isEmpty : stack : IStack<'T> -> bool + +/// +/// Returns the number of elements of the stack. +/// +/// The input stack. +/// The length of the stack. +/// +/// O(n): the interface does not tell the length, so the function counts the elements. +/// +[] +val length : stack : IStack<'T> -> int + +// ---- Access ---- + +/// Returns the only element of the stack. +/// The input stack. +/// The only element. +/// Thrown when the stack does not have exactly one element. +/// O(1). +[] +val exactlyOne : stack : IStack<'T> -> 'T + +/// +/// Returns the first element of the stack, the element on top. +/// +/// The input stack. +/// The first element. +/// Thrown when the stack is empty. +/// +/// O(1). The function wraps . +/// +[] +val head : stack : IStack<'T> -> 'T + +/// +/// Returns the last element of the stack, the element at the bottom. +/// +/// The input stack. +/// The last element. +/// Thrown when the stack is empty. +/// +/// O(n). +/// +[] +val last : stack : IStack<'T> -> 'T + +/// +/// Returns the stack without its first element: the stack below the top. +/// +/// The input stack. +/// The elements after the first one, in a stack of the implementation of the input. +/// Thrown when the stack is empty. +/// +/// O(1). The function wraps . +/// +[] +val tail : stack : IStack<'T> -> IStack<'T> + +/// +/// Returns the only element of the stack, or ValueNone when it does not have exactly one element. +/// +/// The input stack. +/// The only element, or ValueNone. +/// +/// O(1). +/// +[] +val tryExactlyOne : stack : IStack<'T> -> 'T voption + +/// +/// Returns the first element of the stack, or ValueNone when it is empty. +/// +/// The input stack. +/// The first element, or ValueNone. +/// +/// O(1). +/// +[] +val tryHead : stack : IStack<'T> -> 'T voption + +/// +/// Returns the last element of the stack, or ValueNone when it is empty. +/// +/// The input stack. +/// The last element, or ValueNone. +/// +/// O(n). +/// +[] +val tryLast : stack : IStack<'T> -> 'T voption + +/// +/// Returns the stack without its first element, or ValueNone when it is empty. +/// +/// The input stack. +/// The elements after the first one, in a stack of the implementation of the input, or ValueNone. +/// +/// O(1). +/// +[] +val tryTail : stack : IStack<'T> -> IStack<'T> voption + +/// +/// Returns the first element and the other elements, or ValueNone when the stack is empty. +/// +/// The input stack. +/// The first element and the elements after it, or ValueNone. +/// +/// O(1). +/// +[] +val tryUncons : stack : IStack<'T> -> struct ('T * IStack<'T>) voption + +/// +/// Returns the first element and the other elements. +/// +/// The input stack. +/// The first element and the elements after it, in a stack of the implementation of the input. +/// Thrown when the stack is empty. +/// +/// O(1). The function throws what +/// throws. +/// +[] +val uncons : stack : IStack<'T> -> struct ('T * IStack<'T>) + +/// +/// Matches a stack with a first element, and returns that element and the other elements. +/// +/// The input stack. +/// The first element and the elements after it, or ValueNone for an empty stack. +/// +/// O(1). A struct partial pattern, so that a match allocates no option. +/// +[] +[] +val (|Cons|_|) : stack : IStack<'T> -> struct ('T * IStack<'T>) voption + +/// Matches an empty stack. +/// The input stack. +/// when the stack is empty. +/// O(1). +[] +val (|Nil|_|) : stack : IStack<'T> -> bool + +// ---- Search ---- + +/// +/// Returns whether the stack contains an element equal to a value, in F# structural equality. +/// +/// The value to find. +/// The input stack. +/// when an element equals the value. +/// +/// O(n). +/// +[] +val contains : value : 'T -> source : IStack<'T> -> bool when 'T : equality + +/// Returns whether any element satisfies the predicate. +/// The function to test the elements. +/// The input stack. +/// when an element satisfies the predicate. +/// O(n). +[] +val exists : predicate : ('T -> bool) -> stack : IStack<'T> -> bool + +/// +/// Returns whether any pair of elements at the same index satisfies the predicate. +/// +/// The function to test the pairs. +/// The first stack. +/// The second stack. +/// when a pair satisfies the predicate. +/// Thrown when no pair satisfies the predicate and the stacks have different lengths. +/// +/// O(n). +/// +[] +val exists2 : predicate : ('T1 -> 'T2 -> bool) -> stack1 : IStack<'T1> -> stack2 : IStack<'T2> -> bool + +/// Returns the first element that satisfies the predicate. +/// The function to test the elements. +/// The input stack. +/// The first element that satisfies the predicate. +/// Thrown when no element satisfies the predicate. +/// O(n). +[] +val find : predicate : ('T -> bool) -> stack : IStack<'T> -> 'T + +/// Returns the last element that satisfies the predicate. +/// The function to test the elements. +/// The input stack. +/// The last element that satisfies the predicate. +/// Thrown when no element satisfies the predicate. +/// O(n). +[] +val findBack : predicate : ('T -> bool) -> stack : IStack<'T> -> 'T + +/// +/// Returns the index of the first element that satisfies the predicate. +/// +/// The function to test the elements. +/// The input stack. +/// The index of the first element that satisfies the predicate; the top has the index 0. +/// Thrown when no element satisfies the predicate. +/// +/// O(n). +/// +[] +val findIndex : predicate : ('T -> bool) -> stack : IStack<'T> -> int + +/// +/// Returns the index of the last element that satisfies the predicate. +/// +/// The function to test the elements. +/// The input stack. +/// The index of the last element that satisfies the predicate; the top has the index 0. +/// Thrown when no element satisfies the predicate. +/// +/// O(n). +/// +[] +val findIndexBack : predicate : ('T -> bool) -> stack : IStack<'T> -> int + +/// Returns whether all elements satisfy the predicate. +/// The function to test the elements. +/// The input stack. +/// when every element satisfies the predicate. +/// O(n). +[] +val forall : predicate : ('T -> bool) -> stack : IStack<'T> -> bool + +/// +/// Returns whether all pairs of elements at the same index satisfy the predicate. +/// +/// The function to test the pairs. +/// The first stack. +/// The second stack. +/// when every pair satisfies the predicate. +/// Thrown when every pair satisfies the predicate and the stacks have different lengths. +/// +/// O(n). +/// +[] +val forall2 : predicate : ('T1 -> 'T2 -> bool) -> stack1 : IStack<'T1> -> stack2 : IStack<'T2> -> bool + +/// +/// Returns the first value that the chooser returns for the elements. +/// +/// The function that returns a value, or ValueNone to go on. +/// The input stack. +/// The first chosen value. +/// Thrown when the chooser returns ValueNone for every element. +/// +/// O(n). +/// +[] +val pick : chooser : ('T -> 'U voption) -> stack : IStack<'T> -> 'U + +/// +/// Returns the first element that satisfies the predicate, or ValueNone. +/// +/// The function to test the elements. +/// The input stack. +/// The first element that satisfies the predicate, or ValueNone. +/// +/// O(n). +/// +[] +val tryFind : predicate : ('T -> bool) -> stack : IStack<'T> -> 'T voption + +/// +/// Returns the last element that satisfies the predicate, or ValueNone. +/// +/// The function to test the elements. +/// The input stack. +/// The last element that satisfies the predicate, or ValueNone. +/// +/// O(n). +/// +[] +val tryFindBack : predicate : ('T -> bool) -> stack : IStack<'T> -> 'T voption + +/// +/// Returns the index of the first element that satisfies the predicate, or ValueNone. +/// +/// The function to test the elements. +/// The input stack. +/// The index of the first element that satisfies the predicate, or ValueNone. +/// +/// O(n). +/// +[] +val tryFindIndex : predicate : ('T -> bool) -> stack : IStack<'T> -> int voption + +/// +/// Returns the index of the last element that satisfies the predicate, or ValueNone. +/// +/// The function to test the elements. +/// The input stack. +/// The index of the last element that satisfies the predicate, or ValueNone. +/// +/// O(n). +/// +[] +val tryFindIndexBack : predicate : ('T -> bool) -> stack : IStack<'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 stack. +/// The first chosen value, or ValueNone. +/// +/// O(n). +/// +[] +val tryPick : chooser : ('T -> 'U voption) -> stack : IStack<'T> -> 'U voption + +// ---- Transformation ---- + +/// +/// Returns every element of the first stack paired with every element of the second one. +/// +/// The first stack. +/// The second stack. +/// The pairs, in the order of the first stack and then of the second one. +/// +/// O(n × m). +/// +[] +val allPairs : stack1 : IStack<'T1> -> stack2 : IStack<'T2> -> Stack + +/// +/// Returns a stack of the elements of the first stack followed by the elements of the second one. +/// +/// The first stack, whose elements come first, on top. +/// The second stack, whose elements come last. +/// The concatenation, in a stack of the implementation of the second stack. +/// +/// +/// O(n), where n is the length of the first stack. The function pushes the elements of the first stack onto the second +/// one with , from the last element to the +/// first one. When the first stack is empty, the function returns the second one. +/// +/// +/// The collection that receives the elements keeps its implementation (ADR 0001, D14). Here it is the second stack: the +/// result keeps its implementation and its nodes, as +/// +/// shares its second list. +/// +/// adds the elements of the second list to the first one instead, so its result keeps the implementation of the first +/// list. +/// +/// +[] +val append : stack1 : IStack<'T> -> stack2 : IStack<'T> -> IStack<'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 stack. +/// The chosen values, in the order of their elements. +/// +/// O(n). +/// +[] +val choose : chooser : ('T -> 'U voption) -> stack : IStack<'T> -> Stack<'U> + +/// Divides the stack into chunks of a given size. +/// The maximum size of a chunk. +/// The input stack. +/// The chunks; the last one can be smaller. +/// Thrown when is not positive. +/// O(n). +[] +val chunkBySize : chunkSize : int -> stack : IStack<'T> -> Stack> + +/// +/// Concatenates the stacks that a function returns for the elements. +/// +/// The function that returns a stack for each element. +/// The input stack. +/// The concatenation of the returned stacks. +/// +/// O(n + the total length of the returned stacks). +/// +[] +val collect : mapping : ('T -> IStack<'U>) -> stack : IStack<'T> -> Stack<'U> + +/// Concatenates a sequence of stacks. +/// The stacks. +/// The concatenation, in a stack, because the stacks can have different implementations. +/// Thrown when is or a default flat list, or one of its stacks is . +/// O(the total length). +[] +val concat : stacks : seq> -> Stack<'T> + +/// +/// Returns a stack with a value added before the first element: the value pushed on top. +/// +/// The value to add. +/// The input stack. +/// A stack of the implementation of the input with the value and the elements. +/// +/// O(1). The function wraps . +/// +[] +val cons : value : 'T -> stack : IStack<'T> -> IStack<'T> + +/// +/// Returns a stack with values added before the first element, in their order: the first value ends on top. +/// +/// The values to add, in the order in which the result enumerates them. +/// The input stack. +/// A stack of the implementation of the input with the values followed by the elements. +/// Thrown when is or a default flat list. +/// +/// O(m), where m is the number of values. The function pushes the values from the last one to the first one with +/// . +/// +[] +val consMany : values : seq<'T> -> stack : IStack<'T> -> IStack<'T> + +/// Returns the elements that satisfy the predicate. +/// The function to test the elements. +/// The input stack. +/// The elements that satisfy the predicate, in their order, in a stack of the implementation of the input. +/// O(n). +[] +val filter : predicate : ('T -> bool) -> stack : IStack<'T> -> IStack<'T> + +/// Returns every element with its index. +/// The input stack. +/// The pairs of index and element; the top has the index 0. +/// O(n). +[] +val indexed : stack : IStack<'T> -> Stack + +/// 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 stack. +/// The results, in the order of the elements. +/// O(n). +[] +val map : mapping : ('T -> 'U) -> stack : IStack<'T> -> Stack<'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 stack. +/// The second stack. +/// The results. +/// Thrown when the stacks have different lengths. +/// +/// O(n). +/// +[] +val map2 : mapping : ('T1 -> 'T2 -> 'U) -> stack1 : IStack<'T1> -> stack2 : IStack<'T2> -> Stack<'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 stack. +/// The second stack. +/// The third stack. +/// The results. +/// Thrown when the stacks have different lengths. +/// +/// O(n). +/// +[] +val map3 : + mapping : ('T1 -> 'T2 -> 'T3 -> 'U) -> stack1 : IStack<'T1> -> stack2 : IStack<'T2> -> stack3 : IStack<'T3> -> Stack<'U> + +/// +/// Returns the results of a function applied to each element and its index. +/// +/// The function to transform the elements. +/// The input stack. +/// The results. +/// +/// O(n). +/// +[] +val mapi : mapping : (int -> 'T -> 'U) -> stack : IStack<'T> -> Stack<'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 stack. +/// The second stack. +/// The results. +/// Thrown when the stacks have different lengths. +/// +/// O(n). +/// +[] +val mapi2 : mapping : (int -> 'T1 -> 'T2 -> 'U) -> stack1 : IStack<'T1> -> stack2 : IStack<'T2> -> Stack<'U> + +/// Returns every element with its successor. +/// The input stack. +/// The pairs of neighbours; empty for fewer than two elements. +/// O(n). +[] +val pairwise : stack : IStack<'T> -> Stack + +/// +/// 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 stack. +/// The permuted elements, in a stack of the implementation of the input. +/// Thrown when the function does not compute a permutation. +/// +/// O(n). +/// +[] +val permute : indexMap : (int -> int) -> stack : IStack<'T> -> IStack<'T> + +/// +/// Returns the elements in reverse order. +/// +/// The input stack. +/// The reversed elements, in a stack of the implementation of the input. +/// +/// O(n). The function pushes the elements onto the cleared stack in their order. +/// +[] +val rev : stack : IStack<'T> -> IStack<'T> + +/// +/// Returns the transpose of a sequence of stacks of the same length. +/// +/// The stacks. +/// A stack whose element i holds the element i of every input stack. +/// Thrown when is or a default flat list, or one of its stacks is . +/// Thrown when the stacks have different lengths. +/// +/// O(the total length). +/// +[] +val transpose : stacks : seq> -> Stack> + +/// Splits a stack of pairs into two stacks. +/// The input stack. +/// The first components and the second components. +/// O(n). +[] +val unzip : stack : IStack -> struct (Stack<'T1> * Stack<'T2>) + +/// Splits a stack of triples into three stacks. +/// The input stack. +/// The first, the second and the third components. +/// O(n). +[] +val unzip3 : stack : IStack -> struct (Stack<'T1> * Stack<'T2> * Stack<'T3>) + +/// +/// Returns the elements that satisfy the predicate. +/// +/// The function to test the elements. +/// The input stack. +/// The elements that satisfy the predicate, in their order, in a stack of the implementation of the input. +/// +/// O(n). A synonym of . +/// +[] +val where : predicate : ('T -> bool) -> stack : IStack<'T> -> IStack<'T> + +/// +/// Returns the windows of a given size that slide over the stack. +/// +/// The size of a window. +/// The input stack. +/// The windows; empty when the stack is shorter than a window. +/// Thrown when is not positive. +/// +/// O(n × windowSize). +/// +[] +val windowed : windowSize : int -> stack : IStack<'T> -> Stack> + +/// Combines two stacks into a stack of pairs. +/// The first stack. +/// The second stack. +/// The pairs of elements at the same index. +/// Thrown when the stacks have different lengths. +/// O(n). +[] +val zip : stack1 : IStack<'T1> -> stack2 : IStack<'T2> -> Stack + +/// Combines three stacks into a stack of triples. +/// The first stack. +/// The second stack. +/// The third stack. +/// The triples of elements at the same index. +/// Thrown when the stacks have different lengths. +/// O(n). +[] +val zip3 : stack1 : IStack<'T1> -> stack2 : IStack<'T2> -> stack3 : IStack<'T3> -> Stack + +// ---- Folding ---- + +/// Returns the average of the elements. +/// The input stack. +/// The average. +/// Thrown when the stack is empty. +/// O(n). +[] +val inline average : + stack : IStack< ^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 stack. +/// The average of the results. +/// Thrown when the stack is empty. +/// +/// O(n). +/// +[] +val inline averageBy : + projection : ('T -> ^U) -> stack : IStack<'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 stacks element by element with a comparer. +/// The function that compares two elements. +/// The first stack. +/// The second stack. +/// +/// The first non-zero result of the comparer, or the comparison of the lengths when every common element compares equal. +/// +/// O(n). +[] +val compareWith : comparer : ('T -> 'T -> int) -> stack1 : IStack<'T> -> stack2 : IStack<'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 stack. +/// The final state. +/// +/// O(n). +/// +[] +val fold : folder : ('State -> 'T -> 'State) -> state : 'State -> stack : IStack<'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 stack. +/// The second stack. +/// The final state. +/// Thrown when the stacks have different lengths. +/// +/// O(n). +/// +[] +val fold2 : folder : ('State -> 'T1 -> 'T2 -> 'State) -> state : 'State -> stack1 : IStack<'T1> -> stack2 : IStack<'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 stack. +/// The initial state. +/// The final state. +/// +/// O(n). +/// +[] +val foldBack : folder : ('T -> 'State -> 'State) -> stack : IStack<'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 stack. +/// The second stack. +/// The initial state. +/// The final state. +/// Thrown when the stacks have different lengths. +/// +/// O(n). +/// +[] +val foldBack2 : + folder : ('T1 -> 'T2 -> 'State -> 'State) -> stack1 : IStack<'T1> -> stack2 : IStack<'T2> -> state : 'State -> 'State + +/// Applies an action to each element. +/// The action. +/// The input stack. +/// O(n). +[] +val iter : action : ('T -> unit) -> stack : IStack<'T> -> unit + +/// +/// Applies an action to each pair of elements at the same index. +/// +/// The action. +/// The first stack. +/// The second stack. +/// Thrown when the stacks have different lengths. +/// +/// O(n). +/// +[] +val iter2 : action : ('T1 -> 'T2 -> unit) -> stack1 : IStack<'T1> -> stack2 : IStack<'T2> -> unit + +/// Applies an action to each element and its index. +/// The action. +/// The input stack. +/// O(n). +[] +val iteri : action : (int -> 'T -> unit) -> stack : IStack<'T> -> unit + +/// +/// Applies an action to the index and the pair of elements at that index. +/// +/// The action. +/// The first stack. +/// The second stack. +/// Thrown when the stacks have different lengths. +/// +/// O(n). +/// +[] +val iteri2 : action : (int -> 'T1 -> 'T2 -> unit) -> stack1 : IStack<'T1> -> stack2 : IStack<'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 stack. +/// The results and the final state. +/// +/// O(n). +/// +[] +val mapFold : + mapping : ('State -> 'T -> struct ('Result * 'State)) -> + state : 'State -> + stack : IStack<'T> -> + struct (Stack<'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 stack. +/// 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)) -> + stack : IStack<'T> -> + state : 'State -> + struct (Stack<'Result> * 'State) + +/// Returns the greatest element, in F# generic comparison. +/// The input stack. +/// The greatest element. +/// Thrown when the stack is empty. +/// O(n). +[] +val max : stack : IStack<'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 stack. +/// The first element with the greatest result. +/// Thrown when the stack is empty. +/// +/// O(n). +/// +[] +val maxBy : projection : ('T -> 'U) -> stack : IStack<'T> -> 'T when 'U : comparison + +/// Returns the smallest element, in F# generic comparison. +/// The input stack. +/// The smallest element. +/// Thrown when the stack is empty. +/// O(n). +[] +val min : stack : IStack<'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 stack. +/// The first element with the smallest result. +/// Thrown when the stack is empty. +/// +/// O(n). +/// +[] +val minBy : projection : ('T -> 'U) -> stack : IStack<'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 stack. +/// The combined value. +/// Thrown when the stack is empty. +/// +/// O(n). +/// +[] +val reduce : reduction : ('T -> 'T -> 'T) -> stack : IStack<'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 stack. +/// The combined value. +/// Thrown when the stack is empty. +/// +/// O(n). +/// +[] +val reduceBack : reduction : ('T -> 'T -> 'T) -> stack : IStack<'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 stack. +/// The states; one more than the elements. +/// +/// O(n). +/// +[] +val scan : folder : ('State -> 'T -> 'State) -> state : 'State -> stack : IStack<'T> -> Stack<'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 stack. +/// The initial state. +/// The states; one more than the elements. +/// +/// O(n). +/// +[] +val scanBack : folder : ('T -> 'State -> 'State) -> stack : IStack<'T> -> state : 'State -> Stack<'State> + +/// Returns the sum of the elements. +/// The input stack. +/// The sum; zero for an empty stack. +/// O(n). +[] +val inline sum : stack : IStack< ^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 stack. +/// The sum of the results; zero for an empty stack. +/// +/// O(n). +/// +[] +val inline sumBy : + projection : ('T -> ^U) -> stack : IStack<'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 stack. +/// 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) -> stack : IStack<'T> -> Stack when 'Key : equality + +/// +/// Returns the elements without duplicates, in F# structural equality. +/// +/// The input stack. +/// The first occurrence of each element, in their order, in a stack of the implementation of the input. +/// +/// O(n). +/// +[] +val distinct : stack : IStack<'T> -> IStack<'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 stack. +/// The first element of each key, in their order, in a stack of the implementation of the input. +/// +/// O(n). The keys compare in F# structural equality. +/// +[] +val distinctBy : projection : ('T -> 'Key) -> stack : IStack<'T> -> IStack<'T> when 'Key : equality + +/// +/// Returns the distinct elements that do not equal any of the excluded items. +/// +/// The items to remove. +/// The input stack. +/// The distinct remaining elements, in their order, in a stack of the implementation of the input. +/// Thrown when is or a default flat list. +/// +/// O(n + m). An empty stack is returned without a look at the items to exclude. +/// +[] +val except : itemsToExclude : seq<'T> -> stack : IStack<'T> -> IStack<'T> when 'T : equality + +/// Groups the elements by the key of a projection. +/// The function that returns the key of an element. +/// The input stack. +/// 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) -> stack : IStack<'T> -> Stack)> when 'Key : equality + +/// Splits the elements by a predicate. +/// The function to test the elements. +/// The input stack. +/// +/// The elements that satisfy the predicate and the other ones, both in their order, in stacks of the implementation of +/// the input. +/// +/// O(n). +[] +val partition : predicate : ('T -> bool) -> stack : IStack<'T> -> struct (IStack<'T> * IStack<'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 stack. +/// 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>) -> stack : IStack<'T> -> struct (Stack<'T1> * Stack<'T2>) + +/// +/// Returns the stack without its first elements. +/// +/// The number of elements to skip; a negative count skips nothing. +/// The input stack. +/// The remaining elements, in a stack of the implementation of the input. +/// Thrown when the count exceeds the length. +/// +/// O(count). The function pops the elements with . +/// +[] +val skip : count : int -> stack : IStack<'T> -> IStack<'T> + +/// +/// Returns the stack without its last elements. +/// +/// The number of elements to skip; a negative count skips nothing. +/// The input stack. +/// The remaining elements, in a stack of the implementation of the input. +/// Thrown when the count exceeds the length. +/// +/// O(n). The stack itself is returned when nothing is skipped. The checks follow +/// . +/// +[] +val skipLast : count : int -> stack : IStack<'T> -> IStack<'T> + +/// +/// Returns the stack without its first elements that satisfy the predicate. +/// +/// The function to test the elements. +/// The input stack. +/// The elements from the first one that does not satisfy the predicate, in a stack of the implementation of the input. +/// +/// O(k), where k is the number of skipped elements. +/// +[] +val skipWhile : predicate : ('T -> bool) -> stack : IStack<'T> -> IStack<'T> + +/// Splits the stack at an index. +/// The index of the first element of the second part. +/// The input stack. +/// The elements before the index and the elements from the index, in stacks of the implementation of the input. +/// Thrown when the index is negative. +/// Thrown when the index exceeds the length. +/// O(index). +[] +val splitAt : index : int -> stack : IStack<'T> -> struct (IStack<'T> * IStack<'T>) + +/// +/// Splits the stack into at most a given number of parts of nearly equal length. +/// +/// The maximum number of parts. +/// The input stack. +/// 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 -> stack : IStack<'T> -> Stack> + +/// +/// Returns the first elements of the stack. +/// +/// The number of elements to take. +/// The input stack. +/// The first elements, in a stack of the implementation of the input. +/// Thrown when the count is negative. +/// Thrown when the count exceeds the length. +/// +/// O(count). The stack itself is returned when the count is the length. +/// +[] +val take : count : int -> stack : IStack<'T> -> IStack<'T> + +/// +/// Returns the last elements of the stack. +/// +/// The number of elements to take. +/// The input stack. +/// The last elements, in a stack of the implementation of the input. +/// Thrown when the count is negative. +/// Thrown when the count exceeds the length. +/// +/// O(n). The function pops the other elements with . +/// The checks follow +/// . +/// +[] +val takeLast : count : int -> stack : IStack<'T> -> IStack<'T> + +/// +/// Returns the first elements of the stack that satisfy the predicate. +/// +/// The function to test the elements. +/// The input stack. +/// The elements before the first one that does not satisfy the predicate, in a stack of the implementation of the input. +/// +/// O(k), where k is the length of the result. The stack itself is returned when every element satisfies the predicate. +/// +[] +val takeWhile : predicate : ('T -> bool) -> stack : IStack<'T> -> IStack<'T> + +/// +/// Returns at most a given number of first elements. +/// +/// The maximum number of elements; a count that is not positive returns an empty stack. +/// The input stack. +/// The first elements, in a stack of the implementation of the input. +/// +/// O(k), where k is the length of the result. The stack itself is returned when it is not longer than the count. +/// +[] +val truncate : count : int -> stack : IStack<'T> -> IStack<'T> + +// ---- Sorting ---- + +/// +/// Sorts the elements in the order of F# generic comparison. +/// +/// The input stack. +/// The sorted elements, the smallest on top, in a stack 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 : stack : IStack<'T> -> IStack<'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 stack. +/// The sorted elements, in a stack 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) -> stack : IStack<'T> -> IStack<'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 stack. +/// The sorted elements, in a stack 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) -> stack : IStack<'T> -> IStack<'T> when 'Key : comparison + +/// +/// Sorts the elements in descending order of F# generic comparison. +/// +/// The input stack. +/// The sorted elements, the greatest on top, in a stack 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 : stack : IStack<'T> -> IStack<'T> when 'T : comparison + +/// +/// Sorts the elements with a comparison function. +/// +/// The function that compares two elements. +/// The input stack. +/// The sorted elements, in a stack 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) -> stack : IStack<'T> -> IStack<'T> + +/// +/// Sorts the elements with a comparer. +/// +/// The comparer. +/// The input stack. +/// The sorted elements, in a stack 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 : Collections.Generic.IComparer<'T> -> stack : IStack<'T> -> IStack<'T> + +// ---- Random ---- + +/// +/// Returns a random element, chosen with . +/// +/// The input stack. +/// A random element. +/// Thrown when the stack is empty. +/// +/// O(n). +/// +[] +val randomChoice : source : IStack<'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 stack. +/// A random element. +/// Thrown when the stack is empty. +/// Thrown when the randomizer returns a number outside [0, 1). +/// +/// O(n). +/// +[] +val randomChoiceBy : randomizer : (unit -> float) -> source : IStack<'T> -> 'T + +/// +/// Returns a random element, chosen with a given random number generator. +/// +/// The random number generator. +/// The input stack. +/// A random element. +/// Thrown when is . +/// Thrown when the stack is empty. +/// +/// O(n). +/// +[] +val randomChoiceWith : random : Random -> source : IStack<'T> -> 'T + +/// +/// Returns random elements, chosen with ; an element can repeat. +/// +/// The number of elements to choose. +/// The input stack. +/// The chosen elements, in a stack of the implementation of the input. +/// Thrown when the count is negative, or positive for an empty stack. +/// +/// O(n + count). +/// +[] +val randomChoices : count : int -> source : IStack<'T> -> IStack<'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 stack. +/// The chosen elements, in a stack of the implementation of the input. +/// Thrown when the count is negative, or positive for an empty stack. +/// Thrown when the randomizer returns a number outside [0, 1). +/// +/// O(n + count). +/// +[] +val randomChoicesBy : randomizer : (unit -> float) -> count : int -> source : IStack<'T> -> IStack<'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 stack. +/// The chosen elements, in a stack of the implementation of the input. +/// Thrown when is . +/// Thrown when the count is negative, or positive for an empty stack. +/// +/// O(n + count). +/// +[] +val randomChoicesWith : random : Random -> count : int -> source : IStack<'T> -> IStack<'T> + +/// +/// Returns random elements at distinct indices, chosen with . +/// +/// The number of elements to choose. +/// The input stack. +/// The chosen elements, in a stack of the implementation of the input. +/// Thrown when the count is negative or exceeds the length. +/// +/// O(n + count). +/// +[] +val randomSample : count : int -> source : IStack<'T> -> IStack<'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 stack. +/// The chosen elements, in a stack 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 : IStack<'T> -> IStack<'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 stack. +/// The chosen elements, in a stack 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 : IStack<'T> -> IStack<'T> + +/// +/// Returns the elements in a random order, chosen with . +/// +/// The input stack. +/// The shuffled elements, in a stack of the implementation of the input. +/// +/// O(n). +/// +[] +val randomShuffle : source : IStack<'T> -> IStack<'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 stack. +/// The shuffled elements, in a stack of the implementation of the input. +/// Thrown when the randomizer returns a number outside [0, 1). +/// +/// O(n). +/// +[] +val randomShuffleBy : randomizer : (unit -> float) -> source : IStack<'T> -> IStack<'T> + +/// +/// Returns the elements in a random order, chosen with a given random number generator. +/// +/// The random number generator. +/// The input stack. +/// The shuffled elements, in a stack of the implementation of the input. +/// Thrown when is . +/// +/// O(n). +/// +[] +val randomShuffleWith : random : Random -> source : IStack<'T> -> IStack<'T> + +// ---- Conversion ---- + +/// +/// Returns a new array with the elements, from the top to the bottom. +/// +/// The input stack. +/// A copy of the elements. +/// +/// O(n). +/// +[] +val toArray : stack : IStack<'T> -> 'T array + +/// Returns a list of the elements, from the top to the bottom. +/// The input stack. +/// The list of the elements. +/// O(n). +[] +val toList : stack : IStack<'T> -> 'T list + +/// +/// Returns the stack as a sequence, which enumerates the elements from the top to the bottom. +/// +/// The input stack. +/// The sequence of the elements. +/// +/// O(1). +/// +[] +val toSeq : stack : IStack<'T> -> seq<'T> + +// ---- Type-specific ---- + +/// +/// Returns an empty stack of the implementation of the input. +/// +/// The input stack. +/// The empty stack. +/// +/// O(1). The function wraps , which keeps the +/// implementation of the stack. +/// +[] +val clear : stack : IStack<'T> -> IStack<'T> diff --git a/src/FSharp.Collections.Immutable/Stack/IStackOption.fs b/src/FSharp.Collections.Immutable/Stack/IStackOption.fs new file mode 100644 index 00000000..90959656 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Stack/IStackOption.fs @@ -0,0 +1,60 @@ +[] +module FSharp.Collections.Immutable.Option.IStack + +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 (stack : IStack<'T>) = IStack.tryExactlyOne stack |> ValueOption.toOption + +[] +let tryHead (stack : IStack<'T>) = IStack.tryHead stack |> ValueOption.toOption + +[] +let tryLast (stack : IStack<'T>) = IStack.tryLast stack |> ValueOption.toOption + +[] +let tryTail (stack : IStack<'T>) = IStack.tryTail stack |> ValueOption.toOption + +[] +let tryUncons (stack : IStack<'T>) = + match IStack.tryUncons stack with + | ValueSome (struct (head, tail)) -> Some (head, tail) + | ValueNone -> None + +[] +let (|Cons|_|) (stack : IStack<'T>) = tryUncons stack + +// ---- Search ---- + +[] +let pick (chooser : 'T -> 'U option) (stack : IStack<'T>) = IStack.pick (fun item -> chooser item |> ValueOption.ofOption) stack + +[] +let tryFind predicate (stack : IStack<'T>) = IStack.tryFind predicate stack |> ValueOption.toOption + +[] +let tryFindBack predicate (stack : IStack<'T>) = IStack.tryFindBack predicate stack |> ValueOption.toOption + +[] +let tryFindIndex predicate (stack : IStack<'T>) = IStack.tryFindIndex predicate stack |> ValueOption.toOption + +[] +let tryFindIndexBack predicate (stack : IStack<'T>) = + IStack.tryFindIndexBack predicate stack + |> ValueOption.toOption + +[] +let tryPick (chooser : 'T -> 'U option) (stack : IStack<'T>) = + IStack.tryPick (fun item -> chooser item |> ValueOption.ofOption) stack + |> ValueOption.toOption + +// ---- Transformation ---- + +[] +let choose (chooser : 'T -> 'U option) (stack : IStack<'T>) = + IStack.choose (fun item -> chooser item |> ValueOption.ofOption) stack diff --git a/src/FSharp.Collections.Immutable/Stack/IStackOption.fsi b/src/FSharp.Collections.Immutable/Stack/IStackOption.fsi new file mode 100644 index 00000000..227fe537 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Stack/IStackOption.fsi @@ -0,0 +1,171 @@ +/// +/// 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.IStack + +open FSharp.Collections.Immutable + +// ---- Access ---- + +/// +/// Returns the only element of the stack, or None when it does not have exactly one element. +/// +/// The input stack. +/// The only element, or None. +/// +/// O(1). +/// +[] +val tryExactlyOne : stack : IStack<'T> -> 'T option + +/// +/// Returns the first element of the stack, or None when it is empty. +/// +/// The input stack. +/// The first element, or None. +/// +/// O(1). +/// +[] +val tryHead : stack : IStack<'T> -> 'T option + +/// +/// Returns the last element of the stack, or None when it is empty. +/// +/// The input stack. +/// The last element, or None. +/// +/// O(n). +/// +[] +val tryLast : stack : IStack<'T> -> 'T option + +/// +/// Returns the stack without its first element, or None when it is empty. +/// +/// The input stack. +/// The elements after the first one, in a stack of the implementation of the input, or None. +/// +/// O(1). +/// +[] +val tryTail : stack : IStack<'T> -> IStack<'T> option + +/// +/// Returns the first element and the other elements, or None when the stack is empty. +/// +/// The input stack. +/// The first element and the elements after it, or None. +/// +/// O(1). +/// +[] +val tryUncons : stack : IStack<'T> -> ('T * IStack<'T>) option + +/// +/// Matches a stack with a first element, and returns that element and the other elements. +/// +/// The input stack. +/// The first element and the elements after it, or None for an empty stack. +/// +/// O(1). +/// +[] +val (|Cons|_|) : stack : IStack<'T> -> ('T * IStack<'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 stack. +/// The first chosen value. +/// Thrown when the chooser returns None for every element. +/// +/// O(n). +/// +[] +val pick : chooser : ('T -> 'U option) -> stack : IStack<'T> -> 'U + +/// +/// Returns the first element that satisfies the predicate, or None. +/// +/// The function to test the elements. +/// The input stack. +/// The first element that satisfies the predicate, or None. +/// +/// O(n). +/// +[] +val tryFind : predicate : ('T -> bool) -> stack : IStack<'T> -> 'T option + +/// +/// Returns the last element that satisfies the predicate, or None. +/// +/// The function to test the elements. +/// The input stack. +/// The last element that satisfies the predicate, or None. +/// +/// O(n). +/// +[] +val tryFindBack : predicate : ('T -> bool) -> stack : IStack<'T> -> 'T option + +/// +/// Returns the index of the first element that satisfies the predicate, or None. +/// +/// The function to test the elements. +/// The input stack. +/// The index of the first element that satisfies the predicate, or None. +/// +/// O(n). +/// +[] +val tryFindIndex : predicate : ('T -> bool) -> stack : IStack<'T> -> int option + +/// +/// Returns the index of the last element that satisfies the predicate, or None. +/// +/// The function to test the elements. +/// The input stack. +/// The index of the last element that satisfies the predicate, or None. +/// +/// O(n). +/// +[] +val tryFindIndexBack : predicate : ('T -> bool) -> stack : IStack<'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 stack. +/// The first chosen value, or None. +/// +/// O(n). +/// +[] +val tryPick : chooser : ('T -> 'U option) -> stack : IStack<'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 stack. +/// The chosen values, in the order of their elements. +/// +/// O(n). +/// +[] +val choose : chooser : ('T -> 'U option) -> stack : IStack<'T> -> Stack<'U> diff --git a/src/FSharp.Collections.Immutable/Stack/IStackTuple.fs b/src/FSharp.Collections.Immutable/Stack/IStackTuple.fs new file mode 100644 index 00000000..3a33a530 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Stack/IStackTuple.fs @@ -0,0 +1,120 @@ +[] +module FSharp.Collections.Immutable.Tuple.IStack + +open FSharp.Collections.Immutable +open FSharp.Collections.Immutable.StackConstruction + +// 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 (stack : IStack<'T>) = + match IStack.tryUncons stack with + | ValueSome (struct (head, tail)) -> ValueSome (head, tail) + | ValueNone -> ValueNone + +[] +let uncons (stack : IStack<'T>) = + let struct (head, tail) = IStack.uncons stack + head, tail + +[] +[] +let (|Cons|_|) (stack : IStack<'T>) = tryUncons stack + +// ---- Transformation ---- + +[] +let allPairs (stack1 : IStack<'T1>) (stack2 : IStack<'T2>) = + Arrays.allPairs referencePair (Snapshot.ofSeq (nameof stack1) stack1) (Snapshot.ofSeq (nameof stack2) stack2) + |> ofArrayUnchecked + +[] +let indexed (stack : IStack<'T>) = + Arrays.indexed referencePair (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let pairwise (stack : IStack<'T>) = + Arrays.pairwise referencePair (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let unzip (stack : IStack<'T1 * 'T2>) = + let struct (first, second) = + Arrays.unzip (fun (item1, item2) -> struct (item1, item2)) (Snapshot.ofSeq (nameof stack) stack) + + ofArrayUnchecked first, ofArrayUnchecked second + +[] +let unzip3 (stack : IStack<'T1 * 'T2 * 'T3>) = + let struct (first, second, third) = + Arrays.unzip3 (fun (item1, item2, item3) -> struct (item1, item2, item3)) (Snapshot.ofSeq (nameof stack) stack) + + ofArrayUnchecked first, ofArrayUnchecked second, ofArrayUnchecked third + +[] +let zip (stack1 : IStack<'T1>) (stack2 : IStack<'T2>) = + let array1 = Snapshot.ofSeq (nameof stack1) stack1 + let array2 = Snapshot.ofSeq (nameof stack2) stack2 + ListChecks.sameLengths (nameof stack1) array1.Length (nameof stack2) array2.Length + Arrays.zip referencePair array1 array2 |> ofArrayUnchecked + +[] +let zip3 (stack1 : IStack<'T1>) (stack2 : IStack<'T2>) (stack3 : IStack<'T3>) = + let array1 = Snapshot.ofSeq (nameof stack1) stack1 + let array2 = Snapshot.ofSeq (nameof stack2) stack2 + let array3 = Snapshot.ofSeq (nameof stack3) stack3 + ListChecks.zip3 (nameof stack1) array1.Length (nameof stack2) array2.Length (nameof stack3) array3.Length + + Arrays.zip3 referenceTriple array1 array2 array3 + |> ofArrayUnchecked + +// ---- Folding ---- + +[] +let mapFold (mapping : 'State -> 'T -> 'Result * 'State) (state : 'State) (stack : IStack<'T>) = + let mapStruct state item = + let result, next = mapping state item + struct (result, next) + + let struct (results, final) = IStack.mapFold mapStruct state stack + results, final + +[] +let mapFoldBack (mapping : 'T -> 'State -> 'Result * 'State) (stack : IStack<'T>) (state : 'State) = + let mapStruct item state = + let result, next = mapping item state + struct (result, next) + + let struct (results, final) = IStack.mapFoldBack mapStruct stack state + results, final + +// ---- Partitioning and grouping ---- + +[] +let countBy (projection : 'T -> 'Key) (stack : IStack<'T>) = + Arrays.countBy referencePair projection (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let groupBy (projection : 'T -> 'Key) (stack : IStack<'T>) = + Arrays.groupBy (fun key items -> key, ofArrayUnchecked items) projection (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let partition predicate (stack : IStack<'T>) = + let struct (accepted, rejected) = IStack.partition predicate stack + accepted, rejected + +[] +let partitionWith (partitioner : 'T -> Choice<'T1, 'T2>) (stack : IStack<'T>) = + let struct (first, second) = IStack.partitionWith partitioner stack + first, second + +[] +let splitAt index (stack : IStack<'T>) = + let struct (before, after) = IStack.splitAt index stack + before, after diff --git a/src/FSharp.Collections.Immutable/Stack/IStackTuple.fsi b/src/FSharp.Collections.Immutable/Stack/IStackTuple.fsi new file mode 100644 index 00000000..c1d0330f --- /dev/null +++ b/src/FSharp.Collections.Immutable/Stack/IStackTuple.fsi @@ -0,0 +1,189 @@ +/// +/// 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.IStack + +open FSharp.Collections.Immutable + +// ---- Access ---- + +/// +/// Returns the first element and the other elements, or ValueNone when the stack is empty. +/// +/// The input stack. +/// The first element and the elements after it, or ValueNone. +/// +/// O(1). +/// +[] +val tryUncons : stack : IStack<'T> -> ('T * IStack<'T>) voption + +/// Returns the first element and the other elements. +/// The input stack. +/// The first element and the elements after it, in a stack of the implementation of the input. +/// Thrown when the stack is empty. +/// O(1). +[] +val uncons : stack : IStack<'T> -> 'T * IStack<'T> + +/// +/// Matches a stack with a first element, and returns that element and the other elements. +/// +/// The input stack. +/// The first element and the elements after it, or ValueNone for an empty stack. +/// +/// O(1). A struct partial pattern, so that a match allocates no option. +/// +[] +[] +val (|Cons|_|) : stack : IStack<'T> -> ('T * IStack<'T>) voption + +// ---- Transformation ---- + +/// +/// Returns every element of the first stack paired with every element of the second one. +/// +/// The first stack. +/// The second stack. +/// The pairs, in the order of the first stack and then of the second one. +/// +/// O(n × m). +/// +[] +val allPairs : stack1 : IStack<'T1> -> stack2 : IStack<'T2> -> Stack<'T1 * 'T2> + +/// Returns every element with its index. +/// The input stack. +/// The pairs of index and element; the top has the index 0. +/// O(n). +[] +val indexed : stack : IStack<'T> -> Stack + +/// Returns every element with its successor. +/// The input stack. +/// The pairs of neighbours; empty for fewer than two elements. +/// O(n). +[] +val pairwise : stack : IStack<'T> -> Stack<'T * 'T> + +/// Splits a stack of pairs into two stacks. +/// The input stack. +/// The first components and the second components. +/// O(n). +[] +val unzip : stack : IStack<'T1 * 'T2> -> Stack<'T1> * Stack<'T2> + +/// Splits a stack of triples into three stacks. +/// The input stack. +/// The first, the second and the third components. +/// O(n). +[] +val unzip3 : stack : IStack<'T1 * 'T2 * 'T3> -> Stack<'T1> * Stack<'T2> * Stack<'T3> + +/// Combines two stacks into a stack of pairs. +/// The first stack. +/// The second stack. +/// The pairs of elements at the same index. +/// Thrown when the stacks have different lengths. +/// O(n). +[] +val zip : stack1 : IStack<'T1> -> stack2 : IStack<'T2> -> Stack<'T1 * 'T2> + +/// Combines three stacks into a stack of triples. +/// The first stack. +/// The second stack. +/// The third stack. +/// The triples of elements at the same index. +/// Thrown when the stacks have different lengths. +/// O(n). +[] +val zip3 : stack1 : IStack<'T1> -> stack2 : IStack<'T2> -> stack3 : IStack<'T3> -> Stack<'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 stack. +/// The results and the final state. +/// +/// O(n). +/// +[] +val mapFold : mapping : ('State -> 'T -> 'Result * 'State) -> state : 'State -> stack : IStack<'T> -> Stack<'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 stack. +/// The initial state. +/// The results, in the order of the elements, and the final state. +/// +/// O(n). +/// +[] +val mapFoldBack : mapping : ('T -> 'State -> 'Result * 'State) -> stack : IStack<'T> -> state : 'State -> Stack<'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 stack. +/// 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) -> stack : IStack<'T> -> Stack<'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 stack. +/// 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) -> stack : IStack<'T> -> Stack<'Key * Stack<'T>> when 'Key : equality + +/// Splits the elements by a predicate. +/// The function to test the elements. +/// The input stack. +/// +/// The elements that satisfy the predicate and the other ones, both in their order, in stacks of the implementation of +/// the input. +/// +/// O(n). +[] +val partition : predicate : ('T -> bool) -> stack : IStack<'T> -> IStack<'T> * IStack<'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 stack. +/// 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>) -> stack : IStack<'T> -> Stack<'T1> * Stack<'T2> + +/// Splits the stack at an index. +/// The index of the first element of the second part. +/// The input stack. +/// The elements before the index and the elements from the index, in stacks of the implementation of the input. +/// Thrown when the index is negative. +/// Thrown when the index exceeds the length. +/// O(index). +[] +val splitAt : index : int -> stack : IStack<'T> -> IStack<'T> * IStack<'T> diff --git a/src/FSharp.Collections.Immutable/Stack/Stack.fs b/src/FSharp.Collections.Immutable/Stack/Stack.fs new file mode 100644 index 00000000..773aedd7 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Stack/Stack.fs @@ -0,0 +1,1068 @@ +[] +module FSharp.Collections.Immutable.Stack + +open System +open System.Collections.Generic +open System.Collections.Immutable +open System.Linq +// Opened after System.Collections.Generic, so that Stack names the abbreviation of this library, not the mutable stack +open FSharp.Collections.Immutable +open FSharp.Collections.Immutable.StackConstruction + +// The functions call a member of ImmutableStack where it has the semantics of the List function: Push, Peek and Pop work on +// the top in O(1) and share the nodes below it. The other functions take a snapshot of the elements from the top to the +// bottom, apply an Array function or a shared algorithm and push the fresh result onto a new stack from its last element, +// so that the stack keeps the order of the result (ADR 0001, D5). The checks of ListChecks throw what the List functions +// throw, with the names of the parameters of this module (D6); Lengths.stackUpTo counts a stack only as far as a check +// needs it. + +/// The stack below the first elements; the caller checks that the stack has them. +let private dropTop (count : int) (stack : Stack<'T>) = + let mutable node = stack + + for _ in 1..count do + node <- node.Pop () + + node + +/// The first elements in an array and the stack below them; the caller checks that the stack has them. +let private splitTop (count : int) (stack : Stack<'T>) = + let top = Array.zeroCreate count + let mutable node = stack + + for index = 0 to count - 1 do + top[index] <- node.Peek () + node <- node.Pop () + + struct (top, node) + +/// The index and the first element that satisfies the predicate, or ValueNone. +let private firstMatch (predicate : 'T -> bool) (stack : Stack<'T>) = + let mutable node = stack + let mutable index = 0 + let mutable result = ValueNone + + while result.IsNone && not node.IsEmpty do + let item = node.Peek () + + if predicate item then + result <- ValueSome (struct (index, item)) + else + node <- node.Pop () + index <- index + 1 + + result + +/// The index and the last element that satisfies the predicate, or ValueNone; List searches a copy from the end too. +let private lastMatch (predicate : 'T -> bool) (stack : Stack<'T>) = + let array = Snapshot.ofSeq (nameof stack) stack + 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) (stack : Stack<'T>) = + let mutable node = stack + let mutable result = ValueNone + + while result.IsNone && not node.IsEmpty do + result <- chooser (node.Peek ()) + node <- node.Pop () + + result + +// ---- Creation ---- + +[] +[] +let empty<'T> : Stack<'T> = ImmutableStack<'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 |> ofArrayUnchecked + +[] +let ofArray (array : 'T array) = + checkNotNull (nameof array) array + ofArrayUnchecked array + +[] +let ofList (list : 'T list) = List.toArray list |> ofArrayUnchecked + +[] +let ofReadOnlySpan (span : ReadOnlySpan<'T>) = ofArrayUnchecked (span.ToArray ()) + +[] +let ofSeq (source : seq<'T>) = + // A default flat list given as a sequence is a null input; Enumerable.ToArray would fail while it enumerates it + // (ADR 0001, D6) + checkNotNullOrDefaultSeq (nameof source) source + + match source with + // A stack enumerates its elements from the top, so pushing them back from the last one would rebuild it + | :? ImmutableStack<'T> as stack -> stack + | _ -> Snapshot.ofSeq (nameof source) source |> ofArrayUnchecked + +[] +let ofSpan (span : Span<'T>) = ofArrayUnchecked (span.ToArray ()) + +[] +let replicate count (initial : 'T) = + ListChecks.replicate (nameof count) count + let mutable stack = ImmutableStack<'T>.Empty + + for _ in 1..count do + stack <- stack.Push initial + + stack + +[] +let singleton (value : 'T) = ImmutableStack<'T>.Empty.Push value + +[] +let unfold (generator : 'State -> struct ('T * 'State) voption) (state : 'State) = + 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 + + ofArrayUnchecked (items.ToArray ()) + +// ---- Properties ---- + +[] +let isEmpty (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + stack.IsEmpty + +[] +let length (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + // A stack does not store its length + Lengths.stackUpTo Int32.MaxValue stack + +// ---- Access ---- + +[] +let exactlyOne (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + // List.exactlyOne reports its input under the name "source"; two elements tell a longer stack apart + ListChecks.exactlyOne "source" (Lengths.stackUpTo 2 stack) + stack.Peek () + +[] +let head (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + ListChecks.notEmpty (nameof stack) (Lengths.stackUpTo 1 stack) + stack.Peek () + +[] +let last (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + ListChecks.notEmpty (nameof stack) (Lengths.stackUpTo 1 stack) + Enumerable.Last stack + +[] +let tail (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + ListChecks.notEmpty (nameof stack) (Lengths.stackUpTo 1 stack) + stack.Pop () + +[] +let tryExactlyOne (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + + if Lengths.stackUpTo 2 stack = 1 then + ValueSome (stack.Peek ()) + else + ValueNone + +[] +let tryHead (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + if stack.IsEmpty then + ValueNone + else + ValueSome (stack.Peek ()) + +[] +let tryLast (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + + if stack.IsEmpty then + ValueNone + else + ValueSome (Enumerable.Last stack) + +[] +let tryTail (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + if stack.IsEmpty then + ValueNone + else + ValueSome (stack.Pop ()) + +[] +let tryUncons (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + + if stack.IsEmpty then + ValueNone + else + ValueSome (struct (stack.Peek (), stack.Pop ())) + +[] +let uncons (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + // An empty stack throws what List.head throws + ListChecks.notEmpty (nameof stack) (Lengths.stackUpTo 1 stack) + struct (stack.Peek (), stack.Pop ()) + +[] +[] +let (|Cons|_|) (stack : Stack<'T>) = tryUncons stack + +[] +let (|Nil|_|) (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + stack.IsEmpty + +// ---- Search ---- + +[] +let contains (value : 'T) (source : Stack<'T>) = + checkNotNull (nameof source) source + // F# equality with the value on the left, as List.contains compares + (firstMatch (fun item -> value = item) source).IsSome + +[] +let exists (predicate : 'T -> bool) (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + (firstMatch predicate stack).IsSome + +[] +let exists2 predicate (stack1 : Stack<'T1>) (stack2 : Stack<'T2>) = + ListArrays.exists2 (nameof stack2) predicate (Snapshot.ofSeq (nameof stack1) stack1) (Snapshot.ofSeq (nameof stack2) stack2) + +[] +let find (predicate : 'T -> bool) (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + + match firstMatch predicate stack with + | ValueSome (struct (_, item)) -> item + | ValueNone -> Raise.indexNotFound () + +[] +let findBack (predicate : 'T -> bool) (stack : Stack<'T>) = + match lastMatch predicate stack with + | ValueSome (struct (_, item)) -> item + | ValueNone -> Raise.indexNotFound () + +[] +let findIndex (predicate : 'T -> bool) (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + + match firstMatch predicate stack with + | ValueSome (struct (index, _)) -> index + | ValueNone -> Raise.indexNotFound () + +[] +let findIndexBack (predicate : 'T -> bool) (stack : Stack<'T>) = + match lastMatch predicate stack with + | ValueSome (struct (index, _)) -> index + | ValueNone -> Raise.indexNotFound () + +[] +let forall (predicate : 'T -> bool) (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + (firstMatch (fun item -> not (predicate item)) stack).IsNone + +[] +let forall2 predicate (stack1 : Stack<'T1>) (stack2 : Stack<'T2>) = + ListArrays.forall2 + (nameof stack1) + (nameof stack2) + predicate + (Snapshot.ofSeq (nameof stack1) stack1) + (Snapshot.ofSeq (nameof stack2) stack2) + +[] +let pick (chooser : 'T -> 'U voption) (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + + match firstChoice chooser stack with + | ValueSome value -> value + | ValueNone -> Raise.indexNotFound () + +[] +let tryFind (predicate : 'T -> bool) (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + + match firstMatch predicate stack with + | ValueSome (struct (_, item)) -> ValueSome item + | ValueNone -> ValueNone + +[] +let tryFindBack (predicate : 'T -> bool) (stack : Stack<'T>) = + match lastMatch predicate stack with + | ValueSome (struct (_, item)) -> ValueSome item + | ValueNone -> ValueNone + +[] +let tryFindIndex (predicate : 'T -> bool) (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + + match firstMatch predicate stack with + | ValueSome (struct (index, _)) -> ValueSome index + | ValueNone -> ValueNone + +[] +let tryFindIndexBack (predicate : 'T -> bool) (stack : Stack<'T>) = + match lastMatch predicate stack with + | ValueSome (struct (index, _)) -> ValueSome index + | ValueNone -> ValueNone + +[] +let tryPick (chooser : 'T -> 'U voption) (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + firstChoice chooser stack + +// ---- Transformation ---- + +[] +let allPairs (stack1 : Stack<'T1>) (stack2 : Stack<'T2>) = + Arrays.allPairs structPair (Snapshot.ofSeq (nameof stack1) stack1) (Snapshot.ofSeq (nameof stack2) stack2) + |> ofArrayUnchecked + +[] +let append (stack1 : Stack<'T>) (stack2 : Stack<'T>) = + checkNotNull (nameof stack1) stack1 + checkNotNull (nameof stack2) stack2 + + if stack1.IsEmpty then + stack2 + elif stack2.IsEmpty then + stack1 + else + // The elements of the first stack go on top of the second one, whose nodes the result shares + pushArray (Snapshot.ofSeq (nameof stack1) stack1) stack2 + +[] +let choose (chooser : 'T -> 'U voption) (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + let chosen = ResizeArray<'U>() + + for item in stack do + match chooser item with + | ValueSome value -> chosen.Add value + | ValueNone -> () + + ofArrayUnchecked (chosen.ToArray ()) + +[] +let chunkBySize chunkSize (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + ListChecks.positive (nameof chunkSize) chunkSize + + Array.chunkBySize chunkSize (Snapshot.ofSeq (nameof stack) stack) + |> Array.map ofArrayUnchecked + |> ofArrayUnchecked + +[] +let collect (mapping : 'T -> Stack<'U>) (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + let collected = ResizeArray<'U>() + + for item in stack do + collected.AddRange (mapping item) + + ofArrayUnchecked (collected.ToArray ()) + +[] +let concat (stacks : seq>) = + checkNotNullOrDefaultSeq (nameof stacks) stacks + let concatenated = ResizeArray<'T>() + + for stack in stacks do + // A null element is a null input, as a default element is for FlatList.concat (ADR 0001, D6) + checkNotNull (nameof stacks) stack + concatenated.AddRange stack + + ofArrayUnchecked (concatenated.ToArray ()) + +[] +let cons (value : 'T) (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + stack.Push value + +[] +let consMany (values : seq<'T>) (stack : Stack<'T>) = + checkNotNullOrDefaultSeq (nameof values) values + checkNotNull (nameof stack) stack + pushArray (Snapshot.ofSeq (nameof values) values) stack + +[] +let filter (predicate : 'T -> bool) (stack : Stack<'T>) = + Array.filter predicate (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let indexed (stack : Stack<'T>) = + Arrays.indexed structPair (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let map (mapping : 'T -> 'U) (stack : Stack<'T>) = + Array.map mapping (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let map2 (mapping : 'T1 -> 'T2 -> 'U) (stack1 : Stack<'T1>) (stack2 : Stack<'T2>) = + ListArrays.map2 + (nameof stack1) + (nameof stack2) + mapping + (Snapshot.ofSeq (nameof stack1) stack1) + (Snapshot.ofSeq (nameof stack2) stack2) + |> ofArrayUnchecked + +[] +let map3 (mapping : 'T1 -> 'T2 -> 'T3 -> 'U) (stack1 : Stack<'T1>) (stack2 : Stack<'T2>) (stack3 : Stack<'T3>) = + ListArrays.map3 + (nameof stack1) + (nameof stack2) + (nameof stack3) + mapping + (Snapshot.ofSeq (nameof stack1) stack1) + (Snapshot.ofSeq (nameof stack2) stack2) + (Snapshot.ofSeq (nameof stack3) stack3) + |> ofArrayUnchecked + +[] +let mapi (mapping : int -> 'T -> 'U) (stack : Stack<'T>) = + Array.mapi mapping (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let mapi2 (mapping : int -> 'T1 -> 'T2 -> 'U) (stack1 : Stack<'T1>) (stack2 : Stack<'T2>) = + ListArrays.mapi2 + (nameof stack1) + (nameof stack2) + mapping + (Snapshot.ofSeq (nameof stack1) stack1) + (Snapshot.ofSeq (nameof stack2) stack2) + |> ofArrayUnchecked + +[] +let pairwise (stack : Stack<'T>) = + Arrays.pairwise structPair (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let permute indexMap (stack : Stack<'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 stack) stack) + |> ofArrayUnchecked + +[] +let rev (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + + if Lengths.stackUpTo 2 stack <= 1 then + stack + else + // Pushing the elements in their order leaves the last one on top + let mutable reversed = ImmutableStack<'T>.Empty + + for item in stack do + reversed <- reversed.Push item + + reversed + +[] +let transpose (stacks : seq>) = + checkNotNullOrDefaultSeq (nameof stacks) stacks + + let arrays = + stacks + |> Seq.map (fun stack -> Snapshot.ofSeq (nameof stacks) stack) + |> Seq.toArray + + // List.transpose names the lists "list.[0]", "list.[1]", …, which are the stacks "stack.[0]", "stack.[1]", … here; + // once their lengths agree, Array.transpose agrees with it + ListChecks.transpose "stack" (arrays |> Array.map _.Length) + + Array.transpose arrays + |> Array.map ofArrayUnchecked + |> ofArrayUnchecked + +[] +let unzip (stack : Stack) = + let struct (first, second) = Arrays.unzip id (Snapshot.ofSeq (nameof stack) stack) + struct (ofArrayUnchecked first, ofArrayUnchecked second) + +[] +let unzip3 (stack : Stack) = + let struct (first, second, third) = Arrays.unzip3 id (Snapshot.ofSeq (nameof stack) stack) + struct (ofArrayUnchecked first, ofArrayUnchecked second, ofArrayUnchecked third) + +[] +let where (predicate : 'T -> bool) (stack : Stack<'T>) = + Array.filter predicate (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let windowed windowSize (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + ListChecks.positive (nameof windowSize) windowSize + + Array.windowed windowSize (Snapshot.ofSeq (nameof stack) stack) + |> Array.map ofArrayUnchecked + |> ofArrayUnchecked + +[] +let zip (stack1 : Stack<'T1>) (stack2 : Stack<'T2>) = + let array1 = Snapshot.ofSeq (nameof stack1) stack1 + let array2 = Snapshot.ofSeq (nameof stack2) stack2 + ListChecks.sameLengths (nameof stack1) array1.Length (nameof stack2) array2.Length + Arrays.zip structPair array1 array2 |> ofArrayUnchecked + +[] +let zip3 (stack1 : Stack<'T1>) (stack2 : Stack<'T2>) (stack3 : Stack<'T3>) = + let array1 = Snapshot.ofSeq (nameof stack1) stack1 + let array2 = Snapshot.ofSeq (nameof stack2) stack2 + let array3 = Snapshot.ofSeq (nameof stack3) stack3 + ListChecks.zip3 (nameof stack1) array1.Length (nameof stack2) array2.Length (nameof stack3) array3.Length + + Arrays.zip3 structTriple array1 array2 array3 + |> ofArrayUnchecked + +// ---- Folding ---- + +// The inline functions can use public members only, so they check the stack themselves; the empty stack throws what +// List.average and List.averageBy throw, with the name "source" +[] +let inline average (stack : Stack< ^T >) : ^T = + if obj.ReferenceEquals (stack, null) then + nullArg (nameof stack) + + if stack.IsEmpty then + invalidArg "source" ErrorStrings.InputSequenceEmpty + + let mutable sum = LanguagePrimitives.GenericZero< ^T> + let mutable count = 0 + + for item in stack do + sum <- Checked.(+) sum item + count <- count + 1 + + LanguagePrimitives.DivideByInt< ^T> sum count + +[] +let inline averageBy ([] projection : 'T -> ^U) (stack : Stack<'T>) : ^U = + if obj.ReferenceEquals (stack, null) then + nullArg (nameof stack) + + if stack.IsEmpty then + invalidArg "source" ErrorStrings.InputSequenceEmpty + + let mutable sum = LanguagePrimitives.GenericZero< ^U> + let mutable count = 0 + + for item in stack do + sum <- Checked.(+) sum (projection item) + count <- count + 1 + + LanguagePrimitives.DivideByInt< ^U> sum count + +[] +let compareWith (comparer : 'T -> 'T -> int) (stack1 : Stack<'T>) (stack2 : Stack<'T>) = + // Array.compareWith compares as List.compareWith does: the first difference, then the lengths + Array.compareWith comparer (Snapshot.ofSeq (nameof stack1) stack1) (Snapshot.ofSeq (nameof stack2) stack2) + +[] +let fold (folder : 'State -> 'T -> 'State) (state : 'State) (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + let mutable state = state + + for item in stack do + state <- folder state item + + state + +[] +let fold2 (folder : 'State -> 'T1 -> 'T2 -> 'State) (state : 'State) (stack1 : Stack<'T1>) (stack2 : Stack<'T2>) = + ListArrays.fold2 + (nameof stack1) + (nameof stack2) + folder + state + (Snapshot.ofSeq (nameof stack1) stack1) + (Snapshot.ofSeq (nameof stack2) stack2) + +[] +let foldBack (folder : 'T -> 'State -> 'State) (stack : Stack<'T>) (state : 'State) = + Array.foldBack folder (Snapshot.ofSeq (nameof stack) stack) state + +[] +let foldBack2 (folder : 'T1 -> 'T2 -> 'State -> 'State) (stack1 : Stack<'T1>) (stack2 : Stack<'T2>) (state : 'State) = + let array1 = Snapshot.ofSeq (nameof stack1) stack1 + let array2 = Snapshot.ofSeq (nameof stack2) stack2 + // List.foldBack2 checks the lengths before it calls the folder + ListChecks.foldBack2 (nameof stack1) array1.Length (nameof stack2) array2.Length + Array.foldBack2 folder array1 array2 state + +[] +let iter (action : 'T -> unit) (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + + for item in stack do + action item + +[] +let iter2 (action : 'T1 -> 'T2 -> unit) (stack1 : Stack<'T1>) (stack2 : Stack<'T2>) = + ListArrays.iter2 + (nameof stack1) + (nameof stack2) + action + (Snapshot.ofSeq (nameof stack1) stack1) + (Snapshot.ofSeq (nameof stack2) stack2) + +[] +let iteri (action : int -> 'T -> unit) (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + let mutable index = 0 + + for item in stack do + action index item + index <- index + 1 + +[] +let iteri2 (action : int -> 'T1 -> 'T2 -> unit) (stack1 : Stack<'T1>) (stack2 : Stack<'T2>) = + ListArrays.iteri2 + (nameof stack1) + (nameof stack2) + action + (Snapshot.ofSeq (nameof stack1) stack1) + (Snapshot.ofSeq (nameof stack2) stack2) + +[] +let mapFold (mapping : 'State -> 'T -> struct ('Result * 'State)) (state : 'State) (stack : Stack<'T>) = + let source = Snapshot.ofSeq (nameof stack) stack + 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 (ofArrayUnchecked results, state) + +[] +let mapFoldBack (mapping : 'T -> 'State -> struct ('Result * 'State)) (stack : Stack<'T>) (state : 'State) = + let source = Snapshot.ofSeq (nameof stack) stack + 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 (ofArrayUnchecked results, state) + +// Seq.max and the others compare as the List functions do once the stack has elements, so the List checks come first + +[] +let max (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + ListChecks.notEmptySequence (nameof stack) (Lengths.stackUpTo 1 stack) + Seq.max stack + +[] +let maxBy (projection : 'T -> 'U) (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + ListChecks.notEmptySequence (nameof stack) (Lengths.stackUpTo 1 stack) + Seq.maxBy projection stack + +[] +let min (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + ListChecks.notEmptySequence (nameof stack) (Lengths.stackUpTo 1 stack) + Seq.min stack + +[] +let minBy (projection : 'T -> 'U) (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + ListChecks.notEmptySequence (nameof stack) (Lengths.stackUpTo 1 stack) + Seq.minBy projection stack + +[] +let reduce (reduction : 'T -> 'T -> 'T) (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + ListChecks.notEmpty (nameof stack) (Lengths.stackUpTo 1 stack) + Seq.reduce reduction stack + +[] +let reduceBack (reduction : 'T -> 'T -> 'T) (stack : Stack<'T>) = + let array = Snapshot.ofSeq (nameof stack) stack + ListChecks.notEmpty (nameof stack) array.Length + Array.reduceBack reduction array + +[] +let scan (folder : 'State -> 'T -> 'State) (state : 'State) (stack : Stack<'T>) = + Array.scan folder state (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let scanBack (folder : 'T -> 'State -> 'State) (stack : Stack<'T>) (state : 'State) = + Array.scanBack folder (Snapshot.ofSeq (nameof stack) stack) state + |> ofArrayUnchecked + +[] +let inline sum (stack : Stack< ^T >) : ^T = + if obj.ReferenceEquals (stack, null) then + nullArg (nameof stack) + + let mutable sum = LanguagePrimitives.GenericZero< ^T> + + for item in stack do + sum <- Checked.(+) sum item + + sum + +[] +let inline sumBy ([] projection : 'T -> ^U) (stack : Stack<'T>) : ^U = + if obj.ReferenceEquals (stack, null) then + nullArg (nameof stack) + + let mutable sum = LanguagePrimitives.GenericZero< ^U> + + for item in stack do + sum <- Checked.(+) sum (projection item) + + sum + +// ---- Partitioning and grouping ---- + +[] +let countBy (projection : 'T -> 'Key) (stack : Stack<'T>) = + Arrays.countBy structPair projection (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let distinct (stack : Stack<'T>) = + // Array.distinct compares in F# structural equality, as List.distinct does + Array.distinct (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let distinctBy (projection : 'T -> 'Key) (stack : Stack<'T>) = + Array.distinctBy projection (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let except (itemsToExclude : seq<'T>) (stack : Stack<'T>) = + // List.except checks the items to exclude first and does not read them for an empty list + checkNotNullOrDefaultSeq (nameof itemsToExclude) itemsToExclude + checkNotNull (nameof stack) stack + + if stack.IsEmpty then + stack + else + Array.except itemsToExclude (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let groupBy (projection : 'T -> 'Key) (stack : Stack<'T>) = + Arrays.groupBy (fun key items -> struct (key, ofArrayUnchecked items)) projection (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let partition (predicate : 'T -> bool) (stack : Stack<'T>) = + let accepted, rejected = Array.partition predicate (Snapshot.ofSeq (nameof stack) stack) + struct (ofArrayUnchecked accepted, ofArrayUnchecked rejected) + +[] +let partitionWith (partitioner : 'T -> Choice<'T1, 'T2>) (stack : Stack<'T>) = + let first, second = Array.partitionWith partitioner (Snapshot.ofSeq (nameof stack) stack) + struct (ofArrayUnchecked first, ofArrayUnchecked second) + +[] +let skip count (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + // A stack shorter than the count stops the counting at its length, which the exception reports + ListChecks.skip (nameof count) count (Lengths.stackUpTo count stack) + dropTop count stack + +[] +let skipLast count (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + + if count <= 0 then + stack + else + let length = Lengths.stackUpTo Int32.MaxValue stack + // The checks of List.skip + ListChecks.skip (nameof count) count length + let struct (top, _) = splitTop (length - count) stack + ofArrayUnchecked top + +[] +let skipWhile (predicate : 'T -> bool) (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + let mutable node = stack + + while not node.IsEmpty && predicate (node.Peek ()) do + node <- node.Pop () + + node + +[] +let splitAt index (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + // A stack shorter than the index stops the counting at its length, which the exception reports + ListChecks.splitAt (nameof index) index (Lengths.stackUpTo index stack) + let struct (top, rest) = splitTop index stack + struct ((if rest.IsEmpty then stack else ofArrayUnchecked top), rest) + +[] +let splitInto count (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + ListChecks.positive (nameof count) count + + Array.splitInto count (Snapshot.ofSeq (nameof stack) stack) + |> Array.map ofArrayUnchecked + |> ofArrayUnchecked + +[] +let take count (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + // A stack shorter than the count stops the counting at its length, which the exception reports + ListChecks.take (nameof count) count (Lengths.stackUpTo count stack) + let struct (top, rest) = splitTop count stack + if rest.IsEmpty then stack else ofArrayUnchecked top + +[] +let takeLast count (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + let length = Lengths.stackUpTo Int32.MaxValue stack + // The checks of List.take + ListChecks.take (nameof count) count length + dropTop (length - count) stack + +[] +let takeWhile (predicate : 'T -> bool) (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + let taken = ResizeArray<'T>() + let mutable node = stack + let mutable go = true + + while go && not node.IsEmpty do + let item = node.Peek () + + if predicate item then + taken.Add item + node <- node.Pop () + else + go <- false + + if node.IsEmpty then + stack + else + ofArrayUnchecked (taken.ToArray ()) + +[] +let truncate count (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + let struct (top, rest) = splitTop (Lengths.stackUpTo count stack) stack + if rest.IsEmpty then stack else ofArrayUnchecked top + +// ---- 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. The sorted elements go to an array, which +// a stack is pushed from. A stack of one element is returned without a call of the projection or the comparer, as List +// returns it. + +[] +let sort (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + + if Lengths.stackUpTo 2 stack <= 1 then + stack + else + Enumerable.ToArray (Seq.sort stack) |> ofArrayUnchecked + +[] +let sortBy (projection : 'T -> 'Key) (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + + if Lengths.stackUpTo 2 stack <= 1 then + stack + else + stack.OrderBy(Func<'T, 'Key> projection, DefaultComparer<'Key>.Instance).ToArray() + |> ofArrayUnchecked + +[] +let sortByDescending (projection : 'T -> 'Key) (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + + if Lengths.stackUpTo 2 stack <= 1 then + stack + else + Enumerable.ToArray (DescendingSorts.sortByDescending projection stack) + |> ofArrayUnchecked + +[] +let sortDescending (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + + if Lengths.stackUpTo 2 stack <= 1 then + stack + else + Enumerable.ToArray (DescendingSorts.sortDescending stack) + |> ofArrayUnchecked + +[] +let sortWith (comparer : 'T -> 'T -> int) (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + + if Lengths.stackUpTo 2 stack <= 1 then + stack + else + Enumerable.ToArray (Seq.sortWith comparer stack) + |> ofArrayUnchecked + +[] +let sortWithComparer (comparer : IComparer<'T>) (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + + if Lengths.stackUpTo 2 stack <= 1 then + stack + else + stack.Order(comparer).ToArray() |> ofArrayUnchecked + +// ---- Random ---- + +// The functions draw the random numbers as the List functions do, from a snapshot after the List checks: the Array +// functions throw other messages for an empty array + +[] +let randomChoice (source : Stack<'T>) = + let array = Snapshot.ofSeq (nameof source) source + ListChecks.notEmptySequence (nameof source) array.Length + array[Random.Shared.Next (0, array.Length)] + +[] +let randomChoiceBy (randomizer : unit -> float) (source : Stack<'T>) = + let array = Snapshot.ofSeq (nameof source) source + ListChecks.notEmptySequence (nameof source) array.Length + array[ListChecks.randomIndex (nameof randomizer) randomizer array.Length] + +[] +let randomChoiceWith (random : Random) (source : Stack<'T>) = + checkNotNull (nameof random) random + let array = Snapshot.ofSeq (nameof source) source + ListChecks.notEmptySequence (nameof source) array.Length + array[random.Next (0, array.Length)] + +[] +let randomChoices count (source : Stack<'T>) = + let array = Snapshot.ofSeq (nameof source) source + ListChecks.randomChoices (nameof count) count (nameof source) array.Length + Array.randomChoices count array |> ofArrayUnchecked + +[] +let randomChoicesBy (randomizer : unit -> float) count (source : Stack<'T>) = + let array = Snapshot.ofSeq (nameof source) source + ListChecks.randomChoices (nameof count) count (nameof source) array.Length + + Array.randomChoicesBy randomizer count array + |> ofArrayUnchecked + +[] +let randomChoicesWith (random : Random) count (source : Stack<'T>) = + checkNotNull (nameof random) random + let array = Snapshot.ofSeq (nameof source) source + ListChecks.randomChoices (nameof count) count (nameof source) array.Length + + Array.randomChoicesWith random count array + |> ofArrayUnchecked + +[] +let randomSample count (source : Stack<'T>) = + let array = Snapshot.ofSeq (nameof source) source + ListChecks.randomSample (nameof count) count (nameof source) array.Length + Array.randomSample count array |> ofArrayUnchecked + +[] +let randomSampleBy (randomizer : unit -> float) count (source : Stack<'T>) = + let array = Snapshot.ofSeq (nameof source) source + ListChecks.randomSample (nameof count) count (nameof source) array.Length + + Array.randomSampleBy randomizer count array + |> ofArrayUnchecked + +[] +let randomSampleWith (random : Random) count (source : Stack<'T>) = + checkNotNull (nameof random) random + let array = Snapshot.ofSeq (nameof source) source + ListChecks.randomSample (nameof count) count (nameof source) array.Length + + Array.randomSampleWith random count array + |> ofArrayUnchecked + +[] +let randomShuffle (source : Stack<'T>) = + let array = Snapshot.ofSeq (nameof source) source + Array.randomShuffleInPlace array + ofArrayUnchecked array + +[] +let randomShuffleBy (randomizer : unit -> float) (source : Stack<'T>) = + let array = Snapshot.ofSeq (nameof source) source + Array.randomShuffleInPlaceBy randomizer array + ofArrayUnchecked array + +[] +let randomShuffleWith (random : Random) (source : Stack<'T>) = + checkNotNull (nameof random) random + let array = Snapshot.ofSeq (nameof source) source + Array.randomShuffleInPlaceWith random array + ofArrayUnchecked array + +// ---- Conversion ---- + +[] +let toArray (stack : Stack<'T>) = Snapshot.ofSeq (nameof stack) stack + +[] +let toList (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + List.ofSeq stack + +[] +let toSeq (stack : Stack<'T>) = + checkNotNull (nameof stack) stack + stack :> seq<'T> diff --git a/src/FSharp.Collections.Immutable/Stack/Stack.fsi b/src/FSharp.Collections.Immutable/Stack/Stack.fsi new file mode 100644 index 00000000..dfc41ee5 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Stack/Stack.fsi @@ -0,0 +1,1545 @@ +/// +/// Functions for the immutable stack . +/// +/// +/// +/// The module mirrors : each function has the semantics, the +/// argument checks and the exceptions of the function with the same name in that module (ADR 0001), and its sorting +/// functions are stable. Where that module names a parameter list, list1, list2, list3 or +/// lists, this module names it stack, stack1, stack2, stack3 or stacks, and the +/// exceptions report these names. Functions that FSharp.Core lacks wrap a member of +/// or follow the function of that module that they resemble. +/// +/// +/// The order of a stack is the order in which it enumerates its elements, from the top to the bottom: the first element +/// is the top, which +/// returns, and the last element is the bottom. Every function treats a stack as a list in this order. +/// is the +/// inverse of : +/// it puts the first element of the sequence on top. +/// +/// pushes a value, which becomes the first element. +/// +/// +/// A stack reaches its elements from the top only. The functions that remove the first elements, such as +/// and +/// , +/// return the stack below them without a copy. The other transformations copy the elements to an array, compute the +/// result and push it onto a new stack. +/// +/// +/// Every function throws for a stack, 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.Stack + +open System +open System.Collections.Generic +open System.Collections.Immutable +// Opened after System.Collections.Generic, so that Stack names the abbreviation of this library, not the mutable stack +open FSharp.Collections.Immutable + +// ---- Creation ---- + +/// +/// The empty stack. +/// +/// +/// O(1). The value is . +/// +[] +[] +val empty<'T> : Stack<'T> + +/// Creates a stack from a function of the index. +/// The length of the stack. +/// The function that computes the element at each index, called in index order. +/// The created stack, with the element at index 0 on top. +/// Thrown when is negative. +/// O(n). +[] +val init : length : int -> initializer : (int -> 'T) -> Stack<'T> + +/// +/// Creates a stack from a copy of an array. +/// +/// The input array. +/// The stack of the elements of the array, with the first element on top. +/// Thrown when is . +/// +/// O(n). The function pushes the elements from the last one to the first one. Later changes to the array do not change the stack. +/// +[] +val ofArray : array : 'T array -> Stack<'T> + +/// +/// Creates a stack from a list. +/// +/// The input list. +/// The stack of the elements of the list, with the first element on top. +/// +/// O(n). The function pushes the elements from the last one to the first one. +/// +[] +val ofList : list : 'T list -> Stack<'T> + +/// +/// Creates a stack from a copy of a read-only span. +/// +/// The input span. +/// The stack of the elements of the span, with the first element on top. +/// +/// O(n). The function pushes the elements from the last one to the first one. It does not wrap +/// , which leaves the +/// last element on top. +/// +[] +val ofReadOnlySpan : span : ReadOnlySpan<'T> -> Stack<'T> + +/// +/// Creates a stack from a sequence. +/// +/// The input sequence. +/// The stack of the elements of the sequence, with the first element on top. +/// Thrown when is or a default flat list. +/// +/// O(n). The function is the inverse of +/// : it +/// pushes the elements from the last one to the first one, while +/// +/// leaves the last element on top. A stack is returned itself, without a copy. +/// +[] +val ofSeq : source : seq<'T> -> Stack<'T> + +/// +/// Creates a stack from a copy of a span. +/// +/// The input span. +/// The stack of the elements of the span, with the first element on top. +/// +/// O(n). The function pushes the elements from the last one to the first one. It does not wrap +/// , which leaves the +/// last element on top. +/// +[] +val ofSpan : span : Span<'T> -> Stack<'T> + +/// Creates a stack whose elements are all the given value. +/// The length of the stack. +/// The value of every element. +/// The created stack. +/// Thrown when is negative. +/// O(n). +[] +val replicate : count : int -> initial : 'T -> Stack<'T> + +/// Creates a stack of one element. +/// The element. +/// The stack of one element. +/// O(1). +[] +val singleton : value : 'T -> Stack<'T> + +/// +/// Creates a stack 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 stack, with the first generated element on top. +/// +/// O(n). +/// +[] +val unfold : generator : ('State -> struct ('T * 'State) voption) -> state : 'State -> Stack<'T> + +// ---- Properties ---- + +/// +/// Returns whether the stack has no elements. +/// +/// The input stack. +/// when the stack is empty. +/// +/// O(1). The function wraps . +/// +[] +val isEmpty : stack : Stack<'T> -> bool + +/// +/// Returns the number of elements of the stack. +/// +/// The input stack. +/// The length of the stack. +/// +/// O(n): a stack does not store its length, so the function counts the elements. +/// +[] +val length : stack : Stack<'T> -> int + +// ---- Access ---- + +/// Returns the only element of the stack. +/// The input stack. +/// The only element. +/// Thrown when the stack does not have exactly one element. +/// O(1). +[] +val exactlyOne : stack : Stack<'T> -> 'T + +/// +/// Returns the first element of the stack, the element on top. +/// +/// The input stack. +/// The first element. +/// Thrown when the stack is empty. +/// +/// O(1). The function wraps . +/// +[] +val head : stack : Stack<'T> -> 'T + +/// +/// Returns the last element of the stack, the element at the bottom. +/// +/// The input stack. +/// The last element. +/// Thrown when the stack is empty. +/// +/// O(n). +/// +[] +val last : stack : Stack<'T> -> 'T + +/// +/// Returns the stack without its first element: the stack below the top. +/// +/// The input stack. +/// The elements after the first one. +/// Thrown when the stack is empty. +/// +/// O(1). The result is the stack that the top was pushed onto, without a copy. The function wraps +/// . +/// +[] +val tail : stack : Stack<'T> -> Stack<'T> + +/// +/// Returns the only element of the stack, or ValueNone when it does not have exactly one element. +/// +/// The input stack. +/// The only element, or ValueNone. +/// +/// O(1). +/// +[] +val tryExactlyOne : stack : Stack<'T> -> 'T voption + +/// +/// Returns the first element of the stack, or ValueNone when it is empty. +/// +/// The input stack. +/// The first element, or ValueNone. +/// +/// O(1). +/// +[] +val tryHead : stack : Stack<'T> -> 'T voption + +/// +/// Returns the last element of the stack, or ValueNone when it is empty. +/// +/// The input stack. +/// The last element, or ValueNone. +/// +/// O(n). +/// +[] +val tryLast : stack : Stack<'T> -> 'T voption + +/// +/// Returns the stack without its first element, or ValueNone when it is empty. +/// +/// The input stack. +/// The elements after the first one, or ValueNone. +/// +/// O(1). The result is the stack that the top was pushed onto, without a copy. +/// +[] +val tryTail : stack : Stack<'T> -> Stack<'T> voption + +/// +/// Returns the first element and the other elements, or ValueNone when the stack is empty. +/// +/// The input stack. +/// The first element and the elements after it, or ValueNone. +/// +/// O(1). +/// +[] +val tryUncons : stack : Stack<'T> -> struct ('T * Stack<'T>) voption + +/// +/// Returns the first element and the other elements. +/// +/// The input stack. +/// The first element and the elements after it. +/// Thrown when the stack is empty. +/// +/// O(1). The function throws what +/// throws. +/// +[] +val uncons : stack : Stack<'T> -> struct ('T * Stack<'T>) + +/// +/// Matches a stack with a first element, and returns that element and the other elements. +/// +/// The input stack. +/// The first element and the elements after it, or ValueNone for an empty stack. +/// +/// O(1). A struct partial pattern, so that a match allocates no option. +/// +[] +[] +val (|Cons|_|) : stack : Stack<'T> -> struct ('T * Stack<'T>) voption + +/// Matches an empty stack. +/// The input stack. +/// when the stack is empty. +/// O(1). +[] +val (|Nil|_|) : stack : Stack<'T> -> bool + +// ---- Search ---- + +/// +/// Returns whether the stack contains an element equal to a value, in F# structural equality. +/// +/// The value to find. +/// The input stack. +/// when an element equals the value. +/// +/// O(n). +/// +[] +val contains : value : 'T -> source : Stack<'T> -> bool when 'T : equality + +/// Returns whether any element satisfies the predicate. +/// The function to test the elements. +/// The input stack. +/// when an element satisfies the predicate. +/// O(n). +[] +val exists : predicate : ('T -> bool) -> stack : Stack<'T> -> bool + +/// +/// Returns whether any pair of elements at the same index satisfies the predicate. +/// +/// The function to test the pairs. +/// The first stack. +/// The second stack. +/// when a pair satisfies the predicate. +/// Thrown when no pair satisfies the predicate and the stacks have different lengths. +/// +/// O(n). +/// +[] +val exists2 : predicate : ('T1 -> 'T2 -> bool) -> stack1 : Stack<'T1> -> stack2 : Stack<'T2> -> bool + +/// Returns the first element that satisfies the predicate. +/// The function to test the elements. +/// The input stack. +/// The first element that satisfies the predicate. +/// Thrown when no element satisfies the predicate. +/// O(n). +[] +val find : predicate : ('T -> bool) -> stack : Stack<'T> -> 'T + +/// Returns the last element that satisfies the predicate. +/// The function to test the elements. +/// The input stack. +/// The last element that satisfies the predicate. +/// Thrown when no element satisfies the predicate. +/// O(n). +[] +val findBack : predicate : ('T -> bool) -> stack : Stack<'T> -> 'T + +/// +/// Returns the index of the first element that satisfies the predicate. +/// +/// The function to test the elements. +/// The input stack. +/// The index of the first element that satisfies the predicate; the top has the index 0. +/// Thrown when no element satisfies the predicate. +/// +/// O(n). +/// +[] +val findIndex : predicate : ('T -> bool) -> stack : Stack<'T> -> int + +/// +/// Returns the index of the last element that satisfies the predicate. +/// +/// The function to test the elements. +/// The input stack. +/// The index of the last element that satisfies the predicate; the top has the index 0. +/// Thrown when no element satisfies the predicate. +/// +/// O(n). +/// +[] +val findIndexBack : predicate : ('T -> bool) -> stack : Stack<'T> -> int + +/// Returns whether all elements satisfy the predicate. +/// The function to test the elements. +/// The input stack. +/// when every element satisfies the predicate. +/// O(n). +[] +val forall : predicate : ('T -> bool) -> stack : Stack<'T> -> bool + +/// +/// Returns whether all pairs of elements at the same index satisfy the predicate. +/// +/// The function to test the pairs. +/// The first stack. +/// The second stack. +/// when every pair satisfies the predicate. +/// Thrown when every pair satisfies the predicate and the stacks have different lengths. +/// +/// O(n). +/// +[] +val forall2 : predicate : ('T1 -> 'T2 -> bool) -> stack1 : Stack<'T1> -> stack2 : Stack<'T2> -> bool + +/// +/// Returns the first value that the chooser returns for the elements. +/// +/// The function that returns a value, or ValueNone to go on. +/// The input stack. +/// The first chosen value. +/// Thrown when the chooser returns ValueNone for every element. +/// +/// O(n). +/// +[] +val pick : chooser : ('T -> 'U voption) -> stack : Stack<'T> -> 'U + +/// +/// Returns the first element that satisfies the predicate, or ValueNone. +/// +/// The function to test the elements. +/// The input stack. +/// The first element that satisfies the predicate, or ValueNone. +/// +/// O(n). +/// +[] +val tryFind : predicate : ('T -> bool) -> stack : Stack<'T> -> 'T voption + +/// +/// Returns the last element that satisfies the predicate, or ValueNone. +/// +/// The function to test the elements. +/// The input stack. +/// The last element that satisfies the predicate, or ValueNone. +/// +/// O(n). +/// +[] +val tryFindBack : predicate : ('T -> bool) -> stack : Stack<'T> -> 'T voption + +/// +/// Returns the index of the first element that satisfies the predicate, or ValueNone. +/// +/// The function to test the elements. +/// The input stack. +/// The index of the first element that satisfies the predicate, or ValueNone. +/// +/// O(n). +/// +[] +val tryFindIndex : predicate : ('T -> bool) -> stack : Stack<'T> -> int voption + +/// +/// Returns the index of the last element that satisfies the predicate, or ValueNone. +/// +/// The function to test the elements. +/// The input stack. +/// The index of the last element that satisfies the predicate, or ValueNone. +/// +/// O(n). +/// +[] +val tryFindIndexBack : predicate : ('T -> bool) -> stack : Stack<'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 stack. +/// The first chosen value, or ValueNone. +/// +/// O(n). +/// +[] +val tryPick : chooser : ('T -> 'U voption) -> stack : Stack<'T> -> 'U voption + +// ---- Transformation ---- + +/// +/// Returns every element of the first stack paired with every element of the second one. +/// +/// The first stack. +/// The second stack. +/// The pairs, in the order of the first stack and then of the second one. +/// +/// O(n × m). +/// +[] +val allPairs : stack1 : Stack<'T1> -> stack2 : Stack<'T2> -> Stack + +/// +/// Returns a stack of the elements of the first stack followed by the elements of the second one. +/// +/// The first stack, whose elements come first, on top. +/// The second stack, whose elements come last. +/// The concatenation. +/// +/// O(n), where n is the length of the first stack. The function pushes the elements of the first stack onto the second +/// one, from the last element to the first one, so the result shares the nodes of the second stack. When one of the +/// stacks is empty, the function returns the other one. +/// +[] +val append : stack1 : Stack<'T> -> stack2 : Stack<'T> -> Stack<'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 stack. +/// The chosen values, in the order of their elements. +/// +/// O(n). +/// +[] +val choose : chooser : ('T -> 'U voption) -> stack : Stack<'T> -> Stack<'U> + +/// Divides the stack into chunks of a given size. +/// The maximum size of a chunk. +/// The input stack. +/// The chunks; the last one can be smaller. +/// Thrown when is not positive. +/// O(n). +[] +val chunkBySize : chunkSize : int -> stack : Stack<'T> -> Stack> + +/// +/// Concatenates the stacks that a function returns for the elements. +/// +/// The function that returns a stack for each element. +/// The input stack. +/// The concatenation of the returned stacks. +/// +/// O(n + the total length of the returned stacks). +/// +[] +val collect : mapping : ('T -> Stack<'U>) -> stack : Stack<'T> -> Stack<'U> + +/// Concatenates a sequence of stacks. +/// The stacks. +/// The concatenation, with the elements of the first stack on top. +/// Thrown when is or a default flat list, or one of its stacks is . +/// O(the total length). +[] +val concat : stacks : seq> -> Stack<'T> + +/// +/// Returns a stack with a value added before the first element: the value pushed on top. +/// +/// The value to add. +/// The input stack. +/// A stack of the value and the elements. +/// +/// O(1). The result shares the nodes of the input. The function wraps +/// . +/// +[] +val cons : value : 'T -> stack : Stack<'T> -> Stack<'T> + +/// +/// Returns a stack with values added before the first element, in their order: the first value ends on top. +/// +/// The values to add, in the order in which the result enumerates them. +/// The input stack. +/// A stack of the values followed by the elements of the input. +/// Thrown when is or a default flat list. +/// +/// O(m), where m is the number of values. The function pushes the values from the last one to the first one, so the +/// result shares the nodes of the input. +/// +[] +val consMany : values : seq<'T> -> stack : Stack<'T> -> Stack<'T> + +/// Returns the elements that satisfy the predicate. +/// The function to test the elements. +/// The input stack. +/// The elements that satisfy the predicate, in their order. +/// O(n). +[] +val filter : predicate : ('T -> bool) -> stack : Stack<'T> -> Stack<'T> + +/// Returns every element with its index. +/// The input stack. +/// The pairs of index and element; the top has the index 0. +/// O(n). +[] +val indexed : stack : Stack<'T> -> Stack + +/// 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 stack. +/// The results, in the order of the elements. +/// O(n). +[] +val map : mapping : ('T -> 'U) -> stack : Stack<'T> -> Stack<'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 stack. +/// The second stack. +/// The results. +/// Thrown when the stacks have different lengths. +/// +/// O(n). +/// +[] +val map2 : mapping : ('T1 -> 'T2 -> 'U) -> stack1 : Stack<'T1> -> stack2 : Stack<'T2> -> Stack<'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 stack. +/// The second stack. +/// The third stack. +/// The results. +/// Thrown when the stacks have different lengths. +/// +/// O(n). +/// +[] +val map3 : mapping : ('T1 -> 'T2 -> 'T3 -> 'U) -> stack1 : Stack<'T1> -> stack2 : Stack<'T2> -> stack3 : Stack<'T3> -> Stack<'U> + +/// +/// Returns the results of a function applied to each element and its index. +/// +/// The function to transform the elements. +/// The input stack. +/// The results. +/// +/// O(n). +/// +[] +val mapi : mapping : (int -> 'T -> 'U) -> stack : Stack<'T> -> Stack<'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 stack. +/// The second stack. +/// The results. +/// Thrown when the stacks have different lengths. +/// +/// O(n). +/// +[] +val mapi2 : mapping : (int -> 'T1 -> 'T2 -> 'U) -> stack1 : Stack<'T1> -> stack2 : Stack<'T2> -> Stack<'U> + +/// Returns every element with its successor. +/// The input stack. +/// The pairs of neighbours; empty for fewer than two elements. +/// O(n). +[] +val pairwise : stack : Stack<'T> -> Stack + +/// +/// 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 stack. +/// The permuted stack. +/// Thrown when the function does not compute a permutation. +/// +/// O(n). +/// +[] +val permute : indexMap : (int -> int) -> stack : Stack<'T> -> Stack<'T> + +/// +/// Returns the elements in reverse order. +/// +/// The input stack. +/// The reversed stack, whose first element is the last element of the input. +/// +/// O(n). The function pushes the elements onto an empty stack in their order. +/// +[] +val rev : stack : Stack<'T> -> Stack<'T> + +/// +/// Returns the transpose of a sequence of stacks of the same length. +/// +/// The stacks. +/// A stack whose element i holds the element i of every input stack. +/// Thrown when is or a default flat list, or one of its stacks is . +/// Thrown when the stacks have different lengths. +/// +/// O(the total length). +/// +[] +val transpose : stacks : seq> -> Stack> + +/// Splits a stack of pairs into two stacks. +/// The input stack. +/// The first components and the second components. +/// O(n). +[] +val unzip : stack : Stack -> struct (Stack<'T1> * Stack<'T2>) + +/// Splits a stack of triples into three stacks. +/// The input stack. +/// The first, the second and the third components. +/// O(n). +[] +val unzip3 : stack : Stack -> struct (Stack<'T1> * Stack<'T2> * Stack<'T3>) + +/// +/// Returns the elements that satisfy the predicate. +/// +/// The function to test the elements. +/// The input stack. +/// The elements that satisfy the predicate, in their order. +/// +/// O(n). A synonym of . +/// +[] +val where : predicate : ('T -> bool) -> stack : Stack<'T> -> Stack<'T> + +/// +/// Returns the windows of a given size that slide over the stack. +/// +/// The size of a window. +/// The input stack. +/// The windows; empty when the stack is shorter than a window. +/// Thrown when is not positive. +/// +/// O(n × windowSize). +/// +[] +val windowed : windowSize : int -> stack : Stack<'T> -> Stack> + +/// Combines two stacks into a stack of pairs. +/// The first stack. +/// The second stack. +/// The pairs of elements at the same index. +/// Thrown when the stacks have different lengths. +/// O(n). +[] +val zip : stack1 : Stack<'T1> -> stack2 : Stack<'T2> -> Stack + +/// Combines three stacks into a stack of triples. +/// The first stack. +/// The second stack. +/// The third stack. +/// The triples of elements at the same index. +/// Thrown when the stacks have different lengths. +/// O(n). +[] +val zip3 : stack1 : Stack<'T1> -> stack2 : Stack<'T2> -> stack3 : Stack<'T3> -> Stack + +// ---- Folding ---- + +/// Returns the average of the elements. +/// The input stack. +/// The average. +/// Thrown when the stack is empty. +/// O(n). +[] +val inline average : + stack : Stack< ^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 stack. +/// The average of the results. +/// Thrown when the stack is empty. +/// +/// O(n). +/// +[] +val inline averageBy : + projection : ('T -> ^U) -> stack : Stack<'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 stacks element by element with a comparer. +/// The function that compares two elements. +/// The first stack. +/// The second stack. +/// +/// The first non-zero result of the comparer, or the comparison of the lengths when every common element compares equal. +/// +/// O(n). +[] +val compareWith : comparer : ('T -> 'T -> int) -> stack1 : Stack<'T> -> stack2 : Stack<'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 stack. +/// The final state. +/// +/// O(n). +/// +[] +val fold : folder : ('State -> 'T -> 'State) -> state : 'State -> stack : Stack<'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 stack. +/// The second stack. +/// The final state. +/// Thrown when the stacks have different lengths. +/// +/// O(n). +/// +[] +val fold2 : folder : ('State -> 'T1 -> 'T2 -> 'State) -> state : 'State -> stack1 : Stack<'T1> -> stack2 : Stack<'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 stack. +/// The initial state. +/// The final state. +/// +/// O(n). +/// +[] +val foldBack : folder : ('T -> 'State -> 'State) -> stack : Stack<'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 stack. +/// The second stack. +/// The initial state. +/// The final state. +/// Thrown when the stacks have different lengths. +/// +/// O(n). +/// +[] +val foldBack2 : + folder : ('T1 -> 'T2 -> 'State -> 'State) -> stack1 : Stack<'T1> -> stack2 : Stack<'T2> -> state : 'State -> 'State + +/// Applies an action to each element. +/// The action. +/// The input stack. +/// O(n). +[] +val iter : action : ('T -> unit) -> stack : Stack<'T> -> unit + +/// +/// Applies an action to each pair of elements at the same index. +/// +/// The action. +/// The first stack. +/// The second stack. +/// Thrown when the stacks have different lengths. +/// +/// O(n). +/// +[] +val iter2 : action : ('T1 -> 'T2 -> unit) -> stack1 : Stack<'T1> -> stack2 : Stack<'T2> -> unit + +/// Applies an action to each element and its index. +/// The action. +/// The input stack. +/// O(n). +[] +val iteri : action : (int -> 'T -> unit) -> stack : Stack<'T> -> unit + +/// +/// Applies an action to the index and the pair of elements at that index. +/// +/// The action. +/// The first stack. +/// The second stack. +/// Thrown when the stacks have different lengths. +/// +/// O(n). +/// +[] +val iteri2 : action : (int -> 'T1 -> 'T2 -> unit) -> stack1 : Stack<'T1> -> stack2 : Stack<'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 stack. +/// The results and the final state. +/// +/// O(n). +/// +[] +val mapFold : + mapping : ('State -> 'T -> struct ('Result * 'State)) -> + state : 'State -> + stack : Stack<'T> -> + struct (Stack<'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 stack. +/// 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)) -> + stack : Stack<'T> -> + state : 'State -> + struct (Stack<'Result> * 'State) + +/// Returns the greatest element, in F# generic comparison. +/// The input stack. +/// The greatest element. +/// Thrown when the stack is empty. +/// O(n). +[] +val max : stack : Stack<'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 stack. +/// The first element with the greatest result. +/// Thrown when the stack is empty. +/// +/// O(n). +/// +[] +val maxBy : projection : ('T -> 'U) -> stack : Stack<'T> -> 'T when 'U : comparison + +/// Returns the smallest element, in F# generic comparison. +/// The input stack. +/// The smallest element. +/// Thrown when the stack is empty. +/// O(n). +[] +val min : stack : Stack<'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 stack. +/// The first element with the smallest result. +/// Thrown when the stack is empty. +/// +/// O(n). +/// +[] +val minBy : projection : ('T -> 'U) -> stack : Stack<'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 stack. +/// The combined value. +/// Thrown when the stack is empty. +/// +/// O(n). +/// +[] +val reduce : reduction : ('T -> 'T -> 'T) -> stack : Stack<'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 stack. +/// The combined value. +/// Thrown when the stack is empty. +/// +/// O(n). +/// +[] +val reduceBack : reduction : ('T -> 'T -> 'T) -> stack : Stack<'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 stack. +/// The states; one more than the elements. +/// +/// O(n). +/// +[] +val scan : folder : ('State -> 'T -> 'State) -> state : 'State -> stack : Stack<'T> -> Stack<'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 stack. +/// The initial state. +/// The states; one more than the elements. +/// +/// O(n). +/// +[] +val scanBack : folder : ('T -> 'State -> 'State) -> stack : Stack<'T> -> state : 'State -> Stack<'State> + +/// Returns the sum of the elements. +/// The input stack. +/// The sum; zero for an empty stack. +/// O(n). +[] +val inline sum : stack : Stack< ^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 stack. +/// The sum of the results; zero for an empty stack. +/// +/// O(n). +/// +[] +val inline sumBy : + projection : ('T -> ^U) -> stack : Stack<'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 stack. +/// 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) -> stack : Stack<'T> -> Stack when 'Key : equality + +/// +/// Returns the elements without duplicates, in F# structural equality. +/// +/// The input stack. +/// The first occurrence of each element, in their order. +/// +/// O(n). +/// +[] +val distinct : stack : Stack<'T> -> Stack<'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 stack. +/// The first element of each key, in their order. +/// +/// O(n). The keys compare in F# structural equality. +/// +[] +val distinctBy : projection : ('T -> 'Key) -> stack : Stack<'T> -> Stack<'T> when 'Key : equality + +/// +/// Returns the distinct elements that do not equal any of the excluded items. +/// +/// The items to remove. +/// The input stack. +/// The distinct remaining elements, in their order. +/// Thrown when is or a default flat list. +/// +/// O(n + m). An empty stack is returned without a look at the items to exclude. +/// +[] +val except : itemsToExclude : seq<'T> -> stack : Stack<'T> -> Stack<'T> when 'T : equality + +/// Groups the elements by the key of a projection. +/// The function that returns the key of an element. +/// The input stack. +/// 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) -> stack : Stack<'T> -> Stack)> when 'Key : equality + +/// Splits the elements by a predicate. +/// The function to test the elements. +/// The input stack. +/// The elements that satisfy the predicate and the other ones, both in their order. +/// O(n). +[] +val partition : predicate : ('T -> bool) -> stack : Stack<'T> -> struct (Stack<'T> * Stack<'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 stack. +/// 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>) -> stack : Stack<'T> -> struct (Stack<'T1> * Stack<'T2>) + +/// +/// Returns the stack without its first elements. +/// +/// The number of elements to skip; a negative count skips nothing. +/// The input stack. +/// The remaining elements. +/// Thrown when the count exceeds the length. +/// +/// O(count). The result is the stack below the skipped elements, without a copy. +/// +[] +val skip : count : int -> stack : Stack<'T> -> Stack<'T> + +/// +/// Returns the stack without its last elements. +/// +/// The number of elements to skip; a negative count skips nothing. +/// The input stack. +/// The remaining elements. +/// Thrown when the count exceeds the length. +/// +/// O(n). The result is a new stack, unless nothing is skipped. The checks follow +/// . +/// +[] +val skipLast : count : int -> stack : Stack<'T> -> Stack<'T> + +/// +/// Returns the stack without its first elements that satisfy the predicate. +/// +/// The function to test the elements. +/// The input stack. +/// The elements from the first one that does not satisfy the predicate. +/// +/// O(k), where k is the number of skipped elements. The result is the stack below them, without a copy. +/// +[] +val skipWhile : predicate : ('T -> bool) -> stack : Stack<'T> -> Stack<'T> + +/// +/// Splits the stack at an index. +/// +/// The index of the first element of the second part. +/// The input stack. +/// 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(index). The second part is the stack below the first part, without a copy; the first part is a new stack, unless +/// it is empty or the whole stack. +/// +[] +val splitAt : index : int -> stack : Stack<'T> -> struct (Stack<'T> * Stack<'T>) + +/// +/// Splits the stack into at most a given number of parts of nearly equal length. +/// +/// The maximum number of parts. +/// The input stack. +/// 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 -> stack : Stack<'T> -> Stack> + +/// +/// Returns the first elements of the stack. +/// +/// The number of elements to take. +/// The input stack. +/// The first elements. +/// Thrown when the count is negative. +/// Thrown when the count exceeds the length. +/// +/// O(count). The result is a new stack, unless it is empty or the whole stack. +/// +[] +val take : count : int -> stack : Stack<'T> -> Stack<'T> + +/// +/// Returns the last elements of the stack. +/// +/// The number of elements to take. +/// The input stack. +/// The last elements. +/// Thrown when the count is negative. +/// Thrown when the count exceeds the length. +/// +/// O(n). The result is the stack below the other elements, without a copy. The checks follow +/// . +/// +[] +val takeLast : count : int -> stack : Stack<'T> -> Stack<'T> + +/// +/// Returns the first elements of the stack that satisfy the predicate. +/// +/// The function to test the elements. +/// The input stack. +/// The elements before the first one that does not satisfy the predicate. +/// +/// O(k), where k is the length of the result, which is a new stack, unless it is the whole stack. +/// +[] +val takeWhile : predicate : ('T -> bool) -> stack : Stack<'T> -> Stack<'T> + +/// +/// Returns at most a given number of first elements. +/// +/// The maximum number of elements; a count that is not positive returns an empty stack. +/// The input stack. +/// The first elements. +/// +/// O(k), where k is the length of the result, which is a new stack, unless it is empty or the whole stack. +/// +[] +val truncate : count : int -> stack : Stack<'T> -> Stack<'T> + +// ---- Sorting ---- + +/// +/// Sorts the elements in the order of F# generic comparison. +/// +/// The input stack. +/// The sorted stack, with the smallest element on top. +/// +/// 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 : stack : Stack<'T> -> Stack<'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 stack. +/// The sorted stack. +/// +/// O(n log n). This is a stable sort: elements with equal keys keep their order. +/// +[] +val sortBy : projection : ('T -> 'Key) -> stack : Stack<'T> -> Stack<'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 stack. +/// The sorted stack. +/// +/// 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) -> stack : Stack<'T> -> Stack<'T> when 'Key : comparison + +/// +/// Sorts the elements in descending order of F# generic comparison. +/// +/// The input stack. +/// The sorted stack, with the greatest element on top. +/// +/// 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 : stack : Stack<'T> -> Stack<'T> when 'T : comparison + +/// +/// Sorts the elements with a comparison function. +/// +/// The function that compares two elements. +/// The input stack. +/// The sorted stack. +/// +/// 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) -> stack : Stack<'T> -> Stack<'T> + +/// +/// Sorts the elements with a comparer. +/// +/// The comparer. +/// The input stack. +/// The sorted stack. +/// +/// O(n log n). This is a stable sort: elements that compare equal keep their order. The function wraps +/// . +/// +[] +val sortWithComparer : comparer : IComparer<'T> -> stack : Stack<'T> -> Stack<'T> + +// ---- Random ---- + +/// +/// Returns a random element, chosen with . +/// +/// The input stack. +/// A random element. +/// Thrown when the stack is empty. +/// +/// O(n). +/// +[] +val randomChoice : source : Stack<'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 stack. +/// A random element. +/// Thrown when the stack is empty. +/// Thrown when the randomizer returns a number outside [0, 1). +/// +/// O(n). +/// +[] +val randomChoiceBy : randomizer : (unit -> float) -> source : Stack<'T> -> 'T + +/// +/// Returns a random element, chosen with a given random number generator. +/// +/// The random number generator. +/// The input stack. +/// A random element. +/// Thrown when is . +/// Thrown when the stack is empty. +/// +/// O(n). +/// +[] +val randomChoiceWith : random : Random -> source : Stack<'T> -> 'T + +/// +/// Returns random elements, chosen with ; an element can repeat. +/// +/// The number of elements to choose. +/// The input stack. +/// The chosen elements. +/// Thrown when the count is negative, or positive for an empty stack. +/// +/// O(n + count). +/// +[] +val randomChoices : count : int -> source : Stack<'T> -> Stack<'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 stack. +/// The chosen elements. +/// Thrown when the count is negative, or positive for an empty stack. +/// Thrown when the randomizer returns a number outside [0, 1). +/// +/// O(n + count). +/// +[] +val randomChoicesBy : randomizer : (unit -> float) -> count : int -> source : Stack<'T> -> Stack<'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 stack. +/// The chosen elements. +/// Thrown when is . +/// Thrown when the count is negative, or positive for an empty stack. +/// +/// O(n + count). +/// +[] +val randomChoicesWith : random : Random -> count : int -> source : Stack<'T> -> Stack<'T> + +/// +/// Returns random elements at distinct indices, chosen with . +/// +/// The number of elements to choose. +/// The input stack. +/// The chosen elements. +/// Thrown when the count is negative or exceeds the length. +/// +/// O(n + count). +/// +[] +val randomSample : count : int -> source : Stack<'T> -> Stack<'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 stack. +/// 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 : Stack<'T> -> Stack<'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 stack. +/// 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 : Stack<'T> -> Stack<'T> + +/// +/// Returns the elements in a random order, chosen with . +/// +/// The input stack. +/// The shuffled stack. +/// +/// O(n). +/// +[] +val randomShuffle : source : Stack<'T> -> Stack<'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 stack. +/// The shuffled stack. +/// Thrown when the randomizer returns a number outside [0, 1). +/// +/// O(n). +/// +[] +val randomShuffleBy : randomizer : (unit -> float) -> source : Stack<'T> -> Stack<'T> + +/// +/// Returns the elements in a random order, chosen with a given random number generator. +/// +/// The random number generator. +/// The input stack. +/// The shuffled stack. +/// Thrown when is . +/// +/// O(n). +/// +[] +val randomShuffleWith : random : Random -> source : Stack<'T> -> Stack<'T> + +// ---- Conversion ---- + +/// +/// Returns a new array with the elements, from the top to the bottom. +/// +/// The input stack. +/// A copy of the elements. +/// +/// O(n). +/// +[] +val toArray : stack : Stack<'T> -> 'T array + +/// +/// Returns a list of the elements, from the top to the bottom. +/// +/// The input stack. +/// The list of the elements. +/// +/// O(n). The function is the inverse of +/// . +/// +[] +val toList : stack : Stack<'T> -> 'T list + +/// +/// Returns the stack as a sequence, which enumerates the elements from the top to the bottom. +/// +/// The input stack. +/// The sequence of the elements. +/// +/// O(1). +/// +[] +val toSeq : stack : Stack<'T> -> seq<'T> diff --git a/src/FSharp.Collections.Immutable/Stack/StackCE.fs b/src/FSharp.Collections.Immutable/Stack/StackCE.fs new file mode 100644 index 00000000..d943de96 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Stack/StackCE.fs @@ -0,0 +1,73 @@ +namespace FSharp.Collections.Immutable + +type StackBuffer<'T> = ResizeArray<'T> -> unit + +[] +type StackCE () = + + member inline _.Zero () : StackBuffer<'T> = ignore + + member inline _.Yield (item : 'T) : StackBuffer<'T> = fun buffer -> buffer.Add item + + // The checks run when the expression calls the member, as in flatList { }, and name the parameter of the expression: + // List<'T>.AddRange names a null input "collection" (ADR 0001, D6) + + member inline _.YieldFrom (items : seq<'T>) : StackBuffer<'T> = + // A default flat list given as a sequence is a null input, as in Stack.ofSeq + checkNotNullOrDefaultSeq (nameof items) items + fun buffer -> buffer.AddRange items + + member inline _.YieldFrom (items : 'T array) : StackBuffer<'T> = + checkNotNull (nameof items) items + fun buffer -> buffer.AddRange items + + member inline _.YieldFrom (items : Stack<'T>) : StackBuffer<'T> = + checkNotNull (nameof items) items + fun buffer -> buffer.AddRange items + + member inline _.YieldFrom (body : StackBuffer<'T>) : StackBuffer<'T> = body + + member inline _.Combine (first : StackBuffer<'T>, second : StackBuffer<'T>) : StackBuffer<'T> = + fun buffer -> + first buffer + second buffer + + member inline _.Delay ([] delayed : unit -> StackBuffer<'T>) : StackBuffer<'T> = + fun buffer -> delayed () buffer + + member inline _.For (source : seq<'U>, [] body : 'U -> StackBuffer<'T>) : StackBuffer<'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 buffer -> + for item in source do + body item buffer + + member inline _.While ([] guard : unit -> bool, body : StackBuffer<'T>) : StackBuffer<'T> = + fun buffer -> + while guard () do + body buffer + + member inline _.TryWith (body : StackBuffer<'T>, [] handler : exn -> StackBuffer<'T>) : StackBuffer<'T> = + fun buffer -> + try + body buffer + with ex -> + handler ex buffer + + member inline _.TryFinally (body : StackBuffer<'T>, [] compensation : unit -> unit) : StackBuffer<'T> = + fun buffer -> + try + body buffer + finally + compensation () + + member inline _.Using (resource : 'Resource, [] body : 'Resource -> StackBuffer<'T>) : StackBuffer<'T> = + fun buffer -> + use resource = resource + body resource buffer + + member _.Run (body : StackBuffer<'T>) : Stack<'T> = + let buffer = ResizeArray<'T>() + body buffer + StackConstruction.ofArrayUnchecked (buffer.ToArray ()) diff --git a/src/FSharp.Collections.Immutable/Stack/StackCE.fsi b/src/FSharp.Collections.Immutable/Stack/StackCE.fsi new file mode 100644 index 00000000..bd425b51 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Stack/StackCE.fsi @@ -0,0 +1,74 @@ +namespace FSharp.Collections.Immutable + +open System + +/// +/// The body of a stack { } expression: a function that adds the elements to a buffer, in their order. +/// +type StackBuffer<'T> = ResizeArray<'T> -> unit + +/// +/// The builder of the stack { } computation expression. +/// +/// +/// The expression collects its elements in a and pushes them onto an empty +/// stack from the last one to the first one, so that the first element of the expression ends on top and the stack +/// enumerates the elements in the order of the expression (ADR 0001, D7). +/// +[] +type StackCE = + + /// Creates the builder of the expression. + new : unit -> StackCE + + /// An expression without elements. + member inline Zero : unit -> StackBuffer<'T> + + /// Adds an element. + member inline Yield : item : 'T -> StackBuffer<'T> + + /// Adds the elements of a sequence. + /// Thrown when the sequence is or a default flat list. + member inline YieldFrom : items : seq<'T> -> StackBuffer<'T> + + /// Adds the elements of an array. + /// Thrown when the array is . + member inline YieldFrom : items : 'T array -> StackBuffer<'T> + + /// Adds the elements of a stack, from the top to the bottom. + /// Thrown when the stack is . + member inline YieldFrom : items : Stack<'T> -> StackBuffer<'T> + + /// Adds the elements of another expression body. + member inline YieldFrom : body : StackBuffer<'T> -> StackBuffer<'T> + + /// Adds the elements of two parts of the expression, in order. + member inline Combine : first : StackBuffer<'T> * second : StackBuffer<'T> -> StackBuffer<'T> + + /// + /// Runs the body of the expression each time the elements are collected. + /// + member inline Delay : [] delayed : (unit -> StackBuffer<'T>) -> StackBuffer<'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 -> StackBuffer<'T>) -> StackBuffer<'T> + + /// Adds the elements of the body while the guard holds. + member inline While : [] guard : (unit -> bool) * body : StackBuffer<'T> -> StackBuffer<'T> + + /// Handles the exceptions of the body. + member inline TryWith : body : StackBuffer<'T> * [] handler : (exn -> StackBuffer<'T>) -> StackBuffer<'T> + + /// Runs the compensation after the body. + member inline TryFinally : body : StackBuffer<'T> * [] compensation : (unit -> unit) -> StackBuffer<'T> + + /// Disposes a resource after the body. + member inline Using : + resource : 'Resource * [] body : ('Resource -> StackBuffer<'T>) -> StackBuffer<'T> + when 'Resource :> IDisposable | null + + /// + /// Collects the elements of the expression into a stack whose top is the first element. + /// + member Run : body : StackBuffer<'T> -> Stack<'T> diff --git a/src/FSharp.Collections.Immutable/Stack/StackOption.fs b/src/FSharp.Collections.Immutable/Stack/StackOption.fs new file mode 100644 index 00000000..7edc8c7f --- /dev/null +++ b/src/FSharp.Collections.Immutable/Stack/StackOption.fs @@ -0,0 +1,70 @@ +[] +module FSharp.Collections.Immutable.Option.Stack + +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 + + Stack.unfold generateStruct state + +// ---- Access ---- + +[] +let tryExactlyOne (stack : Stack<'T>) = Stack.tryExactlyOne stack |> ValueOption.toOption + +[] +let tryHead (stack : Stack<'T>) = Stack.tryHead stack |> ValueOption.toOption + +[] +let tryLast (stack : Stack<'T>) = Stack.tryLast stack |> ValueOption.toOption + +[] +let tryTail (stack : Stack<'T>) = Stack.tryTail stack |> ValueOption.toOption + +[] +let tryUncons (stack : Stack<'T>) = + match Stack.tryUncons stack with + | ValueSome (struct (head, tail)) -> Some (head, tail) + | ValueNone -> None + +[] +let (|Cons|_|) (stack : Stack<'T>) = tryUncons stack + +// ---- Search ---- + +[] +let pick (chooser : 'T -> 'U option) (stack : Stack<'T>) = Stack.pick (fun item -> chooser item |> ValueOption.ofOption) stack + +[] +let tryFind predicate (stack : Stack<'T>) = Stack.tryFind predicate stack |> ValueOption.toOption + +[] +let tryFindBack predicate (stack : Stack<'T>) = Stack.tryFindBack predicate stack |> ValueOption.toOption + +[] +let tryFindIndex predicate (stack : Stack<'T>) = Stack.tryFindIndex predicate stack |> ValueOption.toOption + +[] +let tryFindIndexBack predicate (stack : Stack<'T>) = + Stack.tryFindIndexBack predicate stack + |> ValueOption.toOption + +[] +let tryPick (chooser : 'T -> 'U option) (stack : Stack<'T>) = + Stack.tryPick (fun item -> chooser item |> ValueOption.ofOption) stack + |> ValueOption.toOption + +// ---- Transformation ---- + +[] +let choose (chooser : 'T -> 'U option) (stack : Stack<'T>) = Stack.choose (fun item -> chooser item |> ValueOption.ofOption) stack diff --git a/src/FSharp.Collections.Immutable/Stack/StackOption.fsi b/src/FSharp.Collections.Immutable/Stack/StackOption.fsi new file mode 100644 index 00000000..fa91d17e --- /dev/null +++ b/src/FSharp.Collections.Immutable/Stack/StackOption.fsi @@ -0,0 +1,185 @@ +/// +/// 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.Stack + +open FSharp.Collections.Immutable + +// ---- Creation ---- + +/// +/// Creates a stack 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 stack, with the first generated element on top. +/// +/// O(n). +/// +[] +val unfold : generator : ('State -> ('T * 'State) option) -> state : 'State -> Stack<'T> + +// ---- Access ---- + +/// +/// Returns the only element of the stack, or None when it does not have exactly one element. +/// +/// The input stack. +/// The only element, or None. +/// +/// O(1). +/// +[] +val tryExactlyOne : stack : Stack<'T> -> 'T option + +/// +/// Returns the first element of the stack, or None when it is empty. +/// +/// The input stack. +/// The first element, or None. +/// +/// O(1). +/// +[] +val tryHead : stack : Stack<'T> -> 'T option + +/// +/// Returns the last element of the stack, or None when it is empty. +/// +/// The input stack. +/// The last element, or None. +/// +/// O(n). +/// +[] +val tryLast : stack : Stack<'T> -> 'T option + +/// +/// Returns the stack without its first element, or None when it is empty. +/// +/// The input stack. +/// The elements after the first one, or None. +/// +/// O(1). The result is the stack that the top was pushed onto, without a copy. +/// +[] +val tryTail : stack : Stack<'T> -> Stack<'T> option + +/// +/// Returns the first element and the other elements, or None when the stack is empty. +/// +/// The input stack. +/// The first element and the elements after it, or None. +/// +/// O(1). +/// +[] +val tryUncons : stack : Stack<'T> -> ('T * Stack<'T>) option + +/// +/// Matches a stack with a first element, and returns that element and the other elements. +/// +/// The input stack. +/// The first element and the elements after it, or None for an empty stack. +/// +/// O(1). +/// +[] +val (|Cons|_|) : stack : Stack<'T> -> ('T * Stack<'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 stack. +/// The first chosen value. +/// Thrown when the chooser returns None for every element. +/// +/// O(n). +/// +[] +val pick : chooser : ('T -> 'U option) -> stack : Stack<'T> -> 'U + +/// +/// Returns the first element that satisfies the predicate, or None. +/// +/// The function to test the elements. +/// The input stack. +/// The first element that satisfies the predicate, or None. +/// +/// O(n). +/// +[] +val tryFind : predicate : ('T -> bool) -> stack : Stack<'T> -> 'T option + +/// +/// Returns the last element that satisfies the predicate, or None. +/// +/// The function to test the elements. +/// The input stack. +/// The last element that satisfies the predicate, or None. +/// +/// O(n). +/// +[] +val tryFindBack : predicate : ('T -> bool) -> stack : Stack<'T> -> 'T option + +/// +/// Returns the index of the first element that satisfies the predicate, or None. +/// +/// The function to test the elements. +/// The input stack. +/// The index of the first element that satisfies the predicate, or None. +/// +/// O(n). +/// +[] +val tryFindIndex : predicate : ('T -> bool) -> stack : Stack<'T> -> int option + +/// +/// Returns the index of the last element that satisfies the predicate, or None. +/// +/// The function to test the elements. +/// The input stack. +/// The index of the last element that satisfies the predicate, or None. +/// +/// O(n). +/// +[] +val tryFindIndexBack : predicate : ('T -> bool) -> stack : Stack<'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 stack. +/// The first chosen value, or None. +/// +/// O(n). +/// +[] +val tryPick : chooser : ('T -> 'U option) -> stack : Stack<'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 stack. +/// The chosen values, in the order of their elements. +/// +/// O(n). +/// +[] +val choose : chooser : ('T -> 'U option) -> stack : Stack<'T> -> Stack<'U> diff --git a/src/FSharp.Collections.Immutable/Stack/StackTuple.fs b/src/FSharp.Collections.Immutable/Stack/StackTuple.fs new file mode 100644 index 00000000..7031bcea --- /dev/null +++ b/src/FSharp.Collections.Immutable/Stack/StackTuple.fs @@ -0,0 +1,131 @@ +[] +module FSharp.Collections.Immutable.Tuple.Stack + +open FSharp.Collections.Immutable +open FSharp.Collections.Immutable.StackConstruction + +// 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 + + Stack.unfold generateStruct state + +// ---- Access ---- + +[] +let tryUncons (stack : Stack<'T>) = + match Stack.tryUncons stack with + | ValueSome (struct (head, tail)) -> ValueSome (head, tail) + | ValueNone -> ValueNone + +[] +let uncons (stack : Stack<'T>) = + let struct (head, tail) = Stack.uncons stack + head, tail + +[] +[] +let (|Cons|_|) (stack : Stack<'T>) = tryUncons stack + +// ---- Transformation ---- + +[] +let allPairs (stack1 : Stack<'T1>) (stack2 : Stack<'T2>) = + Arrays.allPairs referencePair (Snapshot.ofSeq (nameof stack1) stack1) (Snapshot.ofSeq (nameof stack2) stack2) + |> ofArrayUnchecked + +[] +let indexed (stack : Stack<'T>) = + Arrays.indexed referencePair (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let pairwise (stack : Stack<'T>) = + Arrays.pairwise referencePair (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let unzip (stack : Stack<'T1 * 'T2>) = + let struct (first, second) = + Arrays.unzip (fun (item1, item2) -> struct (item1, item2)) (Snapshot.ofSeq (nameof stack) stack) + + ofArrayUnchecked first, ofArrayUnchecked second + +[] +let unzip3 (stack : Stack<'T1 * 'T2 * 'T3>) = + let struct (first, second, third) = + Arrays.unzip3 (fun (item1, item2, item3) -> struct (item1, item2, item3)) (Snapshot.ofSeq (nameof stack) stack) + + ofArrayUnchecked first, ofArrayUnchecked second, ofArrayUnchecked third + +[] +let zip (stack1 : Stack<'T1>) (stack2 : Stack<'T2>) = + let array1 = Snapshot.ofSeq (nameof stack1) stack1 + let array2 = Snapshot.ofSeq (nameof stack2) stack2 + ListChecks.sameLengths (nameof stack1) array1.Length (nameof stack2) array2.Length + Arrays.zip referencePair array1 array2 |> ofArrayUnchecked + +[] +let zip3 (stack1 : Stack<'T1>) (stack2 : Stack<'T2>) (stack3 : Stack<'T3>) = + let array1 = Snapshot.ofSeq (nameof stack1) stack1 + let array2 = Snapshot.ofSeq (nameof stack2) stack2 + let array3 = Snapshot.ofSeq (nameof stack3) stack3 + ListChecks.zip3 (nameof stack1) array1.Length (nameof stack2) array2.Length (nameof stack3) array3.Length + + Arrays.zip3 referenceTriple array1 array2 array3 + |> ofArrayUnchecked + +// ---- Folding ---- + +[] +let mapFold (mapping : 'State -> 'T -> 'Result * 'State) (state : 'State) (stack : Stack<'T>) = + let mapStruct state item = + let result, next = mapping state item + struct (result, next) + + let struct (results, final) = Stack.mapFold mapStruct state stack + results, final + +[] +let mapFoldBack (mapping : 'T -> 'State -> 'Result * 'State) (stack : Stack<'T>) (state : 'State) = + let mapStruct item state = + let result, next = mapping item state + struct (result, next) + + let struct (results, final) = Stack.mapFoldBack mapStruct stack state + results, final + +// ---- Partitioning and grouping ---- + +[] +let countBy (projection : 'T -> 'Key) (stack : Stack<'T>) = + Arrays.countBy referencePair projection (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let groupBy (projection : 'T -> 'Key) (stack : Stack<'T>) = + Arrays.groupBy (fun key items -> key, ofArrayUnchecked items) projection (Snapshot.ofSeq (nameof stack) stack) + |> ofArrayUnchecked + +[] +let partition predicate (stack : Stack<'T>) = + let struct (accepted, rejected) = Stack.partition predicate stack + accepted, rejected + +[] +let partitionWith (partitioner : 'T -> Choice<'T1, 'T2>) (stack : Stack<'T>) = + let struct (first, second) = Stack.partitionWith partitioner stack + first, second + +[] +let splitAt index (stack : Stack<'T>) = + let struct (before, after) = Stack.splitAt index stack + before, after diff --git a/src/FSharp.Collections.Immutable/Stack/StackTuple.fsi b/src/FSharp.Collections.Immutable/Stack/StackTuple.fsi new file mode 100644 index 00000000..98377cb3 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Stack/StackTuple.fsi @@ -0,0 +1,204 @@ +/// +/// 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.Stack + +open FSharp.Collections.Immutable + +// ---- Creation ---- + +/// +/// Creates a stack 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 stack, with the first generated element on top. +/// +/// O(n). +/// +[] +val unfold : generator : ('State -> ('T * 'State) voption) -> state : 'State -> Stack<'T> + +// ---- Access ---- + +/// +/// Returns the first element and the other elements, or ValueNone when the stack is empty. +/// +/// The input stack. +/// The first element and the elements after it, or ValueNone. +/// +/// O(1). +/// +[] +val tryUncons : stack : Stack<'T> -> ('T * Stack<'T>) voption + +/// Returns the first element and the other elements. +/// The input stack. +/// The first element and the elements after it. +/// Thrown when the stack is empty. +/// O(1). +[] +val uncons : stack : Stack<'T> -> 'T * Stack<'T> + +/// +/// Matches a stack with a first element, and returns that element and the other elements. +/// +/// The input stack. +/// The first element and the elements after it, or ValueNone for an empty stack. +/// +/// O(1). A struct partial pattern, so that a match allocates no option. +/// +[] +[] +val (|Cons|_|) : stack : Stack<'T> -> ('T * Stack<'T>) voption + +// ---- Transformation ---- + +/// +/// Returns every element of the first stack paired with every element of the second one. +/// +/// The first stack. +/// The second stack. +/// The pairs, in the order of the first stack and then of the second one. +/// +/// O(n × m). +/// +[] +val allPairs : stack1 : Stack<'T1> -> stack2 : Stack<'T2> -> Stack<'T1 * 'T2> + +/// Returns every element with its index. +/// The input stack. +/// The pairs of index and element; the top has the index 0. +/// O(n). +[] +val indexed : stack : Stack<'T> -> Stack + +/// Returns every element with its successor. +/// The input stack. +/// The pairs of neighbours; empty for fewer than two elements. +/// O(n). +[] +val pairwise : stack : Stack<'T> -> Stack<'T * 'T> + +/// Splits a stack of pairs into two stacks. +/// The input stack. +/// The first components and the second components. +/// O(n). +[] +val unzip : stack : Stack<'T1 * 'T2> -> Stack<'T1> * Stack<'T2> + +/// Splits a stack of triples into three stacks. +/// The input stack. +/// The first, the second and the third components. +/// O(n). +[] +val unzip3 : stack : Stack<'T1 * 'T2 * 'T3> -> Stack<'T1> * Stack<'T2> * Stack<'T3> + +/// Combines two stacks into a stack of pairs. +/// The first stack. +/// The second stack. +/// The pairs of elements at the same index. +/// Thrown when the stacks have different lengths. +/// O(n). +[] +val zip : stack1 : Stack<'T1> -> stack2 : Stack<'T2> -> Stack<'T1 * 'T2> + +/// Combines three stacks into a stack of triples. +/// The first stack. +/// The second stack. +/// The third stack. +/// The triples of elements at the same index. +/// Thrown when the stacks have different lengths. +/// O(n). +[] +val zip3 : stack1 : Stack<'T1> -> stack2 : Stack<'T2> -> stack3 : Stack<'T3> -> Stack<'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 stack. +/// The results and the final state. +/// +/// O(n). +/// +[] +val mapFold : mapping : ('State -> 'T -> 'Result * 'State) -> state : 'State -> stack : Stack<'T> -> Stack<'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 stack. +/// The initial state. +/// The results, in the order of the elements, and the final state. +/// +/// O(n). +/// +[] +val mapFoldBack : mapping : ('T -> 'State -> 'Result * 'State) -> stack : Stack<'T> -> state : 'State -> Stack<'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 stack. +/// 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) -> stack : Stack<'T> -> Stack<'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 stack. +/// 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) -> stack : Stack<'T> -> Stack<'Key * Stack<'T>> when 'Key : equality + +/// Splits the elements by a predicate. +/// The function to test the elements. +/// The input stack. +/// The elements that satisfy the predicate and the other ones, both in their order. +/// O(n). +[] +val partition : predicate : ('T -> bool) -> stack : Stack<'T> -> Stack<'T> * Stack<'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 stack. +/// 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>) -> stack : Stack<'T> -> Stack<'T1> * Stack<'T2> + +/// +/// Splits the stack at an index. +/// +/// The index of the first element of the second part. +/// The input stack. +/// 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(index). The second part is the stack below the first part, without a copy. +/// +[] +val splitAt : index : int -> stack : Stack<'T> -> Stack<'T> * Stack<'T> diff --git a/src/FSharp.Collections.Immutable/Stack/StackTypes.fs b/src/FSharp.Collections.Immutable/Stack/StackTypes.fs new file mode 100644 index 00000000..1c323736 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Stack/StackTypes.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 stack, . +/// +/// +/// The stack is a linked list of nodes: pushing, reading and popping the top element take O(1), and a stack shares the +/// nodes of the stack that it was pushed onto. +/// +type Stack<'T> = ImmutableStack<'T> + +/// +/// An immutable stack of any implementation, . +/// +/// +/// implements the interface. +/// +type IStack<'T> = IImmutableStack<'T> 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 12eeec14..5fc1b286 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj +++ b/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj @@ -80,6 +80,16 @@ + + + + + + + + + + diff --git a/tests/FSharp.Collections.Immutable.Tests/Stack/IStackOps.fs b/tests/FSharp.Collections.Immutable.Tests/Stack/IStackOps.fs new file mode 100644 index 00000000..df4db7b1 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Stack/IStackOps.fs @@ -0,0 +1,169 @@ +/// The functions of the `IStack` module for the contract tests, over stacks seen through the interface. +[] +module FSharp.Collections.Immutable.Tests.IStackOps + +open System.Collections.Immutable +open FSharp.Collections.Immutable + +/// A stack of the elements, the first one on top, seen through the interface. +let private ofSeq (source : seq<'T>) : IImmutableStack<'T> = Stack.ofSeq source + +let private toArrays (stacks : Stack>) = stacks |> Stack.toArray |> Array.map Stack.toArray + +let private toArrayPair (struct (first : Stack<'T1>, second : Stack<'T2>)) = struct (Stack.toArray first, Stack.toArray second) + +/// The functions of the sequence-like facet. The functions that change the element type return a stack, which the +/// instance sees through the interface again where the record expects the collection of the module. +let seqLike : SeqOps> = { + Name = nameof IStack + Mirror = ListMirror + ParamName = StackOps.paramName + OfSeq = ofSeq + ToArray = IStack.toArray + + IsEmpty = IStack.isEmpty + Length = IStack.length + + ExactlyOne = IStack.exactlyOne + Head = IStack.head + Last = IStack.last + Tail = IStack.tail + TryExactlyOne = IStack.tryExactlyOne + TryHead = IStack.tryHead + TryLast = IStack.tryLast + TryTail = IStack.tryTail + + Contains = IStack.contains + Exists = IStack.exists + Exists2 = IStack.exists2 + Find = IStack.find + FindBack = IStack.findBack + FindIndex = IStack.findIndex + FindIndexBack = IStack.findIndexBack + Forall = IStack.forall + Forall2 = IStack.forall2 + Pick = IStack.pick + TryFind = IStack.tryFind + TryFindBack = IStack.tryFindBack + TryFindIndex = IStack.tryFindIndex + TryFindIndexBack = IStack.tryFindIndexBack + TryPick = IStack.tryPick + + AllPairs = fun stack1 stack2 -> IStack.allPairs stack1 stack2 |> Stack.toArray + Append = IStack.append + Choose = fun chooser stack -> IStack.choose chooser stack + ChunkBySize = fun chunkSize stack -> IStack.chunkBySize chunkSize stack |> toArrays + Collect = fun mapping stack -> IStack.collect mapping stack + Concat = fun stacks -> IStack.concat stacks + Filter = IStack.filter + Indexed = IStack.indexed >> Stack.toArray + Map = fun mapping stack -> IStack.map mapping stack + Map2 = fun mapping stack1 stack2 -> IStack.map2 mapping stack1 stack2 + Map3 = fun mapping stack1 stack2 stack3 -> IStack.map3 mapping stack1 stack2 stack3 + Mapi = fun mapping stack -> IStack.mapi mapping stack + Mapi2 = fun mapping stack1 stack2 -> IStack.mapi2 mapping stack1 stack2 + Pairwise = IStack.pairwise >> Stack.toArray + Permute = IStack.permute + Rev = IStack.rev + Transpose = fun stacks -> IStack.transpose stacks |> toArrays + Unzip = fun pairs -> IStack.unzip (ofSeq pairs) |> toArrayPair + Unzip3 = + fun triples -> + let struct (first, second, third) = IStack.unzip3 (ofSeq triples) + struct (Stack.toArray first, Stack.toArray second, Stack.toArray third) + Where = IStack.where + Windowed = fun windowSize stack -> IStack.windowed windowSize stack |> toArrays + Zip = fun stack1 stack2 -> IStack.zip stack1 stack2 |> Stack.toArray + Zip3 = fun stack1 stack2 stack3 -> IStack.zip3 stack1 stack2 stack3 |> Stack.toArray + + Average = fun items -> IStack.average (ofSeq items) + AverageBy = IStack.averageBy + CompareWith = IStack.compareWith + Fold = IStack.fold + Fold2 = IStack.fold2 + FoldBack = IStack.foldBack + FoldBack2 = IStack.foldBack2 + Iter = IStack.iter + Iter2 = IStack.iter2 + Iteri = IStack.iteri + Iteri2 = IStack.iteri2 + MapFold = + fun mapping state stack -> + let struct (results, final) = IStack.mapFold mapping state stack + struct (Stack.toArray results, final) + MapFoldBack = + fun mapping stack state -> + let struct (results, final) = IStack.mapFoldBack mapping stack state + struct (Stack.toArray results, final) + Max = IStack.max + MaxBy = IStack.maxBy + Min = IStack.min + MinBy = IStack.minBy + Reduce = IStack.reduce + ReduceBack = IStack.reduceBack + Scan = fun folder state stack -> IStack.scan folder state stack |> Stack.toArray + ScanBack = fun folder stack state -> IStack.scanBack folder stack state |> Stack.toArray + Sum = IStack.sum + SumBy = IStack.sumBy + + CountBy = fun projection stack -> IStack.countBy projection stack |> Stack.toArray + Distinct = IStack.distinct + DistinctBy = IStack.distinctBy + Except = IStack.except + GroupBy = + fun projection stack -> + IStack.groupBy projection stack + |> Stack.toArray + |> Array.map (fun struct (key, items) -> struct (key, Stack.toArray items)) + Partition = IStack.partition + PartitionWith = fun partitioner stack -> IStack.partitionWith partitioner stack |> toArrayPair + Skip = IStack.skip + SkipLast = IStack.skipLast + SkipWhile = IStack.skipWhile + SplitAt = IStack.splitAt + SplitInto = fun count stack -> IStack.splitInto count stack |> toArrays + Take = IStack.take + TakeLast = IStack.takeLast + TakeWhile = IStack.takeWhile + Truncate = IStack.truncate + + Sort = IStack.sort + SortBy = IStack.sortBy + SortByDescending = IStack.sortByDescending + SortDescending = IStack.sortDescending + SortWith = IStack.sortWith + SortWithComparer = IStack.sortWithComparer + + RandomChoice = IStack.randomChoice + RandomChoiceBy = IStack.randomChoiceBy + RandomChoiceWith = IStack.randomChoiceWith + RandomChoices = IStack.randomChoices + RandomChoicesBy = IStack.randomChoicesBy + RandomChoicesWith = IStack.randomChoicesWith + RandomSample = IStack.randomSample + RandomSampleBy = IStack.randomSampleBy + RandomSampleWith = IStack.randomSampleWith + RandomShuffle = IStack.randomShuffle + RandomShuffleBy = IStack.randomShuffleBy + RandomShuffleWith = IStack.randomShuffleWith + + ToList = IStack.toList + ToSeq = IStack.toSeq +} + +/// The functions of the stable sorting facet. +let stableSort : StableSortOps> = { + Name = nameof IStack + OfSeq = ofSeq + ToArray = IStack.toArray + + Sort = fun items -> ofSeq items |> IStack.sort |> IStack.toArray + SortBy = IStack.sortBy + SortByDescending = IStack.sortByDescending + SortDescending = fun items -> ofSeq items |> IStack.sortDescending |> IStack.toArray + SortWith = IStack.sortWith + SortWithComparer = IStack.sortWithComparer + + SortIntegersDescending = fun items -> ofSeq items |> IStack.sortDescending |> IStack.toArray + SortIntegersByDescending = fun items -> ofSeq items |> IStack.sortByDescending id |> IStack.toArray +} diff --git a/tests/FSharp.Collections.Immutable.Tests/Stack/IStackOptionTests.fs b/tests/FSharp.Collections.Immutable.Tests/Stack/IStackOptionTests.fs new file mode 100644 index 00000000..154d24f9 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Stack/IStackOptionTests.fs @@ -0,0 +1,195 @@ +namespace FSharp.Collections.Immutable.Tests + +open System.Collections.Immutable +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 `IStack` module of the `Option` namespace. +[] +type IStackOptionTests () = + + static let several : IImmutableStack = Stack.ofArray Inputs.Several + + static let empty : IImmutableStack = Stack.empty + + static let negatedIfNegative (item : int) = if item < 0 then Some (-item) else None + + [] + member _.``tryExactlyOne returns an option`` () = + let single : IImmutableStack = Stack.singleton 5 + let found : int option = IStack.tryExactlyOne single + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IStack.tryExactlyOne single, + found, + "Option.IStack.tryExactlyOne must match the default after conversion" + ) + + [] + member _.``tryHead returns an option`` () = + let found : int option = IStack.tryHead several + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IStack.tryHead several, + found, + "Option.IStack.tryHead must match the default" + ) + + [] + member _.``tryLast returns an option`` () = + let found : int option = IStack.tryLast several + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IStack.tryLast several, + found, + "Option.IStack.tryLast must match the default" + ) + + [] + member _.``tryTail returns an option`` () = + let found : IImmutableStack option = IStack.tryTail several + + // The equality of an option compares the elements of an array that it holds + Assert.AreEqual( + FSharp.Collections.Immutable.IStack.tryTail several + |> ValueOption.map IStack.toArray + |> ValueOption.toOption, + found |> Option.map IStack.toArray, + "Option.IStack.tryTail must match the default" + ) + + [] + member _.``tryUncons returns an option of a reference tuple`` () = + let found : (int * IImmutableStack) option = IStack.tryUncons several + + // A tuple compares an array by reference, so the first element and the other elements are compared apart + match found with + | Some (head, tail) -> + Assert.AreEqual(Array.head Inputs.Several, head, "Option.IStack.tryUncons must return the first element") + + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + IStack.toArray tail, + "Option.IStack.tryUncons must return the other elements" + ) + | None -> Assert.Fail "Option.IStack.tryUncons must return the elements of a stack with elements" + + Assert.IsTrue ((IStack.tryUncons empty).IsNone, "Option.IStack.tryUncons must return None for an empty stack") + + [] + member _.``Cons is a partial pattern of options`` () = + let pattern : IImmutableStack -> (int * IImmutableStack) option = IStack.(|Cons|_|) + + match several with + | IStack.Cons (head, tail) -> + Assert.AreEqual(Array.head Inputs.Several, head, "Option.IStack.Cons must match the first element") + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + IStack.toArray tail, + "Option.IStack.Cons must match the other elements" + ) + | _ -> Assert.Fail "Option.IStack.Cons must match a stack with elements" + + Assert.IsTrue ((pattern empty).IsNone, "Option.IStack.Cons must not match an empty stack") + + [] + member _.``pick takes a chooser of options`` () = + let picked : int = IStack.pick negatedIfNegative several + Assert.AreEqual( + Array.pick negatedIfNegative Inputs.Several, + picked, + "Option.IStack.pick must pick as List.pick does" + ) + + [] + member _.``tryFind returns an option`` () = + let found : int option = IStack.tryFind (fun item -> item > 6) several + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IStack.tryFind (fun item -> item > 6) several, + found, + "Option.IStack.tryFind must match the default" + ) + + [] + member _.``tryFindBack returns an option`` () = + let found : int option = IStack.tryFindBack (fun item -> item > 6) several + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IStack.tryFindBack (fun item -> item > 6) several, + found, + "Option.IStack.tryFindBack must match the default" + ) + + [] + member _.``tryFindIndex returns an option`` () = + let found : int option = IStack.tryFindIndex (fun item -> item > 6) several + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IStack.tryFindIndex (fun item -> item > 6) several, + found, + "Option.IStack.tryFindIndex must match the default" + ) + + [] + member _.``tryFindIndexBack returns an option`` () = + let found : int option = IStack.tryFindIndexBack (fun item -> item > 6) several + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IStack.tryFindIndexBack (fun item -> item > 6) several, + found, + "Option.IStack.tryFindIndexBack must match the default" + ) + + [] + member _.``tryPick takes a chooser of options and returns an option`` () = + let found : int option = IStack.tryPick negatedIfNegative several + + Assert.AreEqual( + Array.tryPick negatedIfNegative Inputs.Several, + found, + "Option.IStack.tryPick must pick as List.tryPick does" + ) + + [] + member _.``choose takes a chooser of options`` () = + let chosen : Stack = IStack.choose negatedIfNegative several + + CollectionAssert.AreEqual ( + Array.choose negatedIfNegative Inputs.Several, + Stack.toArray chosen, + "Option.IStack.choose must choose as List.choose does" + ) + + [] + member _.``the functions without an option keep resolving to the default module`` () = + let filtered : IImmutableStack = IStack.filter (fun item -> item > 0) several + let zipped : Stack = IStack.zip several several + + CollectionAssert.AreEqual ( + Array.filter (fun item -> item > 0) Inputs.Several, + IStack.toArray filtered, + "IStack.filter must resolve to the default module" + ) + + CollectionAssert.AreEqual ( + Array.zip Inputs.Several Inputs.Several, + Stack.toArray zipped, + "IStack.zip must resolve to the Tuple module" + ) + + [] + [] + [] + [] + member _.``the stack functions compile with the names of the stack operations of the BCL`` + (name : string, compiledName : string) + = + Assert.AreEqual( + name, + StackOps.sourceName "FSharp.Collections.Immutable.Option.IStack" compiledName, + "Option.IStack must compile the function under the name of its stack operation in the BCL" + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Stack/IStackTests.fs b/tests/FSharp.Collections.Immutable.Tests/Stack/IStackTests.fs new file mode 100644 index 00000000..139e34dc --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Stack/IStackTests.fs @@ -0,0 +1,560 @@ +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 immutable stack, to +/// tell whether a function keeps the implementation of its input. +/// +/// +/// Every member that returns a stack returns a tagged stack. +/// +type TaggedStack<'T> (items : ImmutableStack<'T>) = + + static let wrap (items : ImmutableStack<'T>) = TaggedStack items :> IImmutableStack<'T> + + interface IImmutableStack<'T> with + member _.Clear () = wrap (items.Clear ()) + member _.IsEmpty = items.IsEmpty + member _.Peek () = items.Peek () + member _.Pop () = wrap (items.Pop ()) + member _.Push value = wrap (items.Push value) + + interface IEnumerable<'T> with + member _.GetEnumerator () = (items :> IEnumerable<'T>).GetEnumerator() + + interface IEnumerable with + member _.GetEnumerator () = (items :> IEnumerable).GetEnumerator() + +/// The behaviour of `IStack` that the shared contracts do not cover: the implementation of the results, the order of the +/// elements, null inputs and the patterns. +[] +type IStackTests () = + + static let nullStack = Unchecked.defaultof> + + static let several () : IImmutableStack = Stack.ofArray Inputs.Several + + static let tagged () : IImmutableStack = TaggedStack (Stack.ofArray Inputs.Several) + + // 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 : IImmutableStack, _ : IImmutableStack)) = first + + static let second (struct (_ : IImmutableStack, second : IImmutableStack)) = 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 -> IImmutableStack> = + Map.ofArray [| + nameof IStack.tail, IStack.tail + nameof IStack.tryTail, (fun stack -> (IStack.tryTail stack).Value) + (nameof IStack.uncons, + fun stack -> + let struct (_, tail) = IStack.uncons stack + tail) + // The second stack receives the elements of the first one, so the result has its implementation (ADR 0001, D14) + nameof IStack.append, (fun stack -> IStack.append (Stack.ofArray [| 1; 2 |]) stack) + nameof IStack.cons, IStack.cons 99 + nameof IStack.consMany, IStack.consMany [| 98; 99 |] + nameof IStack.filter, IStack.filter (fun item -> item > 0) + nameof IStack.permute, IStack.permute (fun index -> (index + 3) % 8) + nameof IStack.rev, IStack.rev + nameof IStack.where, IStack.where (fun item -> item > 0) + nameof IStack.distinct, IStack.distinct + nameof IStack.distinctBy, IStack.distinctBy abs + nameof IStack.except, IStack.except [| 5 |] + $"%s{nameof IStack.partition} (accepted)", IStack.partition (fun item -> item > 0) >> first + $"%s{nameof IStack.partition} (rejected)", IStack.partition (fun item -> item > 0) >> second + nameof IStack.skip, IStack.skip 3 + nameof IStack.skipLast, IStack.skipLast 3 + nameof IStack.skipWhile, IStack.skipWhile (fun item -> item > 0) + $"%s{nameof IStack.splitAt} (first part)", IStack.splitAt 3 >> first + $"%s{nameof IStack.splitAt} (second part)", IStack.splitAt 3 >> second + nameof IStack.take, IStack.take 3 + nameof IStack.takeLast, IStack.takeLast 3 + nameof IStack.takeWhile, IStack.takeWhile (fun item -> item > 0) + nameof IStack.truncate, IStack.truncate 3 + nameof IStack.sort, IStack.sort + nameof IStack.sortBy, IStack.sortBy abs + nameof IStack.sortByDescending, IStack.sortByDescending abs + nameof IStack.sortDescending, IStack.sortDescending + nameof IStack.sortWith, IStack.sortWith compare + nameof IStack.sortWithComparer, IStack.sortWithComparer Comparer.Default + nameof IStack.randomChoicesWith, (fun stack -> IStack.randomChoicesWith (random ()) 3 stack) + nameof IStack.randomChoicesBy, (fun stack -> IStack.randomChoicesBy (randomizer ()) 3 stack) + nameof IStack.randomSampleWith, (fun stack -> IStack.randomSampleWith (random ()) 3 stack) + nameof IStack.randomSampleBy, (fun stack -> IStack.randomSampleBy (randomizer ()) 3 stack) + nameof IStack.randomShuffleWith, (fun stack -> IStack.randomShuffleWith (random ()) stack) + nameof IStack.randomShuffleBy, (fun stack -> IStack.randomShuffleBy (randomizer ()) stack) + nameof IStack.clear, IStack.clear + |] + + /// The functions that take a stack, by name, with the parameter that names the null stack and the function on a null + /// stack. + static let nullStackRejections = + let stack = several () + let pairs = Unchecked.defaultof> + let triples = Unchecked.defaultof> + let floats = Unchecked.defaultof> + + [| + struct (nameof IStack.isEmpty, "stack", (fun () -> IStack.isEmpty nullStack |> ignore)) + struct (nameof IStack.length, "stack", (fun () -> IStack.length nullStack |> ignore)) + struct (nameof IStack.exactlyOne, "stack", (fun () -> IStack.exactlyOne nullStack |> ignore)) + struct (nameof IStack.head, "stack", (fun () -> IStack.head nullStack |> ignore)) + struct (nameof IStack.last, "stack", (fun () -> IStack.last nullStack |> ignore)) + struct (nameof IStack.tail, "stack", (fun () -> IStack.tail nullStack |> ignore)) + struct (nameof IStack.tryExactlyOne, "stack", (fun () -> IStack.tryExactlyOne nullStack |> ignore)) + struct (nameof IStack.tryHead, "stack", (fun () -> IStack.tryHead nullStack |> ignore)) + struct (nameof IStack.tryLast, "stack", (fun () -> IStack.tryLast nullStack |> ignore)) + struct (nameof IStack.tryTail, "stack", (fun () -> IStack.tryTail nullStack |> ignore)) + struct (nameof IStack.tryUncons, "stack", (fun () -> IStack.tryUncons nullStack |> ignore)) + struct (nameof IStack.uncons, "stack", (fun () -> IStack.uncons nullStack |> ignore)) + struct (nameof IStack.(|Cons|_|), "stack", (fun () -> IStack.(|Cons|_|) nullStack |> ignore)) + struct (nameof IStack.(|Nil|_|), "stack", (fun () -> IStack.(|Nil|_|) nullStack |> ignore)) + struct (nameof IStack.contains, "source", (fun () -> IStack.contains 1 nullStack |> ignore)) + struct (nameof IStack.exists, "stack", (fun () -> IStack.exists ((=) 1) nullStack |> ignore)) + struct (nameof IStack.exists2, "stack1", (fun () -> IStack.exists2 (=) nullStack stack |> ignore)) + struct (nameof IStack.find, "stack", (fun () -> IStack.find ((=) 1) nullStack |> ignore)) + struct (nameof IStack.findBack, "stack", (fun () -> IStack.findBack ((=) 1) nullStack |> ignore)) + struct (nameof IStack.findIndex, "stack", (fun () -> IStack.findIndex ((=) 1) nullStack |> ignore)) + struct (nameof IStack.findIndexBack, "stack", (fun () -> IStack.findIndexBack ((=) 1) nullStack |> ignore)) + struct (nameof IStack.forall, "stack", (fun () -> IStack.forall ((=) 1) nullStack |> ignore)) + struct (nameof IStack.forall2, "stack2", (fun () -> IStack.forall2 (=) stack nullStack |> ignore)) + struct (nameof IStack.pick, "stack", (fun () -> IStack.pick ValueSome nullStack |> ignore)) + struct (nameof IStack.tryFind, "stack", (fun () -> IStack.tryFind ((=) 1) nullStack |> ignore)) + struct (nameof IStack.tryFindBack, "stack", (fun () -> IStack.tryFindBack ((=) 1) nullStack |> ignore)) + struct (nameof IStack.tryFindIndex, "stack", (fun () -> IStack.tryFindIndex ((=) 1) nullStack |> ignore)) + struct (nameof IStack.tryFindIndexBack, "stack", (fun () -> IStack.tryFindIndexBack ((=) 1) nullStack |> ignore)) + struct (nameof IStack.tryPick, "stack", (fun () -> IStack.tryPick ValueSome nullStack |> ignore)) + struct (nameof IStack.allPairs, "stack2", (fun () -> IStack.allPairs stack nullStack |> ignore)) + struct (nameof IStack.append, "stack1", (fun () -> IStack.append nullStack stack |> ignore)) + struct (nameof IStack.choose, "stack", (fun () -> IStack.choose ValueSome nullStack |> ignore)) + struct (nameof IStack.chunkBySize, "stack", (fun () -> IStack.chunkBySize 2 nullStack |> ignore)) + struct (nameof IStack.collect, "stack", (fun () -> IStack.collect (fun _ -> stack) nullStack |> ignore)) + struct (nameof IStack.concat, "stacks", (fun () -> IStack.concat [| stack; nullStack |] |> ignore)) + struct (nameof IStack.cons, "stack", (fun () -> IStack.cons 1 nullStack |> ignore)) + struct (nameof IStack.consMany, "stack", (fun () -> IStack.consMany [| 1 |] nullStack |> ignore)) + struct (nameof IStack.filter, "stack", (fun () -> IStack.filter ((=) 1) nullStack |> ignore)) + struct (nameof IStack.indexed, "stack", (fun () -> IStack.indexed nullStack |> ignore)) + struct (nameof IStack.map, "stack", (fun () -> IStack.map id nullStack |> ignore)) + struct (nameof IStack.map2, "stack1", (fun () -> IStack.map2 (+) nullStack stack |> ignore)) + struct (nameof IStack.map3, + "stack2", + (fun () -> + IStack.map3 (fun a b c -> a + b + c) stack nullStack stack + |> ignore + )) + struct (nameof IStack.mapi, "stack", (fun () -> IStack.mapi (+) nullStack |> ignore)) + struct (nameof IStack.mapi2, + "stack2", + (fun () -> + IStack.mapi2 (fun index a b -> index + a + b) stack nullStack + |> ignore + )) + struct (nameof IStack.pairwise, "stack", (fun () -> IStack.pairwise nullStack |> ignore)) + struct (nameof IStack.permute, "stack", (fun () -> IStack.permute id nullStack |> ignore)) + struct (nameof IStack.rev, "stack", (fun () -> IStack.rev nullStack |> ignore)) + struct (nameof IStack.transpose, "stacks", (fun () -> IStack.transpose [| nullStack |] |> ignore)) + struct (nameof IStack.unzip, "stack", (fun () -> IStack.unzip pairs |> ignore)) + struct (nameof IStack.unzip3, "stack", (fun () -> IStack.unzip3 triples |> ignore)) + struct (nameof IStack.where, "stack", (fun () -> IStack.where ((=) 1) nullStack |> ignore)) + struct (nameof IStack.windowed, "stack", (fun () -> IStack.windowed 2 nullStack |> ignore)) + struct (nameof IStack.zip, "stack2", (fun () -> IStack.zip stack nullStack |> ignore)) + struct (nameof IStack.zip3, "stack3", (fun () -> IStack.zip3 stack stack nullStack |> ignore)) + struct (nameof IStack.average, "stack", (fun () -> IStack.average floats |> ignore)) + struct (nameof IStack.averageBy, "stack", (fun () -> IStack.averageBy float nullStack |> ignore)) + struct (nameof IStack.compareWith, "stack1", (fun () -> IStack.compareWith compare nullStack stack |> ignore)) + struct (nameof IStack.fold, "stack", (fun () -> IStack.fold (+) 0 nullStack |> ignore)) + struct (nameof IStack.fold2, + "stack1", + (fun () -> + IStack.fold2 (fun state a b -> state + a + b) 0 nullStack stack + |> ignore + )) + struct (nameof IStack.foldBack, "stack", (fun () -> IStack.foldBack (+) nullStack 0 |> ignore)) + struct (nameof IStack.foldBack2, + "stack2", + (fun () -> + IStack.foldBack2 (fun a b state -> state + a + b) stack nullStack 0 + |> ignore + )) + struct (nameof IStack.iter, "stack", (fun () -> IStack.iter ignore nullStack)) + struct (nameof IStack.iter2, "stack1", (fun () -> IStack.iter2 (fun _ _ -> ()) nullStack stack)) + struct (nameof IStack.iteri, "stack", (fun () -> IStack.iteri (fun _ _ -> ()) nullStack)) + struct (nameof IStack.iteri2, "stack2", (fun () -> IStack.iteri2 (fun _ _ _ -> ()) stack nullStack)) + struct (nameof IStack.mapFold, + "stack", + (fun () -> + IStack.mapFold (fun state item -> struct (item, state)) 0 nullStack + |> ignore + )) + struct (nameof IStack.mapFoldBack, + "stack", + (fun () -> + IStack.mapFoldBack (fun item state -> struct (item, state)) nullStack 0 + |> ignore + )) + struct (nameof IStack.max, "stack", (fun () -> IStack.max nullStack |> ignore)) + struct (nameof IStack.maxBy, "stack", (fun () -> IStack.maxBy id nullStack |> ignore)) + struct (nameof IStack.min, "stack", (fun () -> IStack.min nullStack |> ignore)) + struct (nameof IStack.minBy, "stack", (fun () -> IStack.minBy id nullStack |> ignore)) + struct (nameof IStack.reduce, "stack", (fun () -> IStack.reduce (+) nullStack |> ignore)) + struct (nameof IStack.reduceBack, "stack", (fun () -> IStack.reduceBack (+) nullStack |> ignore)) + struct (nameof IStack.scan, "stack", (fun () -> IStack.scan (+) 0 nullStack |> ignore)) + struct (nameof IStack.scanBack, "stack", (fun () -> IStack.scanBack (+) nullStack 0 |> ignore)) + struct (nameof IStack.sum, "stack", (fun () -> IStack.sum nullStack |> ignore)) + struct (nameof IStack.sumBy, "stack", (fun () -> IStack.sumBy id nullStack |> ignore)) + struct (nameof IStack.countBy, "stack", (fun () -> IStack.countBy id nullStack |> ignore)) + struct (nameof IStack.distinct, "stack", (fun () -> IStack.distinct nullStack |> ignore)) + struct (nameof IStack.distinctBy, "stack", (fun () -> IStack.distinctBy id nullStack |> ignore)) + struct (nameof IStack.except, "stack", (fun () -> IStack.except [| 1 |] nullStack |> ignore)) + struct (nameof IStack.groupBy, "stack", (fun () -> IStack.groupBy id nullStack |> ignore)) + struct (nameof IStack.partition, "stack", (fun () -> IStack.partition ((=) 1) nullStack |> ignore)) + struct (nameof IStack.partitionWith, "stack", (fun () -> IStack.partitionWith Choice1Of2 nullStack |> ignore)) + struct (nameof IStack.skip, "stack", (fun () -> IStack.skip 0 nullStack |> ignore)) + struct (nameof IStack.skipLast, "stack", (fun () -> IStack.skipLast 0 nullStack |> ignore)) + struct (nameof IStack.skipWhile, "stack", (fun () -> IStack.skipWhile ((=) 1) nullStack |> ignore)) + struct (nameof IStack.splitAt, "stack", (fun () -> IStack.splitAt 0 nullStack |> ignore)) + struct (nameof IStack.splitInto, "stack", (fun () -> IStack.splitInto 2 nullStack |> ignore)) + struct (nameof IStack.take, "stack", (fun () -> IStack.take 0 nullStack |> ignore)) + struct (nameof IStack.takeLast, "stack", (fun () -> IStack.takeLast 0 nullStack |> ignore)) + struct (nameof IStack.takeWhile, "stack", (fun () -> IStack.takeWhile ((=) 1) nullStack |> ignore)) + struct (nameof IStack.truncate, "stack", (fun () -> IStack.truncate 1 nullStack |> ignore)) + struct (nameof IStack.sort, "stack", (fun () -> IStack.sort nullStack |> ignore)) + struct (nameof IStack.sortBy, "stack", (fun () -> IStack.sortBy id nullStack |> ignore)) + struct (nameof IStack.sortByDescending, "stack", (fun () -> IStack.sortByDescending id nullStack |> ignore)) + struct (nameof IStack.sortDescending, "stack", (fun () -> IStack.sortDescending nullStack |> ignore)) + struct (nameof IStack.sortWith, "stack", (fun () -> IStack.sortWith compare nullStack |> ignore)) + struct (nameof IStack.sortWithComparer, + "stack", + (fun () -> + IStack.sortWithComparer Comparer.Default nullStack + |> ignore + )) + struct (nameof IStack.randomChoice, "source", (fun () -> IStack.randomChoice nullStack |> ignore)) + struct (nameof IStack.randomChoiceBy, "source", (fun () -> IStack.randomChoiceBy (fun () -> 0.5) nullStack |> ignore)) + struct (nameof IStack.randomChoiceWith, "source", (fun () -> IStack.randomChoiceWith (Random 1) nullStack |> ignore)) + struct (nameof IStack.randomChoices, "source", (fun () -> IStack.randomChoices 1 nullStack |> ignore)) + struct (nameof IStack.randomChoicesBy, + "source", + (fun () -> IStack.randomChoicesBy (fun () -> 0.5) 1 nullStack |> ignore)) + struct (nameof IStack.randomChoicesWith, + "source", + (fun () -> IStack.randomChoicesWith (Random 1) 1 nullStack |> ignore)) + struct (nameof IStack.randomSample, "source", (fun () -> IStack.randomSample 1 nullStack |> ignore)) + struct (nameof IStack.randomSampleBy, + "source", + (fun () -> IStack.randomSampleBy (fun () -> 0.5) 1 nullStack |> ignore)) + struct (nameof IStack.randomSampleWith, "source", (fun () -> IStack.randomSampleWith (Random 1) 1 nullStack |> ignore)) + struct (nameof IStack.randomShuffle, "source", (fun () -> IStack.randomShuffle nullStack |> ignore)) + struct (nameof IStack.randomShuffleBy, + "source", + (fun () -> IStack.randomShuffleBy (fun () -> 0.5) nullStack |> ignore)) + struct (nameof IStack.randomShuffleWith, "source", (fun () -> IStack.randomShuffleWith (Random 1) nullStack |> ignore)) + struct (nameof IStack.toArray, "stack", (fun () -> IStack.toArray nullStack |> ignore)) + struct (nameof IStack.toList, "stack", (fun () -> IStack.toList nullStack |> ignore)) + struct (nameof IStack.toSeq, "stack", (fun () -> IStack.toSeq nullStack |> ignore)) + struct (nameof IStack.clear, "stack", (fun () -> IStack.clear nullStack |> 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 for concat, transpose and except; the rows are consMany, which no contract covers, 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))> = + let widenedItems = FlatListOracles.defaultStringsAsObjects + let objects = Stack.ofArray [| "a" :> obj |] :> IImmutableStack + let defaultItems = Inputs.DefaultFlatListAsSeq() + + Map.ofArray [| + "consMany (default flat list)", struct ("values", (fun () -> IStack.consMany defaultItems (several ()) |> ignore)) + "consMany (default flat list of strings as objects)", + struct ("values", (fun () -> IStack.consMany widenedItems objects |> ignore)) + "except (default flat list of strings as objects)", + struct ("itemsToExclude", (fun () -> IStack.except widenedItems objects |> ignore)) + // List.except checks the items to exclude before the list + "except (default flat list and null stack)", + struct ("itemsToExclude", (fun () -> IStack.except defaultItems nullStack |> ignore)) + |] + + /// 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 a stack, as the rows of their test. + static member NullStackRejections = nullStackRejections.Keys + + /// The functions that take a sequence and the inputs that they reject, as the rows of their test. + static member SequenceRejections = sequenceRejections.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, $"IStack.%s{name} must keep the implementation of its input") + |> ignore + + CollectionAssert.AreEqual ( + IStack.toArray (apply (several ())), + IStack.toArray result, + $"IStack.%s{name} must return the same elements for every implementation" + ) + + [] + member _.``append pushes the first stack onto the second one, whose implementation and nodes it keeps`` + (length1 : int, length2 : int) + = + // The first stack has another implementation, so that the result shows whose implementation it keeps, also when + // the second stack is empty + let first : IImmutableStack = + TaggedStack (Stack.ofArray (Array.init length1 (fun index -> 10 + index))) + let second : IImmutableStack = Stack.ofArray (Array.init length2 id) + let appended = IStack.append first second + + Assert.IsInstanceOfType>(appended, "IStack.append must keep the implementation of the second stack") + |> ignore + + CollectionAssert.AreEqual ( + Array.append (IStack.toArray first) (IStack.toArray second), + IStack.toArray appended, + "IStack.append must keep the elements of the first stack on top of the elements of the second one" + ) + + // List.append shares its second list in the same way; an empty first stack gives the second stack itself + Assert.AreSame ( + second, + IStack.skip length1 appended, + "IStack.append must push the first stack onto the second one, whose nodes the result shares" + ) + + [] + member _.``functions that combine stacks or change the element type return a stack of the elements`` () = + let stack = tagged () + + CollectionAssert.AreEqual ( + Array.map string Inputs.Several, + Stack.toArray (IStack.map string stack), + "IStack.map must map the elements of any implementation into a stack, in their order" + ) + + let concatenated = IStack.concat [| stack; several () |] + + CollectionAssert.AreEqual ( + Array.append Inputs.Several Inputs.Several, + Stack.toArray concatenated, + "IStack.concat must concatenate stacks of different implementations into a stack, in their order" + ) + + [] + member _.``the functions read any implementation from the top`` () = + let stack = tagged () + + Assert.AreEqual(Array.head Inputs.Several, IStack.head stack, "IStack.head must return the top") + Assert.AreEqual(Array.last Inputs.Several, IStack.last stack, "IStack.last must return the bottom") + Assert.AreEqual(Array.length Inputs.Several, IStack.length stack, "IStack.length must count the elements") + CollectionAssert.AreEqual (Inputs.Several, IStack.toArray stack, "IStack.toArray must copy the elements from the top") + + CollectionAssert.AreEqual ( + Array.rev Inputs.Several, + IStack.toArray (IStack.rev stack), + "IStack.rev must put the last element on top" + ) + + [] + member _.``the sorts order strings in F# generic comparison, which is ordinal`` () = + // A culture-sensitive comparer, such as Comparer.Default, puts "a" before "B" + let input = [| "b"; "A"; "a"; "B" |] + let stack : IImmutableStack = Stack.ofArray input + + CollectionAssert.AreEqual ( + Array.sort input, + IStack.toArray (IStack.sort stack), + "IStack.sort must order as Array.sort does" + ) + + CollectionAssert.AreEqual ( + Array.sortDescending input, + IStack.toArray (IStack.sortDescending stack), + "IStack.sortDescending must order as Array.sortDescending does" + ) + + CollectionAssert.AreEqual ( + Array.sortBy id input, + IStack.toArray (IStack.sortBy id stack), + "IStack.sortBy must order the keys as Array.sortBy does" + ) + + CollectionAssert.AreEqual ( + Array.sortByDescending id input, + IStack.toArray (IStack.sortByDescending id stack), + "IStack.sortByDescending must order the keys as Array.sortByDescending does" + ) + + [] + member _.``functions reject a null stack`` (name : string) = + let struct (paramName, action) = nullStackRejections[name] + + let ex = + Assert.ThrowsExactly(Action action, $"IStack.%s{name} must reject a null stack") + + Assert.AreEqual(paramName, ex.ParamName, $"IStack.%s{name} must name the parameter of the null stack") + + [] + member _.``consMany rejects null values with the name of the parameter`` () = + let ex = + Assert.ThrowsExactly( + Action (fun () -> + IStack.consMany (Unchecked.defaultof>) (several ()) + |> ignore + ), + "IStack.consMany must reject null values" + ) + + Assert.AreEqual("values", ex.ParamName, "IStack.consMany must name the values parameter") + + [] + member _.``functions that take a sequence reject a default flat list and check their inputs in order`` (name : string) = + let struct (paramName, actual) = sequenceRejections[name] + FlatListAssert.RejectsNull ($"IStack.%s{name}", paramName, actual) + + [] + member _.``a flat list that is not default passes as a sequence`` () = + let flat = FlatList.ofArray [| 1; 2; 3 |] :> seq + + let stacks = + FlatList.ofArray [| Stack.ofArray [| 1; 2 |] :> IStack; Stack.ofArray [| 3; 4 |] :> IStack |] + :> seq> + + let single = IStack.cons 99 (IStack.clear (tagged ())) + + CollectionAssert.AreEqual ( + [| 1; 2; 3; 99 |], + IStack.toArray (IStack.consMany flat single), + "IStack.consMany must take the elements of a flat list" + ) + + CollectionAssert.AreEqual ( + [| 99 |], + IStack.toArray (IStack.except flat (IStack.cons 2 single)), + "IStack.except must take the elements of a flat list" + ) + + CollectionAssert.AreEqual ( + [| 1; 2; 3; 4 |], + Stack.toArray (IStack.concat stacks), + "IStack.concat must take the stacks of a flat list" + ) + + CollectionAssert.AreEqual ( + [| 1; 3 |], + Stack.toArray (Stack.head (IStack.transpose stacks)), + "IStack.transpose must take the stacks of a flat list" + ) + + [] + 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 = Stack.ofArray (asObjects [| "c" |]) :> IStack + + CollectionAssert.AreEqual ( + asObjects [| "a"; "b"; "c" |], + IStack.toArray (IStack.consMany widened objects), + "IStack.consMany must take the elements of a flat list of strings as objects" + ) + + CollectionAssert.AreEqual ( + asObjects [| "c" |], + IStack.toArray (IStack.except widened (Stack.ofArray (asObjects [| "a"; "c"; "b" |]))), + "IStack.except must take the elements of a flat list of strings as objects" + ) + + [] + member _.``uncons throws for an empty stack as List.head does`` () = + ExceptionAssert.Same ( + StackOps.paramName, + (fun () -> List.head List.empty |> ignore), + (fun () -> IStack.uncons (IStack.clear (tagged ())) |> ignore), + "IStack.uncons of an empty stack must throw as List.head does" + ) + + [] + member _.``cons and consMany push the values on top`` () = + CollectionAssert.AreEqual ( + Array.append [| 99 |] Inputs.Several, + IStack.toArray (IStack.cons 99 (tagged ())), + "IStack.cons must add the value before the first element" + ) + + CollectionAssert.AreEqual ( + Array.append [| 98; 99 |] Inputs.Several, + IStack.toArray (IStack.consMany [| 98; 99 |] (tagged ())), + "IStack.consMany must add the values before the elements, the first value on top" + ) + + [] + member _.``Cons matches the top and the stack below it`` () = + let rec elements (stack : IImmutableStack) = + match stack with + | IStack.Cons (struct (head, tail)) -> Array.append [| head |] (elements tail) + | _ -> [||] + + CollectionAssert.AreEqual (Inputs.Several, elements (tagged ()), "IStack.Cons must match the elements from the top") + + match tagged () with + | IStack.Cons (struct (_, tail)) -> + Assert.IsInstanceOfType>(tail, "IStack.Cons must keep the implementation of the tail") + |> ignore + | _ -> Assert.Fail "IStack.Cons must match a stack with elements" + + [] + member _.``Nil matches an empty stack only`` () = + let describe (stack : IImmutableStack) = + match stack with + | IStack.Nil -> "empty" + | IStack.Cons (struct (head, _)) -> $"%d{head} on top" + | _ -> "unreachable" + + Assert.AreEqual("empty", describe (IStack.clear (tagged ())), "IStack.Nil must match an empty stack") + Assert.AreEqual("5 on top", describe (several ()), "IStack.Nil must not match a stack with elements") + + [] + member _.``contains compares in F# structural equality`` () = + let arrays : IImmutableStack = Stack.singleton [| 1; 2 |] + Assert.IsTrue ( + IStack.contains [| 1; 2 |] arrays, + "IStack.contains must compare arrays by their elements, as List.contains does" + ) + + [] + [] + [] + [] + [] + [] + [] + [] + [] + member _.``the stack functions compile with the names of the stack operations of the BCL`` + (name : string, compiledName : string) + = + Assert.AreEqual( + name, + StackOps.sourceName "FSharp.Collections.Immutable.IStack" compiledName, + "IStack must compile the function under the name of its stack operation in the BCL" + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Stack/IStackTupleTests.fs b/tests/FSharp.Collections.Immutable.Tests/Stack/IStackTupleTests.fs new file mode 100644 index 00000000..05e404ac --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Stack/IStackTupleTests.fs @@ -0,0 +1,268 @@ +namespace FSharp.Collections.Immutable.Tests + +open System.Collections.Immutable +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 `IStack` module of the `Tuple` namespace. +[] +type IStackTupleTests () = + + static let several : IImmutableStack = Stack.ofArray Inputs.Several + + static let other : IImmutableStack = Stack.ofArray Inputs.Other + + static let third : IImmutableStack = Stack.ofArray Inputs.Third + + [] + member _.``tryUncons returns a value option of a reference tuple`` () = + let found : (int * IImmutableStack) voption = IStack.tryUncons several + + // A tuple compares an array by reference, so the first element and the other elements are compared apart + match found with + | ValueSome (head, tail) -> + Assert.AreEqual(Array.head Inputs.Several, head, "Tuple.IStack.tryUncons must return the first element") + + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + IStack.toArray tail, + "Tuple.IStack.tryUncons must return the other elements" + ) + | ValueNone -> Assert.Fail "Tuple.IStack.tryUncons must return the elements of a stack with elements" + + [] + member _.``uncons returns a reference tuple`` () = + let (head : int), (tail : IImmutableStack) = IStack.uncons several + Assert.AreEqual(Array.head Inputs.Several, head, "Tuple.IStack.uncons must return the first element") + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + IStack.toArray tail, + "Tuple.IStack.uncons must return the other elements" + ) + + [] + member _.``Cons is a struct partial pattern of reference tuples`` () = + let pattern : IImmutableStack -> (int * IImmutableStack) voption = IStack.(|Cons|_|) + + match several with + | IStack.Cons (head, tail) -> + Assert.AreEqual(Array.head Inputs.Several, head, "Tuple.IStack.Cons must match the first element") + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + IStack.toArray tail, + "Tuple.IStack.Cons must match the other elements" + ) + | _ -> Assert.Fail "Tuple.IStack.Cons must match a stack with elements" + + Assert.IsTrue ((pattern (IStack.clear several)).IsNone, "Tuple.IStack.Cons must not match an empty stack") + + [] + member _.``allPairs returns reference tuples`` () = + let pairs : Stack = IStack.allPairs several other + + CollectionAssert.AreEqual ( + Array.allPairs Inputs.Several Inputs.Other, + Stack.toArray pairs, + "Tuple.IStack.allPairs must pair as List.allPairs does" + ) + + [] + member _.``indexed returns reference tuples`` () = + let pairs : Stack = IStack.indexed several + + CollectionAssert.AreEqual ( + Array.indexed Inputs.Several, + Stack.toArray pairs, + "Tuple.IStack.indexed must index as List.indexed does" + ) + + [] + member _.``pairwise returns reference tuples`` () = + let pairs : Stack = IStack.pairwise several + + CollectionAssert.AreEqual ( + Array.pairwise Inputs.Several, + Stack.toArray pairs, + "Tuple.IStack.pairwise must pair as List.pairwise does" + ) + + [] + member _.``unzip takes and returns reference tuples`` () = + let pairs : IImmutableStack = IStack.zip several other + let (first : Stack), (second : Stack) = IStack.unzip pairs + CollectionAssert.AreEqual (Inputs.Several, Stack.toArray first, "Tuple.IStack.unzip must return the first components") + CollectionAssert.AreEqual (Inputs.Other, Stack.toArray second, "Tuple.IStack.unzip must return the second components") + + [] + member _.``unzip3 takes and returns reference tuples`` () = + let triples : IImmutableStack = IStack.zip3 several other third + let (first : Stack), (second : Stack), (last : Stack) = IStack.unzip3 triples + CollectionAssert.AreEqual (Inputs.Several, Stack.toArray first, "Tuple.IStack.unzip3 must return the first components") + CollectionAssert.AreEqual (Inputs.Other, Stack.toArray second, "Tuple.IStack.unzip3 must return the second components") + CollectionAssert.AreEqual (Inputs.Third, Stack.toArray last, "Tuple.IStack.unzip3 must return the third components") + + [] + member _.``zip returns reference tuples`` () = + let pairs : Stack = IStack.zip several other + + CollectionAssert.AreEqual ( + Array.zip Inputs.Several Inputs.Other, + Stack.toArray pairs, + "Tuple.IStack.zip must zip as List.zip does" + ) + + [] + member _.``zip and zip3 throw for different lengths as the defaults do`` () = + let short : IImmutableStack = Stack.singleton 1 + + ExceptionAssert.Same ( + id, + (fun () -> + FSharp.Collections.Immutable.IStack.zip several short + |> ignore + ), + (fun () -> IStack.zip several short |> ignore), + "Tuple.IStack.zip must throw as the default zip does" + ) + + ExceptionAssert.Same ( + id, + (fun () -> + FSharp.Collections.Immutable.IStack.zip3 several short other + |> ignore + ), + (fun () -> IStack.zip3 several short other |> ignore), + "Tuple.IStack.zip3 must throw as the default zip3 does" + ) + + [] + member _.``zip3 returns reference tuples`` () = + let triples : Stack = IStack.zip3 several other third + + CollectionAssert.AreEqual ( + Array.zip3 Inputs.Several Inputs.Other Inputs.Third, + Stack.toArray triples, + "Tuple.IStack.zip3 must zip as List.zip3 does" + ) + + [] + member _.``mapFold takes and returns reference tuples`` () = + let (results : Stack), (state : int) = + IStack.mapFold (fun state item -> item * state, state + 1) 1 several + + let expectedResults, expectedState = + Array.mapFold (fun state item -> item * state, state + 1) 1 Inputs.Several + + CollectionAssert.AreEqual (expectedResults, Stack.toArray results, "Tuple.IStack.mapFold must map as List.mapFold does") + Assert.AreEqual(expectedState, state, "Tuple.IStack.mapFold must thread the state as List.mapFold does") + + [] + member _.``mapFoldBack takes and returns reference tuples`` () = + let (results : Stack), (state : int) = + IStack.mapFoldBack (fun item state -> item * state, state + 1) several 1 + + let expectedResults, expectedState = + Array.mapFoldBack (fun item state -> item * state, state + 1) Inputs.Several 1 + + CollectionAssert.AreEqual ( + expectedResults, + Stack.toArray results, + "Tuple.IStack.mapFoldBack must map as List.mapFoldBack does" + ) + + Assert.AreEqual(expectedState, state, "Tuple.IStack.mapFoldBack must thread the state as List.mapFoldBack does") + + [] + member _.``countBy returns reference tuples`` () = + let counts : Stack = IStack.countBy (fun item -> item % 3) several + + CollectionAssert.AreEqual ( + Array.countBy (fun item -> item % 3) Inputs.Several, + Stack.toArray counts, + "Tuple.IStack.countBy must count as List.countBy does" + ) + + [] + member _.``groupBy returns reference tuples`` () = + let groups : Stack> = IStack.groupBy (fun item -> item % 3) several + 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 |> Stack.toArray |> Array.map fst, + "Tuple.IStack.groupBy must return the keys as List.groupBy does" + ) + + CollectionAssert.AreEqual ( + Array.map snd expected, + groups + |> Stack.toArray + |> Array.map (fun (_, items) -> Stack.toArray items), + "Tuple.IStack.groupBy must group the elements as List.groupBy does" + ) + + [] + member _.``partition returns a reference tuple`` () = + let (accepted : IImmutableStack), (rejected : IImmutableStack) = + IStack.partition (fun item -> item > 0) several + + let expectedAccepted, expectedRejected = Array.partition (fun item -> item > 0) Inputs.Several + + CollectionAssert.AreEqual ( + expectedAccepted, + IStack.toArray accepted, + "Tuple.IStack.partition must return the accepted elements" + ) + + CollectionAssert.AreEqual ( + expectedRejected, + IStack.toArray rejected, + "Tuple.IStack.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 : Stack), (second : Stack) = IStack.partitionWith partitioner several + let expectedFirst, expectedSecond = Array.partitionWith partitioner Inputs.Several + CollectionAssert.AreEqual (expectedFirst, Stack.toArray first, "Tuple.IStack.partitionWith must return the first choices") + + CollectionAssert.AreEqual ( + expectedSecond, + Stack.toArray second, + "Tuple.IStack.partitionWith must return the second choices" + ) + + [] + member _.``splitAt returns a reference tuple`` () = + let (before : IImmutableStack), (after : IImmutableStack) = IStack.splitAt 3 several + let expectedBefore, expectedAfter = Array.splitAt 3 Inputs.Several + CollectionAssert.AreEqual (expectedBefore, IStack.toArray before, "Tuple.IStack.splitAt must return the first part") + CollectionAssert.AreEqual (expectedAfter, IStack.toArray after, "Tuple.IStack.splitAt must return the second part") + + [] + member _.``the functions without a tuple keep resolving to the default module`` () = + let found : int voption = IStack.tryFind (fun item -> item > 6) several + Assert.AreEqual(ValueSome 8, found, "IStack.tryFind must resolve to the default module") + + [] + [] + [] + member _.``the stack functions compile with the names of the stack operations of the BCL`` + (name : string, compiledName : string) + = + Assert.AreEqual( + name, + StackOps.sourceName "FSharp.Collections.Immutable.Tuple.IStack" compiledName, + "Tuple.IStack must compile the function under the name of its stack operation in the BCL" + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Stack/StackComputationExpressionTests.fs b/tests/FSharp.Collections.Immutable.Tests/Stack/StackComputationExpressionTests.fs new file mode 100644 index 00000000..b9625b12 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Stack/StackComputationExpressionTests.fs @@ -0,0 +1,288 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// The `stack { }` computation expression, whose first element ends on top. +[] +type StackComputationExpressionTests () = + + /// 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 Stack> = + Map.ofArray [| + "sequence", (fun () -> stack { yield! Unchecked.defaultof> }) + "array", (fun () -> stack { yield! Unchecked.defaultof }) + "stack", (fun () -> stack { 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 () -> stack { yield! nullSequence } |> ignore)) + "yield! (default flat list)", struct ("items", (fun () -> stack { yield! defaultSequence } |> ignore)) + "yield! (default flat list of strings as objects)", + struct ("items", (fun () -> stack { yield! widenedSequence } |> ignore)) + "for (null sequence)", + struct ("source", + (fun () -> + stack { + for item in nullSequence do + yield item + } + |> ignore + )) + "for (default flat list)", + struct ("source", + (fun () -> + stack { + for item in defaultSequence do + yield item + } + |> ignore + )) + "for (default flat list of strings as objects)", + struct ("source", + (fun () -> + stack { + for item in widenedSequence do + yield item + } + |> ignore + )) + |] + + /// The members of the builder that take a collection, called on the builder without running an expression: the builder + /// checks the input when the expression calls the member, not when the collected elements are added to the buffer. + static let builderCalls : Map unit))> = + let nullSequence = Unchecked.defaultof> + let defaultSequence = Inputs.DefaultFlatListAsSeq() + + Map.ofArray [| + "YieldFrom (null sequence)", struct ("items", (fun () -> stack.YieldFrom nullSequence |> ignore)) + "YieldFrom (default flat list)", struct ("items", (fun () -> stack.YieldFrom defaultSequence |> ignore)) + "YieldFrom (null array)", struct ("items", (fun () -> stack.YieldFrom (Unchecked.defaultof) |> ignore)) + "YieldFrom (null stack)", struct ("items", (fun () -> stack.YieldFrom (Unchecked.defaultof>) |> ignore)) + "For (null sequence)", + struct ("source", + (fun () -> + stack.For (nullSequence, (fun item -> stack.Yield item)) + |> ignore + )) + "For (default flat list)", + struct ("source", + (fun () -> + stack.For (defaultSequence, (fun item -> stack.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 + + /// The members of the builder that take a collection, as the rows of the test of the inputs that they reject. + static member BuilderCalls = builderCalls.Keys + + [] + member _.``an expression without elements is empty`` () = + let result : Stack = stack { () } + Assert.IsTrue (Stack.isEmpty result, "stack { () } must be empty") + + [] + member _.``yield adds the elements in order, the first one on top`` () = + let result = stack { + yield 1 + yield 2 + yield 3 + } + + CollectionAssert.AreEqual ([| 1; 2; 3 |], Stack.toArray result, "stack must keep the order of yield") + Assert.AreEqual(1, result.Peek (), "stack must put the first element on top") + + CollectionAssert.AreEqual ( + Stack.toArray (Stack.ofArray [| 1; 2; 3 |]), + Stack.toArray result, + "stack must build what Stack.ofArray builds" + ) + + [] + member _.``implicit yields add the elements in order`` () = + let result = stack { + 1 + 2 + 3 + } + + CollectionAssert.AreEqual ([| 1; 2; 3 |], Stack.toArray result, "stack must keep the order of implicit yields") + + [] + member _.``yield! adds a sequence, an array, a stack and another expression in their order`` () = + let inner = stack { yield 7 } + + let result = stack { + yield! [ 1; 2 ] + yield! [| 3; 4 |] + yield! Stack.ofArray [| 5; 6 |] + yield! inner + } + + CollectionAssert.AreEqual ( + [| 1; 2; 3; 4; 5; 6; 7 |], + Stack.toArray result, + "stack must add the elements of yield! in their order" + ) + + [] + member _.``yield! rejects a null input with the name of its parameter`` (shape : string) = + let ex = + Assert.ThrowsExactly( + Action (fun () -> nullYields[shape]() |> ignore), + $"stack must reject a null %s{shape} in yield!" + ) + + Assert.AreEqual("items", ex.ParamName, $"stack must name the parameter of the null %s{shape}") + + [] + 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 ($"stack with %s{name}", paramName, expression) + + [] + member _.``the builder checks its input when the expression calls the member`` (name : string) = + let struct (paramName, call) = builderCalls[name] + FlatListAssert.RejectsNull ($"StackCE.%s{name}", paramName, call) + + [] + member _.``yield! and for take a flat list that is not default as a sequence`` () = + let flat = FlatList.ofArray [| 1; 2 |] :> seq + let widened = FlatListOracles.stringsAsObjects (FlatList.ofArray [| "a"; "b" |]) + + let numbers = stack { + yield! flat + + for item in flat do + yield item * 10 + } + + CollectionAssert.AreEqual ( + [| 1; 2; 10; 20 |], + Stack.toArray numbers, + "stack must take the elements of a flat list in yield! and for" + ) + + let objects = stack { + yield! widened + + for item in widened do + yield item + } + + CollectionAssert.AreEqual ( + [| "a" :> obj; "b" :> obj; "a" :> obj; "b" :> obj |], + Stack.toArray objects, + "stack 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 result = stack { + for item in 1..3 do + yield item * 10 + } + + CollectionAssert.AreEqual ([| 10; 20; 30 |], Stack.toArray result, "stack must run the body of for for each item") + + [] + member _.``while adds the elements while the guard holds`` () = + let mutable count = 0 + + let result = stack { + while count < 3 do + count <- count + 1 + yield count + } + + CollectionAssert.AreEqual ([| 1; 2; 3 |], Stack.toArray result, "stack must run the body of while while the guard holds") + + [] + member _.``if without else and following elements combine`` () = + let result = stack { + if false then + yield 0 + + yield 1 + + if true then + yield 2 + } + + CollectionAssert.AreEqual ([| 1; 2 |], Stack.toArray result, "stack must combine conditional and plain elements") + + [] + member _.``try with adds the elements of the handler after an exception`` () = + let result = stack { + try + yield 1 + failwith "fails" + with _ -> + yield 2 + } + + CollectionAssert.AreEqual ( + [| 1; 2 |], + Stack.toArray result, + "stack must keep the elements before the exception and add the handler's" + ) + + [] + member _.``try finally runs the compensation`` () = + let mutable compensated = false + + let result = stack { + try + yield 1 + finally + compensated <- true + } + + Assert.IsTrue (compensated, "stack must run the compensation of try finally") + CollectionAssert.AreEqual ([| 1 |], Stack.toArray result, "stack must keep the elements of the body of try finally") + + [] + member _.``use disposes the resource after the body`` () = + let mutable disposed = false + + let result = stack { + use _resource = { + new IDisposable with + member _.Dispose () = disposed <- true + } + + yield 1 + } + + Assert.IsTrue (disposed, "stack must dispose the resource of use") + CollectionAssert.AreEqual ([| 1 |], Stack.toArray result, "stack must keep the elements of the body of use") + + [] + member _.``each run collects the elements again`` () = + let mutable runs = 0 + + let build () = stack { + runs <- runs + 1 + yield runs + } + + CollectionAssert.AreEqual ([| 1 |], Stack.toArray (build ()), "stack must run the body when it builds the stack") + CollectionAssert.AreEqual ([| 2 |], Stack.toArray (build ()), "stack must run the body again for the next stack") diff --git a/tests/FSharp.Collections.Immutable.Tests/Stack/StackContractTests.fs b/tests/FSharp.Collections.Immutable.Tests/Stack/StackContractTests.fs new file mode 100644 index 00000000..379a19db --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Stack/StackContractTests.fs @@ -0,0 +1,30 @@ +namespace FSharp.Collections.Immutable.Tests + +open System.Collections.Immutable +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// The sequence-like contract for `Stack`. +[] +type StackSeqLikeTests () = + inherit SeqLikeContract> (StackOps.seqLike) + +/// The creation contract for `Stack`. +[] +type StackCreationTests () = + inherit SeqCreationContract> (StackOps.creation) + +/// The stable sorting contract for `Stack`. +[] +type StackStableSortTests () = + inherit StableSortContract> (StackOps.stableSort) + +/// The sequence-like contract for `IStack`. +[] +type IStackSeqLikeTests () = + inherit SeqLikeContract> (IStackOps.seqLike) + +/// The stable sorting contract for `IStack`. +[] +type IStackStableSortTests () = + inherit StableSortContract> (IStackOps.stableSort) diff --git a/tests/FSharp.Collections.Immutable.Tests/Stack/StackOps.fs b/tests/FSharp.Collections.Immutable.Tests/Stack/StackOps.fs new file mode 100644 index 00000000..b39516a4 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Stack/StackOps.fs @@ -0,0 +1,223 @@ +/// The functions of the `Stack` module for the contract tests. +[] +module FSharp.Collections.Immutable.Tests.StackOps + +open System +open System.Reflection +open FSharp.Collections.Immutable + +/// +/// Maps the name of a parameter that a List function reports to the name that the stack modules report. +/// +/// +/// The stack modules name their stacks where List names its lists: list, list1, lists and +/// the list names that List composes, such as "list1, list2" and "list.[1]", become stack, +/// stack1, stacks, "stack1, stack2" and "stack.[1]". Every other name, such as +/// source, stays. +/// +let paramName (name : string) = name.Replace ("list", "stack", StringComparison.Ordinal) + +/// +/// The name in F# of the function that a module of the stacks compiles to a method of the given name, or +/// when the module has no such method or the method has its name in F# too. +/// +/// The full name of the module, such as FSharp.Collections.Immutable.Option.Stack. +/// The name of the method. +/// +/// F# records the name of a function whose differs from it in a +/// of the method. +/// +let sourceName (moduleName : string) (compiledName : string) : string | null = + let moduleType = + typeof.Assembly.GetType(moduleName, true) + |> nonNull + + match moduleType.GetMethod (compiledName, BindingFlags.Public ||| BindingFlags.Static) with + | null -> null + | methodInfo -> + match methodInfo.GetCustomAttribute() with + | null -> null + | attribute -> attribute.SourceName + +let private toArrays (stacks : Stack>) = stacks |> Stack.toArray |> Array.map Stack.toArray + +let private toArrayPair (struct (first : Stack<'T1>, second : Stack<'T2>)) = struct (Stack.toArray first, Stack.toArray second) + +/// The functions of the sequence-like facet. +let seqLike : SeqOps> = { + Name = nameof Stack + Mirror = ListMirror + ParamName = paramName + OfSeq = Stack.ofSeq + ToArray = Stack.toArray + + IsEmpty = Stack.isEmpty + Length = Stack.length + + ExactlyOne = Stack.exactlyOne + Head = Stack.head + Last = Stack.last + Tail = Stack.tail + TryExactlyOne = Stack.tryExactlyOne + TryHead = Stack.tryHead + TryLast = Stack.tryLast + TryTail = Stack.tryTail + + Contains = Stack.contains + Exists = Stack.exists + Exists2 = Stack.exists2 + Find = Stack.find + FindBack = Stack.findBack + FindIndex = Stack.findIndex + FindIndexBack = Stack.findIndexBack + Forall = Stack.forall + Forall2 = Stack.forall2 + Pick = Stack.pick + TryFind = Stack.tryFind + TryFindBack = Stack.tryFindBack + TryFindIndex = Stack.tryFindIndex + TryFindIndexBack = Stack.tryFindIndexBack + TryPick = Stack.tryPick + + AllPairs = fun stack1 stack2 -> Stack.allPairs stack1 stack2 |> Stack.toArray + Append = Stack.append + Choose = Stack.choose + ChunkBySize = fun chunkSize stack -> Stack.chunkBySize chunkSize stack |> toArrays + Collect = Stack.collect + Concat = Stack.concat + Filter = Stack.filter + Indexed = Stack.indexed >> Stack.toArray + Map = Stack.map + Map2 = Stack.map2 + Map3 = Stack.map3 + Mapi = Stack.mapi + Mapi2 = Stack.mapi2 + Pairwise = Stack.pairwise >> Stack.toArray + Permute = Stack.permute + Rev = Stack.rev + Transpose = fun stacks -> Stack.transpose stacks |> toArrays + Unzip = fun pairs -> Stack.unzip (Stack.ofArray pairs) |> toArrayPair + Unzip3 = + fun triples -> + let struct (first, second, third) = Stack.unzip3 (Stack.ofArray triples) + struct (Stack.toArray first, Stack.toArray second, Stack.toArray third) + Where = Stack.where + Windowed = fun windowSize stack -> Stack.windowed windowSize stack |> toArrays + Zip = fun stack1 stack2 -> Stack.zip stack1 stack2 |> Stack.toArray + Zip3 = fun stack1 stack2 stack3 -> Stack.zip3 stack1 stack2 stack3 |> Stack.toArray + + Average = fun items -> Stack.average (Stack.ofArray items) + AverageBy = Stack.averageBy + CompareWith = Stack.compareWith + Fold = Stack.fold + Fold2 = Stack.fold2 + FoldBack = Stack.foldBack + FoldBack2 = Stack.foldBack2 + Iter = Stack.iter + Iter2 = Stack.iter2 + Iteri = Stack.iteri + Iteri2 = Stack.iteri2 + MapFold = + fun mapping state stack -> + let struct (results, final) = Stack.mapFold mapping state stack + struct (Stack.toArray results, final) + MapFoldBack = + fun mapping stack state -> + let struct (results, final) = Stack.mapFoldBack mapping stack state + struct (Stack.toArray results, final) + Max = Stack.max + MaxBy = Stack.maxBy + Min = Stack.min + MinBy = Stack.minBy + Reduce = Stack.reduce + ReduceBack = Stack.reduceBack + Scan = fun folder state stack -> Stack.scan folder state stack |> Stack.toArray + ScanBack = fun folder stack state -> Stack.scanBack folder stack state |> Stack.toArray + Sum = Stack.sum + SumBy = Stack.sumBy + + CountBy = fun projection stack -> Stack.countBy projection stack |> Stack.toArray + Distinct = Stack.distinct + DistinctBy = Stack.distinctBy + Except = Stack.except + GroupBy = + fun projection stack -> + Stack.groupBy projection stack + |> Stack.toArray + |> Array.map (fun struct (key, items) -> struct (key, Stack.toArray items)) + Partition = Stack.partition + PartitionWith = fun partitioner stack -> Stack.partitionWith partitioner stack |> toArrayPair + Skip = Stack.skip + SkipLast = Stack.skipLast + SkipWhile = Stack.skipWhile + SplitAt = Stack.splitAt + SplitInto = fun count stack -> Stack.splitInto count stack |> toArrays + Take = Stack.take + TakeLast = Stack.takeLast + TakeWhile = Stack.takeWhile + Truncate = Stack.truncate + + Sort = Stack.sort + SortBy = Stack.sortBy + SortByDescending = Stack.sortByDescending + SortDescending = Stack.sortDescending + SortWith = Stack.sortWith + SortWithComparer = Stack.sortWithComparer + + RandomChoice = Stack.randomChoice + RandomChoiceBy = Stack.randomChoiceBy + RandomChoiceWith = Stack.randomChoiceWith + RandomChoices = Stack.randomChoices + RandomChoicesBy = Stack.randomChoicesBy + RandomChoicesWith = Stack.randomChoicesWith + RandomSample = Stack.randomSample + RandomSampleBy = Stack.randomSampleBy + RandomSampleWith = Stack.randomSampleWith + RandomShuffle = Stack.randomShuffle + RandomShuffleBy = Stack.randomShuffleBy + RandomShuffleWith = Stack.randomShuffleWith + + ToList = Stack.toList + ToSeq = Stack.toSeq +} + +/// The functions of the creation facet. Each function creates a stack whose first element is on top, so the stack +/// enumerates the elements in the order of the input, as the facet expects. +let creation : SeqCreationOps> = { + Name = nameof Stack + Mirror = ListMirror + ParamName = paramName + ToArray = Stack.toArray + + Empty = fun () -> Stack.empty + Init = Stack.init + OfArray = Stack.ofArray + OfList = Stack.ofList + OfReadOnlySpan = fun array -> Stack.ofReadOnlySpan (ReadOnlySpan array) + OfSeq = Stack.ofSeq + OfSpan = fun array -> Stack.ofSpan (Span array) + Replicate = Stack.replicate + Singleton = Stack.singleton + Unfold = Stack.unfold +} + +/// The functions of the stable sorting facet. +let stableSort : StableSortOps> = { + Name = nameof Stack + OfSeq = Stack.ofSeq + ToArray = Stack.toArray + + Sort = fun items -> Stack.ofArray items |> Stack.sort |> Stack.toArray + SortBy = Stack.sortBy + SortByDescending = Stack.sortByDescending + SortDescending = fun items -> Stack.ofArray items |> Stack.sortDescending |> Stack.toArray + SortWith = Stack.sortWith + SortWithComparer = Stack.sortWithComparer + + SortIntegersDescending = fun items -> Stack.ofArray items |> Stack.sortDescending |> Stack.toArray + SortIntegersByDescending = + fun items -> + Stack.ofArray items + |> Stack.sortByDescending id + |> Stack.toArray +} diff --git a/tests/FSharp.Collections.Immutable.Tests/Stack/StackOptionTests.fs b/tests/FSharp.Collections.Immutable.Tests/Stack/StackOptionTests.fs new file mode 100644 index 00000000..24bcb17e --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Stack/StackOptionTests.fs @@ -0,0 +1,212 @@ +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 `Stack` module of the `Option` namespace. +[] +type StackOptionTests () = + + static let several = Stack.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 : Stack = Stack.unfold generator 3 + + CollectionAssert.AreEqual ( + Array.unfold generator 3, + Stack.toArray generated, + "Option.Stack.unfold must generate as List.unfold does, the first element on top" + ) + + [] + member _.``tryExactlyOne returns an option`` () = + let found : int option = Stack.tryExactlyOne (Stack.singleton 5) + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.Stack.tryExactlyOne (Stack.singleton 5), + found, + "Option.Stack.tryExactlyOne must match the default after conversion" + ) + + [] + member _.``tryHead returns an option`` () = + let found : int option = Stack.tryHead several + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.Stack.tryHead several, + found, + "Option.Stack.tryHead must match the default" + ) + + [] + member _.``tryLast returns an option`` () = + let found : int option = Stack.tryLast several + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.Stack.tryLast several, + found, + "Option.Stack.tryLast must match the default" + ) + + [] + member _.``tryTail returns an option`` () = + let found : Stack option = Stack.tryTail several + + // The equality of an option compares the elements of an array that it holds + Assert.AreEqual( + FSharp.Collections.Immutable.Stack.tryTail several + |> ValueOption.map Stack.toArray + |> ValueOption.toOption, + found |> Option.map Stack.toArray, + "Option.Stack.tryTail must match the default" + ) + + [] + member _.``tryUncons returns an option of a reference tuple`` () = + let found : (int * Stack) option = Stack.tryUncons several + + // A tuple compares an array by reference, so the first element and the other elements are compared apart + match found with + | Some (head, tail) -> + Assert.AreEqual(Array.head Inputs.Several, head, "Option.Stack.tryUncons must return the first element") + + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + Stack.toArray tail, + "Option.Stack.tryUncons must return the other elements" + ) + | None -> Assert.Fail "Option.Stack.tryUncons must return the elements of a stack with elements" + + Assert.IsTrue ((Stack.tryUncons Stack.empty).IsNone, "Option.Stack.tryUncons must return None for an empty stack") + + [] + member _.``Cons is a partial pattern of options`` () = + let pattern : Stack -> (int * Stack) option = Stack.(|Cons|_|) + + match several with + | Stack.Cons (head, tail) -> + Assert.AreEqual(Array.head Inputs.Several, head, "Option.Stack.Cons must match the first element") + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + Stack.toArray tail, + "Option.Stack.Cons must match the other elements" + ) + | _ -> Assert.Fail "Option.Stack.Cons must match a stack with elements" + + Assert.IsTrue ((pattern Stack.empty).IsNone, "Option.Stack.Cons must not match an empty stack") + + [] + member _.``pick takes a chooser of options`` () = + let picked : int = Stack.pick negatedIfNegative several + Assert.AreEqual(Array.pick negatedIfNegative Inputs.Several, picked, "Option.Stack.pick must pick as List.pick does") + + [] + member _.``pick throws as List.pick does`` () = + ExceptionAssert.Same ( + StackOps.paramName, + (fun () -> + List.pick (fun _ -> None) (List.ofArray Inputs.Several) + |> ignore + ), + (fun () -> Stack.pick (fun _ -> None) several |> ignore), + "Option.Stack.pick without a choice must throw as List.pick does" + ) + + [] + member _.``tryFind returns an option`` () = + let found : int option = Stack.tryFind (fun item -> item > 6) several + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.Stack.tryFind (fun item -> item > 6) several, + found, + "Option.Stack.tryFind must match the default" + ) + + [] + member _.``tryFindBack returns an option`` () = + let found : int option = Stack.tryFindBack (fun item -> item > 6) several + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.Stack.tryFindBack (fun item -> item > 6) several, + found, + "Option.Stack.tryFindBack must match the default" + ) + + [] + member _.``tryFindIndex returns an option`` () = + let found : int option = Stack.tryFindIndex (fun item -> item > 6) several + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.Stack.tryFindIndex (fun item -> item > 6) several, + found, + "Option.Stack.tryFindIndex must match the default" + ) + + [] + member _.``tryFindIndexBack returns an option`` () = + let found : int option = Stack.tryFindIndexBack (fun item -> item > 6) several + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.Stack.tryFindIndexBack (fun item -> item > 6) several, + found, + "Option.Stack.tryFindIndexBack must match the default" + ) + + [] + member _.``tryPick takes a chooser of options and returns an option`` () = + let found : int option = Stack.tryPick negatedIfNegative several + + Assert.AreEqual( + Array.tryPick negatedIfNegative Inputs.Several, + found, + "Option.Stack.tryPick must pick as List.tryPick does" + ) + + [] + member _.``choose takes a chooser of options`` () = + let chosen : Stack = Stack.choose negatedIfNegative several + + CollectionAssert.AreEqual ( + Array.choose negatedIfNegative Inputs.Several, + Stack.toArray chosen, + "Option.Stack.choose must choose as List.choose does" + ) + + [] + member _.``the functions without an option keep resolving to the default module`` () = + let mapped : Stack = Stack.map (fun item -> item * 2) several + let zipped : Stack = Stack.zip several several + + CollectionAssert.AreEqual ( + Array.map (fun item -> item * 2) Inputs.Several, + Stack.toArray mapped, + "Stack.map must resolve to the default module" + ) + + CollectionAssert.AreEqual ( + Array.zip Inputs.Several Inputs.Several, + Stack.toArray zipped, + "Stack.zip must resolve to the Tuple module" + ) + + [] + [] + [] + [] + member _.``the stack functions compile with the names of the stack operations of the BCL`` + (name : string, compiledName : string) + = + Assert.AreEqual( + name, + StackOps.sourceName "FSharp.Collections.Immutable.Option.Stack" compiledName, + "Option.Stack must compile the function under the name of its stack operation in the BCL" + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Stack/StackTests.fs b/tests/FSharp.Collections.Immutable.Tests/Stack/StackTests.fs new file mode 100644 index 00000000..800fe4f5 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Stack/StackTests.fs @@ -0,0 +1,906 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open System.Collections.Generic +open System.Collections.Immutable +open System.Globalization +open System.Text.RegularExpressions +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// The behaviour of `Stack` that the shared contracts do not cover: the order of the elements, which puts the first element +/// on top, the shared nodes, null inputs, the extras and the details of the semantics of the List module. +[] +type StackTests () = + + static let nullStack = Unchecked.defaultof> + + static let several () = Stack.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 names of the lists in a message of List, such as "list1", "list.[1]" and "(Parameter 'list')", as the names of + /// the stacks; the words of the text, such as "The input list was empty.", stay. + static let stackNames (message : string) = Regex.Replace (message, @"\blist(?=\d|\.\[|')", "stack") + + /// The creation functions, by name, as functions that create a stack of 1, 2 and 3. The rows of the test are the + /// names: a function has no readable name in a row. + static let creations : Map Stack> = + Map.ofArray [| + nameof Stack.ofArray, (fun () -> Stack.ofArray [| 1; 2; 3 |]) + nameof Stack.ofList, (fun () -> Stack.ofList [ 1; 2; 3 ]) + nameof Stack.ofReadOnlySpan, (fun () -> Stack.ofReadOnlySpan (ReadOnlySpan [| 1; 2; 3 |])) + nameof Stack.ofSeq, (fun () -> Stack.ofSeq (Seq.map id [| 1; 2; 3 |])) + nameof Stack.ofSpan, (fun () -> Stack.ofSpan (Span [| 1; 2; 3 |])) + nameof Stack.init, (fun () -> Stack.init 3 (fun index -> index + 1)) + (nameof Stack.unfold, + fun () -> + Stack.unfold + (fun state -> + if state <= 3 then + ValueSome (struct (state, state + 1)) + else + ValueNone + ) + 1) + |] + + /// The functions that remove the first elements, by name, with the number of elements that they remove from the + /// input with several elements. + static let topRemovals : Map -> Stack) * int)> = + Map.ofArray [| + nameof Stack.tail, struct (Stack.tail, 1) + nameof Stack.tryTail, struct ((fun stack -> (Stack.tryTail stack).Value), 1) + nameof Stack.skip, struct (Stack.skip 3, 3) + nameof Stack.skipWhile, struct (Stack.skipWhile (fun item -> item <> 8), 2) + nameof Stack.takeLast, struct (Stack.takeLast 5, 3) + (nameof Stack.splitAt, + struct ((fun stack -> + let struct (_, after) = Stack.splitAt 3 stack + after + ), + 3)) + |] + + /// The functions that keep every element of the input with several elements, by name. + static let wholeStacks : Map -> Stack> = + Map.ofArray [| + nameof Stack.ofSeq, (fun stack -> Stack.ofSeq stack) + nameof Stack.take, Stack.take 8 + nameof Stack.takeLast, Stack.takeLast 8 + nameof Stack.takeWhile, Stack.takeWhile (fun _ -> true) + nameof Stack.truncate, Stack.truncate 100 + nameof Stack.skip, Stack.skip -1 + nameof Stack.skipLast, Stack.skipLast 0 + nameof Stack.skipWhile, Stack.skipWhile (fun _ -> false) + (nameof Stack.splitAt, + fun stack -> + let struct (before, _) = Stack.splitAt 8 stack + before) + |] + + /// The sorting functions, by name, with a projection or a comparison function that must not run on one element. + static let singleElementSorts : Map -> Stack> = + Map.ofArray [| + nameof Stack.sort, Stack.sort + nameof Stack.sortBy, Stack.sortBy (fun _ -> failwith "The projection must not run") + (nameof Stack.sortByDescending, Stack.sortByDescending (fun _ -> failwith "The projection must not run")) + nameof Stack.sortDescending, Stack.sortDescending + nameof Stack.sortWith, Stack.sortWith (fun _ _ -> failwith "The comparer must not run") + nameof Stack.sortWithComparer, Stack.sortWithComparer Comparer.Default + |] + + /// The functions that take a stack, by name, with the parameter that names the null stack and the function on a null + /// stack. The rows of the test are the names: a function has no readable name in a row. + static let nullStackRejections = + let stack = several () + let pairs = Unchecked.defaultof> + let triples = Unchecked.defaultof> + let floats = Unchecked.defaultof> + + [| + struct (nameof Stack.isEmpty, "stack", (fun () -> Stack.isEmpty nullStack |> ignore)) + struct (nameof Stack.length, "stack", (fun () -> Stack.length nullStack |> ignore)) + struct (nameof Stack.exactlyOne, "stack", (fun () -> Stack.exactlyOne nullStack |> ignore)) + struct (nameof Stack.head, "stack", (fun () -> Stack.head nullStack |> ignore)) + struct (nameof Stack.last, "stack", (fun () -> Stack.last nullStack |> ignore)) + struct (nameof Stack.tail, "stack", (fun () -> Stack.tail nullStack |> ignore)) + struct (nameof Stack.tryExactlyOne, "stack", (fun () -> Stack.tryExactlyOne nullStack |> ignore)) + struct (nameof Stack.tryHead, "stack", (fun () -> Stack.tryHead nullStack |> ignore)) + struct (nameof Stack.tryLast, "stack", (fun () -> Stack.tryLast nullStack |> ignore)) + struct (nameof Stack.tryTail, "stack", (fun () -> Stack.tryTail nullStack |> ignore)) + struct (nameof Stack.tryUncons, "stack", (fun () -> Stack.tryUncons nullStack |> ignore)) + struct (nameof Stack.uncons, "stack", (fun () -> Stack.uncons nullStack |> ignore)) + struct (nameof Stack.(|Cons|_|), "stack", (fun () -> Stack.(|Cons|_|) nullStack |> ignore)) + struct (nameof Stack.(|Nil|_|), "stack", (fun () -> Stack.(|Nil|_|) nullStack |> ignore)) + struct (nameof Stack.contains, "source", (fun () -> Stack.contains 1 nullStack |> ignore)) + struct (nameof Stack.exists, "stack", (fun () -> Stack.exists ((=) 1) nullStack |> ignore)) + struct (nameof Stack.exists2, "stack2", (fun () -> Stack.exists2 (=) stack nullStack |> ignore)) + struct (nameof Stack.find, "stack", (fun () -> Stack.find ((=) 1) nullStack |> ignore)) + struct (nameof Stack.findBack, "stack", (fun () -> Stack.findBack ((=) 1) nullStack |> ignore)) + struct (nameof Stack.findIndex, "stack", (fun () -> Stack.findIndex ((=) 1) nullStack |> ignore)) + struct (nameof Stack.findIndexBack, "stack", (fun () -> Stack.findIndexBack ((=) 1) nullStack |> ignore)) + struct (nameof Stack.forall, "stack", (fun () -> Stack.forall ((=) 1) nullStack |> ignore)) + struct (nameof Stack.forall2, "stack1", (fun () -> Stack.forall2 (=) nullStack stack |> ignore)) + struct (nameof Stack.pick, "stack", (fun () -> Stack.pick ValueSome nullStack |> ignore)) + struct (nameof Stack.tryFind, "stack", (fun () -> Stack.tryFind ((=) 1) nullStack |> ignore)) + struct (nameof Stack.tryFindBack, "stack", (fun () -> Stack.tryFindBack ((=) 1) nullStack |> ignore)) + struct (nameof Stack.tryFindIndex, "stack", (fun () -> Stack.tryFindIndex ((=) 1) nullStack |> ignore)) + struct (nameof Stack.tryFindIndexBack, "stack", (fun () -> Stack.tryFindIndexBack ((=) 1) nullStack |> ignore)) + struct (nameof Stack.tryPick, "stack", (fun () -> Stack.tryPick ValueSome nullStack |> ignore)) + struct (nameof Stack.allPairs, "stack1", (fun () -> Stack.allPairs nullStack stack |> ignore)) + struct (nameof Stack.append, "stack2", (fun () -> Stack.append stack nullStack |> ignore)) + struct (nameof Stack.choose, "stack", (fun () -> Stack.choose ValueSome nullStack |> ignore)) + struct (nameof Stack.chunkBySize, "stack", (fun () -> Stack.chunkBySize 2 nullStack |> ignore)) + struct (nameof Stack.collect, "stack", (fun () -> Stack.collect Stack.singleton nullStack |> ignore)) + struct (nameof Stack.concat, "stacks", (fun () -> Stack.concat [| stack; nullStack |] |> ignore)) + struct (nameof Stack.cons, "stack", (fun () -> Stack.cons 1 nullStack |> ignore)) + struct (nameof Stack.consMany, "stack", (fun () -> Stack.consMany [| 1 |] nullStack |> ignore)) + struct (nameof Stack.filter, "stack", (fun () -> Stack.filter ((=) 1) nullStack |> ignore)) + struct (nameof Stack.indexed, "stack", (fun () -> Stack.indexed nullStack |> ignore)) + struct (nameof Stack.map, "stack", (fun () -> Stack.map id nullStack |> ignore)) + struct (nameof Stack.map2, "stack2", (fun () -> Stack.map2 (+) stack nullStack |> ignore)) + struct (nameof Stack.map3, + "stack3", + (fun () -> + Stack.map3 (fun a b c -> a + b + c) stack stack nullStack + |> ignore + )) + struct (nameof Stack.mapi, "stack", (fun () -> Stack.mapi (+) nullStack |> ignore)) + struct (nameof Stack.mapi2, + "stack1", + (fun () -> + Stack.mapi2 (fun index a b -> index + a + b) nullStack stack + |> ignore + )) + struct (nameof Stack.pairwise, "stack", (fun () -> Stack.pairwise nullStack |> ignore)) + struct (nameof Stack.permute, "stack", (fun () -> Stack.permute id nullStack |> ignore)) + struct (nameof Stack.rev, "stack", (fun () -> Stack.rev nullStack |> ignore)) + struct (nameof Stack.transpose, "stacks", (fun () -> Stack.transpose [| nullStack |] |> ignore)) + struct (nameof Stack.unzip, "stack", (fun () -> Stack.unzip pairs |> ignore)) + struct (nameof Stack.unzip3, "stack", (fun () -> Stack.unzip3 triples |> ignore)) + struct (nameof Stack.where, "stack", (fun () -> Stack.where ((=) 1) nullStack |> ignore)) + struct (nameof Stack.windowed, "stack", (fun () -> Stack.windowed 2 nullStack |> ignore)) + struct (nameof Stack.zip, "stack1", (fun () -> Stack.zip nullStack stack |> ignore)) + struct (nameof Stack.zip3, "stack2", (fun () -> Stack.zip3 stack nullStack stack |> ignore)) + struct (nameof Stack.average, "stack", (fun () -> Stack.average floats |> ignore)) + struct (nameof Stack.averageBy, "stack", (fun () -> Stack.averageBy float nullStack |> ignore)) + struct (nameof Stack.compareWith, "stack2", (fun () -> Stack.compareWith compare stack nullStack |> ignore)) + struct (nameof Stack.fold, "stack", (fun () -> Stack.fold (+) 0 nullStack |> ignore)) + struct (nameof Stack.fold2, + "stack2", + (fun () -> + Stack.fold2 (fun state a b -> state + a + b) 0 stack nullStack + |> ignore + )) + struct (nameof Stack.foldBack, "stack", (fun () -> Stack.foldBack (+) nullStack 0 |> ignore)) + struct (nameof Stack.foldBack2, + "stack1", + (fun () -> + Stack.foldBack2 (fun a b state -> state + a + b) nullStack stack 0 + |> ignore + )) + struct (nameof Stack.iter, "stack", (fun () -> Stack.iter ignore nullStack)) + struct (nameof Stack.iter2, "stack2", (fun () -> Stack.iter2 (fun _ _ -> ()) stack nullStack)) + struct (nameof Stack.iteri, "stack", (fun () -> Stack.iteri (fun _ _ -> ()) nullStack)) + struct (nameof Stack.iteri2, "stack1", (fun () -> Stack.iteri2 (fun _ _ _ -> ()) nullStack stack)) + struct (nameof Stack.mapFold, + "stack", + (fun () -> + Stack.mapFold (fun state item -> struct (item, state)) 0 nullStack + |> ignore + )) + struct (nameof Stack.mapFoldBack, + "stack", + (fun () -> + Stack.mapFoldBack (fun item state -> struct (item, state)) nullStack 0 + |> ignore + )) + struct (nameof Stack.max, "stack", (fun () -> Stack.max nullStack |> ignore)) + struct (nameof Stack.maxBy, "stack", (fun () -> Stack.maxBy id nullStack |> ignore)) + struct (nameof Stack.min, "stack", (fun () -> Stack.min nullStack |> ignore)) + struct (nameof Stack.minBy, "stack", (fun () -> Stack.minBy id nullStack |> ignore)) + struct (nameof Stack.reduce, "stack", (fun () -> Stack.reduce (+) nullStack |> ignore)) + struct (nameof Stack.reduceBack, "stack", (fun () -> Stack.reduceBack (+) nullStack |> ignore)) + struct (nameof Stack.scan, "stack", (fun () -> Stack.scan (+) 0 nullStack |> ignore)) + struct (nameof Stack.scanBack, "stack", (fun () -> Stack.scanBack (+) nullStack 0 |> ignore)) + struct (nameof Stack.sum, "stack", (fun () -> Stack.sum nullStack |> ignore)) + struct (nameof Stack.sumBy, "stack", (fun () -> Stack.sumBy id nullStack |> ignore)) + struct (nameof Stack.countBy, "stack", (fun () -> Stack.countBy id nullStack |> ignore)) + struct (nameof Stack.distinct, "stack", (fun () -> Stack.distinct nullStack |> ignore)) + struct (nameof Stack.distinctBy, "stack", (fun () -> Stack.distinctBy id nullStack |> ignore)) + struct (nameof Stack.except, "stack", (fun () -> Stack.except [| 1 |] nullStack |> ignore)) + struct (nameof Stack.groupBy, "stack", (fun () -> Stack.groupBy id nullStack |> ignore)) + struct (nameof Stack.partition, "stack", (fun () -> Stack.partition ((=) 1) nullStack |> ignore)) + struct (nameof Stack.partitionWith, "stack", (fun () -> Stack.partitionWith Choice1Of2 nullStack |> ignore)) + struct (nameof Stack.skip, "stack", (fun () -> Stack.skip 0 nullStack |> ignore)) + struct (nameof Stack.skipLast, "stack", (fun () -> Stack.skipLast 0 nullStack |> ignore)) + struct (nameof Stack.skipWhile, "stack", (fun () -> Stack.skipWhile ((=) 1) nullStack |> ignore)) + struct (nameof Stack.splitAt, "stack", (fun () -> Stack.splitAt 0 nullStack |> ignore)) + struct (nameof Stack.splitInto, "stack", (fun () -> Stack.splitInto 2 nullStack |> ignore)) + struct (nameof Stack.take, "stack", (fun () -> Stack.take 0 nullStack |> ignore)) + struct (nameof Stack.takeLast, "stack", (fun () -> Stack.takeLast 0 nullStack |> ignore)) + struct (nameof Stack.takeWhile, "stack", (fun () -> Stack.takeWhile ((=) 1) nullStack |> ignore)) + struct (nameof Stack.truncate, "stack", (fun () -> Stack.truncate 1 nullStack |> ignore)) + struct (nameof Stack.sort, "stack", (fun () -> Stack.sort nullStack |> ignore)) + struct (nameof Stack.sortBy, "stack", (fun () -> Stack.sortBy id nullStack |> ignore)) + struct (nameof Stack.sortByDescending, "stack", (fun () -> Stack.sortByDescending id nullStack |> ignore)) + struct (nameof Stack.sortDescending, "stack", (fun () -> Stack.sortDescending nullStack |> ignore)) + struct (nameof Stack.sortWith, "stack", (fun () -> Stack.sortWith compare nullStack |> ignore)) + struct (nameof Stack.sortWithComparer, + "stack", + (fun () -> + Stack.sortWithComparer Comparer.Default nullStack + |> ignore + )) + struct (nameof Stack.randomChoice, "source", (fun () -> Stack.randomChoice nullStack |> ignore)) + struct (nameof Stack.randomChoiceBy, "source", (fun () -> Stack.randomChoiceBy (fun () -> 0.5) nullStack |> ignore)) + struct (nameof Stack.randomChoiceWith, "source", (fun () -> Stack.randomChoiceWith (Random 1) nullStack |> ignore)) + struct (nameof Stack.randomChoices, "source", (fun () -> Stack.randomChoices 1 nullStack |> ignore)) + struct (nameof Stack.randomChoicesBy, + "source", + (fun () -> Stack.randomChoicesBy (fun () -> 0.5) 1 nullStack |> ignore)) + struct (nameof Stack.randomChoicesWith, "source", (fun () -> Stack.randomChoicesWith (Random 1) 1 nullStack |> ignore)) + struct (nameof Stack.randomSample, "source", (fun () -> Stack.randomSample 1 nullStack |> ignore)) + struct (nameof Stack.randomSampleBy, "source", (fun () -> Stack.randomSampleBy (fun () -> 0.5) 1 nullStack |> ignore)) + struct (nameof Stack.randomSampleWith, "source", (fun () -> Stack.randomSampleWith (Random 1) 1 nullStack |> ignore)) + struct (nameof Stack.randomShuffle, "source", (fun () -> Stack.randomShuffle nullStack |> ignore)) + struct (nameof Stack.randomShuffleBy, "source", (fun () -> Stack.randomShuffleBy (fun () -> 0.5) nullStack |> ignore)) + struct (nameof Stack.randomShuffleWith, "source", (fun () -> Stack.randomShuffleWith (Random 1) nullStack |> ignore)) + struct (nameof Stack.toArray, "stack", (fun () -> Stack.toArray nullStack |> ignore)) + struct (nameof Stack.toList, "stack", (fun () -> Stack.toList nullStack |> ignore)) + struct (nameof Stack.toSeq, "stack", (fun () -> Stack.toSeq nullStack |> 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. The shared contracts cover a null sequence for ofSeq, concat, transpose and except. + static let otherNullRejections = + let stack = several () + + [| + struct (nameof Stack.ofArray, "array", (fun () -> Stack.ofArray (Unchecked.defaultof) |> ignore)) + struct (nameof Stack.consMany, + "values", + (fun () -> + Stack.consMany (Unchecked.defaultof>) stack + |> 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 for ofSeq, concat, transpose and except; the rows are consMany, which no contract covers, 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))> = + let widenedItems = FlatListOracles.defaultStringsAsObjects + let objects = Stack.ofArray [| "a" :> obj |] + let defaultItems = Inputs.DefaultFlatListAsSeq() + + Map.ofArray [| + "ofSeq (default flat list of strings as objects)", struct ("source", (fun () -> Stack.ofSeq widenedItems |> ignore)) + "consMany (default flat list)", struct ("values", (fun () -> Stack.consMany defaultItems (several ()) |> ignore)) + "consMany (default flat list of strings as objects)", + struct ("values", (fun () -> Stack.consMany widenedItems objects |> ignore)) + "except (default flat list of strings as objects)", + struct ("itemsToExclude", (fun () -> Stack.except widenedItems objects |> ignore)) + // List.except checks the items to exclude before the list + "except (default flat list and null stack)", + struct ("itemsToExclude", (fun () -> Stack.except defaultItems nullStack |> ignore)) + |] + + /// The functions of two stacks, 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 input. + static let commonPrefixWalks = + let short = [| 1; 2 |] + let long = [| 10; 20; 30 |] + + Map.ofArray [| + (nameof Stack.iter2, + struct ((fun (action : int -> int -> unit) -> List.iter2 action (List.ofArray short) (List.ofArray long)), + (fun (action : int -> int -> unit) -> Stack.iter2 action (Stack.ofArray short) (Stack.ofArray long)))) + (nameof Stack.map2, + struct ((fun (action : int -> int -> unit) -> + List.map2 action (List.ofArray long) (List.ofArray short) + |> ignore + ), + (fun (action : int -> int -> unit) -> + Stack.map2 action (Stack.ofArray long) (Stack.ofArray short) + |> ignore + ))) + (nameof Stack.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) -> + Stack.fold2 (fun () a b -> action a b) () (Stack.ofArray short) (Stack.ofArray long) + ))) + (nameof Stack.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) -> + Stack.foldBack2 (fun a b () -> action a b) (Stack.ofArray short) (Stack.ofArray long) () + ))) + |] + + /// The functions whose exceptions List writes with more than one line or with the names of its lists, 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 Stack.zip3, + struct ((fun () -> + List.zip3 (List.ofArray three) (List.ofArray one) (List.ofArray four) + |> ignore + ), + (fun () -> + Stack.zip3 (Stack.ofArray three) (Stack.ofArray one) (Stack.ofArray four) + |> ignore + ))) + (nameof Stack.map3, + struct ((fun () -> + List.map3 (fun a b c -> a + b + c) (List.ofArray three) (List.ofArray one) (List.ofArray four) + |> ignore + ), + (fun () -> + Stack.map3 (fun a b c -> a + b + c) (Stack.ofArray three) (Stack.ofArray one) (Stack.ofArray four) + |> ignore + ))) + (nameof Stack.zip, + struct ((fun () -> List.zip (List.ofArray four) (List.ofArray one) |> ignore), + (fun () -> Stack.zip (Stack.ofArray four) (Stack.ofArray one) |> ignore))) + (nameof Stack.foldBack2, + struct ((fun () -> + List.foldBack2 (fun _ _ state -> state) (List.ofArray one) (List.ofArray four) 0 + |> ignore + ), + (fun () -> + Stack.foldBack2 (fun _ _ state -> state) (Stack.ofArray one) (Stack.ofArray four) 0 + |> ignore + ))) + ($"%s{nameof Stack.foldBack2} of an empty first stack", + struct ((fun () -> + List.foldBack2 (fun _ _ state -> state) List.empty (List.ofArray four) 0 + |> ignore + ), + (fun () -> + Stack.foldBack2 (fun _ _ state -> state) Stack.empty (Stack.ofArray four) 0 + |> ignore + ))) + (nameof Stack.exactlyOne, + struct ((fun () -> List.exactlyOne (List.ofArray three) |> ignore), + (fun () -> Stack.exactlyOne (Stack.ofArray three) |> ignore))) + (nameof Stack.head, + struct ((fun () -> List.head List.empty |> ignore), (fun () -> Stack.head Stack.empty |> ignore))) + (nameof Stack.take, + struct ((fun () -> List.take 5 (List.ofArray three) |> ignore), + (fun () -> Stack.take 5 (Stack.ofArray three) |> ignore))) + (nameof Stack.takeLast, + struct ((fun () -> List.take 5 (List.ofArray three) |> ignore), + (fun () -> Stack.takeLast 5 (Stack.ofArray three) |> ignore))) + (nameof Stack.skip, + struct ((fun () -> List.skip 5 (List.ofArray three) |> ignore), + (fun () -> Stack.skip 5 (Stack.ofArray three) |> ignore))) + (nameof Stack.skipLast, + struct ((fun () -> List.skip 5 (List.ofArray three) |> ignore), + (fun () -> Stack.skipLast 5 (Stack.ofArray three) |> ignore))) + (nameof Stack.splitAt, + struct ((fun () -> List.splitAt 5 (List.ofArray three) |> ignore), + (fun () -> Stack.splitAt 5 (Stack.ofArray three) |> ignore))) + (nameof Stack.init, struct ((fun () -> List.init -2 id |> ignore), (fun () -> Stack.init -2 id |> ignore))) + (nameof Stack.windowed, + struct ((fun () -> List.windowed 0 (List.ofArray three) |> ignore), + (fun () -> Stack.windowed 0 (Stack.ofArray three) |> ignore))) + (nameof Stack.transpose, + struct ((fun () -> + List.transpose [| List.ofArray four; List.ofArray three; List.ofArray four |] + |> ignore + ), + (fun () -> + Stack.transpose [| Stack.ofArray four; Stack.ofArray three; Stack.ofArray four |] + |> ignore + ))) + (nameof Stack.randomChoiceBy, + struct ((fun () -> + List.randomChoiceBy (fun () -> 1.5) (List.ofArray three) + |> ignore + ), + (fun () -> + Stack.randomChoiceBy (fun () -> 1.5) (Stack.ofArray three) + |> ignore + ))) + |] + + /// The names of the creation functions, as the rows of their test. + static member Creations = creations.Keys + + /// The names of the functions that remove the first elements, as the rows of their test. + static member TopRemovals = topRemovals.Keys + + /// The names of the functions that keep every element, as the rows of their test. + static member WholeStacks = wholeStacks.Keys + + /// The names of the sorting functions, as the rows of the test of one element. + static member SingleElementSorts = singleElementSorts.Keys + + /// The names of the functions that take a stack, as the rows of their test. + static member NullStackRejections = nullStackRejections.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 functions that take a sequence and the inputs that they reject, as the rows of their test. + static member SequenceRejections = sequenceRejections.Keys + + /// The names of the functions of two stacks, as the rows of the test of the common prefix. + static member CommonPrefixWalks = commonPrefixWalks.Keys + + /// The names of the functions whose exceptions have complete messages, as the rows of the test of the messages. + static member CompleteMessages = completeMessages.Keys + + // ---- Order ---- + + [] + member _.``ofSeq puts the first element on top, unlike ImmutableStack.CreateRange`` () = + let stack = Stack.ofSeq [| 1; 2; 3 |] + + Assert.AreEqual(1, Stack.head stack, "Stack.ofSeq must put the first element on top") + Assert.AreEqual(1, stack.Peek (), "The top of the stack must be the first element of the sequence") + Assert.AreEqual(3, Stack.last stack, "Stack.ofSeq must put the last element at the bottom") + + Assert.AreEqual( + 3, + ImmutableStack.CreateRange([| 1; 2; 3 |]).Peek(), + "ImmutableStack.CreateRange puts the last element on top, which Stack.ofSeq must not copy" + ) + + [)>] + member _.``ofSeq and toList are inverse functions`` (input : int array) = + let stack = Stack.ofSeq input + + CollectionAssert.AreEqual (input, List.toArray (Stack.toList stack), "Stack.toList must undo Stack.ofSeq") + + CollectionAssert.AreEqual ( + Stack.toArray stack, + Stack.toArray (Stack.ofSeq (Stack.toList stack)), + "Stack.ofSeq must undo Stack.toList" + ) + + CollectionAssert.AreEqual (input, Seq.toArray (Stack.toSeq stack), "Stack.toSeq must enumerate the elements from the top") + CollectionAssert.AreEqual (input, Stack.toArray stack, "Stack.toArray must copy the elements from the top") + + [] + member _.``every creation function puts the first element on top`` (name : string) = + let stack = creations[name]() + + Assert.AreEqual(1, Stack.head stack, $"Stack.%s{name} must put the first element on top") + CollectionAssert.AreEqual ([| 1; 2; 3 |], Stack.toArray stack, $"Stack.%s{name} must keep the order of the elements") + + [] + member _.``head returns the top, the element that Peek returns`` () = + let stack = several () + Assert.AreEqual(stack.Peek (), Stack.head stack, "Stack.head must return the top of the stack") + Assert.AreEqual(Array.head Inputs.Several, Stack.head stack, "Stack.head must return the first element of ofArray") + + [] + member _.``cons pushes the value on top`` () = + let stack = several () + let pushed = Stack.cons 99 stack + + CollectionAssert.AreEqual ( + Array.append [| 99 |] Inputs.Several, + Stack.toArray pushed, + "Stack.cons must add the value before the first element" + ) + + Assert.AreEqual(99, pushed.Peek (), "Stack.cons must push the value on top") + Assert.AreSame (stack, pushed.Pop (), "Stack.cons must push onto the input, whose nodes the result shares") + + [] + member _.``consMany pushes the values so that the first one ends on top`` () = + let stack = Stack.ofArray [| 4; 5 |] + let pushed = Stack.consMany [| 1; 2; 3 |] stack + + CollectionAssert.AreEqual ( + [| 1; 2; 3; 4; 5 |], + Stack.toArray pushed, + "Stack.consMany must add the values before the elements" + ) + Assert.AreEqual(1, Stack.head pushed, "Stack.consMany must leave the first value on top") + Assert.AreSame (stack, Stack.skip 3 pushed, "Stack.consMany must push onto the input, whose nodes the result shares") + + CollectionAssert.AreEqual ( + Stack.toArray (Array.foldBack Stack.cons [| 1; 2; 3 |] stack), + Stack.toArray pushed, + "Stack.consMany must give what cons gives from the last value to the first one" + ) + + Assert.AreSame (stack, Stack.consMany [||] stack, "Stack.consMany of no values must return the input") + + [] + member _.``rev reverses the elements, so the last element ends on top`` () = + let stack = Stack.ofArray [| 1; 2; 3 |] + let reversed = Stack.rev stack + + CollectionAssert.AreEqual ([| 3; 2; 1 |], Stack.toArray reversed, "Stack.rev must reverse the elements") + Assert.AreEqual(Stack.last stack, Stack.head reversed, "Stack.rev must put the last element on top") + CollectionAssert.AreEqual ([| 1; 2; 3 |], Stack.toArray (Stack.rev reversed), "Stack.rev must undo itself") + + let single = Stack.singleton 1 + Assert.AreSame (single, Stack.rev single, "Stack.rev of one element must return the input, as List.rev does") + + [] + member _.``append puts the elements of the first stack on top of the second one`` () = + let first = Stack.ofArray [| 1; 2 |] + let second = Stack.ofArray [| 3; 4 |] + let appended = Stack.append first second + + CollectionAssert.AreEqual ( + [| 1; 2; 3; 4 |], + Stack.toArray appended, + "Stack.append must keep the first stack before the second one" + ) + Assert.AreEqual(1, Stack.head appended, "Stack.append must leave the top of the first stack on top") + Assert.AreSame (second, Stack.skip 2 appended, "Stack.append must push onto the second stack, whose nodes it shares") + + Assert.AreSame ( + second, + Stack.append Stack.empty second, + "Stack.append of an empty first stack must return the second one" + ) + + Assert.AreSame (first, Stack.append first Stack.empty, "Stack.append of an empty second stack must return the first one") + + [] + member _.``uncons returns the top and the stack below it`` () = + let stack = several () + let struct (head, tail) = Stack.uncons stack + + Assert.AreEqual(Array.head Inputs.Several, head, "Stack.uncons must return the first element") + CollectionAssert.AreEqual (Array.tail Inputs.Several, Stack.toArray tail, "Stack.uncons must return the other elements") + Assert.AreSame (stack.Pop (), tail, "Stack.uncons must return the stack below the top, without a copy") + + [] + member _.``uncons throws for an empty stack as List.head does`` () = + ExceptionAssert.Same ( + StackOps.paramName, + (fun () -> List.head List.empty |> ignore), + (fun () -> Stack.uncons Stack.empty |> ignore), + "Stack.uncons of an empty stack must throw as List.head does" + ) + + [] + member _.``tryUncons returns the top and the stack below it or nothing`` () = + let stack = several () + + match Stack.tryUncons stack with + | ValueSome (struct (head, tail)) -> + Assert.AreEqual(Array.head Inputs.Several, head, "Stack.tryUncons must return the first element") + Assert.AreSame (stack.Pop (), tail, "Stack.tryUncons must return the stack below the top") + | ValueNone -> Assert.Fail "Stack.tryUncons must return the elements of a stack with elements" + + Assert.IsTrue ((Stack.tryUncons Stack.empty).IsNone, "Stack.tryUncons of an empty stack must return nothing") + + [] + member _.``Cons matches the top and the stack below it`` () = + let rec elements (stack : Stack) = + match stack with + | Stack.Cons (struct (head, tail)) -> Array.append [| head |] (elements tail) + | _ -> [||] + + CollectionAssert.AreEqual (Inputs.Several, elements (several ()), "Stack.Cons must match the elements from the top") + + match Stack.empty with + | Stack.Cons _ -> Assert.Fail "Stack.Cons must not match an empty stack" + | _ -> () + + [] + member _.``Nil matches an empty stack only`` () = + let describe (stack : Stack) = + match stack with + | Stack.Nil -> "empty" + | Stack.Cons (struct (head, _)) -> $"%d{head} on top" + | _ -> "unreachable" + + Assert.AreEqual("empty", describe Stack.empty, "Stack.Nil must match an empty stack") + Assert.AreEqual("5 on top", describe (several ()), "Stack.Nil must not match a stack with elements") + + [] + member _.``the indices count from the top`` () = + let stack = Stack.ofArray [| 10; 20; 30 |] + Assert.AreEqual(0, Stack.findIndex ((=) 10) stack, "Stack.findIndex must give the top the index 0") + Assert.AreEqual(2, Stack.findIndexBack ((=) 30) stack, "Stack.findIndexBack must give the bottom the last index") + + CollectionAssert.AreEqual ( + [| struct (0, 10); struct (1, 20); struct (2, 30) |], + Stack.toArray (Stack.indexed stack), + "Stack.indexed must give the top the index 0" + ) + + [] + member _.``the functions call their callbacks from the top`` () = + let stack = Stack.init 100 id + let visited = ResizeArray() + + Stack.map visited.Add stack |> ignore + CollectionAssert.AreEqual (Array.init 100 id, visited.ToArray (), "Stack.map must call the mapping from the top") + + visited.Clear () + Stack.iter visited.Add stack + CollectionAssert.AreEqual (Array.init 100 id, visited.ToArray (), "Stack.iter must call the action from the top") + + visited.Clear () + Stack.foldBack (fun item () -> visited.Add item) stack () + + CollectionAssert.AreEqual ( + Array.init 100 (fun index -> 99 - index), + visited.ToArray (), + "Stack.foldBack must start at the bottom" + ) + + // ---- Shared nodes ---- + + [] + member _.``the functions that remove the first elements return the stack below them`` (name : string) = + let struct (remove, count) = topRemovals[name] + let stack = several () + let mutable below = stack + + for _ in 1..count do + below <- below.Pop () + + Assert.AreSame (below, remove stack, $"Stack.%s{name} must return the stack below the removed elements, without a copy") + + [] + member _.``the functions return the input itself when they keep every element`` (name : string) = + let stack = several () + Assert.AreSame (stack, wholeStacks[name] stack, $"Stack.%s{name} of every element must return the input") + + [] + member _.``sorts return a stack of one element itself, as List does`` (name : string) = + let single = Stack.singleton 1 + Assert.AreSame (single, singleElementSorts[name] single, $"Stack.%s{name} of one element must return the input") + + [] + member _.``the sorts order strings in F# generic comparison, which is ordinal`` () = + // A culture-sensitive comparer, such as Comparer.Default, puts "a" before "B" + let input = [| "b"; "A"; "a"; "B" |] + let stack = Stack.ofArray input + + CollectionAssert.AreEqual ( + [| "A"; "B"; "a"; "b" |], + Array.sort input, + "F# generic comparison must order strings ordinally" + ) + CollectionAssert.AreEqual (Array.sort input, Stack.toArray (Stack.sort stack), "Stack.sort must order as Array.sort does") + + CollectionAssert.AreEqual ( + Array.sortDescending input, + Stack.toArray (Stack.sortDescending stack), + "Stack.sortDescending must order as Array.sortDescending does" + ) + + CollectionAssert.AreEqual ( + Array.sortBy id input, + Stack.toArray (Stack.sortBy id stack), + "Stack.sortBy must order the keys as Array.sortBy does" + ) + + CollectionAssert.AreEqual ( + Array.sortByDescending id input, + Stack.toArray (Stack.sortByDescending id stack), + "Stack.sortByDescending must order the keys as Array.sortByDescending does" + ) + + // ---- Null inputs ---- + + [] + member _.``functions reject a null stack`` (name : string) = + let struct (paramName, action) = nullStackRejections[name] + + let ex = + Assert.ThrowsExactly(Action action, $"Stack.%s{name} must reject a null stack") + + Assert.AreEqual(paramName, ex.ParamName, $"Stack.%s{name} must name the parameter of the null stack") + + [] + member _.``functions reject other null inputs with the names of their parameters`` (name : string) = + let struct (paramName, action) = otherNullRejections[name] + + let ex = + Assert.ThrowsExactly(Action action, $"Stack.%s{name} must reject a null input") + + Assert.AreEqual(paramName, ex.ParamName, $"Stack.%s{name} must name the null parameter") + + [] + member _.``functions that take a sequence reject a default flat list and check their inputs in order`` (name : string) = + let struct (paramName, actual) = sequenceRejections[name] + FlatListAssert.RejectsNull ($"Stack.%s{name}", paramName, actual) + + [] + member _.``a flat list that is not default passes as a sequence`` () = + let flat = FlatList.ofArray [| 1; 2; 3 |] :> seq + let stacks = + FlatList.ofArray [| Stack.ofArray [| 1; 2 |]; Stack.ofArray [| 3; 4 |] |] :> seq> + + CollectionAssert.AreEqual ( + [| 1; 2; 3 |], + Stack.toArray (Stack.ofSeq flat), + "Stack.ofSeq must take the elements of a flat list" + ) + + CollectionAssert.AreEqual ( + [| 1; 2; 3; 9 |], + Stack.toArray (Stack.consMany flat (Stack.ofArray [| 9 |])), + "Stack.consMany must take the elements of a flat list" + ) + + CollectionAssert.AreEqual ( + [| 9 |], + Stack.toArray (Stack.except flat (Stack.ofArray [| 2; 9; 3 |])), + "Stack.except must take the elements of a flat list" + ) + + CollectionAssert.AreEqual ( + [| 1; 2; 3; 4 |], + Stack.toArray (Stack.concat stacks), + "Stack.concat must take the stacks of a flat list" + ) + + CollectionAssert.AreEqual ( + [| 1; 3 |], + Stack.toArray (Stack.head (Stack.transpose stacks)), + "Stack.transpose must take the stacks of a flat list" + ) + + [] + 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) + + CollectionAssert.AreEqual ( + asObjects [| "a"; "b" |], + Stack.toArray (Stack.ofSeq widened), + "Stack.ofSeq must take the elements of a flat list of strings as objects" + ) + + CollectionAssert.AreEqual ( + asObjects [| "a"; "b"; "c" |], + Stack.toArray (Stack.consMany widened (Stack.ofArray (asObjects [| "c" |]))), + "Stack.consMany must take the elements of a flat list of strings as objects" + ) + + CollectionAssert.AreEqual ( + asObjects [| "c" |], + Stack.toArray (Stack.except widened (Stack.ofArray (asObjects [| "a"; "c"; "b" |]))), + "Stack.except must take the elements of a flat list of strings as objects" + ) + + // ---- Semantics of List ---- + + [] + member _.``contains compares in F# structural equality`` () = + let arrays = Stack.singleton [| 1; 2 |] + Assert.IsTrue ( + Stack.contains [| 1; 2 |] arrays, + "Stack.contains must compare arrays by their elements, as List.contains does" + ) + + let nan = Stack.singleton Double.NaN + Assert.AreEqual( + List.contains Double.NaN [ Double.NaN ], + Stack.contains Double.NaN nan, + "Stack.contains must compare NaN as List.contains does" + ) + + [] + member _.``the functions of two stacks 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, + $"Stack.%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), + Stack.exists2 (=) (Stack.ofArray short) (Stack.ofArray long), + "Stack.exists2 must return true for a match before the end of the shorter stack" + ) + + Assert.AreEqual( + List.forall2 (=) (List.ofArray short) (List.ofArray long), + Stack.forall2 (=) (Stack.ofArray short) (Stack.ofArray long), + "Stack.forall2 must return false for a mismatch before the end of the shorter stack" + ) + + [] + member _.``except does not read the items to exclude for an empty stack, as List does`` () = + let items = seq { failwith "The items to exclude must not be read" } + Assert.IsEmpty (Stack.except items Stack.empty, "Stack.except of an empty stack must be empty") + + [] + member _.``transpose names the stack that ends first as List does`` () = + ExceptionAssert.Same ( + StackOps.paramName, + (fun () -> List.transpose [| [ 1 ]; [ 1; 2 ] |] |> ignore), + (fun () -> + Stack.transpose [| Stack.ofArray [| 1 |]; Stack.ofArray [| 1; 2 |] |] + |> ignore + ), + "Stack.transpose of a shorter first stack must throw as List.transpose does" + ) + + [] + member _.``splitAt of an empty stack throws as List.splitAt does`` () = + ExceptionAssert.Same ( + StackOps.paramName, + (fun () -> List.splitAt 1 List.empty |> ignore), + (fun () -> Stack.splitAt 1 Stack.empty |> ignore), + "Stack.splitAt 1 of an empty stack must throw as List.splitAt does" + ) + + [] + member _.``the exceptions have the complete messages of List with the names of the stacks`` (name : string) = + let struct (oracle, actual) = completeMessages[name] + let expected = thrown oracle $"List.%s{name} must throw" + let ex = thrown actual $"Stack.%s{name} must throw" + Assert.AreEqual(expected.GetType (), ex.GetType (), $"Stack.%s{name} must throw the type of exception of List") + + Assert.AreEqual( + stackNames expected.Message, + ex.Message, + $"Stack.%s{name} must throw the message of List with the names of the stacks" + ) + + [] + [] + [] + [] + [] + [] + [] + [] + [] + member _.``the stack functions compile with the names of the stack operations of the BCL`` + (name : string, compiledName : string) + = + Assert.AreEqual( + name, + StackOps.sourceName "FSharp.Collections.Immutable.Stack" compiledName, + "Stack must compile the function under the name of its stack operation in the BCL" + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Stack/StackTupleTests.fs b/tests/FSharp.Collections.Immutable.Tests/Stack/StackTupleTests.fs new file mode 100644 index 00000000..a8a66b0e --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Stack/StackTupleTests.fs @@ -0,0 +1,292 @@ +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 `Stack` module of the `Tuple` namespace. +[] +type StackTupleTests () = + + static let several = Stack.ofArray Inputs.Several + + static let other = Stack.ofArray Inputs.Other + + static let third = Stack.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 : Stack = Stack.unfold generator 3 + CollectionAssert.AreEqual ( + [| 3; 2; 1 |], + Stack.toArray generated, + "Tuple.Stack.unfold must generate the elements, the first one on top" + ) + + [] + member _.``tryUncons returns a value option of a reference tuple`` () = + let found : (int * Stack) voption = Stack.tryUncons several + + // A tuple compares an array by reference, so the first element and the other elements are compared apart + match found with + | ValueSome (head, tail) -> + Assert.AreEqual(Array.head Inputs.Several, head, "Tuple.Stack.tryUncons must return the first element") + + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + Stack.toArray tail, + "Tuple.Stack.tryUncons must return the other elements" + ) + | ValueNone -> Assert.Fail "Tuple.Stack.tryUncons must return the elements of a stack with elements" + + [] + member _.``uncons returns a reference tuple`` () = + let (head : int), (tail : Stack) = Stack.uncons several + Assert.AreEqual(Array.head Inputs.Several, head, "Tuple.Stack.uncons must return the first element") + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + Stack.toArray tail, + "Tuple.Stack.uncons must return the other elements" + ) + + [] + member _.``uncons throws as the default does`` () = + ExceptionAssert.Same ( + id, + (fun () -> + FSharp.Collections.Immutable.Stack.uncons Stack.empty + |> ignore + ), + (fun () -> Stack.uncons Stack.empty |> ignore), + "Tuple.Stack.uncons must throw as the default uncons does" + ) + + [] + member _.``Cons is a struct partial pattern of reference tuples`` () = + let pattern : Stack -> (int * Stack) voption = Stack.(|Cons|_|) + + match several with + | Stack.Cons (head, tail) -> + Assert.AreEqual(Array.head Inputs.Several, head, "Tuple.Stack.Cons must match the first element") + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + Stack.toArray tail, + "Tuple.Stack.Cons must match the other elements" + ) + | _ -> Assert.Fail "Tuple.Stack.Cons must match a stack with elements" + + Assert.IsTrue ((pattern Stack.empty).IsNone, "Tuple.Stack.Cons must not match an empty stack") + + [] + member _.``allPairs returns reference tuples`` () = + let pairs : Stack = Stack.allPairs several other + + CollectionAssert.AreEqual ( + Array.allPairs Inputs.Several Inputs.Other, + Stack.toArray pairs, + "Tuple.Stack.allPairs must pair as List.allPairs does" + ) + + [] + member _.``indexed returns reference tuples`` () = + let pairs : Stack = Stack.indexed several + + CollectionAssert.AreEqual ( + Array.indexed Inputs.Several, + Stack.toArray pairs, + "Tuple.Stack.indexed must index as List.indexed does" + ) + + [] + member _.``pairwise returns reference tuples`` () = + let pairs : Stack = Stack.pairwise several + + CollectionAssert.AreEqual ( + Array.pairwise Inputs.Several, + Stack.toArray pairs, + "Tuple.Stack.pairwise must pair as List.pairwise does" + ) + + [] + member _.``unzip takes and returns reference tuples`` () = + let (first : Stack), (second : Stack) = Stack.unzip (Stack.zip several other) + CollectionAssert.AreEqual (Inputs.Several, Stack.toArray first, "Tuple.Stack.unzip must return the first components") + CollectionAssert.AreEqual (Inputs.Other, Stack.toArray second, "Tuple.Stack.unzip must return the second components") + + [] + member _.``unzip3 takes and returns reference tuples`` () = + let (first : Stack), (second : Stack), (last : Stack) = + Stack.unzip3 (Stack.zip3 several other third) + + CollectionAssert.AreEqual (Inputs.Several, Stack.toArray first, "Tuple.Stack.unzip3 must return the first components") + CollectionAssert.AreEqual (Inputs.Other, Stack.toArray second, "Tuple.Stack.unzip3 must return the second components") + CollectionAssert.AreEqual (Inputs.Third, Stack.toArray last, "Tuple.Stack.unzip3 must return the third components") + + [] + member _.``zip returns reference tuples`` () = + let pairs : Stack = Stack.zip several other + + CollectionAssert.AreEqual ( + Array.zip Inputs.Several Inputs.Other, + Stack.toArray pairs, + "Tuple.Stack.zip must zip as List.zip does" + ) + + [] + member _.``zip and zip3 throw for different lengths as the defaults do`` () = + let short = Stack.singleton 1 + + ExceptionAssert.Same ( + id, + (fun () -> + FSharp.Collections.Immutable.Stack.zip several short + |> ignore + ), + (fun () -> Stack.zip several short |> ignore), + "Tuple.Stack.zip must throw as the default zip does" + ) + + ExceptionAssert.Same ( + id, + (fun () -> + FSharp.Collections.Immutable.Stack.zip3 several short other + |> ignore + ), + (fun () -> Stack.zip3 several short other |> ignore), + "Tuple.Stack.zip3 must throw as the default zip3 does" + ) + + [] + member _.``zip3 returns reference tuples`` () = + let triples : Stack = Stack.zip3 several other third + + CollectionAssert.AreEqual ( + Array.zip3 Inputs.Several Inputs.Other Inputs.Third, + Stack.toArray triples, + "Tuple.Stack.zip3 must zip as List.zip3 does" + ) + + [] + member _.``mapFold takes and returns reference tuples`` () = + let (results : Stack), (state : int) = + Stack.mapFold (fun state item -> item * state, state + 1) 1 several + + let expectedResults, expectedState = + Array.mapFold (fun state item -> item * state, state + 1) 1 Inputs.Several + + CollectionAssert.AreEqual (expectedResults, Stack.toArray results, "Tuple.Stack.mapFold must map as List.mapFold does") + Assert.AreEqual(expectedState, state, "Tuple.Stack.mapFold must thread the state as List.mapFold does") + + [] + member _.``mapFoldBack takes and returns reference tuples`` () = + let (results : Stack), (state : int) = + Stack.mapFoldBack (fun item state -> item * state, state + 1) several 1 + + let expectedResults, expectedState = + Array.mapFoldBack (fun item state -> item * state, state + 1) Inputs.Several 1 + + CollectionAssert.AreEqual ( + expectedResults, + Stack.toArray results, + "Tuple.Stack.mapFoldBack must map as List.mapFoldBack does" + ) + + Assert.AreEqual(expectedState, state, "Tuple.Stack.mapFoldBack must thread the state as List.mapFoldBack does") + + [] + member _.``countBy returns reference tuples`` () = + let counts : Stack = Stack.countBy (fun item -> item % 3) several + + CollectionAssert.AreEqual ( + Array.countBy (fun item -> item % 3) Inputs.Several, + Stack.toArray counts, + "Tuple.Stack.countBy must count as List.countBy does" + ) + + [] + member _.``groupBy returns reference tuples`` () = + let groups : Stack> = Stack.groupBy (fun item -> item % 3) several + 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 |> Stack.toArray |> Array.map fst, + "Tuple.Stack.groupBy must return the keys as List.groupBy does" + ) + + CollectionAssert.AreEqual ( + Array.map snd expected, + groups + |> Stack.toArray + |> Array.map (fun (_, items) -> Stack.toArray items), + "Tuple.Stack.groupBy must group the elements as List.groupBy does" + ) + + [] + member _.``partition returns a reference tuple`` () = + let (accepted : Stack), (rejected : Stack) = Stack.partition (fun item -> item > 0) several + let expectedAccepted, expectedRejected = Array.partition (fun item -> item > 0) Inputs.Several + + CollectionAssert.AreEqual ( + expectedAccepted, + Stack.toArray accepted, + "Tuple.Stack.partition must return the accepted elements" + ) + + CollectionAssert.AreEqual ( + expectedRejected, + Stack.toArray rejected, + "Tuple.Stack.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 : Stack), (second : Stack) = Stack.partitionWith partitioner several + let expectedFirst, expectedSecond = Array.partitionWith partitioner Inputs.Several + CollectionAssert.AreEqual (expectedFirst, Stack.toArray first, "Tuple.Stack.partitionWith must return the first choices") + + CollectionAssert.AreEqual ( + expectedSecond, + Stack.toArray second, + "Tuple.Stack.partitionWith must return the second choices" + ) + + [] + member _.``splitAt returns a reference tuple`` () = + let (before : Stack), (after : Stack) = Stack.splitAt 3 several + let expectedBefore, expectedAfter = Array.splitAt 3 Inputs.Several + CollectionAssert.AreEqual (expectedBefore, Stack.toArray before, "Tuple.Stack.splitAt must return the first part") + CollectionAssert.AreEqual (expectedAfter, Stack.toArray after, "Tuple.Stack.splitAt must return the second part") + + [] + member _.``the functions without a tuple keep resolving to the default module`` () = + let found : int voption = Stack.tryFind (fun item -> item > 6) several + Assert.AreEqual(ValueSome 8, found, "Stack.tryFind must resolve to the default module") + + [] + [] + [] + member _.``the stack functions compile with the names of the stack operations of the BCL`` + (name : string, compiledName : string) + = + Assert.AreEqual( + name, + StackOps.sourceName "FSharp.Collections.Immutable.Tuple.Stack" compiledName, + "Tuple.Stack must compile the function under the name of its stack operation in the BCL" + )