From af20fe7c2ab11878dc16fd03923165c4e6f08e39 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 14:07:26 +0200 Subject: [PATCH 1/3] feat: `Queue` and `IQueue` modules with the vocabulary of the FSharp.Core `List` module `Queue` (`ImmutableQueue<'T>`) replaces the old module, which worked on `IImmutableQueue<'T>`. It has every function of `List` that applies to an immutable queue, over the elements in the order in which they leave the queue, with the checks and the exceptions of `List` and with stable sorts. Its parameters and exceptions name the queues `queue`, `queue1`, `queue2`, `queue3` and `queues` where `List` names its lists. The extras of ADR 0001 are `enqueue`, `enqueueMany`, `uncons`, `tryUncons`, the struct partial patterns `Cons` and `Nil`, `ofReadOnlySpan`, `ofSpan`, `skipLast`, `takeLast`, `tryTail` and `sortWithComparer`; `enqueue`, `head`, `tail` and `uncons` compile as `Enqueue`, `Peek`, `Dequeue` and `PeekDequeue`. `IQueue` has the same functions for `IImmutableQueue<'T>` without the creation functions, plus `clear`. A result with the element type of the input keeps the implementation of the input through `Clear` and `Enqueue` or through `Dequeue`; the other results are immutable queues. The `Option` and `Tuple` namespaces repeat the functions that use value options or struct tuples, and `queue { }` builds a queue. A queue does not store its length, so `length` is O(n). `head`, `tail`, `skip`, `skipWhile`, `take`, `truncate` and `splitAt` walk only the elements that they need, and the functions that check against the length take a snapshot first. The `ImmutableQueue` module alias of the old module is removed. Co-Authored-By: Claude Opus 5.5 --- .../ComputationExpressions.fs | 2 + .../ComputationExpressions.fsi | 14 + .../FSharp.Collections.Immutable.fsproj | 17 +- .../Helper/Queues.fs | 137 ++ src/FSharp.Collections.Immutable/Queue.fs | 132 -- .../Queue/IQueue.fs | 945 +++++++++++ .../Queue/IQueue.fsi | 1432 ++++++++++++++++ .../Queue/IQueueOption.fs | 75 + .../Queue/IQueueOption.fsi | 171 ++ .../Queue/IQueueTuple.fs | 126 ++ .../Queue/IQueueTuple.fsi | 189 +++ .../Queue/Queue.fs | 1028 +++++++++++ .../Queue/Queue.fsi | 1512 +++++++++++++++++ .../Queue/QueueCE.fs | 76 + .../Queue/QueueCE.fsi | 74 + .../Queue/QueueOption.fs | 86 + .../Queue/QueueOption.fsi | 185 ++ .../Queue/QueueTuple.fs | 130 ++ .../Queue/QueueTuple.fsi | 204 +++ .../Queue/QueueTypes.fs | 24 + 20 files changed, 6426 insertions(+), 133 deletions(-) create mode 100644 src/FSharp.Collections.Immutable/Helper/Queues.fs delete mode 100644 src/FSharp.Collections.Immutable/Queue.fs create mode 100644 src/FSharp.Collections.Immutable/Queue/IQueue.fs create mode 100644 src/FSharp.Collections.Immutable/Queue/IQueue.fsi create mode 100644 src/FSharp.Collections.Immutable/Queue/IQueueOption.fs create mode 100644 src/FSharp.Collections.Immutable/Queue/IQueueOption.fsi create mode 100644 src/FSharp.Collections.Immutable/Queue/IQueueTuple.fs create mode 100644 src/FSharp.Collections.Immutable/Queue/IQueueTuple.fsi create mode 100644 src/FSharp.Collections.Immutable/Queue/Queue.fs create mode 100644 src/FSharp.Collections.Immutable/Queue/Queue.fsi create mode 100644 src/FSharp.Collections.Immutable/Queue/QueueCE.fs create mode 100644 src/FSharp.Collections.Immutable/Queue/QueueCE.fsi create mode 100644 src/FSharp.Collections.Immutable/Queue/QueueOption.fs create mode 100644 src/FSharp.Collections.Immutable/Queue/QueueOption.fsi create mode 100644 src/FSharp.Collections.Immutable/Queue/QueueTuple.fs create mode 100644 src/FSharp.Collections.Immutable/Queue/QueueTuple.fsi create mode 100644 src/FSharp.Collections.Immutable/Queue/QueueTypes.fs diff --git a/src/FSharp.Collections.Immutable/ComputationExpressions.fs b/src/FSharp.Collections.Immutable/ComputationExpressions.fs index 972f77ec..a7aab05c 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 queue = QueueCE () diff --git a/src/FSharp.Collections.Immutable/ComputationExpressions.fsi b/src/FSharp.Collections.Immutable/ComputationExpressions.fsi index e588c81b..c684d123 100644 --- a/src/FSharp.Collections.Immutable/ComputationExpressions.fsi +++ b/src/FSharp.Collections.Immutable/ComputationExpressions.fsi @@ -33,3 +33,17 @@ val flatList : FlatListCE /// Evaluates to an indexed list of 0, 1, 4, 9, 100 and 200. /// val indexedList : IndexedListCE + +/// Builds a queue. +/// +/// +/// let squares = queue { +/// yield 0 +/// for i in 1..3 do +/// yield i * i +/// yield! [ 100; 200 ] +/// } +/// +/// Evaluates to a queue of 0, 1, 4, 9, 100 and 200, from which 0 leaves first. +/// +val queue : QueueCE diff --git a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj index f66316ea..226c8ff6 100644 --- a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj +++ b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj @@ -40,6 +40,7 @@ + @@ -68,8 +69,22 @@ + + + + + + + + + + + + + + + - diff --git a/src/FSharp.Collections.Immutable/Helper/Queues.fs b/src/FSharp.Collections.Immutable/Helper/Queues.fs new file mode 100644 index 00000000..ca0c691d --- /dev/null +++ b/src/FSharp.Collections.Immutable/Helper/Queues.fs @@ -0,0 +1,137 @@ +/// +/// The helpers that the queue modules share (ADR 0001, D5): the queues of a fresh array and of a buffer, and the walks that +/// read the elements of a queue only up to the ones that a function of +/// or needs. +/// +/// +/// +/// The walks take the interface, which both modules pass, and the caller checks the queue for . They +/// enumerate an with its struct enumerator, which allocates +/// nothing, also when a function of receives it, and any other queue +/// with its own enumerator, which they dispose. +/// +/// +/// Each walk is one generic loop over an enumerator type that implements +/// . The loop runs with the struct ImmutableQueueEnumerator +/// or with the enumerator of the other queue. The JIT compiles generic code for each struct type argument and calls its +/// members without boxing it, so the struct enumerator of the queue advances in place. Member constraints of an inline +/// loop would need the struct to be public, because they resolve only to members that are accessible from everywhere. +/// +/// +module internal FSharp.Collections.Immutable.Queues + +open System +open System.Collections.Generic +open System.Collections.Immutable +open System.Runtime.InteropServices + +/// The struct enumerator of an ImmutableQueue as an IEnumerator, for the loops below, which take it as a type argument. +[] +type ImmutableQueueEnumerator<'T> = + val mutable private enumerator : ImmutableQueue<'T>.Enumerator + new (queue : ImmutableQueue<'T>) = { enumerator = queue.GetEnumerator () } + + interface IEnumerator<'T> with + member this.Current = this.enumerator.Current + + interface Collections.IEnumerator with + member this.Current = box this.enumerator.Current + member this.MoveNext () = this.enumerator.MoveNext () + // The walks never reset an enumerator, and the struct enumerator of the queue cannot be reset + member _.Reset () = raise (NotSupportedException ()) + + interface IDisposable with + member _.Dispose () = () + +/// Runs a loop over the struct enumerator of an ImmutableQueue, or over the enumerator of another queue, which it +/// disposes. The callers pass the same generic loop twice, once for each type of enumerator. +let inline private walk + ([] immutableQueueLoop : ImmutableQueueEnumerator<'T> -> 'Result) + ([] otherQueueLoop : IEnumerator<'T> -> 'Result) + (queue : IImmutableQueue<'T>) + = + match queue with + | :? ImmutableQueue<'T> as queue -> immutableQueueLoop (new ImmutableQueueEnumerator<'T> (queue)) + | _ -> + use enumerator = queue.GetEnumerator () + otherQueueLoop enumerator + +/// The loop of firstMatch. +let private firstMatchLoop (predicate : 'T -> bool) (enumerator : 'Enumerator :> IEnumerator<'T>) = + // A struct enumerator advances only as a mutable value; a call on the parameter would advance a copy + let mutable enumerator = enumerator + 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 loop of firstChoice. +let private firstChoiceLoop (chooser : 'T -> 'U voption) (enumerator : 'Enumerator :> IEnumerator<'T>) = + let mutable enumerator = enumerator + let mutable result = ValueNone + + while result.IsNone && enumerator.MoveNext () do + result <- chooser enumerator.Current + + result + +/// The loop of front. +let private frontLoop (count : int) (enumerator : 'Enumerator :> IEnumerator<'T>) = + let mutable enumerator = enumerator + let items = ResizeArray<'T>() + let mutable more = enumerator.MoveNext () + + while more && items.Count < count do + items.Add enumerator.Current + more <- enumerator.MoveNext () + + struct (items, more) + +/// +/// A queue of the elements of a fresh array, without the null check of +/// . +/// +/// +/// copies the elements +/// into the front stack of the queue, from the last one to the first one, so that the queue dequeues them without +/// reversing a back stack first. +/// +let inline ofFreshArray (array : 'T array) = ImmutableQueue.Create<'T>(ReadOnlySpan<'T> array) + +/// A queue of the elements of a buffer that nobody changes any more, built as ofFreshArray builds it, without a copy of +/// the buffer. +let ofBuffer (buffer : ResizeArray<'T>) = + ImmutableQueue.Create<'T>(Span<'T>.op_Implicit(CollectionsMarshal.AsSpan buffer)) + +/// The index and the first element that satisfies the predicate, or ValueNone. +let firstMatch (predicate : 'T -> bool) (queue : IImmutableQueue<'T>) = + walk (firstMatchLoop predicate) (firstMatchLoop predicate) queue + +/// The first value that the chooser returns for the elements, in their order, or ValueNone. +let firstChoice (chooser : 'T -> 'U voption) (queue : IImmutableQueue<'T>) = + walk (firstChoiceLoop chooser) (firstChoiceLoop chooser) queue + +/// The first elements of a queue, at most the count, and whether the queue has more elements. +let front (count : int) (queue : IImmutableQueue<'T>) = walk (frontLoop count) (frontLoop count) queue + +/// The index and the last element that satisfies the predicate, or ValueNone; List searches a copy from the end too. A +/// null queue throws ArgumentNullException for "queue", the name of the parameter in both modules. +let lastMatch (predicate : 'T -> bool) (queue : IImmutableQueue<'T>) = + let array = Snapshot.ofSeq (nameof queue) queue + 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])) diff --git a/src/FSharp.Collections.Immutable/Queue.fs b/src/FSharp.Collections.Immutable/Queue.fs deleted file mode 100644 index ba9d1f39..00000000 --- a/src/FSharp.Collections.Immutable/Queue.fs +++ /dev/null @@ -1,132 +0,0 @@ -namespace FSharp.Collections.Immutable - -type IQueue<'T> = System.Collections.Immutable.IImmutableQueue<'T> - -type Queue<'T> = System.Collections.Immutable.ImmutableQueue<'T> - -[] -module Queue = - - type internal QueueFactory = System.Collections.Immutable.ImmutableQueue - - let inline private check (queue : IQueue<_>) = checkNotNull (nameof queue) queue - - let inline empty<'T> : Queue<'T> = QueueFactory.Create<'T>() - - let inline singleton<'T> (item : 'T) : Queue<'T> = QueueFactory.Create<'T>(item) - - let inline ofSeq (source : 'T seq) : Queue<'T> = QueueFactory.CreateRange source - - let inline toSeq (queue : Queue<_>) = queue :> seq<_> - - let isEmpty queue = - check queue - queue.IsEmpty - - let clear queue : IQueue<_> = - check queue - queue.Clear () - - let enqueue item queue : IQueue<_> = - check queue - queue.Enqueue item - - let head queue = - check queue - queue.Peek () - - let tail queue : IQueue<_> = - check queue - queue.Dequeue () - - ////////// - - let (|Cons|Nil|) queue = // consider renaming - if isEmpty queue then Nil else Cons (head queue, tail queue) - - ////////// Predicate based ////////// - - let filter predicate queue = - let rec loop queue result = - match queue with - | Cons (head, tail) -> - loop tail - <| if predicate head then enqueue head result else result - | Nil -> result - - loop queue <| clear queue - - ////////// Seq-based ////////// - - let find predicate queue = - check queue - Seq.find predicate queue - - let tryFind predicate queue = - check queue - Seq.tryFind predicate queue - - let findIndex predicate queue = - check queue - Seq.findIndex predicate queue - - let tryFindIndex predicate queue = - check queue - Seq.tryFindIndex predicate queue - - let pick chooser queue = - check queue - Seq.pick chooser queue - - let tryPick chooser queue = - check queue - Seq.tryPick chooser queue - - let iter action queue = - check queue - Seq.iter action queue - - let iteri action queue = - check queue - Seq.iteri action queue - - let iter2 action (queue1 : IQueue<_>) (queue2 : IQueue<_>) = - checkNotNull (nameof queue1) queue1 - checkNotNull (nameof queue2) queue2 - Seq.iter2 action queue1 queue2 - - let fold folder state queue = - check queue - Seq.fold folder state queue - - let forall predicate queue = - check queue - Seq.forall predicate - - let exists predicate queue = - check queue - Seq.exists predicate queue - - let reduce reduction queue = - check queue - Seq.reduce reduction queue - - let inline sum queue = - check queue - Seq.sum queue - - let inline sumBy projection queue = - check queue - Seq.sumBy projection queue - - let inline average queue = - check queue - Seq.average queue - - let inline averageBy projection queue = - check queue - Seq.averageBy projection - -module ImmutableQueue = Queue diff --git a/src/FSharp.Collections.Immutable/Queue/IQueue.fs b/src/FSharp.Collections.Immutable/Queue/IQueue.fs new file mode 100644 index 00000000..de6393ff --- /dev/null +++ b/src/FSharp.Collections.Immutable/Queue/IQueue.fs @@ -0,0 +1,945 @@ +[] +module FSharp.Collections.Immutable.IQueue + +open System +open System.Collections.Immutable +open System.Linq + +// The interface has no members beyond Clear, Enqueue, Dequeue and Peek, so the functions walk the elements, here or in the +// walks of Queues that Queue shares, or take a snapshot of them, apply an Array function or a shared algorithm and build the +// result. A result with the element type of the input keeps the implementation of the input: it comes from Clear and +// Enqueue, or from Dequeue. A result of another element type is an ImmutableQueue. The checks of ListChecks throw what the +// List functions throw (ADR 0001, D5 and D6); the lengths that they check come from Lengths.queueUpTo. +// +// The module does not open System.Collections.Generic: its Queue would shadow the Queue<'T> of this namespace. + +/// A queue of the implementation of the input with the given elements: the empty queue of the input, with the elements +/// enqueued in their order. +let private rebuild (queue : IQueue<'T>) (items : seq<'T>) = + let mutable result = queue.Clear () + + for item in items do + result <- result.Enqueue item + + result + +// ---- Properties ---- + +[] +let isEmpty (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + queue.IsEmpty + +[] +let length (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + let mutable count = 0 + + for _ in queue do + count <- count + 1 + + count + +// ---- Access ---- + +[] +let exactlyOne (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + // List.exactlyOne reports its input under the name "source" + ListChecks.exactlyOne "source" (Lengths.queueUpTo 2 queue) + queue.Peek () + +[] +let head (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + ListChecks.notEmpty (nameof queue) (Lengths.queueUpTo 1 queue) + queue.Peek () + +[] +let last (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + ListChecks.notEmpty (nameof queue) (Lengths.queueUpTo 1 queue) + Enumerable.Last queue + +[] +let tail (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + ListChecks.notEmpty (nameof queue) (Lengths.queueUpTo 1 queue) + queue.Dequeue () + +[] +let tryExactlyOne (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + + if Lengths.queueUpTo 2 queue = 1 then + ValueSome (queue.Peek ()) + else + ValueNone + +[] +let tryHead (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + + if queue.IsEmpty then + ValueNone + else + ValueSome (queue.Peek ()) + +[] +let tryLast (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + + if queue.IsEmpty then + ValueNone + else + ValueSome (Enumerable.Last queue) + +[] +let tryTail (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + + if queue.IsEmpty then + ValueNone + else + ValueSome (queue.Dequeue ()) + +[] +let tryUncons (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + + if queue.IsEmpty then + ValueNone + else + ValueSome (struct (queue.Peek (), queue.Dequeue ())) + +[] +let uncons (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + // An empty queue throws what List.head throws + ListChecks.notEmpty (nameof queue) (Lengths.queueUpTo 1 queue) + struct (queue.Peek (), queue.Dequeue ()) + +[] +[] +let (|Cons|_|) (queue : IQueue<'T>) = tryUncons queue + +[] +let (|Nil|_|) (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + queue.IsEmpty + +// ---- Search ---- + +[] +let contains (value : 'T) (source : IQueue<'T>) = + checkNotNull (nameof source) source + // F# equality with the value on the left, as List.contains compares + Seq.contains value source + +[] +let exists (predicate : 'T -> bool) (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + Seq.exists predicate queue + +[] +let exists2 predicate (queue1 : IQueue<'T1>) (queue2 : IQueue<'T2>) = + ListArrays.exists2 (nameof queue2) predicate (Snapshot.ofSeq (nameof queue1) queue1) (Snapshot.ofSeq (nameof queue2) queue2) + +[] +let find (predicate : 'T -> bool) (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + + match Queues.firstMatch predicate queue with + | ValueSome (struct (_, item)) -> item + | ValueNone -> Raise.indexNotFound () + +[] +let findBack (predicate : 'T -> bool) (queue : IQueue<'T>) = + match Queues.lastMatch predicate queue with + | ValueSome (struct (_, item)) -> item + | ValueNone -> Raise.indexNotFound () + +[] +let findIndex (predicate : 'T -> bool) (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + + match Queues.firstMatch predicate queue with + | ValueSome (struct (index, _)) -> index + | ValueNone -> Raise.indexNotFound () + +[] +let findIndexBack (predicate : 'T -> bool) (queue : IQueue<'T>) = + match Queues.lastMatch predicate queue with + | ValueSome (struct (index, _)) -> index + | ValueNone -> Raise.indexNotFound () + +[] +let forall (predicate : 'T -> bool) (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + Seq.forall predicate queue + +[] +let forall2 predicate (queue1 : IQueue<'T1>) (queue2 : IQueue<'T2>) = + ListArrays.forall2 + (nameof queue1) + (nameof queue2) + predicate + (Snapshot.ofSeq (nameof queue1) queue1) + (Snapshot.ofSeq (nameof queue2) queue2) + +[] +let pick (chooser : 'T -> 'U voption) (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + + match Queues.firstChoice chooser queue with + | ValueSome value -> value + | ValueNone -> Raise.indexNotFound () + +[] +let tryFind (predicate : 'T -> bool) (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + + match Queues.firstMatch predicate queue with + | ValueSome (struct (_, item)) -> ValueSome item + | ValueNone -> ValueNone + +[] +let tryFindBack (predicate : 'T -> bool) (queue : IQueue<'T>) = + match Queues.lastMatch predicate queue with + | ValueSome (struct (_, item)) -> ValueSome item + | ValueNone -> ValueNone + +[] +let tryFindIndex (predicate : 'T -> bool) (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + + match Queues.firstMatch predicate queue with + | ValueSome (struct (index, _)) -> ValueSome index + | ValueNone -> ValueNone + +[] +let tryFindIndexBack (predicate : 'T -> bool) (queue : IQueue<'T>) = + match Queues.lastMatch predicate queue with + | ValueSome (struct (index, _)) -> ValueSome index + | ValueNone -> ValueNone + +[] +let tryPick (chooser : 'T -> 'U voption) (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + Queues.firstChoice chooser queue + +// ---- Transformation ---- + +[] +let allPairs (queue1 : IQueue<'T1>) (queue2 : IQueue<'T2>) = + Arrays.allPairs structPair (Snapshot.ofSeq (nameof queue1) queue1) (Snapshot.ofSeq (nameof queue2) queue2) + |> Queue.ofArray + +[] +let append (queue1 : IQueue<'T>) (queue2 : IQueue<'T>) = + checkNotNull (nameof queue1) queue1 + checkNotNull (nameof queue2) queue2 + // Enqueueing into the first queue keeps its implementation, also when it is empty + let mutable result = queue1 + + for item in queue2 do + result <- result.Enqueue item + + result + +[] +let choose (chooser : 'T -> 'U voption) (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + let chosen = ResizeArray<'U>() + + for item in queue do + match chooser item with + | ValueSome value -> chosen.Add value + | ValueNone -> () + + Queues.ofBuffer chosen + +[] +let chunkBySize chunkSize (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + ListChecks.positive (nameof chunkSize) chunkSize + + Array.chunkBySize chunkSize (Snapshot.ofSeq (nameof queue) queue) + |> Array.map Queue.ofArray + |> Queue.ofArray + +[] +let collect (mapping : 'T -> IQueue<'U>) (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + let collected = ResizeArray<'U>() + + for item in queue do + collected.AddRange (mapping item) + + Queues.ofBuffer collected + +[] +let concat (queues : seq>) = + checkNotNullOrDefaultSeq (nameof queues) queues + let concatenated = ResizeArray<'T>() + + for queue in queues do + // A null queue is a null input, as a null queue is for Queue.concat (ADR 0001, D6) + checkNotNull (nameof queues) queue + concatenated.AddRange queue + + Queues.ofBuffer concatenated + +[] +let enqueue (value : 'T) (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + queue.Enqueue value + +[] +let enqueueMany (values : seq<'T>) (queue : IQueue<'T>) = + checkNotNullOrDefaultSeq (nameof values) values + checkNotNull (nameof queue) queue + let mutable result = queue + + for value in values do + result <- result.Enqueue value + + result + +[] +let filter (predicate : 'T -> bool) (queue : IQueue<'T>) = + let array = Snapshot.ofSeq (nameof queue) queue + rebuild queue (Array.filter predicate array) + +[] +let indexed (queue : IQueue<'T>) = + Arrays.indexed structPair (Snapshot.ofSeq (nameof queue) queue) + |> Queue.ofArray + +[] +let map (mapping : 'T -> 'U) (queue : IQueue<'T>) = + Array.map mapping (Snapshot.ofSeq (nameof queue) queue) + |> Queue.ofArray + +[] +let map2 (mapping : 'T1 -> 'T2 -> 'U) (queue1 : IQueue<'T1>) (queue2 : IQueue<'T2>) = + ListArrays.map2 + (nameof queue1) + (nameof queue2) + mapping + (Snapshot.ofSeq (nameof queue1) queue1) + (Snapshot.ofSeq (nameof queue2) queue2) + |> Queue.ofArray + +[] +let map3 (mapping : 'T1 -> 'T2 -> 'T3 -> 'U) (queue1 : IQueue<'T1>) (queue2 : IQueue<'T2>) (queue3 : IQueue<'T3>) = + ListArrays.map3 + (nameof queue1) + (nameof queue2) + (nameof queue3) + mapping + (Snapshot.ofSeq (nameof queue1) queue1) + (Snapshot.ofSeq (nameof queue2) queue2) + (Snapshot.ofSeq (nameof queue3) queue3) + |> Queue.ofArray + +[] +let mapi (mapping : int -> 'T -> 'U) (queue : IQueue<'T>) = + Array.mapi mapping (Snapshot.ofSeq (nameof queue) queue) + |> Queue.ofArray + +[] +let mapi2 (mapping : int -> 'T1 -> 'T2 -> 'U) (queue1 : IQueue<'T1>) (queue2 : IQueue<'T2>) = + ListArrays.mapi2 + (nameof queue1) + (nameof queue2) + mapping + (Snapshot.ofSeq (nameof queue1) queue1) + (Snapshot.ofSeq (nameof queue2) queue2) + |> Queue.ofArray + +[] +let pairwise (queue : IQueue<'T>) = + Arrays.pairwise structPair (Snapshot.ofSeq (nameof queue) queue) + |> Queue.ofArray + +[] +let permute indexMap (queue : IQueue<'T>) = + // List.permute applies the algorithm of Array.permute to a copy of the list, with the same checks + let array = Snapshot.ofSeq (nameof queue) queue + rebuild queue (Array.permute indexMap array) + +[] +let rev (queue : IQueue<'T>) = + let array = Snapshot.ofSeq (nameof queue) queue + rebuild queue (Array.rev array) + +[] +let transpose (queues : seq>) = + checkNotNullOrDefaultSeq (nameof queues) queues + + let arrays = + queues + |> Seq.map (fun queue -> Snapshot.ofSeq (nameof queues) queue) + |> Seq.toArray + + // List.transpose names the lists "list.[0]", "list.[1]", …; this module names them "queue.[0]", "queue.[1]", … + // Once their lengths agree, Array.transpose agrees with List.transpose + ListChecks.transpose "queue" (arrays |> Array.map _.Length) + + Array.transpose arrays + |> Array.map Queue.ofArray + |> Queue.ofArray + +[] +let unzip (queue : IQueue) = + let struct (first, second) = Arrays.unzip id (Snapshot.ofSeq (nameof queue) queue) + struct (Queue.ofArray first, Queue.ofArray second) + +[] +let unzip3 (queue : IQueue) = + let struct (first, second, third) = Arrays.unzip3 id (Snapshot.ofSeq (nameof queue) queue) + struct (Queue.ofArray first, Queue.ofArray second, Queue.ofArray third) + +[] +let where (predicate : 'T -> bool) (queue : IQueue<'T>) = filter predicate queue + +[] +let windowed windowSize (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + ListChecks.positive (nameof windowSize) windowSize + + Array.windowed windowSize (Snapshot.ofSeq (nameof queue) queue) + |> Array.map Queue.ofArray + |> Queue.ofArray + +[] +let zip (queue1 : IQueue<'T1>) (queue2 : IQueue<'T2>) = + let array1 = Snapshot.ofSeq (nameof queue1) queue1 + let array2 = Snapshot.ofSeq (nameof queue2) queue2 + ListChecks.sameLengths (nameof queue1) array1.Length (nameof queue2) array2.Length + Arrays.zip structPair array1 array2 |> Queue.ofArray + +[] +let zip3 (queue1 : IQueue<'T1>) (queue2 : IQueue<'T2>) (queue3 : IQueue<'T3>) = + let array1 = Snapshot.ofSeq (nameof queue1) queue1 + let array2 = Snapshot.ofSeq (nameof queue2) queue2 + let array3 = Snapshot.ofSeq (nameof queue3) queue3 + ListChecks.zip3 (nameof queue1) array1.Length (nameof queue2) array2.Length (nameof queue3) array3.Length + Arrays.zip3 structTriple array1 array2 array3 + |> Queue.ofArray + +// ---- Folding ---- + +// The inline functions can use public members only, so they check the queue themselves and count its elements while +// they add them; the empty queue throws what List.average and List.averageBy throw, with the name "source" +[] +let inline average (queue : IQueue< ^T >) : ^T = + if obj.ReferenceEquals (queue, null) then + nullArg (nameof queue) + + if queue.IsEmpty then + invalidArg "source" ErrorStrings.InputSequenceEmpty + + let mutable sum = LanguagePrimitives.GenericZero< ^T> + let mutable count = 0 + + for item in queue do + sum <- Checked.(+) sum item + count <- count + 1 + + LanguagePrimitives.DivideByInt< ^T> sum count + +[] +let inline averageBy ([] projection : 'T -> ^U) (queue : IQueue<'T>) : ^U = + if obj.ReferenceEquals (queue, null) then + nullArg (nameof queue) + + if queue.IsEmpty then + invalidArg "source" ErrorStrings.InputSequenceEmpty + + let mutable sum = LanguagePrimitives.GenericZero< ^U> + let mutable count = 0 + + for item in queue do + sum <- Checked.(+) sum (projection item) + count <- count + 1 + + LanguagePrimitives.DivideByInt< ^U> sum count + +[] +let compareWith (comparer : 'T -> 'T -> int) (queue1 : IQueue<'T>) (queue2 : IQueue<'T>) = + // Array.compareWith compares as List.compareWith does: the first difference, then the lengths + Array.compareWith comparer (Snapshot.ofSeq (nameof queue1) queue1) (Snapshot.ofSeq (nameof queue2) queue2) + +[] +let fold (folder : 'State -> 'T -> 'State) (state : 'State) (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + let mutable state = state + + for item in queue do + state <- folder state item + + state + +[] +let fold2 (folder : 'State -> 'T1 -> 'T2 -> 'State) (state : 'State) (queue1 : IQueue<'T1>) (queue2 : IQueue<'T2>) = + ListArrays.fold2 + (nameof queue1) + (nameof queue2) + folder + state + (Snapshot.ofSeq (nameof queue1) queue1) + (Snapshot.ofSeq (nameof queue2) queue2) + +[] +let foldBack (folder : 'T -> 'State -> 'State) (queue : IQueue<'T>) (state : 'State) = + Array.foldBack folder (Snapshot.ofSeq (nameof queue) queue) state + +[] +let foldBack2 (folder : 'T1 -> 'T2 -> 'State -> 'State) (queue1 : IQueue<'T1>) (queue2 : IQueue<'T2>) (state : 'State) = + let array1 = Snapshot.ofSeq (nameof queue1) queue1 + let array2 = Snapshot.ofSeq (nameof queue2) queue2 + // List.foldBack2 checks the lengths before it calls the folder + ListChecks.foldBack2 (nameof queue1) array1.Length (nameof queue2) array2.Length + Array.foldBack2 folder array1 array2 state + +[] +let iter (action : 'T -> unit) (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + + for item in queue do + action item + +[] +let iter2 (action : 'T1 -> 'T2 -> unit) (queue1 : IQueue<'T1>) (queue2 : IQueue<'T2>) = + ListArrays.iter2 + (nameof queue1) + (nameof queue2) + action + (Snapshot.ofSeq (nameof queue1) queue1) + (Snapshot.ofSeq (nameof queue2) queue2) + +[] +let iteri (action : int -> 'T -> unit) (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + let mutable index = 0 + + for item in queue do + action index item + index <- index + 1 + +[] +let iteri2 (action : int -> 'T1 -> 'T2 -> unit) (queue1 : IQueue<'T1>) (queue2 : IQueue<'T2>) = + ListArrays.iteri2 + (nameof queue1) + (nameof queue2) + action + (Snapshot.ofSeq (nameof queue1) queue1) + (Snapshot.ofSeq (nameof queue2) queue2) + +[] +let mapFold (mapping : 'State -> 'T -> struct ('Result * 'State)) (state : 'State) (queue : IQueue<'T>) = + let source = Snapshot.ofSeq (nameof queue) queue + 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 (Queue.ofArray results, state) + +[] +let mapFoldBack (mapping : 'T -> 'State -> struct ('Result * 'State)) (queue : IQueue<'T>) (state : 'State) = + let source = Snapshot.ofSeq (nameof queue) queue + 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 (Queue.ofArray results, state) + +// Seq.max and the others compare as the List functions do once the queue has elements, so the List checks come first + +[] +let max (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + ListChecks.notEmptySequence (nameof queue) (Lengths.queueUpTo 1 queue) + Seq.max queue + +[] +let maxBy (projection : 'T -> 'U) (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + ListChecks.notEmptySequence (nameof queue) (Lengths.queueUpTo 1 queue) + Seq.maxBy projection queue + +[] +let min (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + ListChecks.notEmptySequence (nameof queue) (Lengths.queueUpTo 1 queue) + Seq.min queue + +[] +let minBy (projection : 'T -> 'U) (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + ListChecks.notEmptySequence (nameof queue) (Lengths.queueUpTo 1 queue) + Seq.minBy projection queue + +[] +let reduce (reduction : 'T -> 'T -> 'T) (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + ListChecks.notEmpty (nameof queue) (Lengths.queueUpTo 1 queue) + Seq.reduce reduction queue + +[] +let reduceBack (reduction : 'T -> 'T -> 'T) (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + ListChecks.notEmpty (nameof queue) (Lengths.queueUpTo 1 queue) + Array.reduceBack reduction (Snapshot.ofSeq (nameof queue) queue) + +[] +let scan (folder : 'State -> 'T -> 'State) (state : 'State) (queue : IQueue<'T>) = + Array.scan folder state (Snapshot.ofSeq (nameof queue) queue) + |> Queue.ofArray + +[] +let scanBack (folder : 'T -> 'State -> 'State) (queue : IQueue<'T>) (state : 'State) = + Array.scanBack folder (Snapshot.ofSeq (nameof queue) queue) state + |> Queue.ofArray + +[] +let inline sum (queue : IQueue< ^T >) : ^T = + if obj.ReferenceEquals (queue, null) then + nullArg (nameof queue) + + let mutable sum = LanguagePrimitives.GenericZero< ^T> + + for item in queue do + sum <- Checked.(+) sum item + + sum + +[] +let inline sumBy ([] projection : 'T -> ^U) (queue : IQueue<'T>) : ^U = + if obj.ReferenceEquals (queue, null) then + nullArg (nameof queue) + + let mutable sum = LanguagePrimitives.GenericZero< ^U> + + for item in queue do + sum <- Checked.(+) sum (projection item) + + sum + +// ---- Partitioning and grouping ---- + +[] +let countBy (projection : 'T -> 'Key) (queue : IQueue<'T>) = + Arrays.countBy structPair projection (Snapshot.ofSeq (nameof queue) queue) + |> Queue.ofArray + +[] +let distinct (queue : IQueue<'T>) = + let array = Snapshot.ofSeq (nameof queue) queue + rebuild queue (Array.distinct array) + +[] +let distinctBy (projection : 'T -> 'Key) (queue : IQueue<'T>) = + let array = Snapshot.ofSeq (nameof queue) queue + rebuild queue (Array.distinctBy projection array) + +[] +let except (itemsToExclude : seq<'T>) (queue : IQueue<'T>) = + // List.except checks the items to exclude first and does not read them for an empty list + checkNotNullOrDefaultSeq (nameof itemsToExclude) itemsToExclude + checkNotNull (nameof queue) queue + + if queue.IsEmpty then + queue + else + rebuild queue (Array.except itemsToExclude (Snapshot.ofSeq (nameof queue) queue)) + +[] +let groupBy (projection : 'T -> 'Key) (queue : IQueue<'T>) = + Arrays.groupBy (fun key items -> struct (key, Queue.ofArray items)) projection (Snapshot.ofSeq (nameof queue) queue) + |> Queue.ofArray + +[] +let partition (predicate : 'T -> bool) (queue : IQueue<'T>) = + let accepted, rejected = Array.partition predicate (Snapshot.ofSeq (nameof queue) queue) + struct (rebuild queue accepted, rebuild queue rejected) + +[] +let partitionWith (partitioner : 'T -> Choice<'T1, 'T2>) (queue : IQueue<'T>) = + let first, second = Array.partitionWith partitioner (Snapshot.ofSeq (nameof queue) queue) + struct (Queue.ofArray first, Queue.ofArray second) + +[] +let skip count (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + let mutable rest = queue + let mutable skipped = 0 + + while skipped < count && not rest.IsEmpty do + rest <- rest.Dequeue () + skipped <- skipped + 1 + + // The loop stops at the count or at the end of the queue, where the number of skipped elements is the length; a + // count beyond the length throws what List.skip throws + ListChecks.skip (nameof count) count skipped + rest + +[] +let skipLast count (queue : IQueue<'T>) = + let array = Snapshot.ofSeq (nameof queue) queue + // The checks of List.skip + ListChecks.skip (nameof count) count array.Length + + if count <= 0 then + queue + else + rebuild queue (ArraySegment<'T>(array, 0, array.Length - count)) + +[] +let skipWhile (predicate : 'T -> bool) (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + let mutable rest = queue + + while not rest.IsEmpty && predicate (rest.Peek ()) do + rest <- rest.Dequeue () + + rest + +[] +let splitAt index (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + let before = ResizeArray<'T>() + let mutable rest = queue + + while before.Count < index && not rest.IsEmpty do + before.Add (rest.Peek ()) + rest <- rest.Dequeue () + + // The loop stops at the index or at the end of the queue, where the number of taken elements is the length + ListChecks.splitAt (nameof index) index before.Count + struct ((if rest.IsEmpty then queue else rebuild queue before), rest) + +[] +let splitInto count (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + ListChecks.positive (nameof count) count + + Array.splitInto count (Snapshot.ofSeq (nameof queue) queue) + |> Array.map Queue.ofArray + |> Queue.ofArray + +[] +let take count (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + let struct (items, more) = Queues.front count queue + // The front stops at the count or at the end of the queue, where the number of taken elements is the length + ListChecks.take (nameof count) count items.Count + if more then rebuild queue items else queue + +[] +let takeLast count (queue : IQueue<'T>) = + let array = Snapshot.ofSeq (nameof queue) queue + // The checks of List.take + ListChecks.take (nameof count) count array.Length + + if count = array.Length then + queue + else + rebuild queue (ArraySegment<'T>(array, array.Length - count, count)) + +[] +let takeWhile (predicate : 'T -> bool) (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + let items = ResizeArray<'T>() + use enumerator = queue.GetEnumerator () + let mutable more = enumerator.MoveNext () + + while more && predicate enumerator.Current do + items.Add enumerator.Current + more <- enumerator.MoveNext () + + if more then rebuild queue items else queue + +[] +let truncate count (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + let struct (items, more) = Queues.front count queue + if more then rebuild queue items else queue + +// ---- Sorting ---- + +// The sorts are stable, as the sorts of List are (ADR 0001, D5). The functions that compare the elements, or that call the +// projection in the comparison as List.sortByDescending does, sort with the Seq functions of FSharp.Core, which share the +// stable sort of List: the same comparisons run in Array.Sort, which wraps the exception of a comparison in an +// InvalidOperationException (D6). Seq.sortDescending and Seq.sortByDescending are inline and box the elements in every +// comparison once they are compiled into a generic function, so the two descending sorts call Seq.sortWith through +// DescendingSorts, with the cached comparer. sortBy and sortWithComparer sort with LINQ, which computes the keys first, as +// List.sortBy does, and wraps the exception of a comparison as Array.Sort does. A queue of one element is returned +// without a call of the projection or the comparer, as List returns a list of one element. + +[] +let sort (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + + if Lengths.queueUpTo 2 queue <= 1 then + queue + else + rebuild queue (Seq.sort queue) + +[] +let sortBy (projection : 'T -> 'Key) (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + + if Lengths.queueUpTo 2 queue <= 1 then + queue + else + rebuild queue (queue.OrderBy(Func<'T, 'Key> projection, DefaultComparer<'Key>.Instance).ToArray()) + +[] +let sortByDescending (projection : 'T -> 'Key) (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + + if Lengths.queueUpTo 2 queue <= 1 then + queue + else + rebuild queue (DescendingSorts.sortByDescending projection queue) + +[] +let sortDescending (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + + if Lengths.queueUpTo 2 queue <= 1 then + queue + else + rebuild queue (DescendingSorts.sortDescending queue) + +[] +let sortWith (comparer : 'T -> 'T -> int) (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + + if Lengths.queueUpTo 2 queue <= 1 then + queue + else + rebuild queue (Seq.sortWith comparer queue) + +[] +let sortWithComparer (comparer : System.Collections.Generic.IComparer<'T>) (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + + if Lengths.queueUpTo 2 queue <= 1 then + queue + else + rebuild queue (queue.Order(comparer).ToArray()) + +// ---- Random ---- + +// The functions draw the random numbers as the List functions do: they take a snapshot, run the List checks with its +// length and choose from it, or apply the Array function to it + +[] +let randomChoice (source : IQueue<'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 : IQueue<'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 : IQueue<'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 : IQueue<'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 : IQueue<'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 : IQueue<'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 : IQueue<'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 : IQueue<'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 : IQueue<'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 : IQueue<'T>) = + let array = Snapshot.ofSeq (nameof source) source + Array.randomShuffleInPlace array + rebuild source array + +[] +let randomShuffleBy (randomizer : unit -> float) (source : IQueue<'T>) = + let array = Snapshot.ofSeq (nameof source) source + Array.randomShuffleInPlaceBy randomizer array + rebuild source array + +[] +let randomShuffleWith (random : Random) (source : IQueue<'T>) = + checkNotNull (nameof random) random + let array = Snapshot.ofSeq (nameof source) source + Array.randomShuffleInPlaceWith random array + rebuild source array + +// ---- Conversion ---- + +[] +let toArray (queue : IQueue<'T>) = Snapshot.ofSeq (nameof queue) queue + +[] +let toList (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + List.ofSeq queue + +[] +let toSeq (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + queue :> seq<'T> + +// ---- Type-specific ---- + +[] +let clear (queue : IQueue<'T>) = + checkNotNull (nameof queue) queue + queue.Clear () diff --git a/src/FSharp.Collections.Immutable/Queue/IQueue.fsi b/src/FSharp.Collections.Immutable/Queue/IQueue.fsi new file mode 100644 index 00000000..62c65b1c --- /dev/null +++ b/src/FSharp.Collections.Immutable/Queue/IQueue.fsi @@ -0,0 +1,1432 @@ +/// +/// Functions for , the interface of the immutable queues. +/// +/// +/// +/// The module mirrors as +/// does, over the elements in the order in which they leave the queue: +/// 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. The module has no creation functions, because an interface +/// cannot create a queue. +/// +/// +/// 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, followed by +/// , or +/// . A function that changes the element type, or +/// that combines several queues into one, returns an . +/// +/// +/// Every function throws for a queue. The +/// complexities in the remarks are the ones of an , which +/// does not store its length. +/// +/// +/// 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.IQueue + +open System +open System.Collections.Immutable + +// ---- Properties ---- + +/// Returns whether the queue has no elements. +/// The input queue. +/// when the queue is empty. +/// O(1). +[] +val isEmpty : queue : IQueue<'T> -> bool + +/// +/// Returns the number of elements of the queue. +/// +/// The input queue. +/// The length of the queue. +/// +/// O(n): the interface has no length, so the function counts the elements. +/// +[] +val length : queue : IQueue<'T> -> int + +// ---- Access ---- + +/// Returns the only element of the queue. +/// The input queue. +/// The only element. +/// Thrown when the queue does not have exactly one element. +/// Amortized O(1). +[] +val exactlyOne : queue : IQueue<'T> -> 'T + +/// +/// Returns the first element of the queue, the one that leaves it next. +/// +/// The input queue. +/// The first element. +/// Thrown when the queue is empty. +/// +/// O(1). The function wraps . +/// +[] +val head : queue : IQueue<'T> -> 'T + +/// +/// Returns the last element of the queue, the one that joined it last. +/// +/// The input queue. +/// The last element. +/// Thrown when the queue is empty. +/// +/// O(n). The function wraps . +/// +[] +val last : queue : IQueue<'T> -> 'T + +/// +/// Returns the queue without its first element. +/// +/// The input queue. +/// The elements after the first one, in a queue of the implementation of the input. +/// Thrown when the queue is empty. +/// +/// Amortized O(1). The function wraps . +/// +[] +val tail : queue : IQueue<'T> -> IQueue<'T> + +/// +/// Returns the only element of the queue, or ValueNone when it does not have exactly one element. +/// +/// The input queue. +/// The only element, or ValueNone. +/// +/// Amortized O(1). +/// +[] +val tryExactlyOne : queue : IQueue<'T> -> 'T voption + +/// +/// Returns the first element of the queue, or ValueNone when it is empty. +/// +/// The input queue. +/// The first element, or ValueNone. +/// +/// O(1). +/// +[] +val tryHead : queue : IQueue<'T> -> 'T voption + +/// +/// Returns the last element of the queue, or ValueNone when it is empty. +/// +/// The input queue. +/// The last element, or ValueNone. +/// +/// O(n). +/// +[] +val tryLast : queue : IQueue<'T> -> 'T voption + +/// +/// Returns the queue without its first element, or ValueNone when it is empty. +/// +/// The input queue. +/// The elements after the first one, in a queue of the implementation of the input, or ValueNone. +/// +/// Amortized O(1). +/// +[] +val tryTail : queue : IQueue<'T> -> IQueue<'T> voption + +/// +/// Returns the first element and the other elements, or ValueNone when the queue is empty. +/// +/// The input queue. +/// The first element and the elements after it, or ValueNone. +/// +/// Amortized O(1). +/// +[] +val tryUncons : queue : IQueue<'T> -> struct ('T * IQueue<'T>) voption + +/// +/// Returns the first element and the other elements. +/// +/// The input queue. +/// The first element and the elements after it, in a queue of the implementation of the input. +/// Thrown when the queue is empty. +/// +/// Amortized O(1). The function throws what +/// throws. +/// +[] +val uncons : queue : IQueue<'T> -> struct ('T * IQueue<'T>) + +/// +/// Matches a queue with a first element, and returns that element and the other elements. +/// +/// The input queue. +/// The first element and the elements after it, or ValueNone for an empty queue. +/// +/// Amortized O(1). A struct partial pattern, so that a match allocates no option. +/// +[] +[] +val (|Cons|_|) : queue : IQueue<'T> -> struct ('T * IQueue<'T>) voption + +/// Matches an empty queue. +/// The input queue. +/// when the queue is empty. +/// O(1). +[] +val (|Nil|_|) : queue : IQueue<'T> -> bool + +// ---- Search ---- + +/// +/// Returns whether the queue contains an element equal to a value, in F# structural equality. +/// +/// The value to find. +/// The input queue. +/// when an element equals the value. +/// +/// O(n). +/// +[] +val contains : value : 'T -> source : IQueue<'T> -> bool when 'T : equality + +/// Returns whether any element satisfies the predicate. +/// The function to test the elements. +/// The input queue. +/// when an element satisfies the predicate. +/// O(n). +[] +val exists : predicate : ('T -> bool) -> queue : IQueue<'T> -> bool + +/// +/// Returns whether any pair of elements at the same index satisfies the predicate. +/// +/// The function to test the pairs. +/// The first queue. +/// The second queue. +/// when a pair satisfies the predicate. +/// Thrown when no pair satisfies the predicate and the queues have different lengths. +/// +/// O(n). +/// +[] +val exists2 : predicate : ('T1 -> 'T2 -> bool) -> queue1 : IQueue<'T1> -> queue2 : IQueue<'T2> -> bool + +/// Returns the first element that satisfies the predicate. +/// The function to test the elements. +/// The input queue. +/// The first element that satisfies the predicate. +/// Thrown when no element satisfies the predicate. +/// O(n). +[] +val find : predicate : ('T -> bool) -> queue : IQueue<'T> -> 'T + +/// Returns the last element that satisfies the predicate. +/// The function to test the elements. +/// The input queue. +/// The last element that satisfies the predicate. +/// Thrown when no element satisfies the predicate. +/// O(n). +[] +val findBack : predicate : ('T -> bool) -> queue : IQueue<'T> -> 'T + +/// +/// Returns the index of the first element that satisfies the predicate. +/// +/// The function to test the elements. +/// The input queue. +/// The index of the first element that satisfies the predicate. +/// Thrown when no element satisfies the predicate. +/// +/// O(n). +/// +[] +val findIndex : predicate : ('T -> bool) -> queue : IQueue<'T> -> int + +/// +/// Returns the index of the last element that satisfies the predicate. +/// +/// The function to test the elements. +/// The input queue. +/// The index of the last element that satisfies the predicate. +/// Thrown when no element satisfies the predicate. +/// +/// O(n). +/// +[] +val findIndexBack : predicate : ('T -> bool) -> queue : IQueue<'T> -> int + +/// Returns whether all elements satisfy the predicate. +/// The function to test the elements. +/// The input queue. +/// when every element satisfies the predicate. +/// O(n). +[] +val forall : predicate : ('T -> bool) -> queue : IQueue<'T> -> bool + +/// +/// Returns whether all pairs of elements at the same index satisfy the predicate. +/// +/// The function to test the pairs. +/// The first queue. +/// The second queue. +/// when every pair satisfies the predicate. +/// Thrown when every pair satisfies the predicate and the queues have different lengths. +/// +/// O(n). +/// +[] +val forall2 : predicate : ('T1 -> 'T2 -> bool) -> queue1 : IQueue<'T1> -> queue2 : IQueue<'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 queue. +/// The first chosen value. +/// Thrown when the chooser returns ValueNone for every element. +/// +/// O(n). +/// +[] +val pick : chooser : ('T -> 'U voption) -> queue : IQueue<'T> -> 'U + +/// +/// Returns the first element that satisfies the predicate, or ValueNone. +/// +/// The function to test the elements. +/// The input queue. +/// The first element that satisfies the predicate, or ValueNone. +/// +/// O(n). +/// +[] +val tryFind : predicate : ('T -> bool) -> queue : IQueue<'T> -> 'T voption + +/// +/// Returns the last element that satisfies the predicate, or ValueNone. +/// +/// The function to test the elements. +/// The input queue. +/// The last element that satisfies the predicate, or ValueNone. +/// +/// O(n). +/// +[] +val tryFindBack : predicate : ('T -> bool) -> queue : IQueue<'T> -> 'T voption + +/// +/// Returns the index of the first element that satisfies the predicate, or ValueNone. +/// +/// The function to test the elements. +/// The input queue. +/// The index of the first element that satisfies the predicate, or ValueNone. +/// +/// O(n). +/// +[] +val tryFindIndex : predicate : ('T -> bool) -> queue : IQueue<'T> -> int voption + +/// +/// Returns the index of the last element that satisfies the predicate, or ValueNone. +/// +/// The function to test the elements. +/// The input queue. +/// The index of the last element that satisfies the predicate, or ValueNone. +/// +/// O(n). +/// +[] +val tryFindIndexBack : predicate : ('T -> bool) -> queue : IQueue<'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 queue. +/// The first chosen value, or ValueNone. +/// +/// O(n). +/// +[] +val tryPick : chooser : ('T -> 'U voption) -> queue : IQueue<'T> -> 'U voption + +// ---- Transformation ---- + +/// +/// Returns every element of the first queue paired with every element of the second one. +/// +/// The first queue. +/// The second queue. +/// The pairs, in the order of the first queue and then of the second one. +/// +/// O(n × m). +/// +[] +val allPairs : queue1 : IQueue<'T1> -> queue2 : IQueue<'T2> -> Queue + +/// +/// Returns a queue of the elements of the first queue followed by the elements of the second one. +/// +/// The first queue. +/// The second queue. +/// The concatenation, in a queue of the implementation of the first queue. +/// +/// +/// O(m), where m is the length of the second queue: the function enqueues its elements into the first queue with +/// . +/// +/// +/// The result keeps the implementation of the first queue, which receives the elements, as +/// keeps it (ADR 0001, D14). It does so also +/// when the first queue is empty: the function does not return the second queue then, as +/// +/// does, because the second queue can have another implementation. +/// +/// +[] +val append : queue1 : IQueue<'T> -> queue2 : IQueue<'T> -> IQueue<'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 queue. +/// The chosen values. +/// +/// O(n). +/// +[] +val choose : chooser : ('T -> 'U voption) -> queue : IQueue<'T> -> Queue<'U> + +/// Divides the queue into chunks of a given size. +/// The maximum size of a chunk. +/// The input queue. +/// The chunks; the last one can be smaller. +/// Thrown when is not positive. +/// O(n). +[] +val chunkBySize : chunkSize : int -> queue : IQueue<'T> -> Queue> + +/// +/// Concatenates the queues that a function returns for the elements. +/// +/// The function that returns a queue for each element. +/// The input queue. +/// The concatenation of the returned queues. +/// +/// O(n + the total length of the returned queues). +/// +[] +val collect : mapping : ('T -> IQueue<'U>) -> queue : IQueue<'T> -> Queue<'U> + +/// Concatenates a sequence of queues. +/// The queues. +/// The concatenation, in an immutable queue, because the queues can have different implementations. +/// Thrown when is or a default flat list, or one of its queues is . +/// O(the total length). +[] +val concat : queues : seq> -> Queue<'T> + +/// +/// Returns a queue with a value added after the last element. +/// +/// The value to add. +/// The input queue. +/// A queue of the implementation of the input with the elements and the value, which leaves it last. +/// +/// O(1). The function wraps . +/// +[] +val enqueue : value : 'T -> queue : IQueue<'T> -> IQueue<'T> + +/// +/// Returns a queue with values added after the last element, in their order. +/// +/// The values to add. +/// The input queue. +/// A queue of the implementation of the input with the elements and then the values. +/// Thrown when is or a default flat list. +/// +/// O(m), where m is the number of values; without values, the function returns the input. The function calls +/// for each value. +/// +[] +val enqueueMany : values : seq<'T> -> queue : IQueue<'T> -> IQueue<'T> + +/// Returns the elements that satisfy the predicate. +/// The function to test the elements. +/// The input queue. +/// The elements that satisfy the predicate, in their order, in a queue of the implementation of the input. +/// O(n). +[] +val filter : predicate : ('T -> bool) -> queue : IQueue<'T> -> IQueue<'T> + +/// Returns every element with its index. +/// The input queue. +/// The pairs of index and element. +/// O(n). +[] +val indexed : queue : IQueue<'T> -> Queue + +/// 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 queue. +/// The results, in the order of the elements. +/// O(n). +[] +val map : mapping : ('T -> 'U) -> queue : IQueue<'T> -> Queue<'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 queue. +/// The second queue. +/// The results. +/// Thrown when the queues have different lengths. +/// +/// O(n). +/// +[] +val map2 : mapping : ('T1 -> 'T2 -> 'U) -> queue1 : IQueue<'T1> -> queue2 : IQueue<'T2> -> Queue<'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 queue. +/// The second queue. +/// The third queue. +/// The results. +/// Thrown when the queues have different lengths. +/// +/// O(n). +/// +[] +val map3 : + mapping : ('T1 -> 'T2 -> 'T3 -> 'U) -> queue1 : IQueue<'T1> -> queue2 : IQueue<'T2> -> queue3 : IQueue<'T3> -> Queue<'U> + +/// +/// Returns the results of a function applied to each element and its index. +/// +/// The function to transform the elements. +/// The input queue. +/// The results. +/// +/// O(n). +/// +[] +val mapi : mapping : (int -> 'T -> 'U) -> queue : IQueue<'T> -> Queue<'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 queue. +/// The second queue. +/// The results. +/// Thrown when the queues have different lengths. +/// +/// O(n). +/// +[] +val mapi2 : mapping : (int -> 'T1 -> 'T2 -> 'U) -> queue1 : IQueue<'T1> -> queue2 : IQueue<'T2> -> Queue<'U> + +/// Returns every element with its successor. +/// The input queue. +/// The pairs of neighbours; empty for fewer than two elements. +/// O(n). +[] +val pairwise : queue : IQueue<'T> -> Queue + +/// +/// 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 queue. +/// The permuted elements, in a queue of the implementation of the input. +/// Thrown when the function does not compute a permutation. +/// +/// O(n). +/// +[] +val permute : indexMap : (int -> int) -> queue : IQueue<'T> -> IQueue<'T> + +/// Returns the elements in reverse order. +/// The input queue. +/// The reversed elements, in a queue of the implementation of the input. +/// O(n). +[] +val rev : queue : IQueue<'T> -> IQueue<'T> + +/// +/// Returns the transpose of a sequence of queues of the same length. +/// +/// The queues. +/// An immutable queue whose element i holds the element i of every input queue. +/// Thrown when is or a default flat list, or one of its queues is . +/// Thrown when the queues have different lengths. +/// +/// O(the total length). +/// +[] +val transpose : queues : seq> -> Queue> + +/// Splits a queue of pairs into two immutable queues. +/// The input queue. +/// The first components and the second components. +/// O(n). +[] +val unzip : queue : IQueue -> struct (Queue<'T1> * Queue<'T2>) + +/// Splits a queue of triples into three immutable queues. +/// The input queue. +/// The first, the second and the third components. +/// O(n). +[] +val unzip3 : queue : IQueue -> struct (Queue<'T1> * Queue<'T2> * Queue<'T3>) + +/// +/// Returns the elements that satisfy the predicate. +/// +/// The function to test the elements. +/// The input queue. +/// The elements that satisfy the predicate, in their order, in a queue of the implementation of the input. +/// +/// O(n). A synonym of . +/// +[] +val where : predicate : ('T -> bool) -> queue : IQueue<'T> -> IQueue<'T> + +/// +/// Returns the windows of a given size that slide over the queue. +/// +/// The size of a window. +/// The input queue. +/// The windows; empty when the queue is shorter than a window. +/// Thrown when is not positive. +/// +/// O(n × windowSize). +/// +[] +val windowed : windowSize : int -> queue : IQueue<'T> -> Queue> + +/// Combines two queues into an immutable queue of pairs. +/// The first queue. +/// The second queue. +/// The pairs of elements at the same index. +/// Thrown when the queues have different lengths. +/// O(n). +[] +val zip : queue1 : IQueue<'T1> -> queue2 : IQueue<'T2> -> Queue + +/// Combines three queues into an immutable queue of triples. +/// The first queue. +/// The second queue. +/// The third queue. +/// The triples of elements at the same index. +/// Thrown when the queues have different lengths. +/// O(n). +[] +val zip3 : queue1 : IQueue<'T1> -> queue2 : IQueue<'T2> -> queue3 : IQueue<'T3> -> Queue + +// ---- Folding ---- + +/// Returns the average of the elements. +/// The input queue. +/// The average. +/// Thrown when the queue is empty. +/// O(n). +[] +val inline average : + queue : IQueue< ^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 queue. +/// The average of the results. +/// Thrown when the queue is empty. +/// +/// O(n). +/// +[] +val inline averageBy : + projection : ('T -> ^U) -> queue : IQueue<'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 queues element by element with a comparer. +/// The function that compares two elements. +/// The first queue. +/// The second queue. +/// +/// 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) -> queue1 : IQueue<'T> -> queue2 : IQueue<'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 queue. +/// The final state. +/// +/// O(n). +/// +[] +val fold : folder : ('State -> 'T -> 'State) -> state : 'State -> queue : IQueue<'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 queue. +/// The second queue. +/// The final state. +/// Thrown when the queues have different lengths. +/// +/// O(n). +/// +[] +val fold2 : folder : ('State -> 'T1 -> 'T2 -> 'State) -> state : 'State -> queue1 : IQueue<'T1> -> queue2 : IQueue<'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 queue. +/// The initial state. +/// The final state. +/// +/// O(n). +/// +[] +val foldBack : folder : ('T -> 'State -> 'State) -> queue : IQueue<'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 queue. +/// The second queue. +/// The initial state. +/// The final state. +/// Thrown when the queues have different lengths. +/// +/// O(n). +/// +[] +val foldBack2 : + folder : ('T1 -> 'T2 -> 'State -> 'State) -> queue1 : IQueue<'T1> -> queue2 : IQueue<'T2> -> state : 'State -> 'State + +/// Applies an action to each element. +/// The action. +/// The input queue. +/// O(n). +[] +val iter : action : ('T -> unit) -> queue : IQueue<'T> -> unit + +/// +/// Applies an action to each pair of elements at the same index. +/// +/// The action. +/// The first queue. +/// The second queue. +/// Thrown when the queues have different lengths. +/// +/// O(n). +/// +[] +val iter2 : action : ('T1 -> 'T2 -> unit) -> queue1 : IQueue<'T1> -> queue2 : IQueue<'T2> -> unit + +/// Applies an action to each element and its index. +/// The action. +/// The input queue. +/// O(n). +[] +val iteri : action : (int -> 'T -> unit) -> queue : IQueue<'T> -> unit + +/// +/// Applies an action to the index and the pair of elements at that index. +/// +/// The action. +/// The first queue. +/// The second queue. +/// Thrown when the queues have different lengths. +/// +/// O(n). +/// +[] +val iteri2 : action : (int -> 'T1 -> 'T2 -> unit) -> queue1 : IQueue<'T1> -> queue2 : IQueue<'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 queue. +/// The results and the final state. +/// +/// O(n). +/// +[] +val mapFold : + mapping : ('State -> 'T -> struct ('Result * 'State)) -> + state : 'State -> + queue : IQueue<'T> -> + struct (Queue<'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 queue. +/// 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)) -> + queue : IQueue<'T> -> + state : 'State -> + struct (Queue<'Result> * 'State) + +/// Returns the greatest element, in F# generic comparison. +/// The input queue. +/// The greatest element. +/// Thrown when the queue is empty. +/// O(n). +[] +val max : queue : IQueue<'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 queue. +/// The first element with the greatest result. +/// Thrown when the queue is empty. +/// +/// O(n). +/// +[] +val maxBy : projection : ('T -> 'U) -> queue : IQueue<'T> -> 'T when 'U : comparison + +/// Returns the smallest element, in F# generic comparison. +/// The input queue. +/// The smallest element. +/// Thrown when the queue is empty. +/// O(n). +[] +val min : queue : IQueue<'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 queue. +/// The first element with the smallest result. +/// Thrown when the queue is empty. +/// +/// O(n). +/// +[] +val minBy : projection : ('T -> 'U) -> queue : IQueue<'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 queue. +/// The combined value. +/// Thrown when the queue is empty. +/// +/// O(n). +/// +[] +val reduce : reduction : ('T -> 'T -> 'T) -> queue : IQueue<'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 queue. +/// The combined value. +/// Thrown when the queue is empty. +/// +/// O(n). +/// +[] +val reduceBack : reduction : ('T -> 'T -> 'T) -> queue : IQueue<'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 queue. +/// The states; one more than the elements. +/// +/// O(n). +/// +[] +val scan : folder : ('State -> 'T -> 'State) -> state : 'State -> queue : IQueue<'T> -> Queue<'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 queue. +/// The initial state. +/// The states; one more than the elements. +/// +/// O(n). +/// +[] +val scanBack : folder : ('T -> 'State -> 'State) -> queue : IQueue<'T> -> state : 'State -> Queue<'State> + +/// Returns the sum of the elements. +/// The input queue. +/// The sum; zero for an empty queue. +/// O(n). +[] +val inline sum : queue : IQueue< ^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 queue. +/// The sum of the results; zero for an empty queue. +/// +/// O(n). +/// +[] +val inline sumBy : + projection : ('T -> ^U) -> queue : IQueue<'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 queue. +/// 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) -> queue : IQueue<'T> -> Queue when 'Key : equality + +/// +/// Returns the elements without duplicates, in F# structural equality. +/// +/// The input queue. +/// The first occurrence of each element, in their order, in a queue of the implementation of the input. +/// +/// O(n). +/// +[] +val distinct : queue : IQueue<'T> -> IQueue<'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 queue. +/// The first element of each key, in their order, in a queue of the implementation of the input. +/// +/// O(n). The keys compare in F# structural equality. +/// +[] +val distinctBy : projection : ('T -> 'Key) -> queue : IQueue<'T> -> IQueue<'T> when 'Key : equality + +/// +/// Returns the distinct elements that do not equal any of the excluded items. +/// +/// The items to remove. +/// The input queue. +/// The distinct remaining elements, in their order, in a queue of the implementation of the input. +/// Thrown when is or a default flat list. +/// +/// O(n + m). An empty queue is returned without a look at the items to exclude. +/// +[] +val except : itemsToExclude : seq<'T> -> queue : IQueue<'T> -> IQueue<'T> when 'T : equality + +/// Groups the elements by the key of a projection. +/// The function that returns the key of an element. +/// The input queue. +/// 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) -> queue : IQueue<'T> -> Queue)> when 'Key : equality + +/// Splits the elements by a predicate. +/// The function to test the elements. +/// The input queue. +/// +/// The elements that satisfy the predicate and the other ones, both in their order, in queues of the implementation of +/// the input. +/// +/// O(n). +[] +val partition : predicate : ('T -> bool) -> queue : IQueue<'T> -> struct (IQueue<'T> * IQueue<'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 queue. +/// 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>) -> queue : IQueue<'T> -> struct (Queue<'T1> * Queue<'T2>) + +/// +/// Returns the queue without its first elements. +/// +/// The number of elements to skip; a negative count skips nothing. +/// The input queue. +/// The remaining elements, in a queue of the implementation of the input. +/// Thrown when the count exceeds the length. +/// +/// Amortized O(count). The function dequeues the elements with +/// ; a count that is not positive returns the input. +/// +[] +val skip : count : int -> queue : IQueue<'T> -> IQueue<'T> + +/// +/// Returns the queue without its last elements. +/// +/// The number of elements to skip; a negative count skips nothing. +/// The input queue. +/// The remaining elements, in a queue of the implementation of the input. +/// Thrown when the count exceeds the length. +/// +/// O(n). A count that is not positive returns the input. The checks follow +/// . +/// +[] +val skipLast : count : int -> queue : IQueue<'T> -> IQueue<'T> + +/// +/// Returns the queue without its first elements that satisfy the predicate. +/// +/// The function to test the elements. +/// The input queue. +/// The elements from the first one that does not satisfy the predicate, in a queue of the implementation of the input. +/// +/// Amortized O(k), where k is the number of skipped elements. The function dequeues the elements with +/// . +/// +[] +val skipWhile : predicate : ('T -> bool) -> queue : IQueue<'T> -> IQueue<'T> + +/// +/// Splits the queue at an index. +/// +/// The index of the first element of the second part. +/// The input queue. +/// The elements before the index and the elements from the index, in queues of the implementation of the input. +/// Thrown when the index is negative. +/// Thrown when the index exceeds the length. +/// +/// Amortized O(index). The function dequeues the elements of the first part from the input, which gives the second part; +/// the first part is the input itself when the index is the length. +/// +[] +val splitAt : index : int -> queue : IQueue<'T> -> struct (IQueue<'T> * IQueue<'T>) + +/// +/// Splits the queue into at most a given number of parts of nearly equal length. +/// +/// The maximum number of parts. +/// The input queue. +/// 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 -> queue : IQueue<'T> -> Queue> + +/// +/// Returns the first elements of the queue. +/// +/// The number of elements to take. +/// The input queue. +/// The first elements, in a queue of the implementation of the input. +/// Thrown when the count is negative. +/// Thrown when the count exceeds the length. +/// +/// O(count). The input itself is returned when the count is the length. +/// +[] +val take : count : int -> queue : IQueue<'T> -> IQueue<'T> + +/// +/// Returns the last elements of the queue. +/// +/// The number of elements to take. +/// The input queue. +/// The last elements, in a queue of the implementation of the input. +/// Thrown when the count is negative. +/// Thrown when the count exceeds the length. +/// +/// O(n). The input itself is returned when the count is the length. The checks follow +/// . +/// +[] +val takeLast : count : int -> queue : IQueue<'T> -> IQueue<'T> + +/// +/// Returns the first elements of the queue that satisfy the predicate. +/// +/// The function to test the elements. +/// The input queue. +/// The elements before the first one that does not satisfy the predicate, in a queue of the implementation of the input. +/// +/// O(k), where k is the length of the result. The input itself is returned when every element satisfies the predicate. +/// +[] +val takeWhile : predicate : ('T -> bool) -> queue : IQueue<'T> -> IQueue<'T> + +/// +/// Returns at most a given number of first elements. +/// +/// The maximum number of elements; a count that is not positive returns an empty queue. +/// The input queue. +/// The first elements, in a queue of the implementation of the input. +/// +/// O(count). The input itself is returned when it is not longer than the count. +/// +[] +val truncate : count : int -> queue : IQueue<'T> -> IQueue<'T> + +// ---- Sorting ---- + +/// +/// Sorts the elements in the order of F# generic comparison. +/// +/// The input queue. +/// The sorted elements, in a queue 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 : queue : IQueue<'T> -> IQueue<'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 queue. +/// The sorted elements, in a queue 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) -> queue : IQueue<'T> -> IQueue<'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 queue. +/// The sorted elements, in a queue 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) -> queue : IQueue<'T> -> IQueue<'T> when 'Key : comparison + +/// +/// Sorts the elements in descending order of F# generic comparison. +/// +/// The input queue. +/// The sorted elements, in a queue 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 : queue : IQueue<'T> -> IQueue<'T> when 'T : comparison + +/// +/// Sorts the elements with a comparison function. +/// +/// The function that compares two elements. +/// The input queue. +/// The sorted elements, in a queue 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) -> queue : IQueue<'T> -> IQueue<'T> + +/// +/// Sorts the elements with a comparer. +/// +/// The comparer. +/// The input queue. +/// The sorted elements, in a queue 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 : System.Collections.Generic.IComparer<'T> -> queue : IQueue<'T> -> IQueue<'T> + +// ---- Random ---- + +/// +/// Returns a random element, chosen with . +/// +/// The input queue. +/// A random element. +/// Thrown when the queue is empty. +/// +/// O(n). +/// +[] +val randomChoice : source : IQueue<'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 queue. +/// A random element. +/// Thrown when the queue is empty. +/// Thrown when the randomizer returns a number outside [0, 1). +/// +/// O(n). +/// +[] +val randomChoiceBy : randomizer : (unit -> float) -> source : IQueue<'T> -> 'T + +/// +/// Returns a random element, chosen with a given random number generator. +/// +/// The random number generator. +/// The input queue. +/// A random element. +/// Thrown when is . +/// Thrown when the queue is empty. +/// +/// O(n). +/// +[] +val randomChoiceWith : random : Random -> source : IQueue<'T> -> 'T + +/// +/// Returns random elements, chosen with ; an element can repeat. +/// +/// The number of elements to choose. +/// The input queue. +/// The chosen elements, in a queue of the implementation of the input. +/// Thrown when the count is negative, or positive for an empty queue. +/// +/// O(n + count). +/// +[] +val randomChoices : count : int -> source : IQueue<'T> -> IQueue<'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 queue. +/// The chosen elements, in a queue of the implementation of the input. +/// Thrown when the count is negative, or positive for an empty queue. +/// Thrown when the randomizer returns a number outside [0, 1). +/// +/// O(n + count). +/// +[] +val randomChoicesBy : randomizer : (unit -> float) -> count : int -> source : IQueue<'T> -> IQueue<'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 queue. +/// The chosen elements, in a queue of the implementation of the input. +/// Thrown when is . +/// Thrown when the count is negative, or positive for an empty queue. +/// +/// O(n + count). +/// +[] +val randomChoicesWith : random : Random -> count : int -> source : IQueue<'T> -> IQueue<'T> + +/// +/// Returns random elements at distinct indices, chosen with . +/// +/// The number of elements to choose. +/// The input queue. +/// The chosen elements, in a queue of the implementation of the input. +/// Thrown when the count is negative or exceeds the length. +/// +/// O(n + count). +/// +[] +val randomSample : count : int -> source : IQueue<'T> -> IQueue<'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 queue. +/// The chosen elements, in a queue 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 : IQueue<'T> -> IQueue<'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 queue. +/// The chosen elements, in a queue 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 : IQueue<'T> -> IQueue<'T> + +/// +/// Returns the elements in a random order, chosen with . +/// +/// The input queue. +/// The shuffled elements, in a queue of the implementation of the input. +/// +/// O(n). +/// +[] +val randomShuffle : source : IQueue<'T> -> IQueue<'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 queue. +/// The shuffled elements, in a queue of the implementation of the input. +/// Thrown when the randomizer returns a number outside [0, 1). +/// +/// O(n). +/// +[] +val randomShuffleBy : randomizer : (unit -> float) -> source : IQueue<'T> -> IQueue<'T> + +/// +/// Returns the elements in a random order, chosen with a given random number generator. +/// +/// The random number generator. +/// The input queue. +/// The shuffled elements, in a queue of the implementation of the input. +/// Thrown when is . +/// +/// O(n). +/// +[] +val randomShuffleWith : random : Random -> source : IQueue<'T> -> IQueue<'T> + +// ---- Conversion ---- + +/// +/// Returns a new array with the elements, the first element of the queue first. +/// +/// The input queue. +/// A copy of the elements. +/// +/// O(n). +/// +[] +val toArray : queue : IQueue<'T> -> 'T array + +/// +/// Returns a list of the elements, the first element of the queue first. +/// +/// The input queue. +/// The list of the elements. +/// +/// O(n). +/// +[] +val toList : queue : IQueue<'T> -> 'T list + +/// +/// Returns the queue as a sequence, which enumerates the elements in the order in which they leave the queue. +/// +/// The input queue. +/// The sequence of the elements. +/// +/// O(1). +/// +[] +val toSeq : queue : IQueue<'T> -> seq<'T> + +// ---- Type-specific ---- + +/// +/// Returns an empty queue of the implementation of the input. +/// +/// The input queue. +/// The empty queue. +/// +/// O(1). The function wraps , which keeps the +/// implementation of the queue and its settings. +/// +[] +val clear : queue : IQueue<'T> -> IQueue<'T> diff --git a/src/FSharp.Collections.Immutable/Queue/IQueueOption.fs b/src/FSharp.Collections.Immutable/Queue/IQueueOption.fs new file mode 100644 index 00000000..005f056c --- /dev/null +++ b/src/FSharp.Collections.Immutable/Queue/IQueueOption.fs @@ -0,0 +1,75 @@ +[] +module FSharp.Collections.Immutable.Option.IQueue + +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 (queue : IQueue<'T>) = + FSharp.Collections.Immutable.IQueue.tryExactlyOne queue + |> ValueOption.toOption + +[] +let tryHead (queue : IQueue<'T>) = + FSharp.Collections.Immutable.IQueue.tryHead queue + |> ValueOption.toOption + +[] +let tryLast (queue : IQueue<'T>) = + FSharp.Collections.Immutable.IQueue.tryLast queue + |> ValueOption.toOption + +[] +let tryTail (queue : IQueue<'T>) = + FSharp.Collections.Immutable.IQueue.tryTail queue + |> ValueOption.toOption + +[] +let tryUncons (queue : IQueue<'T>) = + match FSharp.Collections.Immutable.IQueue.tryUncons queue with + | ValueSome (struct (head, tail)) -> Some (head, tail) + | ValueNone -> None + +[] +let (|Cons|_|) (queue : IQueue<'T>) = tryUncons queue + +// ---- Search ---- + +[] +let pick (chooser : 'T -> 'U option) (queue : IQueue<'T>) = + FSharp.Collections.Immutable.IQueue.pick (fun item -> chooser item |> ValueOption.ofOption) queue + +[] +let tryFind predicate (queue : IQueue<'T>) = + FSharp.Collections.Immutable.IQueue.tryFind predicate queue + |> ValueOption.toOption + +[] +let tryFindBack predicate (queue : IQueue<'T>) = + FSharp.Collections.Immutable.IQueue.tryFindBack predicate queue + |> ValueOption.toOption + +[] +let tryFindIndex predicate (queue : IQueue<'T>) = + FSharp.Collections.Immutable.IQueue.tryFindIndex predicate queue + |> ValueOption.toOption + +[] +let tryFindIndexBack predicate (queue : IQueue<'T>) = + FSharp.Collections.Immutable.IQueue.tryFindIndexBack predicate queue + |> ValueOption.toOption + +[] +let tryPick (chooser : 'T -> 'U option) (queue : IQueue<'T>) = + FSharp.Collections.Immutable.IQueue.tryPick (fun item -> chooser item |> ValueOption.ofOption) queue + |> ValueOption.toOption + +// ---- Transformation ---- + +[] +let choose (chooser : 'T -> 'U option) (queue : IQueue<'T>) = + FSharp.Collections.Immutable.IQueue.choose (fun item -> chooser item |> ValueOption.ofOption) queue diff --git a/src/FSharp.Collections.Immutable/Queue/IQueueOption.fsi b/src/FSharp.Collections.Immutable/Queue/IQueueOption.fsi new file mode 100644 index 00000000..4ed99649 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Queue/IQueueOption.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.IQueue + +open FSharp.Collections.Immutable + +// ---- Access ---- + +/// +/// Returns the only element of the queue, or None when it does not have exactly one element. +/// +/// The input queue. +/// The only element, or None. +/// +/// Amortized O(1). +/// +[] +val tryExactlyOne : queue : IQueue<'T> -> 'T option + +/// +/// Returns the first element of the queue, or None when it is empty. +/// +/// The input queue. +/// The first element, or None. +/// +/// O(1). +/// +[] +val tryHead : queue : IQueue<'T> -> 'T option + +/// +/// Returns the last element of the queue, or None when it is empty. +/// +/// The input queue. +/// The last element, or None. +/// +/// O(n). +/// +[] +val tryLast : queue : IQueue<'T> -> 'T option + +/// +/// Returns the queue without its first element, or None when it is empty. +/// +/// The input queue. +/// The elements after the first one, in a queue of the implementation of the input, or None. +/// +/// Amortized O(1). +/// +[] +val tryTail : queue : IQueue<'T> -> IQueue<'T> option + +/// +/// Returns the first element and the other elements, or None when the queue is empty. +/// +/// The input queue. +/// The first element and the elements after it, or None. +/// +/// Amortized O(1). +/// +[] +val tryUncons : queue : IQueue<'T> -> ('T * IQueue<'T>) option + +/// +/// Matches a queue with a first element, and returns that element and the other elements. +/// +/// The input queue. +/// The first element and the elements after it, or None for an empty queue. +/// +/// Amortized O(1). +/// +[] +val (|Cons|_|) : queue : IQueue<'T> -> ('T * IQueue<'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 queue. +/// The first chosen value. +/// Thrown when the chooser returns None for every element. +/// +/// O(n). +/// +[] +val pick : chooser : ('T -> 'U option) -> queue : IQueue<'T> -> 'U + +/// +/// Returns the first element that satisfies the predicate, or None. +/// +/// The function to test the elements. +/// The input queue. +/// The first element that satisfies the predicate, or None. +/// +/// O(n). +/// +[] +val tryFind : predicate : ('T -> bool) -> queue : IQueue<'T> -> 'T option + +/// +/// Returns the last element that satisfies the predicate, or None. +/// +/// The function to test the elements. +/// The input queue. +/// The last element that satisfies the predicate, or None. +/// +/// O(n). +/// +[] +val tryFindBack : predicate : ('T -> bool) -> queue : IQueue<'T> -> 'T option + +/// +/// Returns the index of the first element that satisfies the predicate, or None. +/// +/// The function to test the elements. +/// The input queue. +/// The index of the first element that satisfies the predicate, or None. +/// +/// O(n). +/// +[] +val tryFindIndex : predicate : ('T -> bool) -> queue : IQueue<'T> -> int option + +/// +/// Returns the index of the last element that satisfies the predicate, or None. +/// +/// The function to test the elements. +/// The input queue. +/// The index of the last element that satisfies the predicate, or None. +/// +/// O(n). +/// +[] +val tryFindIndexBack : predicate : ('T -> bool) -> queue : IQueue<'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 queue. +/// The first chosen value, or None. +/// +/// O(n). +/// +[] +val tryPick : chooser : ('T -> 'U option) -> queue : IQueue<'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 queue. +/// The chosen values. +/// +/// O(n). +/// +[] +val choose : chooser : ('T -> 'U option) -> queue : IQueue<'T> -> Queue<'U> diff --git a/src/FSharp.Collections.Immutable/Queue/IQueueTuple.fs b/src/FSharp.Collections.Immutable/Queue/IQueueTuple.fs new file mode 100644 index 00000000..1611049c --- /dev/null +++ b/src/FSharp.Collections.Immutable/Queue/IQueueTuple.fs @@ -0,0 +1,126 @@ +[] +module FSharp.Collections.Immutable.Tuple.IQueue + +open FSharp.Collections.Immutable + +// The functions that build a tuple per element share their algorithm with the default module and only pass another +// tuple constructor; the others delegate to the default module and repack the tuples, which costs no second pass. + +// ---- Access ---- + +[] +let tryUncons (queue : IQueue<'T>) = + match FSharp.Collections.Immutable.IQueue.tryUncons queue with + | ValueSome (struct (head, tail)) -> ValueSome (head, tail) + | ValueNone -> ValueNone + +[] +let uncons (queue : IQueue<'T>) = + let struct (head, tail) = FSharp.Collections.Immutable.IQueue.uncons queue + head, tail + +[] +[] +let (|Cons|_|) (queue : IQueue<'T>) = tryUncons queue + +// ---- Transformation ---- + +[] +let allPairs (queue1 : IQueue<'T1>) (queue2 : IQueue<'T2>) = + Arrays.allPairs referencePair (Snapshot.ofSeq (nameof queue1) queue1) (Snapshot.ofSeq (nameof queue2) queue2) + |> FSharp.Collections.Immutable.Queue.ofArray + +[] +let indexed (queue : IQueue<'T>) = + Arrays.indexed referencePair (Snapshot.ofSeq (nameof queue) queue) + |> FSharp.Collections.Immutable.Queue.ofArray + +[] +let pairwise (queue : IQueue<'T>) = + Arrays.pairwise referencePair (Snapshot.ofSeq (nameof queue) queue) + |> FSharp.Collections.Immutable.Queue.ofArray + +[] +let unzip (queue : IQueue<'T1 * 'T2>) = + let struct (first, second) = + Arrays.unzip (fun (item1, item2) -> struct (item1, item2)) (Snapshot.ofSeq (nameof queue) queue) + + FSharp.Collections.Immutable.Queue.ofArray first, FSharp.Collections.Immutable.Queue.ofArray second + +[] +let unzip3 (queue : IQueue<'T1 * 'T2 * 'T3>) = + let struct (first, second, third) = + Arrays.unzip3 (fun (item1, item2, item3) -> struct (item1, item2, item3)) (Snapshot.ofSeq (nameof queue) queue) + + FSharp.Collections.Immutable.Queue.ofArray first, + FSharp.Collections.Immutable.Queue.ofArray second, + FSharp.Collections.Immutable.Queue.ofArray third + +[] +let zip (queue1 : IQueue<'T1>) (queue2 : IQueue<'T2>) = + let array1 = Snapshot.ofSeq (nameof queue1) queue1 + let array2 = Snapshot.ofSeq (nameof queue2) queue2 + ListChecks.sameLengths (nameof queue1) array1.Length (nameof queue2) array2.Length + + Arrays.zip referencePair array1 array2 + |> FSharp.Collections.Immutable.Queue.ofArray + +[] +let zip3 (queue1 : IQueue<'T1>) (queue2 : IQueue<'T2>) (queue3 : IQueue<'T3>) = + let array1 = Snapshot.ofSeq (nameof queue1) queue1 + let array2 = Snapshot.ofSeq (nameof queue2) queue2 + let array3 = Snapshot.ofSeq (nameof queue3) queue3 + ListChecks.zip3 (nameof queue1) array1.Length (nameof queue2) array2.Length (nameof queue3) array3.Length + + Arrays.zip3 referenceTriple array1 array2 array3 + |> FSharp.Collections.Immutable.Queue.ofArray + +// ---- Folding ---- + +[] +let mapFold (mapping : 'State -> 'T -> 'Result * 'State) (state : 'State) (queue : IQueue<'T>) = + let mapStruct state item = + let result, next = mapping state item + struct (result, next) + + let struct (results, final) = FSharp.Collections.Immutable.IQueue.mapFold mapStruct state queue + results, final + +[] +let mapFoldBack (mapping : 'T -> 'State -> 'Result * 'State) (queue : IQueue<'T>) (state : 'State) = + let mapStruct item state = + let result, next = mapping item state + struct (result, next) + + let struct (results, final) = FSharp.Collections.Immutable.IQueue.mapFoldBack mapStruct queue state + results, final + +// ---- Partitioning and grouping ---- + +[] +let countBy (projection : 'T -> 'Key) (queue : IQueue<'T>) = + Arrays.countBy referencePair projection (Snapshot.ofSeq (nameof queue) queue) + |> FSharp.Collections.Immutable.Queue.ofArray + +[] +let groupBy (projection : 'T -> 'Key) (queue : IQueue<'T>) = + Arrays.groupBy + (fun key items -> key, FSharp.Collections.Immutable.Queue.ofArray items) + projection + (Snapshot.ofSeq (nameof queue) queue) + |> FSharp.Collections.Immutable.Queue.ofArray + +[] +let partition predicate (queue : IQueue<'T>) = + let struct (accepted, rejected) = FSharp.Collections.Immutable.IQueue.partition predicate queue + accepted, rejected + +[] +let partitionWith (partitioner : 'T -> Choice<'T1, 'T2>) (queue : IQueue<'T>) = + let struct (first, second) = FSharp.Collections.Immutable.IQueue.partitionWith partitioner queue + first, second + +[] +let splitAt index (queue : IQueue<'T>) = + let struct (before, after) = FSharp.Collections.Immutable.IQueue.splitAt index queue + before, after diff --git a/src/FSharp.Collections.Immutable/Queue/IQueueTuple.fsi b/src/FSharp.Collections.Immutable/Queue/IQueueTuple.fsi new file mode 100644 index 00000000..6d038974 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Queue/IQueueTuple.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. +/// +[] +module FSharp.Collections.Immutable.Tuple.IQueue + +open FSharp.Collections.Immutable + +// ---- Access ---- + +/// +/// Returns the first element and the other elements, or ValueNone when the queue is empty. +/// +/// The input queue. +/// The first element and the elements after it, or ValueNone. +/// +/// Amortized O(1). +/// +[] +val tryUncons : queue : IQueue<'T> -> ('T * IQueue<'T>) voption + +/// Returns the first element and the other elements. +/// The input queue. +/// The first element and the elements after it, in a queue of the implementation of the input. +/// Thrown when the queue is empty. +/// Amortized O(1). +[] +val uncons : queue : IQueue<'T> -> 'T * IQueue<'T> + +/// +/// Matches a queue with a first element, and returns that element and the other elements. +/// +/// The input queue. +/// The first element and the elements after it, or ValueNone for an empty queue. +/// +/// Amortized O(1). A struct partial pattern, so that a match allocates no option. +/// +[] +[] +val (|Cons|_|) : queue : IQueue<'T> -> ('T * IQueue<'T>) voption + +// ---- Transformation ---- + +/// +/// Returns every element of the first queue paired with every element of the second one. +/// +/// The first queue. +/// The second queue. +/// The pairs, in the order of the first queue and then of the second one. +/// +/// O(n × m). +/// +[] +val allPairs : queue1 : IQueue<'T1> -> queue2 : IQueue<'T2> -> Queue<'T1 * 'T2> + +/// Returns every element with its index. +/// The input queue. +/// The pairs of index and element. +/// O(n). +[] +val indexed : queue : IQueue<'T> -> Queue + +/// Returns every element with its successor. +/// The input queue. +/// The pairs of neighbours; empty for fewer than two elements. +/// O(n). +[] +val pairwise : queue : IQueue<'T> -> Queue<'T * 'T> + +/// Splits a queue of pairs into two immutable queues. +/// The input queue. +/// The first components and the second components. +/// O(n). +[] +val unzip : queue : IQueue<'T1 * 'T2> -> Queue<'T1> * Queue<'T2> + +/// Splits a queue of triples into three immutable queues. +/// The input queue. +/// The first, the second and the third components. +/// O(n). +[] +val unzip3 : queue : IQueue<'T1 * 'T2 * 'T3> -> Queue<'T1> * Queue<'T2> * Queue<'T3> + +/// Combines two queues into an immutable queue of pairs. +/// The first queue. +/// The second queue. +/// The pairs of elements at the same index. +/// Thrown when the queues have different lengths. +/// O(n). +[] +val zip : queue1 : IQueue<'T1> -> queue2 : IQueue<'T2> -> Queue<'T1 * 'T2> + +/// Combines three queues into an immutable queue of triples. +/// The first queue. +/// The second queue. +/// The third queue. +/// The triples of elements at the same index. +/// Thrown when the queues have different lengths. +/// O(n). +[] +val zip3 : queue1 : IQueue<'T1> -> queue2 : IQueue<'T2> -> queue3 : IQueue<'T3> -> Queue<'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 queue. +/// The results and the final state. +/// +/// O(n). +/// +[] +val mapFold : mapping : ('State -> 'T -> 'Result * 'State) -> state : 'State -> queue : IQueue<'T> -> Queue<'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 queue. +/// The initial state. +/// The results, in the order of the elements, and the final state. +/// +/// O(n). +/// +[] +val mapFoldBack : mapping : ('T -> 'State -> 'Result * 'State) -> queue : IQueue<'T> -> state : 'State -> Queue<'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 queue. +/// 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) -> queue : IQueue<'T> -> Queue<'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 queue. +/// 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) -> queue : IQueue<'T> -> Queue<'Key * Queue<'T>> when 'Key : equality + +/// Splits the elements by a predicate. +/// The function to test the elements. +/// The input queue. +/// +/// The elements that satisfy the predicate and the other ones, both in their order, in queues of the implementation of +/// the input. +/// +/// O(n). +[] +val partition : predicate : ('T -> bool) -> queue : IQueue<'T> -> IQueue<'T> * IQueue<'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 queue. +/// 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>) -> queue : IQueue<'T> -> Queue<'T1> * Queue<'T2> + +/// Splits the queue at an index. +/// The index of the first element of the second part. +/// The input queue. +/// The elements before the index and the elements from the index, in queues of the implementation of the input. +/// Thrown when the index is negative. +/// Thrown when the index exceeds the length. +/// Amortized O(index). +[] +val splitAt : index : int -> queue : IQueue<'T> -> IQueue<'T> * IQueue<'T> diff --git a/src/FSharp.Collections.Immutable/Queue/Queue.fs b/src/FSharp.Collections.Immutable/Queue/Queue.fs new file mode 100644 index 00000000..99ca4d67 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Queue/Queue.fs @@ -0,0 +1,1028 @@ +[] +module FSharp.Collections.Immutable.Queue + +open System +open System.Collections.Generic +open System.Collections.Immutable +open System.Linq +// Opened after System.Collections.Generic, so that Queue names the abbreviation of this library, not the mutable queue +open FSharp.Collections.Immutable + +// The functions call a member of ImmutableQueue where it has the semantics of the List function, walk the elements with the +// struct enumerator of the queue, here or in the walks of Queues that IQueue shares, or take a snapshot of the elements in +// an array, apply an Array function or a shared algorithm and build a new queue from the fresh result (ADR 0001, D5). The +// checks of ListChecks throw what the List functions throw (D6), with the names of the parameters of this module; the +// lengths that they check come from Lengths.queueUpTo, which counts only as far as a check needs. + +/// A queue of the elements of a sequence that a Seq function sorts when it is read: the buffer reads it once, and the queue +/// dequeues the elements without reversing a back stack first. +let private ofSorted (sorted : seq<'T>) = Queues.ofBuffer (ResizeArray sorted) + +// ---- Creation ---- + +[] +[] +let empty<'T> : Queue<'T> = ImmutableQueue<'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 |> Queues.ofFreshArray + +[] +let ofArray (array : 'T array) = + checkNotNull (nameof array) array + ImmutableQueue.Create<'T>(ReadOnlySpan<'T> array) + +[] +let ofList (list : 'T list) = List.toArray list |> Queues.ofFreshArray + +[] +let ofReadOnlySpan (span : ReadOnlySpan<'T>) = ImmutableQueue.Create<'T> span + +[] +let ofSeq (source : seq<'T>) = + // A default flat list given as a sequence is a null input; the BCL would fail while it enumerates it (ADR 0001, D6) + checkNotNullOrDefaultSeq (nameof source) source + + match source with + // An immutable queue cannot change, so it needs no copy + | :? ImmutableQueue<'T> as queue -> queue + | _ -> ImmutableQueue.CreateRange source + +[] +let ofSpan (span : Span<'T>) = ImmutableQueue.Create<'T>(Span<'T>.op_Implicit span) + +[] +let replicate count (initial : 'T) = + ListChecks.replicate (nameof count) count + Array.create count initial |> Queues.ofFreshArray + +[] +let singleton (value : 'T) = ImmutableQueue.Create<'T> 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 + + Queues.ofBuffer items + +// ---- Properties ---- + +[] +let isEmpty (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + queue.IsEmpty + +[] +let length (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + let mutable count = 0 + + for _ in queue do + count <- count + 1 + + count + +// ---- Access ---- + +[] +let exactlyOne (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + // List.exactlyOne reports its input under the name "source" + ListChecks.exactlyOne "source" (Lengths.queueUpTo 2 queue) + queue.Peek () + +[] +let head (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + ListChecks.notEmpty (nameof queue) (Lengths.queueUpTo 1 queue) + queue.Peek () + +[] +let last (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + ListChecks.notEmpty (nameof queue) (Lengths.queueUpTo 1 queue) + Enumerable.Last queue + +[] +let tail (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + ListChecks.notEmpty (nameof queue) (Lengths.queueUpTo 1 queue) + queue.Dequeue () + +[] +let tryExactlyOne (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + + if Lengths.queueUpTo 2 queue = 1 then + ValueSome (queue.Peek ()) + else + ValueNone + +[] +let tryHead (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + + if queue.IsEmpty then + ValueNone + else + ValueSome (queue.Peek ()) + +[] +let tryLast (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + + if queue.IsEmpty then + ValueNone + else + ValueSome (Enumerable.Last queue) + +[] +let tryTail (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + + if queue.IsEmpty then + ValueNone + else + ValueSome (queue.Dequeue ()) + +[] +let tryUncons (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + + if queue.IsEmpty then + ValueNone + else + ValueSome (struct (queue.Peek (), queue.Dequeue ())) + +[] +let uncons (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + // An empty queue throws what List.head throws + ListChecks.notEmpty (nameof queue) (Lengths.queueUpTo 1 queue) + struct (queue.Peek (), queue.Dequeue ()) + +[] +[] +let (|Cons|_|) (queue : Queue<'T>) = tryUncons queue + +[] +let (|Nil|_|) (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + queue.IsEmpty + +// ---- Search ---- + +[] +let contains (value : 'T) (source : Queue<'T>) = + checkNotNull (nameof source) source + let mutable enumerator = source.GetEnumerator () + let mutable found = false + + // F# equality with the value on the left, as List.contains compares + while not found && enumerator.MoveNext () do + found <- value = enumerator.Current + + found + +[] +let exists (predicate : 'T -> bool) (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + let mutable enumerator = queue.GetEnumerator () + let mutable found = false + + while not found && enumerator.MoveNext () do + found <- predicate enumerator.Current + + found + +[] +let exists2 predicate (queue1 : Queue<'T1>) (queue2 : Queue<'T2>) = + ListArrays.exists2 (nameof queue2) predicate (Snapshot.ofSeq (nameof queue1) queue1) (Snapshot.ofSeq (nameof queue2) queue2) + +[] +let find (predicate : 'T -> bool) (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + + match Queues.firstMatch predicate queue with + | ValueSome (struct (_, item)) -> item + | ValueNone -> Raise.indexNotFound () + +[] +let findBack (predicate : 'T -> bool) (queue : Queue<'T>) = + match Queues.lastMatch predicate queue with + | ValueSome (struct (_, item)) -> item + | ValueNone -> Raise.indexNotFound () + +[] +let findIndex (predicate : 'T -> bool) (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + + match Queues.firstMatch predicate queue with + | ValueSome (struct (index, _)) -> index + | ValueNone -> Raise.indexNotFound () + +[] +let findIndexBack (predicate : 'T -> bool) (queue : Queue<'T>) = + match Queues.lastMatch predicate queue with + | ValueSome (struct (index, _)) -> index + | ValueNone -> Raise.indexNotFound () + +[] +let forall (predicate : 'T -> bool) (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + let mutable enumerator = queue.GetEnumerator () + let mutable holds = true + + while holds && enumerator.MoveNext () do + holds <- predicate enumerator.Current + + holds + +[] +let forall2 predicate (queue1 : Queue<'T1>) (queue2 : Queue<'T2>) = + ListArrays.forall2 + (nameof queue1) + (nameof queue2) + predicate + (Snapshot.ofSeq (nameof queue1) queue1) + (Snapshot.ofSeq (nameof queue2) queue2) + +[] +let pick (chooser : 'T -> 'U voption) (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + + match Queues.firstChoice chooser queue with + | ValueSome value -> value + | ValueNone -> Raise.indexNotFound () + +[] +let tryFind (predicate : 'T -> bool) (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + + match Queues.firstMatch predicate queue with + | ValueSome (struct (_, item)) -> ValueSome item + | ValueNone -> ValueNone + +[] +let tryFindBack (predicate : 'T -> bool) (queue : Queue<'T>) = + match Queues.lastMatch predicate queue with + | ValueSome (struct (_, item)) -> ValueSome item + | ValueNone -> ValueNone + +[] +let tryFindIndex (predicate : 'T -> bool) (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + + match Queues.firstMatch predicate queue with + | ValueSome (struct (index, _)) -> ValueSome index + | ValueNone -> ValueNone + +[] +let tryFindIndexBack (predicate : 'T -> bool) (queue : Queue<'T>) = + match Queues.lastMatch predicate queue with + | ValueSome (struct (index, _)) -> ValueSome index + | ValueNone -> ValueNone + +[] +let tryPick (chooser : 'T -> 'U voption) (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + Queues.firstChoice chooser queue + +// ---- Transformation ---- + +[] +let allPairs (queue1 : Queue<'T1>) (queue2 : Queue<'T2>) = + Arrays.allPairs structPair (Snapshot.ofSeq (nameof queue1) queue1) (Snapshot.ofSeq (nameof queue2) queue2) + |> Queues.ofFreshArray + +[] +let append (queue1 : Queue<'T>) (queue2 : Queue<'T>) = + checkNotNull (nameof queue1) queue1 + checkNotNull (nameof queue2) queue2 + + if queue1.IsEmpty then + queue2 + else + // Enqueueing keeps the elements of the first queue where they are + let mutable result = queue1 + + for item in queue2 do + result <- result.Enqueue item + + result + +[] +let choose (chooser : 'T -> 'U voption) (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + let chosen = ResizeArray<'U>() + + for item in queue do + match chooser item with + | ValueSome value -> chosen.Add value + | ValueNone -> () + + Queues.ofBuffer chosen + +[] +let chunkBySize chunkSize (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + ListChecks.positive (nameof chunkSize) chunkSize + + Array.chunkBySize chunkSize (Snapshot.ofSeq (nameof queue) queue) + |> Array.map Queues.ofFreshArray + |> Queues.ofFreshArray + +[] +let collect (mapping : 'T -> Queue<'U>) (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + let collected = ResizeArray<'U>() + + for item in queue do + collected.AddRange (mapping item) + + Queues.ofBuffer collected + +[] +let concat (queues : seq>) = + checkNotNullOrDefaultSeq (nameof queues) queues + let concatenated = ResizeArray<'T>() + + for queue in queues do + // A null queue is a null input, as a null indexed list is for IndexedList.concat (ADR 0001, D6) + checkNotNull (nameof queues) queue + + for item in queue do + concatenated.Add item + + Queues.ofBuffer concatenated + +[] +let enqueue (value : 'T) (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + queue.Enqueue value + +[] +let enqueueMany (values : seq<'T>) (queue : Queue<'T>) = + checkNotNullOrDefaultSeq (nameof values) values + checkNotNull (nameof queue) queue + let mutable result = queue + + for value in values do + result <- result.Enqueue value + + result + +[] +let filter (predicate : 'T -> bool) (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + let kept = ResizeArray<'T>() + + for item in queue do + if predicate item then + kept.Add item + + Queues.ofBuffer kept + +[] +let indexed (queue : Queue<'T>) = + Arrays.indexed structPair (Snapshot.ofSeq (nameof queue) queue) + |> Queues.ofFreshArray + +[] +let map (mapping : 'T -> 'U) (queue : Queue<'T>) = + Array.map mapping (Snapshot.ofSeq (nameof queue) queue) + |> Queues.ofFreshArray + +[] +let map2 (mapping : 'T1 -> 'T2 -> 'U) (queue1 : Queue<'T1>) (queue2 : Queue<'T2>) = + ListArrays.map2 + (nameof queue1) + (nameof queue2) + mapping + (Snapshot.ofSeq (nameof queue1) queue1) + (Snapshot.ofSeq (nameof queue2) queue2) + |> Queues.ofFreshArray + +[] +let map3 (mapping : 'T1 -> 'T2 -> 'T3 -> 'U) (queue1 : Queue<'T1>) (queue2 : Queue<'T2>) (queue3 : Queue<'T3>) = + ListArrays.map3 + (nameof queue1) + (nameof queue2) + (nameof queue3) + mapping + (Snapshot.ofSeq (nameof queue1) queue1) + (Snapshot.ofSeq (nameof queue2) queue2) + (Snapshot.ofSeq (nameof queue3) queue3) + |> Queues.ofFreshArray + +[] +let mapi (mapping : int -> 'T -> 'U) (queue : Queue<'T>) = + Array.mapi mapping (Snapshot.ofSeq (nameof queue) queue) + |> Queues.ofFreshArray + +[] +let mapi2 (mapping : int -> 'T1 -> 'T2 -> 'U) (queue1 : Queue<'T1>) (queue2 : Queue<'T2>) = + ListArrays.mapi2 + (nameof queue1) + (nameof queue2) + mapping + (Snapshot.ofSeq (nameof queue1) queue1) + (Snapshot.ofSeq (nameof queue2) queue2) + |> Queues.ofFreshArray + +[] +let pairwise (queue : Queue<'T>) = + Arrays.pairwise structPair (Snapshot.ofSeq (nameof queue) queue) + |> Queues.ofFreshArray + +[] +let permute indexMap (queue : Queue<'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 queue) queue) + |> Queues.ofFreshArray + +[] +let rev (queue : Queue<'T>) = + Array.rev (Snapshot.ofSeq (nameof queue) queue) + |> Queues.ofFreshArray + +[] +let transpose (queues : seq>) = + checkNotNullOrDefaultSeq (nameof queues) queues + + let arrays = + queues + |> Seq.map (fun queue -> Snapshot.ofSeq (nameof queues) queue) + |> Seq.toArray + + // List.transpose names the lists "list.[0]", "list.[1]", …; this module names them "queue.[0]", "queue.[1]", … + // Once their lengths agree, Array.transpose agrees with List.transpose + ListChecks.transpose "queue" (arrays |> Array.map _.Length) + + Array.transpose arrays + |> Array.map Queues.ofFreshArray + |> Queues.ofFreshArray + +[] +let unzip (queue : Queue) = + let struct (first, second) = Arrays.unzip id (Snapshot.ofSeq (nameof queue) queue) + struct (Queues.ofFreshArray first, Queues.ofFreshArray second) + +[] +let unzip3 (queue : Queue) = + let struct (first, second, third) = Arrays.unzip3 id (Snapshot.ofSeq (nameof queue) queue) + struct (Queues.ofFreshArray first, Queues.ofFreshArray second, Queues.ofFreshArray third) + +[] +let where (predicate : 'T -> bool) (queue : Queue<'T>) = filter predicate queue + +[] +let windowed windowSize (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + ListChecks.positive (nameof windowSize) windowSize + + Array.windowed windowSize (Snapshot.ofSeq (nameof queue) queue) + |> Array.map Queues.ofFreshArray + |> Queues.ofFreshArray + +[] +let zip (queue1 : Queue<'T1>) (queue2 : Queue<'T2>) = + let array1 = Snapshot.ofSeq (nameof queue1) queue1 + let array2 = Snapshot.ofSeq (nameof queue2) queue2 + ListChecks.sameLengths (nameof queue1) array1.Length (nameof queue2) array2.Length + Arrays.zip structPair array1 array2 |> Queues.ofFreshArray + +[] +let zip3 (queue1 : Queue<'T1>) (queue2 : Queue<'T2>) (queue3 : Queue<'T3>) = + let array1 = Snapshot.ofSeq (nameof queue1) queue1 + let array2 = Snapshot.ofSeq (nameof queue2) queue2 + let array3 = Snapshot.ofSeq (nameof queue3) queue3 + ListChecks.zip3 (nameof queue1) array1.Length (nameof queue2) array2.Length (nameof queue3) array3.Length + Arrays.zip3 structTriple array1 array2 array3 + |> Queues.ofFreshArray + +// ---- Folding ---- + +// The inline functions can use public members only, so they check the queue themselves and count its elements while +// they add them; the empty queue throws what List.average and List.averageBy throw, with the name "source" +[] +let inline average (queue : Queue< ^T >) : ^T = + if obj.ReferenceEquals (queue, null) then + nullArg (nameof queue) + + if queue.IsEmpty then + invalidArg "source" ErrorStrings.InputSequenceEmpty + + let mutable sum = LanguagePrimitives.GenericZero< ^T> + let mutable count = 0 + + for item in queue do + sum <- Checked.(+) sum item + count <- count + 1 + + LanguagePrimitives.DivideByInt< ^T> sum count + +[] +let inline averageBy ([] projection : 'T -> ^U) (queue : Queue<'T>) : ^U = + if obj.ReferenceEquals (queue, null) then + nullArg (nameof queue) + + if queue.IsEmpty then + invalidArg "source" ErrorStrings.InputSequenceEmpty + + let mutable sum = LanguagePrimitives.GenericZero< ^U> + let mutable count = 0 + + for item in queue do + sum <- Checked.(+) sum (projection item) + count <- count + 1 + + LanguagePrimitives.DivideByInt< ^U> sum count + +[] +let compareWith (comparer : 'T -> 'T -> int) (queue1 : Queue<'T>) (queue2 : Queue<'T>) = + // Array.compareWith compares as List.compareWith does: the first difference, then the lengths + Array.compareWith comparer (Snapshot.ofSeq (nameof queue1) queue1) (Snapshot.ofSeq (nameof queue2) queue2) + +[] +let fold (folder : 'State -> 'T -> 'State) (state : 'State) (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + let mutable state = state + + for item in queue do + state <- folder state item + + state + +[] +let fold2 (folder : 'State -> 'T1 -> 'T2 -> 'State) (state : 'State) (queue1 : Queue<'T1>) (queue2 : Queue<'T2>) = + ListArrays.fold2 + (nameof queue1) + (nameof queue2) + folder + state + (Snapshot.ofSeq (nameof queue1) queue1) + (Snapshot.ofSeq (nameof queue2) queue2) + +[] +let foldBack (folder : 'T -> 'State -> 'State) (queue : Queue<'T>) (state : 'State) = + Array.foldBack folder (Snapshot.ofSeq (nameof queue) queue) state + +[] +let foldBack2 (folder : 'T1 -> 'T2 -> 'State -> 'State) (queue1 : Queue<'T1>) (queue2 : Queue<'T2>) (state : 'State) = + let array1 = Snapshot.ofSeq (nameof queue1) queue1 + let array2 = Snapshot.ofSeq (nameof queue2) queue2 + // List.foldBack2 checks the lengths before it calls the folder + ListChecks.foldBack2 (nameof queue1) array1.Length (nameof queue2) array2.Length + Array.foldBack2 folder array1 array2 state + +[] +let iter (action : 'T -> unit) (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + + for item in queue do + action item + +[] +let iter2 (action : 'T1 -> 'T2 -> unit) (queue1 : Queue<'T1>) (queue2 : Queue<'T2>) = + ListArrays.iter2 + (nameof queue1) + (nameof queue2) + action + (Snapshot.ofSeq (nameof queue1) queue1) + (Snapshot.ofSeq (nameof queue2) queue2) + +[] +let iteri (action : int -> 'T -> unit) (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + let mutable index = 0 + + for item in queue do + action index item + index <- index + 1 + +[] +let iteri2 (action : int -> 'T1 -> 'T2 -> unit) (queue1 : Queue<'T1>) (queue2 : Queue<'T2>) = + ListArrays.iteri2 + (nameof queue1) + (nameof queue2) + action + (Snapshot.ofSeq (nameof queue1) queue1) + (Snapshot.ofSeq (nameof queue2) queue2) + +[] +let mapFold (mapping : 'State -> 'T -> struct ('Result * 'State)) (state : 'State) (queue : Queue<'T>) = + let source = Snapshot.ofSeq (nameof queue) queue + 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 (Queues.ofFreshArray results, state) + +[] +let mapFoldBack (mapping : 'T -> 'State -> struct ('Result * 'State)) (queue : Queue<'T>) (state : 'State) = + let source = Snapshot.ofSeq (nameof queue) queue + 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 (Queues.ofFreshArray results, state) + +// Seq.max and the others compare as the List functions do once the queue has elements, so the List checks come first + +[] +let max (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + ListChecks.notEmptySequence (nameof queue) (Lengths.queueUpTo 1 queue) + Seq.max queue + +[] +let maxBy (projection : 'T -> 'U) (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + ListChecks.notEmptySequence (nameof queue) (Lengths.queueUpTo 1 queue) + Seq.maxBy projection queue + +[] +let min (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + ListChecks.notEmptySequence (nameof queue) (Lengths.queueUpTo 1 queue) + Seq.min queue + +[] +let minBy (projection : 'T -> 'U) (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + ListChecks.notEmptySequence (nameof queue) (Lengths.queueUpTo 1 queue) + Seq.minBy projection queue + +[] +let reduce (reduction : 'T -> 'T -> 'T) (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + ListChecks.notEmpty (nameof queue) (Lengths.queueUpTo 1 queue) + Seq.reduce reduction queue + +[] +let reduceBack (reduction : 'T -> 'T -> 'T) (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + ListChecks.notEmpty (nameof queue) (Lengths.queueUpTo 1 queue) + Array.reduceBack reduction (Snapshot.ofSeq (nameof queue) queue) + +[] +let scan (folder : 'State -> 'T -> 'State) (state : 'State) (queue : Queue<'T>) = + Array.scan folder state (Snapshot.ofSeq (nameof queue) queue) + |> Queues.ofFreshArray + +[] +let scanBack (folder : 'T -> 'State -> 'State) (queue : Queue<'T>) (state : 'State) = + Array.scanBack folder (Snapshot.ofSeq (nameof queue) queue) state + |> Queues.ofFreshArray + +[] +let inline sum (queue : Queue< ^T >) : ^T = + if obj.ReferenceEquals (queue, null) then + nullArg (nameof queue) + + let mutable sum = LanguagePrimitives.GenericZero< ^T> + + for item in queue do + sum <- Checked.(+) sum item + + sum + +[] +let inline sumBy ([] projection : 'T -> ^U) (queue : Queue<'T>) : ^U = + if obj.ReferenceEquals (queue, null) then + nullArg (nameof queue) + + let mutable sum = LanguagePrimitives.GenericZero< ^U> + + for item in queue do + sum <- Checked.(+) sum (projection item) + + sum + +// ---- Partitioning and grouping ---- + +[] +let countBy (projection : 'T -> 'Key) (queue : Queue<'T>) = + Arrays.countBy structPair projection (Snapshot.ofSeq (nameof queue) queue) + |> Queues.ofFreshArray + +[] +let distinct (queue : Queue<'T>) = + Array.distinct (Snapshot.ofSeq (nameof queue) queue) + |> Queues.ofFreshArray + +[] +let distinctBy (projection : 'T -> 'Key) (queue : Queue<'T>) = + Array.distinctBy projection (Snapshot.ofSeq (nameof queue) queue) + |> Queues.ofFreshArray + +[] +let except (itemsToExclude : seq<'T>) (queue : Queue<'T>) = + // List.except checks the items to exclude first and does not read them for an empty list + checkNotNullOrDefaultSeq (nameof itemsToExclude) itemsToExclude + checkNotNull (nameof queue) queue + + if queue.IsEmpty then + queue + else + Array.except itemsToExclude (Snapshot.ofSeq (nameof queue) queue) + |> Queues.ofFreshArray + +[] +let groupBy (projection : 'T -> 'Key) (queue : Queue<'T>) = + Arrays.groupBy (fun key items -> struct (key, Queues.ofFreshArray items)) projection (Snapshot.ofSeq (nameof queue) queue) + |> Queues.ofFreshArray + +[] +let partition (predicate : 'T -> bool) (queue : Queue<'T>) = + let accepted, rejected = Array.partition predicate (Snapshot.ofSeq (nameof queue) queue) + struct (Queues.ofFreshArray accepted, Queues.ofFreshArray rejected) + +[] +let partitionWith (partitioner : 'T -> Choice<'T1, 'T2>) (queue : Queue<'T>) = + let first, second = Array.partitionWith partitioner (Snapshot.ofSeq (nameof queue) queue) + struct (Queues.ofFreshArray first, Queues.ofFreshArray second) + +[] +let skip count (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + let mutable rest = queue + let mutable skipped = 0 + + while skipped < count && not rest.IsEmpty do + rest <- rest.Dequeue () + skipped <- skipped + 1 + + // The loop stops at the count or at the end of the queue, where the number of skipped elements is the length; a + // count beyond the length throws what List.skip throws + ListChecks.skip (nameof count) count skipped + rest + +[] +let skipLast count (queue : Queue<'T>) = + let array = Snapshot.ofSeq (nameof queue) queue + // The checks of List.skip + ListChecks.skip (nameof count) count array.Length + + if count <= 0 then + queue + else + ImmutableQueue.Create<'T>(ReadOnlySpan<'T>(array, 0, array.Length - count)) + +[] +let skipWhile (predicate : 'T -> bool) (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + let mutable rest = queue + + while not rest.IsEmpty && predicate (rest.Peek ()) do + rest <- rest.Dequeue () + + rest + +[] +let splitAt index (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + let before = ResizeArray<'T>() + let mutable rest = queue + + while before.Count < index && not rest.IsEmpty do + before.Add (rest.Peek ()) + rest <- rest.Dequeue () + + // The loop stops at the index or at the end of the queue, where the number of taken elements is the length + ListChecks.splitAt (nameof index) index before.Count + struct ((if rest.IsEmpty then queue else Queues.ofBuffer before), rest) + +[] +let splitInto count (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + ListChecks.positive (nameof count) count + + Array.splitInto count (Snapshot.ofSeq (nameof queue) queue) + |> Array.map Queues.ofFreshArray + |> Queues.ofFreshArray + +[] +let take count (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + let struct (items, more) = Queues.front count queue + // The front stops at the count or at the end of the queue, where the number of taken elements is the length + ListChecks.take (nameof count) count items.Count + if more then Queues.ofBuffer items else queue + +[] +let takeLast count (queue : Queue<'T>) = + let array = Snapshot.ofSeq (nameof queue) queue + // The checks of List.take + ListChecks.take (nameof count) count array.Length + + if count = array.Length then + queue + else + ImmutableQueue.Create<'T>(ReadOnlySpan<'T>(array, array.Length - count, count)) + +[] +let takeWhile (predicate : 'T -> bool) (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + let items = ResizeArray<'T>() + let mutable enumerator = queue.GetEnumerator () + let mutable more = enumerator.MoveNext () + + while more && predicate enumerator.Current do + items.Add enumerator.Current + more <- enumerator.MoveNext () + + if more then Queues.ofBuffer items else queue + +[] +let truncate count (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + let struct (items, more) = Queues.front count queue + if more then Queues.ofBuffer items else queue + +// ---- Sorting ---- + +// The sorts are stable, as the sorts of List are (ADR 0001, D5). The functions that compare the elements, or that call the +// projection in the comparison as List.sortByDescending does, sort with the Seq functions of FSharp.Core, which share the +// stable sort of List: the same comparisons run in Array.Sort, which wraps the exception of a comparison in an +// InvalidOperationException (D6). Seq.sortDescending and Seq.sortByDescending are inline and box the elements in every +// comparison once they are compiled into a generic function, so the two descending sorts call Seq.sortWith through +// DescendingSorts, with the cached comparer. sortBy and sortWithComparer sort with LINQ, which computes the keys first, as +// List.sortBy does, and wraps the exception of a comparison as Array.Sort does. A queue of one element is returned +// without a call of the projection or the comparer, as List returns a list of one element. + +[] +let sort (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + + if Lengths.queueUpTo 2 queue <= 1 then + queue + else + ofSorted (Seq.sort queue) + +[] +let sortBy (projection : 'T -> 'Key) (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + + if Lengths.queueUpTo 2 queue <= 1 then + queue + else + queue.OrderBy(Func<'T, 'Key> projection, DefaultComparer<'Key>.Instance).ToArray() + |> Queues.ofFreshArray + +[] +let sortByDescending (projection : 'T -> 'Key) (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + + if Lengths.queueUpTo 2 queue <= 1 then + queue + else + ofSorted (DescendingSorts.sortByDescending projection queue) + +[] +let sortDescending (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + + if Lengths.queueUpTo 2 queue <= 1 then + queue + else + ofSorted (DescendingSorts.sortDescending queue) + +[] +let sortWith (comparer : 'T -> 'T -> int) (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + + if Lengths.queueUpTo 2 queue <= 1 then + queue + else + ofSorted (Seq.sortWith comparer queue) + +[] +let sortWithComparer (comparer : IComparer<'T>) (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + + if Lengths.queueUpTo 2 queue <= 1 then + queue + else + queue.Order(comparer).ToArray() |> Queues.ofFreshArray + +// ---- Random ---- + +// The functions draw the random numbers as the List functions do: they take a snapshot, run the List checks with its +// length and choose from it, or apply the Array function to it + +[] +let randomChoice (source : Queue<'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 : Queue<'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 : Queue<'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 : Queue<'T>) = + let array = Snapshot.ofSeq (nameof source) source + ListChecks.randomChoices (nameof count) count (nameof source) array.Length + Array.randomChoices count array |> Queues.ofFreshArray + +[] +let randomChoicesBy (randomizer : unit -> float) count (source : Queue<'T>) = + let array = Snapshot.ofSeq (nameof source) source + ListChecks.randomChoices (nameof count) count (nameof source) array.Length + Array.randomChoicesBy randomizer count array + |> Queues.ofFreshArray + +[] +let randomChoicesWith (random : Random) count (source : Queue<'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 + |> Queues.ofFreshArray + +[] +let randomSample count (source : Queue<'T>) = + let array = Snapshot.ofSeq (nameof source) source + ListChecks.randomSample (nameof count) count (nameof source) array.Length + Array.randomSample count array |> Queues.ofFreshArray + +[] +let randomSampleBy (randomizer : unit -> float) count (source : Queue<'T>) = + let array = Snapshot.ofSeq (nameof source) source + ListChecks.randomSample (nameof count) count (nameof source) array.Length + Array.randomSampleBy randomizer count array + |> Queues.ofFreshArray + +[] +let randomSampleWith (random : Random) count (source : Queue<'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 + |> Queues.ofFreshArray + +[] +let randomShuffle (source : Queue<'T>) = + let array = Snapshot.ofSeq (nameof source) source + Array.randomShuffleInPlace array + Queues.ofFreshArray array + +[] +let randomShuffleBy (randomizer : unit -> float) (source : Queue<'T>) = + let array = Snapshot.ofSeq (nameof source) source + Array.randomShuffleInPlaceBy randomizer array + Queues.ofFreshArray array + +[] +let randomShuffleWith (random : Random) (source : Queue<'T>) = + checkNotNull (nameof random) random + let array = Snapshot.ofSeq (nameof source) source + Array.randomShuffleInPlaceWith random array + Queues.ofFreshArray array + +// ---- Conversion ---- + +[] +let toArray (queue : Queue<'T>) = Snapshot.ofSeq (nameof queue) queue + +[] +let toList (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + List.ofSeq queue + +[] +let toSeq (queue : Queue<'T>) = + checkNotNull (nameof queue) queue + queue :> seq<'T> diff --git a/src/FSharp.Collections.Immutable/Queue/Queue.fsi b/src/FSharp.Collections.Immutable/Queue/Queue.fsi new file mode 100644 index 00000000..9634e2ae --- /dev/null +++ b/src/FSharp.Collections.Immutable/Queue/Queue.fsi @@ -0,0 +1,1512 @@ +/// +/// Functions for the queue, the immutable queue . +/// +/// +/// +/// The module mirrors over the elements in the order in which they +/// leave the queue, first in, first out: the head of the queue is the head of the list. 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. The parameters that the list functions name after the list are named after the queue, 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 queue does not store its length, so counting the elements takes O(n), and the functions that check a count or an +/// index against the length read the elements first. +/// +/// +/// Every function throws for a queue, as +/// does for a array. +/// +/// +/// Functions that return an optional value return a , and functions +/// that return tuples return struct tuples. The namespaces and +/// contain a module with the same name whose functions use options +/// and reference tuples instead. +/// +/// +[] +module FSharp.Collections.Immutable.Queue + +open System +open System.Collections.Generic +open System.Collections.Immutable +// Opened after System.Collections.Generic, so that Queue names the abbreviation of this library, not the mutable queue +open FSharp.Collections.Immutable + +// ---- Creation ---- + +/// +/// The empty queue. +/// +/// +/// O(1). The value is . +/// +[] +[] +val empty<'T> : Queue<'T> + +/// Creates a queue from a function of the index. +/// The length of the queue. +/// The function that computes the element at each index, called in index order. +/// The created queue, whose first element is the one at index 0. +/// Thrown when is negative. +/// O(n). +[] +val init : length : int -> initializer : (int -> 'T) -> Queue<'T> + +/// +/// Creates a queue from a copy of an array. +/// +/// The input array. +/// The queue of the elements of the array, whose first element is the first one of the array. +/// Thrown when is . +/// +/// O(n). Later changes to the array do not change the queue. The function wraps +/// . +/// +[] +val ofArray : array : 'T array -> Queue<'T> + +/// Creates a queue from a list. +/// The input list. +/// The queue of the elements of the list, whose first element is the head of the list. +/// O(n). +[] +val ofList : list : 'T list -> Queue<'T> + +/// +/// Creates a queue from a copy of a read-only span. +/// +/// The input span. +/// The queue of the elements of the span, whose first element is the first one of the span. +/// +/// O(n). The function wraps . +/// +[] +val ofReadOnlySpan : span : ReadOnlySpan<'T> -> Queue<'T> + +/// +/// Creates a queue from a sequence. +/// +/// The input sequence. +/// The queue of the elements of the sequence, whose first element is the first one of the sequence. +/// Thrown when is or a default flat list. +/// +/// O(n). A queue is returned itself, without a copy. The function wraps +/// . +/// +[] +val ofSeq : source : seq<'T> -> Queue<'T> + +/// +/// Creates a queue from a copy of a span. +/// +/// The input span. +/// The queue of the elements of the span, whose first element is the first one of the span. +/// +/// O(n). The function wraps . +/// +[] +val ofSpan : span : Span<'T> -> Queue<'T> + +/// Creates a queue whose elements are all the given value. +/// The length of the queue. +/// The value of every element. +/// The created queue. +/// Thrown when is negative. +/// O(n). +[] +val replicate : count : int -> initial : 'T -> Queue<'T> + +/// Creates a queue of one element. +/// The element. +/// The queue of one element. +/// O(1). +[] +val singleton : value : 'T -> Queue<'T> + +/// +/// Creates a queue 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 queue, whose first element is the first generated one. +/// +/// O(n). +/// +[] +val unfold : generator : ('State -> struct ('T * 'State) voption) -> state : 'State -> Queue<'T> + +// ---- Properties ---- + +/// Returns whether the queue has no elements. +/// The input queue. +/// when the queue is empty. +/// O(1). +[] +val isEmpty : queue : Queue<'T> -> bool + +/// +/// Returns the number of elements of the queue. +/// +/// The input queue. +/// The length of the queue. +/// +/// O(n): the queue does not store its length, so the function counts the elements. +/// +[] +val length : queue : Queue<'T> -> int + +// ---- Access ---- + +/// Returns the only element of the queue. +/// The input queue. +/// The only element. +/// Thrown when the queue does not have exactly one element. +/// Amortized O(1). +[] +val exactlyOne : queue : Queue<'T> -> 'T + +/// +/// Returns the first element of the queue, the one that leaves it next. +/// +/// The input queue. +/// The first element. +/// Thrown when the queue is empty. +/// +/// O(1). The function wraps . +/// +[] +val head : queue : Queue<'T> -> 'T + +/// +/// Returns the last element of the queue, the one that joined it last. +/// +/// The input queue. +/// The last element. +/// Thrown when the queue is empty. +/// +/// O(n). The function wraps . +/// +[] +val last : queue : Queue<'T> -> 'T + +/// +/// Returns the queue without its first element. +/// +/// The input queue. +/// The elements after the first one. +/// Thrown when the queue is empty. +/// +/// Amortized O(1). The result shares its elements with the input. The function wraps +/// . +/// +[] +val tail : queue : Queue<'T> -> Queue<'T> + +/// +/// Returns the only element of the queue, or ValueNone when it does not have exactly one element. +/// +/// The input queue. +/// The only element, or ValueNone. +/// +/// Amortized O(1). +/// +[] +val tryExactlyOne : queue : Queue<'T> -> 'T voption + +/// +/// Returns the first element of the queue, or ValueNone when it is empty. +/// +/// The input queue. +/// The first element, or ValueNone. +/// +/// O(1). +/// +[] +val tryHead : queue : Queue<'T> -> 'T voption + +/// +/// Returns the last element of the queue, or ValueNone when it is empty. +/// +/// The input queue. +/// The last element, or ValueNone. +/// +/// O(n). +/// +[] +val tryLast : queue : Queue<'T> -> 'T voption + +/// +/// Returns the queue without its first element, or ValueNone when it is empty. +/// +/// The input queue. +/// The elements after the first one, or ValueNone. +/// +/// Amortized O(1). The result shares its elements with the input. +/// +[] +val tryTail : queue : Queue<'T> -> Queue<'T> voption + +/// +/// Returns the first element and the other elements, or ValueNone when the queue is empty. +/// +/// The input queue. +/// The first element and the elements after it, or ValueNone. +/// +/// Amortized O(1). +/// +[] +val tryUncons : queue : Queue<'T> -> struct ('T * Queue<'T>) voption + +/// +/// Returns the first element and the other elements. +/// +/// The input queue. +/// The first element and the elements after it. +/// Thrown when the queue is empty. +/// +/// Amortized O(1). The function throws what +/// throws. +/// +[] +val uncons : queue : Queue<'T> -> struct ('T * Queue<'T>) + +/// +/// Matches a queue with a first element, and returns that element and the other elements. +/// +/// The input queue. +/// The first element and the elements after it, or ValueNone for an empty queue. +/// +/// Amortized O(1). A struct partial pattern, so that a match allocates no option. +/// +[] +[] +val (|Cons|_|) : queue : Queue<'T> -> struct ('T * Queue<'T>) voption + +/// Matches an empty queue. +/// The input queue. +/// when the queue is empty. +/// O(1). +[] +val (|Nil|_|) : queue : Queue<'T> -> bool + +// ---- Search ---- + +/// +/// Returns whether the queue contains an element equal to a value, in F# structural equality. +/// +/// The value to find. +/// The input queue. +/// when an element equals the value. +/// +/// O(n). +/// +[] +val contains : value : 'T -> source : Queue<'T> -> bool when 'T : equality + +/// Returns whether any element satisfies the predicate. +/// The function to test the elements. +/// The input queue. +/// when an element satisfies the predicate. +/// O(n). +[] +val exists : predicate : ('T -> bool) -> queue : Queue<'T> -> bool + +/// +/// Returns whether any pair of elements at the same index satisfies the predicate. +/// +/// The function to test the pairs. +/// The first queue. +/// The second queue. +/// when a pair satisfies the predicate. +/// Thrown when no pair satisfies the predicate and the queues have different lengths. +/// +/// O(n). +/// +[] +val exists2 : predicate : ('T1 -> 'T2 -> bool) -> queue1 : Queue<'T1> -> queue2 : Queue<'T2> -> bool + +/// Returns the first element that satisfies the predicate. +/// The function to test the elements. +/// The input queue. +/// The first element that satisfies the predicate. +/// Thrown when no element satisfies the predicate. +/// O(n). +[] +val find : predicate : ('T -> bool) -> queue : Queue<'T> -> 'T + +/// Returns the last element that satisfies the predicate. +/// The function to test the elements. +/// The input queue. +/// The last element that satisfies the predicate. +/// Thrown when no element satisfies the predicate. +/// O(n). +[] +val findBack : predicate : ('T -> bool) -> queue : Queue<'T> -> 'T + +/// +/// Returns the index of the first element that satisfies the predicate. +/// +/// The function to test the elements. +/// The input queue. +/// The index of the first element that satisfies the predicate. +/// Thrown when no element satisfies the predicate. +/// +/// O(n). +/// +[] +val findIndex : predicate : ('T -> bool) -> queue : Queue<'T> -> int + +/// +/// Returns the index of the last element that satisfies the predicate. +/// +/// The function to test the elements. +/// The input queue. +/// The index of the last element that satisfies the predicate. +/// Thrown when no element satisfies the predicate. +/// +/// O(n). +/// +[] +val findIndexBack : predicate : ('T -> bool) -> queue : Queue<'T> -> int + +/// Returns whether all elements satisfy the predicate. +/// The function to test the elements. +/// The input queue. +/// when every element satisfies the predicate. +/// O(n). +[] +val forall : predicate : ('T -> bool) -> queue : Queue<'T> -> bool + +/// +/// Returns whether all pairs of elements at the same index satisfy the predicate. +/// +/// The function to test the pairs. +/// The first queue. +/// The second queue. +/// when every pair satisfies the predicate. +/// Thrown when every pair satisfies the predicate and the queues have different lengths. +/// +/// O(n). +/// +[] +val forall2 : predicate : ('T1 -> 'T2 -> bool) -> queue1 : Queue<'T1> -> queue2 : Queue<'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 queue. +/// The first chosen value. +/// Thrown when the chooser returns ValueNone for every element. +/// +/// O(n). +/// +[] +val pick : chooser : ('T -> 'U voption) -> queue : Queue<'T> -> 'U + +/// +/// Returns the first element that satisfies the predicate, or ValueNone. +/// +/// The function to test the elements. +/// The input queue. +/// The first element that satisfies the predicate, or ValueNone. +/// +/// O(n). +/// +[] +val tryFind : predicate : ('T -> bool) -> queue : Queue<'T> -> 'T voption + +/// +/// Returns the last element that satisfies the predicate, or ValueNone. +/// +/// The function to test the elements. +/// The input queue. +/// The last element that satisfies the predicate, or ValueNone. +/// +/// O(n). +/// +[] +val tryFindBack : predicate : ('T -> bool) -> queue : Queue<'T> -> 'T voption + +/// +/// Returns the index of the first element that satisfies the predicate, or ValueNone. +/// +/// The function to test the elements. +/// The input queue. +/// The index of the first element that satisfies the predicate, or ValueNone. +/// +/// O(n). +/// +[] +val tryFindIndex : predicate : ('T -> bool) -> queue : Queue<'T> -> int voption + +/// +/// Returns the index of the last element that satisfies the predicate, or ValueNone. +/// +/// The function to test the elements. +/// The input queue. +/// The index of the last element that satisfies the predicate, or ValueNone. +/// +/// O(n). +/// +[] +val tryFindIndexBack : predicate : ('T -> bool) -> queue : Queue<'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 queue. +/// The first chosen value, or ValueNone. +/// +/// O(n). +/// +[] +val tryPick : chooser : ('T -> 'U voption) -> queue : Queue<'T> -> 'U voption + +// ---- Transformation ---- + +/// +/// Returns every element of the first queue paired with every element of the second one. +/// +/// The first queue. +/// The second queue. +/// The pairs, in the order of the first queue and then of the second one. +/// +/// O(n × m). +/// +[] +val allPairs : queue1 : Queue<'T1> -> queue2 : Queue<'T2> -> Queue + +/// +/// Returns a queue of the elements of the first queue followed by the elements of the second one. +/// +/// The first queue. +/// The second queue. +/// The concatenation. +/// +/// O(m), where m is the length of the second queue: the function enqueues its elements into the first queue, and the +/// result shares the elements of the first queue. When the first queue is empty, the function returns the second one. +/// +[] +val append : queue1 : Queue<'T> -> queue2 : Queue<'T> -> Queue<'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 queue. +/// The chosen values. +/// +/// O(n). +/// +[] +val choose : chooser : ('T -> 'U voption) -> queue : Queue<'T> -> Queue<'U> + +/// Divides the queue into chunks of a given size. +/// The maximum size of a chunk. +/// The input queue. +/// The chunks; the last one can be smaller. +/// Thrown when is not positive. +/// O(n). +[] +val chunkBySize : chunkSize : int -> queue : Queue<'T> -> Queue> + +/// +/// Concatenates the queues that a function returns for the elements. +/// +/// The function that returns a queue for each element. +/// The input queue. +/// The concatenation of the returned queues. +/// +/// O(n + the total length of the returned queues). +/// +[] +val collect : mapping : ('T -> Queue<'U>) -> queue : Queue<'T> -> Queue<'U> + +/// Concatenates a sequence of queues. +/// The queues. +/// The concatenation. +/// Thrown when is or a default flat list, or one of its queues is . +/// O(the total length). +[] +val concat : queues : seq> -> Queue<'T> + +/// +/// Returns a queue with a value added after the last element. +/// +/// The value to add. +/// The input queue. +/// A queue of the elements and the value, which leaves it last. +/// +/// O(1). The result shares the elements of the input. The function wraps +/// . +/// +[] +val enqueue : value : 'T -> queue : Queue<'T> -> Queue<'T> + +/// +/// Returns a queue with values added after the last element, in their order. +/// +/// The values to add. +/// The input queue. +/// A queue of the elements and then the values. +/// Thrown when is or a default flat list. +/// +/// O(m), where m is the number of values. The result shares the elements of the input; without values, the function +/// returns the input. The function calls for +/// each value. +/// +[] +val enqueueMany : values : seq<'T> -> queue : Queue<'T> -> Queue<'T> + +/// Returns the elements that satisfy the predicate. +/// The function to test the elements. +/// The input queue. +/// The elements that satisfy the predicate, in their order. +/// O(n). +[] +val filter : predicate : ('T -> bool) -> queue : Queue<'T> -> Queue<'T> + +/// Returns every element with its index. +/// The input queue. +/// The pairs of index and element. +/// O(n). +[] +val indexed : queue : Queue<'T> -> Queue + +/// 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 queue. +/// The results, in the order of the elements. +/// O(n). +[] +val map : mapping : ('T -> 'U) -> queue : Queue<'T> -> Queue<'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 queue. +/// The second queue. +/// The results. +/// Thrown when the queues have different lengths. +/// +/// O(n). +/// +[] +val map2 : mapping : ('T1 -> 'T2 -> 'U) -> queue1 : Queue<'T1> -> queue2 : Queue<'T2> -> Queue<'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 queue. +/// The second queue. +/// The third queue. +/// The results. +/// Thrown when the queues have different lengths. +/// +/// O(n). +/// +[] +val map3 : mapping : ('T1 -> 'T2 -> 'T3 -> 'U) -> queue1 : Queue<'T1> -> queue2 : Queue<'T2> -> queue3 : Queue<'T3> -> Queue<'U> + +/// +/// Returns the results of a function applied to each element and its index. +/// +/// The function to transform the elements. +/// The input queue. +/// The results. +/// +/// O(n). +/// +[] +val mapi : mapping : (int -> 'T -> 'U) -> queue : Queue<'T> -> Queue<'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 queue. +/// The second queue. +/// The results. +/// Thrown when the queues have different lengths. +/// +/// O(n). +/// +[] +val mapi2 : mapping : (int -> 'T1 -> 'T2 -> 'U) -> queue1 : Queue<'T1> -> queue2 : Queue<'T2> -> Queue<'U> + +/// Returns every element with its successor. +/// The input queue. +/// The pairs of neighbours; empty for fewer than two elements. +/// O(n). +[] +val pairwise : queue : Queue<'T> -> Queue + +/// +/// 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 queue. +/// The permuted queue. +/// Thrown when the function does not compute a permutation. +/// +/// O(n). +/// +[] +val permute : indexMap : (int -> int) -> queue : Queue<'T> -> Queue<'T> + +/// Returns the elements in reverse order. +/// The input queue. +/// The reversed queue, whose first element is the last one of the input. +/// O(n). +[] +val rev : queue : Queue<'T> -> Queue<'T> + +/// +/// Returns the transpose of a sequence of queues of the same length. +/// +/// The queues. +/// A queue whose element i holds the element i of every input queue. +/// Thrown when is or a default flat list, or one of its queues is . +/// Thrown when the queues have different lengths. +/// +/// O(the total length). +/// +[] +val transpose : queues : seq> -> Queue> + +/// Splits a queue of pairs into two queues. +/// The input queue. +/// The first components and the second components. +/// O(n). +[] +val unzip : queue : Queue -> struct (Queue<'T1> * Queue<'T2>) + +/// Splits a queue of triples into three queues. +/// The input queue. +/// The first, the second and the third components. +/// O(n). +[] +val unzip3 : queue : Queue -> struct (Queue<'T1> * Queue<'T2> * Queue<'T3>) + +/// +/// Returns the elements that satisfy the predicate. +/// +/// The function to test the elements. +/// The input queue. +/// The elements that satisfy the predicate, in their order. +/// +/// O(n). A synonym of . +/// +[] +val where : predicate : ('T -> bool) -> queue : Queue<'T> -> Queue<'T> + +/// +/// Returns the windows of a given size that slide over the queue. +/// +/// The size of a window. +/// The input queue. +/// The windows; empty when the queue is shorter than a window. +/// Thrown when is not positive. +/// +/// O(n × windowSize). +/// +[] +val windowed : windowSize : int -> queue : Queue<'T> -> Queue> + +/// Combines two queues into a queue of pairs. +/// The first queue. +/// The second queue. +/// The pairs of elements at the same index. +/// Thrown when the queues have different lengths. +/// O(n). +[] +val zip : queue1 : Queue<'T1> -> queue2 : Queue<'T2> -> Queue + +/// Combines three queues into a queue of triples. +/// The first queue. +/// The second queue. +/// The third queue. +/// The triples of elements at the same index. +/// Thrown when the queues have different lengths. +/// O(n). +[] +val zip3 : queue1 : Queue<'T1> -> queue2 : Queue<'T2> -> queue3 : Queue<'T3> -> Queue + +// ---- Folding ---- + +/// Returns the average of the elements. +/// The input queue. +/// The average. +/// Thrown when the queue is empty. +/// O(n). +[] +val inline average : + queue : Queue< ^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 queue. +/// The average of the results. +/// Thrown when the queue is empty. +/// +/// O(n). +/// +[] +val inline averageBy : + projection : ('T -> ^U) -> queue : Queue<'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 queues element by element with a comparer. +/// The function that compares two elements. +/// The first queue. +/// The second queue. +/// +/// 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) -> queue1 : Queue<'T> -> queue2 : Queue<'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 queue. +/// The final state. +/// +/// O(n). +/// +[] +val fold : folder : ('State -> 'T -> 'State) -> state : 'State -> queue : Queue<'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 queue. +/// The second queue. +/// The final state. +/// Thrown when the queues have different lengths. +/// +/// O(n). +/// +[] +val fold2 : folder : ('State -> 'T1 -> 'T2 -> 'State) -> state : 'State -> queue1 : Queue<'T1> -> queue2 : Queue<'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 queue. +/// The initial state. +/// The final state. +/// +/// O(n). +/// +[] +val foldBack : folder : ('T -> 'State -> 'State) -> queue : Queue<'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 queue. +/// The second queue. +/// The initial state. +/// The final state. +/// Thrown when the queues have different lengths. +/// +/// O(n). +/// +[] +val foldBack2 : + folder : ('T1 -> 'T2 -> 'State -> 'State) -> queue1 : Queue<'T1> -> queue2 : Queue<'T2> -> state : 'State -> 'State + +/// Applies an action to each element. +/// The action. +/// The input queue. +/// O(n). +[] +val iter : action : ('T -> unit) -> queue : Queue<'T> -> unit + +/// +/// Applies an action to each pair of elements at the same index. +/// +/// The action. +/// The first queue. +/// The second queue. +/// Thrown when the queues have different lengths. +/// +/// O(n). +/// +[] +val iter2 : action : ('T1 -> 'T2 -> unit) -> queue1 : Queue<'T1> -> queue2 : Queue<'T2> -> unit + +/// Applies an action to each element and its index. +/// The action. +/// The input queue. +/// O(n). +[] +val iteri : action : (int -> 'T -> unit) -> queue : Queue<'T> -> unit + +/// +/// Applies an action to the index and the pair of elements at that index. +/// +/// The action. +/// The first queue. +/// The second queue. +/// Thrown when the queues have different lengths. +/// +/// O(n). +/// +[] +val iteri2 : action : (int -> 'T1 -> 'T2 -> unit) -> queue1 : Queue<'T1> -> queue2 : Queue<'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 queue. +/// The results and the final state. +/// +/// O(n). +/// +[] +val mapFold : + mapping : ('State -> 'T -> struct ('Result * 'State)) -> + state : 'State -> + queue : Queue<'T> -> + struct (Queue<'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 queue. +/// 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)) -> + queue : Queue<'T> -> + state : 'State -> + struct (Queue<'Result> * 'State) + +/// Returns the greatest element, in F# generic comparison. +/// The input queue. +/// The greatest element. +/// Thrown when the queue is empty. +/// O(n). +[] +val max : queue : Queue<'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 queue. +/// The first element with the greatest result. +/// Thrown when the queue is empty. +/// +/// O(n). +/// +[] +val maxBy : projection : ('T -> 'U) -> queue : Queue<'T> -> 'T when 'U : comparison + +/// Returns the smallest element, in F# generic comparison. +/// The input queue. +/// The smallest element. +/// Thrown when the queue is empty. +/// O(n). +[] +val min : queue : Queue<'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 queue. +/// The first element with the smallest result. +/// Thrown when the queue is empty. +/// +/// O(n). +/// +[] +val minBy : projection : ('T -> 'U) -> queue : Queue<'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 queue. +/// The combined value. +/// Thrown when the queue is empty. +/// +/// O(n). +/// +[] +val reduce : reduction : ('T -> 'T -> 'T) -> queue : Queue<'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 queue. +/// The combined value. +/// Thrown when the queue is empty. +/// +/// O(n). +/// +[] +val reduceBack : reduction : ('T -> 'T -> 'T) -> queue : Queue<'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 queue. +/// The states; one more than the elements. +/// +/// O(n). +/// +[] +val scan : folder : ('State -> 'T -> 'State) -> state : 'State -> queue : Queue<'T> -> Queue<'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 queue. +/// The initial state. +/// The states; one more than the elements. +/// +/// O(n). +/// +[] +val scanBack : folder : ('T -> 'State -> 'State) -> queue : Queue<'T> -> state : 'State -> Queue<'State> + +/// Returns the sum of the elements. +/// The input queue. +/// The sum; zero for an empty queue. +/// O(n). +[] +val inline sum : queue : Queue< ^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 queue. +/// The sum of the results; zero for an empty queue. +/// +/// O(n). +/// +[] +val inline sumBy : + projection : ('T -> ^U) -> queue : Queue<'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 queue. +/// 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) -> queue : Queue<'T> -> Queue when 'Key : equality + +/// +/// Returns the elements without duplicates, in F# structural equality. +/// +/// The input queue. +/// The first occurrence of each element, in their order. +/// +/// O(n). +/// +[] +val distinct : queue : Queue<'T> -> Queue<'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 queue. +/// The first element of each key, in their order. +/// +/// O(n). The keys compare in F# structural equality. +/// +[] +val distinctBy : projection : ('T -> 'Key) -> queue : Queue<'T> -> Queue<'T> when 'Key : equality + +/// +/// Returns the distinct elements that do not equal any of the excluded items. +/// +/// The items to remove. +/// The input queue. +/// The distinct remaining elements, in their order. +/// Thrown when is or a default flat list. +/// +/// O(n + m). An empty queue is returned without a look at the items to exclude. +/// +[] +val except : itemsToExclude : seq<'T> -> queue : Queue<'T> -> Queue<'T> when 'T : equality + +/// Groups the elements by the key of a projection. +/// The function that returns the key of an element. +/// The input queue. +/// 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) -> queue : Queue<'T> -> Queue)> when 'Key : equality + +/// Splits the elements by a predicate. +/// The function to test the elements. +/// The input queue. +/// The elements that satisfy the predicate and the other ones, both in their order. +/// O(n). +[] +val partition : predicate : ('T -> bool) -> queue : Queue<'T> -> struct (Queue<'T> * Queue<'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 queue. +/// 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>) -> queue : Queue<'T> -> struct (Queue<'T1> * Queue<'T2>) + +/// +/// Returns the queue without its first elements. +/// +/// The number of elements to skip; a negative count skips nothing. +/// The input queue. +/// The remaining elements. +/// Thrown when the count exceeds the length. +/// +/// Amortized O(count). The function dequeues the elements, so the result shares the remaining elements with the input; +/// a count that is not positive returns the input. +/// +[] +val skip : count : int -> queue : Queue<'T> -> Queue<'T> + +/// +/// Returns the queue without its last elements. +/// +/// The number of elements to skip; a negative count skips nothing. +/// The input queue. +/// The remaining elements. +/// Thrown when the count exceeds the length. +/// +/// O(n). A count that is not positive returns the input. The checks follow +/// . +/// +[] +val skipLast : count : int -> queue : Queue<'T> -> Queue<'T> + +/// +/// Returns the queue without its first elements that satisfy the predicate. +/// +/// The function to test the elements. +/// The input queue. +/// The elements from the first one that does not satisfy the predicate. +/// +/// Amortized O(k), where k is the number of skipped elements. The function dequeues the elements, so the result shares +/// the remaining elements with the input. +/// +[] +val skipWhile : predicate : ('T -> bool) -> queue : Queue<'T> -> Queue<'T> + +/// +/// Splits the queue at an index. +/// +/// The index of the first element of the second part. +/// The input queue. +/// The elements before the index and the elements from the index. +/// Thrown when the index is negative. +/// Thrown when the index exceeds the length. +/// +/// Amortized O(index). The second part shares its elements with the input, and the first part is the input itself when +/// the index is the length. +/// +[] +val splitAt : index : int -> queue : Queue<'T> -> struct (Queue<'T> * Queue<'T>) + +/// +/// Splits the queue into at most a given number of parts of nearly equal length. +/// +/// The maximum number of parts. +/// The input queue. +/// 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 -> queue : Queue<'T> -> Queue> + +/// +/// Returns the first elements of the queue. +/// +/// The number of elements to take. +/// The input queue. +/// The first elements. +/// Thrown when the count is negative. +/// Thrown when the count exceeds the length. +/// +/// O(count). The input itself is returned when the count is the length. +/// +[] +val take : count : int -> queue : Queue<'T> -> Queue<'T> + +/// +/// Returns the last elements of the queue. +/// +/// The number of elements to take. +/// The input queue. +/// The last elements. +/// Thrown when the count is negative. +/// Thrown when the count exceeds the length. +/// +/// O(n). The input itself is returned when the count is the length. The checks follow +/// . +/// +[] +val takeLast : count : int -> queue : Queue<'T> -> Queue<'T> + +/// +/// Returns the first elements of the queue that satisfy the predicate. +/// +/// The function to test the elements. +/// The input queue. +/// The elements before the first one that does not satisfy the predicate. +/// +/// O(k), where k is the length of the result. The input itself is returned when every element satisfies the predicate. +/// +[] +val takeWhile : predicate : ('T -> bool) -> queue : Queue<'T> -> Queue<'T> + +/// +/// Returns at most a given number of first elements. +/// +/// The maximum number of elements; a count that is not positive returns an empty queue. +/// The input queue. +/// The first elements. +/// +/// O(count). The input itself is returned when it is not longer than the count. +/// +[] +val truncate : count : int -> queue : Queue<'T> -> Queue<'T> + +// ---- Sorting ---- + +/// +/// Sorts the elements in the order of F# generic comparison. +/// +/// The input queue. +/// The sorted queue. +/// +/// 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 : queue : Queue<'T> -> Queue<'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 queue. +/// The sorted queue. +/// +/// O(n log n). This is a stable sort: elements with equal keys keep their order. +/// +[] +val sortBy : projection : ('T -> 'Key) -> queue : Queue<'T> -> Queue<'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 queue. +/// The sorted queue. +/// +/// 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) -> queue : Queue<'T> -> Queue<'T> when 'Key : comparison + +/// +/// Sorts the elements in descending order of F# generic comparison. +/// +/// The input queue. +/// The sorted queue. +/// +/// 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 : queue : Queue<'T> -> Queue<'T> when 'T : comparison + +/// +/// Sorts the elements with a comparison function. +/// +/// The function that compares two elements. +/// The input queue. +/// The sorted queue. +/// +/// 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) -> queue : Queue<'T> -> Queue<'T> + +/// +/// Sorts the elements with a comparer. +/// +/// The comparer. +/// The input queue. +/// The sorted queue. +/// +/// O(n log n). This is a stable sort: elements that compare equal keep their order. The function wraps +/// . +/// +[] +val sortWithComparer : comparer : IComparer<'T> -> queue : Queue<'T> -> Queue<'T> + +// ---- Random ---- + +/// +/// Returns a random element, chosen with . +/// +/// The input queue. +/// A random element. +/// Thrown when the queue is empty. +/// +/// O(n). +/// +[] +val randomChoice : source : Queue<'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 queue. +/// A random element. +/// Thrown when the queue is empty. +/// Thrown when the randomizer returns a number outside [0, 1). +/// +/// O(n). +/// +[] +val randomChoiceBy : randomizer : (unit -> float) -> source : Queue<'T> -> 'T + +/// +/// Returns a random element, chosen with a given random number generator. +/// +/// The random number generator. +/// The input queue. +/// A random element. +/// Thrown when is . +/// Thrown when the queue is empty. +/// +/// O(n). +/// +[] +val randomChoiceWith : random : Random -> source : Queue<'T> -> 'T + +/// +/// Returns random elements, chosen with ; an element can repeat. +/// +/// The number of elements to choose. +/// The input queue. +/// The chosen elements. +/// Thrown when the count is negative, or positive for an empty queue. +/// +/// O(n + count). +/// +[] +val randomChoices : count : int -> source : Queue<'T> -> Queue<'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 queue. +/// The chosen elements. +/// Thrown when the count is negative, or positive for an empty queue. +/// Thrown when the randomizer returns a number outside [0, 1). +/// +/// O(n + count). +/// +[] +val randomChoicesBy : randomizer : (unit -> float) -> count : int -> source : Queue<'T> -> Queue<'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 queue. +/// The chosen elements. +/// Thrown when is . +/// Thrown when the count is negative, or positive for an empty queue. +/// +/// O(n + count). +/// +[] +val randomChoicesWith : random : Random -> count : int -> source : Queue<'T> -> Queue<'T> + +/// +/// Returns random elements at distinct indices, chosen with . +/// +/// The number of elements to choose. +/// The input queue. +/// The chosen elements. +/// Thrown when the count is negative or exceeds the length. +/// +/// O(n + count). +/// +[] +val randomSample : count : int -> source : Queue<'T> -> Queue<'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 queue. +/// 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 : Queue<'T> -> Queue<'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 queue. +/// 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 : Queue<'T> -> Queue<'T> + +/// +/// Returns the elements in a random order, chosen with . +/// +/// The input queue. +/// The shuffled queue. +/// +/// O(n). +/// +[] +val randomShuffle : source : Queue<'T> -> Queue<'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 queue. +/// The shuffled queue. +/// Thrown when the randomizer returns a number outside [0, 1). +/// +/// O(n). +/// +[] +val randomShuffleBy : randomizer : (unit -> float) -> source : Queue<'T> -> Queue<'T> + +/// +/// Returns the elements in a random order, chosen with a given random number generator. +/// +/// The random number generator. +/// The input queue. +/// The shuffled queue. +/// Thrown when is . +/// +/// O(n). +/// +[] +val randomShuffleWith : random : Random -> source : Queue<'T> -> Queue<'T> + +// ---- Conversion ---- + +/// +/// Returns a new array with the elements, the first element of the queue first. +/// +/// The input queue. +/// A copy of the elements. +/// +/// O(n). +/// +[] +val toArray : queue : Queue<'T> -> 'T array + +/// +/// Returns a list of the elements, the first element of the queue first. +/// +/// The input queue. +/// The list of the elements. +/// +/// O(n). +/// +[] +val toList : queue : Queue<'T> -> 'T list + +/// +/// Returns the queue as a sequence, which enumerates the elements in the order in which they leave the queue. +/// +/// The input queue. +/// The sequence of the elements. +/// +/// O(1). +/// +[] +val toSeq : queue : Queue<'T> -> seq<'T> diff --git a/src/FSharp.Collections.Immutable/Queue/QueueCE.fs b/src/FSharp.Collections.Immutable/Queue/QueueCE.fs new file mode 100644 index 00000000..6d536ad0 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Queue/QueueCE.fs @@ -0,0 +1,76 @@ +namespace FSharp.Collections.Immutable + +type QueueBuffer<'T> = ResizeArray<'T> -> unit + +[] +type QueueCE () = + + member inline _.Zero () : QueueBuffer<'T> = ignore + + member inline _.Yield (item : 'T) : QueueBuffer<'T> = fun buffer -> buffer.Add item + + // The checks run before the buffer is touched and name the parameter of yield!: the buffer would name its own parameter, or + // not check the argument at all (AddRange of a null array converts it to an empty span and adds nothing) + member inline _.YieldFrom (items : seq<'T>) : QueueBuffer<'T> = + // A default flat list given as a sequence is a null input, as in Queue.ofSeq (ADR 0001, D6) + checkNotNullOrDefaultSeq (nameof items) items + fun buffer -> buffer.AddRange items + + member inline _.YieldFrom (items : 'T array) : QueueBuffer<'T> = + checkNotNull (nameof items) items + fun buffer -> buffer.AddRange items + + member inline _.YieldFrom (items : Queue<'T>) : QueueBuffer<'T> = + checkNotNull (nameof items) items + + fun buffer -> + for item in items do + buffer.Add item + + member inline _.YieldFrom (buffer : QueueBuffer<'T>) : QueueBuffer<'T> = buffer + + member inline _.Combine (first : QueueBuffer<'T>, second : QueueBuffer<'T>) : QueueBuffer<'T> = + fun buffer -> + first buffer + second buffer + + member inline _.Delay ([] delayed : unit -> QueueBuffer<'T>) : QueueBuffer<'T> = + fun buffer -> delayed () buffer + + member inline _.For (source : seq<'U>, [] body : 'U -> QueueBuffer<'T>) : QueueBuffer<'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 : QueueBuffer<'T>) : QueueBuffer<'T> = + fun buffer -> + while guard () do + body buffer + + member inline _.TryWith (body : QueueBuffer<'T>, [] handler : exn -> QueueBuffer<'T>) : QueueBuffer<'T> = + fun buffer -> + try + body buffer + with ex -> + handler ex buffer + + member inline _.TryFinally (body : QueueBuffer<'T>, [] compensation : unit -> unit) : QueueBuffer<'T> = + fun buffer -> + try + body buffer + finally + compensation () + + member inline _.Using (resource : 'Resource, [] body : 'Resource -> QueueBuffer<'T>) : QueueBuffer<'T> = + fun buffer -> + use resource = resource + body resource buffer + + member _.Run (buffer : QueueBuffer<'T>) : Queue<'T> = + let items = ResizeArray<'T>() + buffer items + // Nobody else sees the buffer, so the queue takes its elements without a copy of the buffer + Queues.ofBuffer items diff --git a/src/FSharp.Collections.Immutable/Queue/QueueCE.fsi b/src/FSharp.Collections.Immutable/Queue/QueueCE.fsi new file mode 100644 index 00000000..c95a67d7 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Queue/QueueCE.fsi @@ -0,0 +1,74 @@ +namespace FSharp.Collections.Immutable + +open System + +/// +/// The body of a queue { } expression: a function that adds the elements to a buffer. +/// +type QueueBuffer<'T> = ResizeArray<'T> -> unit + +/// +/// The builder of the queue { } computation expression. +/// +/// +/// An immutable queue has no builder, so the expression collects its elements in a +/// and creates the queue from them when it ends (ADR 0001, D7). The first +/// element of the expression is the first one to leave the queue. +/// +[] +type QueueCE = + + /// Creates the builder of the expression. + new : unit -> QueueCE + + /// An expression without elements. + member inline Zero : unit -> QueueBuffer<'T> + + /// Adds an element. + member inline Yield : item : 'T -> QueueBuffer<'T> + + /// Adds the elements of a sequence. + /// Thrown when the sequence is or a default flat list. + member inline YieldFrom : items : seq<'T> -> QueueBuffer<'T> + + /// Adds the elements of an array. + /// Thrown when the array is . + member inline YieldFrom : items : 'T array -> QueueBuffer<'T> + + /// + /// Adds the elements of a queue, in the order in which they leave it. + /// + /// Thrown when the queue is . + member inline YieldFrom : items : Queue<'T> -> QueueBuffer<'T> + + /// Adds the elements of another expression body. + member inline YieldFrom : buffer : QueueBuffer<'T> -> QueueBuffer<'T> + + /// Adds the elements of two parts of the expression, in order. + member inline Combine : first : QueueBuffer<'T> * second : QueueBuffer<'T> -> QueueBuffer<'T> + + /// + /// Runs the body of the expression each time the elements are collected. + /// + member inline Delay : [] delayed : (unit -> QueueBuffer<'T>) -> QueueBuffer<'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 -> QueueBuffer<'T>) -> QueueBuffer<'T> + + /// Adds the elements of the body while the guard holds. + member inline While : [] guard : (unit -> bool) * body : QueueBuffer<'T> -> QueueBuffer<'T> + + /// Handles the exceptions of the body. + member inline TryWith : body : QueueBuffer<'T> * [] handler : (exn -> QueueBuffer<'T>) -> QueueBuffer<'T> + + /// Runs the compensation after the body. + member inline TryFinally : body : QueueBuffer<'T> * [] compensation : (unit -> unit) -> QueueBuffer<'T> + + /// Disposes a resource after the body. + member inline Using : + resource : 'Resource * [] body : ('Resource -> QueueBuffer<'T>) -> QueueBuffer<'T> + when 'Resource :> IDisposable | null + + /// Collects the elements of the expression. + member Run : buffer : QueueBuffer<'T> -> Queue<'T> diff --git a/src/FSharp.Collections.Immutable/Queue/QueueOption.fs b/src/FSharp.Collections.Immutable/Queue/QueueOption.fs new file mode 100644 index 00000000..b5bea515 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Queue/QueueOption.fs @@ -0,0 +1,86 @@ +[] +module FSharp.Collections.Immutable.Option.Queue + +open FSharp.Collections.Immutable + +// The functions delegate to the default module and convert its results, or the results of the callbacks that they pass to +// it, so that both modules have the same checks and exceptions. + +// ---- Creation ---- + +[] +let unfold (generator : 'State -> ('T * 'State) option) (state : 'State) = + let generateStruct state = + match generator state with + | Some (item, next) -> ValueSome (struct (item, next)) + | None -> ValueNone + + FSharp.Collections.Immutable.Queue.unfold generateStruct state + +// ---- Access ---- + +[] +let tryExactlyOne (queue : Queue<'T>) = + FSharp.Collections.Immutable.Queue.tryExactlyOne queue + |> ValueOption.toOption + +[] +let tryHead (queue : Queue<'T>) = + FSharp.Collections.Immutable.Queue.tryHead queue + |> ValueOption.toOption + +[] +let tryLast (queue : Queue<'T>) = + FSharp.Collections.Immutable.Queue.tryLast queue + |> ValueOption.toOption + +[] +let tryTail (queue : Queue<'T>) = + FSharp.Collections.Immutable.Queue.tryTail queue + |> ValueOption.toOption + +[] +let tryUncons (queue : Queue<'T>) = + match FSharp.Collections.Immutable.Queue.tryUncons queue with + | ValueSome (struct (head, tail)) -> Some (head, tail) + | ValueNone -> None + +[] +let (|Cons|_|) (queue : Queue<'T>) = tryUncons queue + +// ---- Search ---- + +[] +let pick (chooser : 'T -> 'U option) (queue : Queue<'T>) = + FSharp.Collections.Immutable.Queue.pick (fun item -> chooser item |> ValueOption.ofOption) queue + +[] +let tryFind predicate (queue : Queue<'T>) = + FSharp.Collections.Immutable.Queue.tryFind predicate queue + |> ValueOption.toOption + +[] +let tryFindBack predicate (queue : Queue<'T>) = + FSharp.Collections.Immutable.Queue.tryFindBack predicate queue + |> ValueOption.toOption + +[] +let tryFindIndex predicate (queue : Queue<'T>) = + FSharp.Collections.Immutable.Queue.tryFindIndex predicate queue + |> ValueOption.toOption + +[] +let tryFindIndexBack predicate (queue : Queue<'T>) = + FSharp.Collections.Immutable.Queue.tryFindIndexBack predicate queue + |> ValueOption.toOption + +[] +let tryPick (chooser : 'T -> 'U option) (queue : Queue<'T>) = + FSharp.Collections.Immutable.Queue.tryPick (fun item -> chooser item |> ValueOption.ofOption) queue + |> ValueOption.toOption + +// ---- Transformation ---- + +[] +let choose (chooser : 'T -> 'U option) (queue : Queue<'T>) = + FSharp.Collections.Immutable.Queue.choose (fun item -> chooser item |> ValueOption.ofOption) queue diff --git a/src/FSharp.Collections.Immutable/Queue/QueueOption.fsi b/src/FSharp.Collections.Immutable/Queue/QueueOption.fsi new file mode 100644 index 00000000..1dfb56c9 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Queue/QueueOption.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.Queue + +open FSharp.Collections.Immutable + +// ---- Creation ---- + +/// +/// Creates a queue 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 queue, whose first element is the first generated one. +/// +/// O(n). +/// +[] +val unfold : generator : ('State -> ('T * 'State) option) -> state : 'State -> Queue<'T> + +// ---- Access ---- + +/// +/// Returns the only element of the queue, or None when it does not have exactly one element. +/// +/// The input queue. +/// The only element, or None. +/// +/// Amortized O(1). +/// +[] +val tryExactlyOne : queue : Queue<'T> -> 'T option + +/// +/// Returns the first element of the queue, or None when it is empty. +/// +/// The input queue. +/// The first element, or None. +/// +/// O(1). +/// +[] +val tryHead : queue : Queue<'T> -> 'T option + +/// +/// Returns the last element of the queue, or None when it is empty. +/// +/// The input queue. +/// The last element, or None. +/// +/// O(n). +/// +[] +val tryLast : queue : Queue<'T> -> 'T option + +/// +/// Returns the queue without its first element, or None when it is empty. +/// +/// The input queue. +/// The elements after the first one, or None. +/// +/// Amortized O(1). +/// +[] +val tryTail : queue : Queue<'T> -> Queue<'T> option + +/// +/// Returns the first element and the other elements, or None when the queue is empty. +/// +/// The input queue. +/// The first element and the elements after it, or None. +/// +/// Amortized O(1). +/// +[] +val tryUncons : queue : Queue<'T> -> ('T * Queue<'T>) option + +/// +/// Matches a queue with a first element, and returns that element and the other elements. +/// +/// The input queue. +/// The first element and the elements after it, or None for an empty queue. +/// +/// Amortized O(1). +/// +[] +val (|Cons|_|) : queue : Queue<'T> -> ('T * Queue<'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 queue. +/// The first chosen value. +/// Thrown when the chooser returns None for every element. +/// +/// O(n). +/// +[] +val pick : chooser : ('T -> 'U option) -> queue : Queue<'T> -> 'U + +/// +/// Returns the first element that satisfies the predicate, or None. +/// +/// The function to test the elements. +/// The input queue. +/// The first element that satisfies the predicate, or None. +/// +/// O(n). +/// +[] +val tryFind : predicate : ('T -> bool) -> queue : Queue<'T> -> 'T option + +/// +/// Returns the last element that satisfies the predicate, or None. +/// +/// The function to test the elements. +/// The input queue. +/// The last element that satisfies the predicate, or None. +/// +/// O(n). +/// +[] +val tryFindBack : predicate : ('T -> bool) -> queue : Queue<'T> -> 'T option + +/// +/// Returns the index of the first element that satisfies the predicate, or None. +/// +/// The function to test the elements. +/// The input queue. +/// The index of the first element that satisfies the predicate, or None. +/// +/// O(n). +/// +[] +val tryFindIndex : predicate : ('T -> bool) -> queue : Queue<'T> -> int option + +/// +/// Returns the index of the last element that satisfies the predicate, or None. +/// +/// The function to test the elements. +/// The input queue. +/// The index of the last element that satisfies the predicate, or None. +/// +/// O(n). +/// +[] +val tryFindIndexBack : predicate : ('T -> bool) -> queue : Queue<'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 queue. +/// The first chosen value, or None. +/// +/// O(n). +/// +[] +val tryPick : chooser : ('T -> 'U option) -> queue : Queue<'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 queue. +/// The chosen values. +/// +/// O(n). +/// +[] +val choose : chooser : ('T -> 'U option) -> queue : Queue<'T> -> Queue<'U> diff --git a/src/FSharp.Collections.Immutable/Queue/QueueTuple.fs b/src/FSharp.Collections.Immutable/Queue/QueueTuple.fs new file mode 100644 index 00000000..9e34ef96 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Queue/QueueTuple.fs @@ -0,0 +1,130 @@ +[] +module FSharp.Collections.Immutable.Tuple.Queue + +open FSharp.Collections.Immutable + +// The functions that build a tuple per element share their algorithm with the default module and only pass another +// tuple constructor; the others delegate to the default module and repack the tuples, which costs no second pass. + +// ---- Creation ---- + +[] +let unfold (generator : 'State -> ('T * 'State) voption) (state : 'State) = + let generateStruct state = + match generator state with + | ValueSome (item, next) -> ValueSome (struct (item, next)) + | ValueNone -> ValueNone + + FSharp.Collections.Immutable.Queue.unfold generateStruct state + +// ---- Access ---- + +[] +let tryUncons (queue : Queue<'T>) = + match FSharp.Collections.Immutable.Queue.tryUncons queue with + | ValueSome (struct (head, tail)) -> ValueSome (head, tail) + | ValueNone -> ValueNone + +[] +let uncons (queue : Queue<'T>) = + let struct (head, tail) = FSharp.Collections.Immutable.Queue.uncons queue + head, tail + +[] +[] +let (|Cons|_|) (queue : Queue<'T>) = tryUncons queue + +// ---- Transformation ---- + +[] +let allPairs (queue1 : Queue<'T1>) (queue2 : Queue<'T2>) = + Arrays.allPairs referencePair (Snapshot.ofSeq (nameof queue1) queue1) (Snapshot.ofSeq (nameof queue2) queue2) + |> Queues.ofFreshArray + +[] +let indexed (queue : Queue<'T>) = + Arrays.indexed referencePair (Snapshot.ofSeq (nameof queue) queue) + |> Queues.ofFreshArray + +[] +let pairwise (queue : Queue<'T>) = + Arrays.pairwise referencePair (Snapshot.ofSeq (nameof queue) queue) + |> Queues.ofFreshArray + +[] +let unzip (queue : Queue<'T1 * 'T2>) = + let struct (first, second) = + Arrays.unzip (fun (item1, item2) -> struct (item1, item2)) (Snapshot.ofSeq (nameof queue) queue) + + Queues.ofFreshArray first, Queues.ofFreshArray second + +[] +let unzip3 (queue : Queue<'T1 * 'T2 * 'T3>) = + let struct (first, second, third) = + Arrays.unzip3 (fun (item1, item2, item3) -> struct (item1, item2, item3)) (Snapshot.ofSeq (nameof queue) queue) + + Queues.ofFreshArray first, Queues.ofFreshArray second, Queues.ofFreshArray third + +[] +let zip (queue1 : Queue<'T1>) (queue2 : Queue<'T2>) = + let array1 = Snapshot.ofSeq (nameof queue1) queue1 + let array2 = Snapshot.ofSeq (nameof queue2) queue2 + ListChecks.sameLengths (nameof queue1) array1.Length (nameof queue2) array2.Length + Arrays.zip referencePair array1 array2 + |> Queues.ofFreshArray + +[] +let zip3 (queue1 : Queue<'T1>) (queue2 : Queue<'T2>) (queue3 : Queue<'T3>) = + let array1 = Snapshot.ofSeq (nameof queue1) queue1 + let array2 = Snapshot.ofSeq (nameof queue2) queue2 + let array3 = Snapshot.ofSeq (nameof queue3) queue3 + ListChecks.zip3 (nameof queue1) array1.Length (nameof queue2) array2.Length (nameof queue3) array3.Length + Arrays.zip3 referenceTriple array1 array2 array3 + |> Queues.ofFreshArray + +// ---- Folding ---- + +[] +let mapFold (mapping : 'State -> 'T -> 'Result * 'State) (state : 'State) (queue : Queue<'T>) = + let mapStruct state item = + let result, next = mapping state item + struct (result, next) + + let struct (results, final) = FSharp.Collections.Immutable.Queue.mapFold mapStruct state queue + results, final + +[] +let mapFoldBack (mapping : 'T -> 'State -> 'Result * 'State) (queue : Queue<'T>) (state : 'State) = + let mapStruct item state = + let result, next = mapping item state + struct (result, next) + + let struct (results, final) = FSharp.Collections.Immutable.Queue.mapFoldBack mapStruct queue state + results, final + +// ---- Partitioning and grouping ---- + +[] +let countBy (projection : 'T -> 'Key) (queue : Queue<'T>) = + Arrays.countBy referencePair projection (Snapshot.ofSeq (nameof queue) queue) + |> Queues.ofFreshArray + +[] +let groupBy (projection : 'T -> 'Key) (queue : Queue<'T>) = + Arrays.groupBy (fun key items -> key, Queues.ofFreshArray items) projection (Snapshot.ofSeq (nameof queue) queue) + |> Queues.ofFreshArray + +[] +let partition predicate (queue : Queue<'T>) = + let struct (accepted, rejected) = FSharp.Collections.Immutable.Queue.partition predicate queue + accepted, rejected + +[] +let partitionWith (partitioner : 'T -> Choice<'T1, 'T2>) (queue : Queue<'T>) = + let struct (first, second) = FSharp.Collections.Immutable.Queue.partitionWith partitioner queue + first, second + +[] +let splitAt index (queue : Queue<'T>) = + let struct (before, after) = FSharp.Collections.Immutable.Queue.splitAt index queue + before, after diff --git a/src/FSharp.Collections.Immutable/Queue/QueueTuple.fsi b/src/FSharp.Collections.Immutable/Queue/QueueTuple.fsi new file mode 100644 index 00000000..7584e52f --- /dev/null +++ b/src/FSharp.Collections.Immutable/Queue/QueueTuple.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.Queue + +open FSharp.Collections.Immutable + +// ---- Creation ---- + +/// +/// Creates a queue 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 queue, whose first element is the first generated one. +/// +/// O(n). +/// +[] +val unfold : generator : ('State -> ('T * 'State) voption) -> state : 'State -> Queue<'T> + +// ---- Access ---- + +/// +/// Returns the first element and the other elements, or ValueNone when the queue is empty. +/// +/// The input queue. +/// The first element and the elements after it, or ValueNone. +/// +/// Amortized O(1). +/// +[] +val tryUncons : queue : Queue<'T> -> ('T * Queue<'T>) voption + +/// Returns the first element and the other elements. +/// The input queue. +/// The first element and the elements after it. +/// Thrown when the queue is empty. +/// Amortized O(1). +[] +val uncons : queue : Queue<'T> -> 'T * Queue<'T> + +/// +/// Matches a queue with a first element, and returns that element and the other elements. +/// +/// The input queue. +/// The first element and the elements after it, or ValueNone for an empty queue. +/// +/// Amortized O(1). A struct partial pattern, so that a match allocates no option. +/// +[] +[] +val (|Cons|_|) : queue : Queue<'T> -> ('T * Queue<'T>) voption + +// ---- Transformation ---- + +/// +/// Returns every element of the first queue paired with every element of the second one. +/// +/// The first queue. +/// The second queue. +/// The pairs, in the order of the first queue and then of the second one. +/// +/// O(n × m). +/// +[] +val allPairs : queue1 : Queue<'T1> -> queue2 : Queue<'T2> -> Queue<'T1 * 'T2> + +/// Returns every element with its index. +/// The input queue. +/// The pairs of index and element. +/// O(n). +[] +val indexed : queue : Queue<'T> -> Queue + +/// Returns every element with its successor. +/// The input queue. +/// The pairs of neighbours; empty for fewer than two elements. +/// O(n). +[] +val pairwise : queue : Queue<'T> -> Queue<'T * 'T> + +/// Splits a queue of pairs into two queues. +/// The input queue. +/// The first components and the second components. +/// O(n). +[] +val unzip : queue : Queue<'T1 * 'T2> -> Queue<'T1> * Queue<'T2> + +/// Splits a queue of triples into three queues. +/// The input queue. +/// The first, the second and the third components. +/// O(n). +[] +val unzip3 : queue : Queue<'T1 * 'T2 * 'T3> -> Queue<'T1> * Queue<'T2> * Queue<'T3> + +/// Combines two queues into a queue of pairs. +/// The first queue. +/// The second queue. +/// The pairs of elements at the same index. +/// Thrown when the queues have different lengths. +/// O(n). +[] +val zip : queue1 : Queue<'T1> -> queue2 : Queue<'T2> -> Queue<'T1 * 'T2> + +/// Combines three queues into a queue of triples. +/// The first queue. +/// The second queue. +/// The third queue. +/// The triples of elements at the same index. +/// Thrown when the queues have different lengths. +/// O(n). +[] +val zip3 : queue1 : Queue<'T1> -> queue2 : Queue<'T2> -> queue3 : Queue<'T3> -> Queue<'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 queue. +/// The results and the final state. +/// +/// O(n). +/// +[] +val mapFold : mapping : ('State -> 'T -> 'Result * 'State) -> state : 'State -> queue : Queue<'T> -> Queue<'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 queue. +/// The initial state. +/// The results, in the order of the elements, and the final state. +/// +/// O(n). +/// +[] +val mapFoldBack : mapping : ('T -> 'State -> 'Result * 'State) -> queue : Queue<'T> -> state : 'State -> Queue<'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 queue. +/// 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) -> queue : Queue<'T> -> Queue<'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 queue. +/// 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) -> queue : Queue<'T> -> Queue<'Key * Queue<'T>> when 'Key : equality + +/// Splits the elements by a predicate. +/// The function to test the elements. +/// The input queue. +/// The elements that satisfy the predicate and the other ones, both in their order. +/// O(n). +[] +val partition : predicate : ('T -> bool) -> queue : Queue<'T> -> Queue<'T> * Queue<'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 queue. +/// 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>) -> queue : Queue<'T> -> Queue<'T1> * Queue<'T2> + +/// +/// Splits the queue at an index. +/// +/// The index of the first element of the second part. +/// The input queue. +/// The elements before the index and the elements from the index. +/// Thrown when the index is negative. +/// Thrown when the index exceeds the length. +/// +/// Amortized O(index). The second part shares its elements with the input. +/// +[] +val splitAt : index : int -> queue : Queue<'T> -> Queue<'T> * Queue<'T> diff --git a/src/FSharp.Collections.Immutable/Queue/QueueTypes.fs b/src/FSharp.Collections.Immutable/Queue/QueueTypes.fs new file mode 100644 index 00000000..71f4a5e9 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Queue/QueueTypes.fs @@ -0,0 +1,24 @@ +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 first-in, first-out queue, . +/// +/// +/// The queue keeps its elements in two immutable stacks: the front one, from which they leave, and the back one, onto +/// which they join. Enqueueing and reading the first element take O(1). Dequeueing takes amortized O(1): when the front +/// runs out, the queue reverses the back once. The queue does not store its length. +/// +type Queue<'T> = ImmutableQueue<'T> + +/// +/// An immutable queue of any implementation, . +/// +/// +/// implements the interface. +/// +type IQueue<'T> = IImmutableQueue<'T> From ba060e72f87b543c69891dc18ba4d14b2754b54e Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 14:07:36 +0200 Subject: [PATCH 2/3] test: contracts of `Queue` and `IQueue` The sequence-like, creation and stable sorting contracts run for `Queue`, and the sequence-like and stable sorting contracts for `IQueue`, over immutable queues seen through the interface. `QueueOps.paramName` maps the names that the `List` exceptions report to the names of the queue modules, also inside the names of several parameters and the `list.[i]` names of `List.transpose`. Further tests cover null inputs, the first-in, first-out order, `enqueue` and `enqueueMany`, `head`, `tail`, `uncons` and `tryUncons`, the `Cons` and `Nil` patterns, queues whose elements lie in both stacks of `ImmutableQueue`, the results that are their inputs, the order in which the functions of two queues call their callbacks before they throw, the complete messages of the `List` exceptions with the names of the queues, the implementation that `IQueue` keeps, `queue { }` and the `Option` and `Tuple` namespaces. Co-Authored-By: Claude Opus 5.5 --- .../FSharp.Collections.Immutable.Tests.fsproj | 10 + .../Queue/IQueueOps.fs | 168 ++++ .../Queue/IQueueOptionTests.fs | 197 ++++ .../Queue/IQueueTests.fs | 738 +++++++++++++++ .../Queue/IQueueTupleTests.fs | 262 ++++++ .../Queue/QueueComputationExpressionTests.fs | 218 +++++ .../Queue/QueueContractTests.fs | 29 + .../Queue/QueueOps.fs | 248 +++++ .../Queue/QueueOptionTests.fs | 217 +++++ .../Queue/QueueTests.fs | 853 ++++++++++++++++++ .../Queue/QueueTupleTests.fs | 285 ++++++ 11 files changed, 3225 insertions(+) create mode 100644 tests/FSharp.Collections.Immutable.Tests/Queue/IQueueOps.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Queue/IQueueOptionTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Queue/IQueueTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Queue/IQueueTupleTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Queue/QueueComputationExpressionTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Queue/QueueContractTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Queue/QueueOps.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Queue/QueueOptionTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Queue/QueueTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Queue/QueueTupleTests.fs diff --git a/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj b/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj index 12eeec14..327f45b9 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/Queue/IQueueOps.fs b/tests/FSharp.Collections.Immutable.Tests/Queue/IQueueOps.fs new file mode 100644 index 00000000..68d3d50a --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Queue/IQueueOps.fs @@ -0,0 +1,168 @@ +/// The functions of the `IQueue` module for the contract tests, over queues seen through the interface. +[] +module FSharp.Collections.Immutable.Tests.IQueueOps + +open FSharp.Collections.Immutable + +/// A queue of the elements, seen through the interface. +let private ofSeq (source : seq<'T>) : IQueue<'T> = Queue.ofSeq source + +let private toArrays (queues : Queue>) = queues |> Queue.toArray |> Array.map Queue.toArray + +let private toArrayPair (struct (first : Queue<'T1>, second : Queue<'T2>)) = struct (Queue.toArray first, Queue.toArray second) + +/// The functions of the sequence-like facet. The functions that change the element type return a queue, which the +/// instance sees through the interface again where the record expects the collection of the module. +let seqLike : SeqOps> = { + Name = nameof IQueue + Mirror = ListMirror + ParamName = QueueOps.paramName + OfSeq = ofSeq + ToArray = IQueue.toArray + + IsEmpty = IQueue.isEmpty + Length = IQueue.length + + ExactlyOne = IQueue.exactlyOne + Head = IQueue.head + Last = IQueue.last + Tail = IQueue.tail + TryExactlyOne = IQueue.tryExactlyOne + TryHead = IQueue.tryHead + TryLast = IQueue.tryLast + TryTail = IQueue.tryTail + + Contains = IQueue.contains + Exists = IQueue.exists + Exists2 = IQueue.exists2 + Find = IQueue.find + FindBack = IQueue.findBack + FindIndex = IQueue.findIndex + FindIndexBack = IQueue.findIndexBack + Forall = IQueue.forall + Forall2 = IQueue.forall2 + Pick = IQueue.pick + TryFind = IQueue.tryFind + TryFindBack = IQueue.tryFindBack + TryFindIndex = IQueue.tryFindIndex + TryFindIndexBack = IQueue.tryFindIndexBack + TryPick = IQueue.tryPick + + AllPairs = fun queue1 queue2 -> IQueue.allPairs queue1 queue2 |> Queue.toArray + Append = IQueue.append + Choose = fun chooser queue -> IQueue.choose chooser queue + ChunkBySize = fun chunkSize queue -> IQueue.chunkBySize chunkSize queue |> toArrays + Collect = fun mapping queue -> IQueue.collect mapping queue + Concat = fun queues -> IQueue.concat queues + Filter = IQueue.filter + Indexed = IQueue.indexed >> Queue.toArray + Map = fun mapping queue -> IQueue.map mapping queue + Map2 = fun mapping queue1 queue2 -> IQueue.map2 mapping queue1 queue2 + Map3 = fun mapping queue1 queue2 queue3 -> IQueue.map3 mapping queue1 queue2 queue3 + Mapi = fun mapping queue -> IQueue.mapi mapping queue + Mapi2 = fun mapping queue1 queue2 -> IQueue.mapi2 mapping queue1 queue2 + Pairwise = IQueue.pairwise >> Queue.toArray + Permute = IQueue.permute + Rev = IQueue.rev + Transpose = fun queues -> IQueue.transpose queues |> toArrays + Unzip = fun pairs -> IQueue.unzip (ofSeq pairs) |> toArrayPair + Unzip3 = + fun triples -> + let struct (first, second, third) = IQueue.unzip3 (ofSeq triples) + struct (Queue.toArray first, Queue.toArray second, Queue.toArray third) + Where = IQueue.where + Windowed = fun windowSize queue -> IQueue.windowed windowSize queue |> toArrays + Zip = fun queue1 queue2 -> IQueue.zip queue1 queue2 |> Queue.toArray + Zip3 = fun queue1 queue2 queue3 -> IQueue.zip3 queue1 queue2 queue3 |> Queue.toArray + + Average = fun items -> IQueue.average (ofSeq items) + AverageBy = IQueue.averageBy + CompareWith = IQueue.compareWith + Fold = IQueue.fold + Fold2 = IQueue.fold2 + FoldBack = IQueue.foldBack + FoldBack2 = IQueue.foldBack2 + Iter = IQueue.iter + Iter2 = IQueue.iter2 + Iteri = IQueue.iteri + Iteri2 = IQueue.iteri2 + MapFold = + fun mapping state queue -> + let struct (results, final) = IQueue.mapFold mapping state queue + struct (Queue.toArray results, final) + MapFoldBack = + fun mapping queue state -> + let struct (results, final) = IQueue.mapFoldBack mapping queue state + struct (Queue.toArray results, final) + Max = IQueue.max + MaxBy = IQueue.maxBy + Min = IQueue.min + MinBy = IQueue.minBy + Reduce = IQueue.reduce + ReduceBack = IQueue.reduceBack + Scan = fun folder state queue -> IQueue.scan folder state queue |> Queue.toArray + ScanBack = fun folder queue state -> IQueue.scanBack folder queue state |> Queue.toArray + Sum = IQueue.sum + SumBy = IQueue.sumBy + + CountBy = fun projection queue -> IQueue.countBy projection queue |> Queue.toArray + Distinct = IQueue.distinct + DistinctBy = IQueue.distinctBy + Except = IQueue.except + GroupBy = + fun projection queue -> + IQueue.groupBy projection queue + |> Queue.toArray + |> Array.map (fun struct (key, items) -> struct (key, Queue.toArray items)) + Partition = IQueue.partition + PartitionWith = fun partitioner queue -> IQueue.partitionWith partitioner queue |> toArrayPair + Skip = IQueue.skip + SkipLast = IQueue.skipLast + SkipWhile = IQueue.skipWhile + SplitAt = IQueue.splitAt + SplitInto = fun count queue -> IQueue.splitInto count queue |> toArrays + Take = IQueue.take + TakeLast = IQueue.takeLast + TakeWhile = IQueue.takeWhile + Truncate = IQueue.truncate + + Sort = IQueue.sort + SortBy = IQueue.sortBy + SortByDescending = IQueue.sortByDescending + SortDescending = IQueue.sortDescending + SortWith = IQueue.sortWith + SortWithComparer = IQueue.sortWithComparer + + RandomChoice = IQueue.randomChoice + RandomChoiceBy = IQueue.randomChoiceBy + RandomChoiceWith = IQueue.randomChoiceWith + RandomChoices = IQueue.randomChoices + RandomChoicesBy = IQueue.randomChoicesBy + RandomChoicesWith = IQueue.randomChoicesWith + RandomSample = IQueue.randomSample + RandomSampleBy = IQueue.randomSampleBy + RandomSampleWith = IQueue.randomSampleWith + RandomShuffle = IQueue.randomShuffle + RandomShuffleBy = IQueue.randomShuffleBy + RandomShuffleWith = IQueue.randomShuffleWith + + ToList = IQueue.toList + ToSeq = IQueue.toSeq +} + +/// The functions of the stable sorting facet. +let stableSort : StableSortOps> = { + Name = nameof IQueue + OfSeq = ofSeq + ToArray = IQueue.toArray + + Sort = fun items -> ofSeq items |> IQueue.sort |> IQueue.toArray + SortBy = IQueue.sortBy + SortByDescending = IQueue.sortByDescending + SortDescending = fun items -> ofSeq items |> IQueue.sortDescending |> IQueue.toArray + SortWith = IQueue.sortWith + SortWithComparer = IQueue.sortWithComparer + + SortIntegersDescending = fun items -> ofSeq items |> IQueue.sortDescending |> IQueue.toArray + SortIntegersByDescending = fun items -> ofSeq items |> IQueue.sortByDescending id |> IQueue.toArray +} diff --git a/tests/FSharp.Collections.Immutable.Tests/Queue/IQueueOptionTests.fs b/tests/FSharp.Collections.Immutable.Tests/Queue/IQueueOptionTests.fs new file mode 100644 index 00000000..ae44cbf0 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Queue/IQueueOptionTests.fs @@ -0,0 +1,197 @@ +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 `IQueue` module of the `Option` namespace. +[] +type IQueueOptionTests () = + + static let several : IQueue = Queue.ofArray Inputs.Several + + static let empty : IQueue = Queue.empty + + static let negatedIfNegative (item : int) = if item < 0 then Some (-item) else None + + [] + member _.``tryExactlyOne returns an option`` () = + let single : IQueue = Queue.singleton 5 + let found : int option = IQueue.tryExactlyOne single + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IQueue.tryExactlyOne single, + found, + "Option.IQueue.tryExactlyOne must match the default after conversion" + ) + + [] + member _.``tryHead returns an option`` () = + let found : int option = IQueue.tryHead several + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IQueue.tryHead several, + found, + "Option.IQueue.tryHead must match the default" + ) + + [] + member _.``tryLast returns an option`` () = + let found : int option = IQueue.tryLast several + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IQueue.tryLast several, + found, + "Option.IQueue.tryLast must match the default" + ) + + [] + member _.``tryTail returns an option`` () = + let found : IQueue option = IQueue.tryTail several + + // The equality of an option compares the elements of an array that it holds + Assert.AreEqual( + FSharp.Collections.Immutable.IQueue.tryTail several + |> ValueOption.map IQueue.toArray + |> ValueOption.toOption, + found |> Option.map IQueue.toArray, + "Option.IQueue.tryTail must match the default" + ) + + [] + member _.``tryUncons returns an option of a reference tuple`` () = + let found : (int * IQueue) option = IQueue.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.IQueue.tryUncons must return the first element") + + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + IQueue.toArray tail, + "Option.IQueue.tryUncons must return the other elements" + ) + | None -> Assert.Fail "Option.IQueue.tryUncons must return the elements of a queue with elements" + + Assert.IsTrue ((IQueue.tryUncons empty).IsNone, "Option.IQueue.tryUncons must return None for an empty queue") + + [] + member _.``Cons is a partial pattern of options`` () = + let pattern : IQueue -> (int * IQueue) option = IQueue.(|Cons|_|) + + match several with + | IQueue.Cons (head, tail) -> + Assert.AreEqual(Array.head Inputs.Several, head, "Option.IQueue.Cons must match the first element") + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + IQueue.toArray tail, + "Option.IQueue.Cons must match the other elements" + ) + | _ -> Assert.Fail "Option.IQueue.Cons must match a queue with elements" + + Assert.IsTrue ((pattern empty).IsNone, "Option.IQueue.Cons must not match an empty queue") + + [] + member _.``pick takes a chooser of options`` () = + let picked : int = IQueue.pick negatedIfNegative several + + Assert.AreEqual( + Array.pick negatedIfNegative Inputs.Several, + picked, + "Option.IQueue.pick must pick as Array.pick does" + ) + + [] + member _.``tryFind returns an option`` () = + let found : int option = IQueue.tryFind (fun item -> item > 6) several + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IQueue.tryFind (fun item -> item > 6) several, + found, + "Option.IQueue.tryFind must match the default" + ) + + [] + member _.``tryFindBack returns an option`` () = + let found : int option = IQueue.tryFindBack (fun item -> item > 6) several + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IQueue.tryFindBack (fun item -> item > 6) several, + found, + "Option.IQueue.tryFindBack must match the default" + ) + + [] + member _.``tryFindIndex returns an option`` () = + let found : int option = IQueue.tryFindIndex (fun item -> item > 6) several + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IQueue.tryFindIndex (fun item -> item > 6) several, + found, + "Option.IQueue.tryFindIndex must match the default" + ) + + [] + member _.``tryFindIndexBack returns an option`` () = + let found : int option = IQueue.tryFindIndexBack (fun item -> item > 6) several + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.IQueue.tryFindIndexBack (fun item -> item > 6) several, + found, + "Option.IQueue.tryFindIndexBack must match the default" + ) + + [] + member _.``tryPick takes a chooser of options and returns an option`` () = + let found : int option = IQueue.tryPick negatedIfNegative several + + Assert.AreEqual( + Array.tryPick negatedIfNegative Inputs.Several, + found, + "Option.IQueue.tryPick must pick as Array.tryPick does" + ) + + [] + member _.``choose takes a chooser of options`` () = + let chosen : Queue = IQueue.choose negatedIfNegative several + + CollectionAssert.AreEqual ( + Array.choose negatedIfNegative Inputs.Several, + Queue.toArray chosen, + "Option.IQueue.choose must choose as Array.choose does" + ) + + [] + member _.``the functions without an option keep resolving to the default module`` () = + let filtered : IQueue = IQueue.filter (fun item -> item > 0) several + let zipped : Queue = IQueue.zip several several + + CollectionAssert.AreEqual ( + Array.filter (fun item -> item > 0) Inputs.Several, + IQueue.toArray filtered, + "IQueue.filter must resolve to the default module" + ) + + CollectionAssert.AreEqual ( + Array.zip Inputs.Several Inputs.Several, + Queue.toArray zipped, + "IQueue.zip must resolve to the Tuple module" + ) + + [] + [] + [] + [] + member _.``the queue functions compile with the names of the queue operations of the BCL`` + (name : string, compiledName : string) + = + Assert.AreEqual( + name, + QueueOps.sourceName "FSharp.Collections.Immutable.Option.IQueue" compiledName, + "Option.IQueue must compile the function under the name of its queue operation in the BCL" + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Queue/IQueueTests.fs b/tests/FSharp.Collections.Immutable.Tests/Queue/IQueueTests.fs new file mode 100644 index 00000000..93c03d14 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Queue/IQueueTests.fs @@ -0,0 +1,738 @@ +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 queue, to +/// tell whether a function keeps the implementation of its input. +/// +/// +/// Every member that returns a queue returns a tagged queue. +/// +type TaggedQueue<'T> (items : ImmutableQueue<'T>) = + + static let wrap (items : ImmutableQueue<'T>) = TaggedQueue items :> IImmutableQueue<'T> + + interface IImmutableQueue<'T> with + member _.Clear () = wrap (items.Clear ()) + member _.Dequeue () = wrap (items.Dequeue ()) + member _.Enqueue value = wrap (items.Enqueue value) + member _.IsEmpty = items.IsEmpty + member _.Peek () = items.Peek () + + interface IEnumerable<'T> with + member _.GetEnumerator () = (items :> IEnumerable<'T>).GetEnumerator() + + interface IEnumerable with + member _.GetEnumerator () = (items :> IEnumerable).GetEnumerator() + +/// +/// An implementation of that is not an immutable queue and +/// counts what its enumerators do, to tell how far a function reads a queue of another implementation and whether it +/// disposes the enumerator. +/// +/// +/// The members that return a queue return an immutable queue. +/// +type CountingQueue<'T> (items : ImmutableQueue<'T>) = + + let mutable enumerators = 0 + let mutable disposals = 0 + let mutable elementsRead = 0 + + /// The number of enumerators that the queue gave out. + member _.Enumerators = enumerators + + /// The number of calls of Dispose on the enumerators of the queue. + member _.Disposals = disposals + + /// The number of elements that the enumerators of the queue read. + member _.ElementsRead = elementsRead + + interface IImmutableQueue<'T> with + member _.Clear () = items.Clear () :> _ + member _.Dequeue () = items.Dequeue () :> _ + member _.Enqueue value = items.Enqueue value :> _ + member _.IsEmpty = items.IsEmpty + member _.Peek () = items.Peek () + + interface IEnumerable<'T> with + member _.GetEnumerator () = + enumerators <- enumerators + 1 + let inner = (items :> IEnumerable<'T>).GetEnumerator() + + { + new IEnumerator<'T> with + member _.Current = inner.Current + interface IEnumerator with + member _.Current = box inner.Current + + member _.MoveNext () = + let moved = inner.MoveNext () + + if moved then + elementsRead <- elementsRead + 1 + + moved + + member _.Reset () = inner.Reset () + interface IDisposable with + member _.Dispose () = + disposals <- disposals + 1 + inner.Dispose () + } + + interface IEnumerable with + member this.GetEnumerator () = (this :> IEnumerable<'T>).GetEnumerator() :> IEnumerator + +/// The behaviour of `IQueue` that the shared contracts do not cover: the implementation of the results, null inputs, the +/// patterns, the reading of queues of other implementations, the order of strings and the compiled names. +[] +type IQueueTests () = + + static let nullQueue = Unchecked.defaultof> + + static let several () : IQueue = Queue.ofArray Inputs.Several + + static let tagged () : IQueue = TaggedQueue (ImmutableQueue.CreateRange 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 : IQueue, _ : IQueue)) = first + + static let second (struct (_ : IQueue, second : IQueue)) = 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 -> IQueue> = + Map.ofArray [| + nameof IQueue.tail, IQueue.tail + nameof IQueue.tryTail, (fun queue -> (IQueue.tryTail queue).Value) + (nameof IQueue.uncons, + fun queue -> + let struct (_, tail) = IQueue.uncons queue + tail) + nameof IQueue.append, (fun queue -> IQueue.append queue (Queue.ofArray [| 1; 2 |])) + ($"%s{nameof IQueue.append} to an empty queue", + fun queue -> IQueue.append (IQueue.clear queue) (Queue.ofArray [| 1; 2 |])) + nameof IQueue.enqueue, IQueue.enqueue 99 + nameof IQueue.enqueueMany, IQueue.enqueueMany [| 98; 99 |] + nameof IQueue.filter, IQueue.filter (fun item -> item > 0) + nameof IQueue.permute, IQueue.permute (fun index -> (index + 3) % 8) + nameof IQueue.rev, IQueue.rev + nameof IQueue.where, IQueue.where (fun item -> item > 0) + nameof IQueue.distinct, IQueue.distinct + nameof IQueue.distinctBy, IQueue.distinctBy abs + nameof IQueue.except, IQueue.except [| 5 |] + $"%s{nameof IQueue.partition} (accepted)", IQueue.partition (fun item -> item > 0) >> first + $"%s{nameof IQueue.partition} (rejected)", IQueue.partition (fun item -> item > 0) >> second + nameof IQueue.skip, IQueue.skip 3 + nameof IQueue.skipLast, IQueue.skipLast 3 + nameof IQueue.skipWhile, IQueue.skipWhile (fun item -> item > 0) + $"%s{nameof IQueue.splitAt} (first part)", IQueue.splitAt 3 >> first + $"%s{nameof IQueue.splitAt} (second part)", IQueue.splitAt 3 >> second + nameof IQueue.take, IQueue.take 3 + nameof IQueue.takeLast, IQueue.takeLast 3 + nameof IQueue.takeWhile, IQueue.takeWhile (fun item -> item > 0) + nameof IQueue.truncate, IQueue.truncate 3 + nameof IQueue.sort, IQueue.sort + nameof IQueue.sortBy, IQueue.sortBy abs + nameof IQueue.sortByDescending, IQueue.sortByDescending abs + nameof IQueue.sortDescending, IQueue.sortDescending + nameof IQueue.sortWith, IQueue.sortWith compare + nameof IQueue.sortWithComparer, IQueue.sortWithComparer Comparer.Default + nameof IQueue.randomChoicesWith, (fun queue -> IQueue.randomChoicesWith (random ()) 3 queue) + nameof IQueue.randomChoicesBy, (fun queue -> IQueue.randomChoicesBy (randomizer ()) 3 queue) + nameof IQueue.randomSampleWith, (fun queue -> IQueue.randomSampleWith (random ()) 3 queue) + nameof IQueue.randomSampleBy, (fun queue -> IQueue.randomSampleBy (randomizer ()) 3 queue) + nameof IQueue.randomShuffleWith, (fun queue -> IQueue.randomShuffleWith (random ()) queue) + nameof IQueue.randomShuffleBy, (fun queue -> IQueue.randomShuffleBy (randomizer ()) queue) + nameof IQueue.clear, IQueue.clear + |] + + /// The functions that take a queue, by name, with the parameter that names the null queue and the function on a null + /// queue. + static let nullQueueRejections = + let pairs = Unchecked.defaultof> + let triples = Unchecked.defaultof> + let floats = Unchecked.defaultof> + + [| + struct (nameof IQueue.isEmpty, "queue", (fun () -> IQueue.isEmpty nullQueue |> ignore)) + struct (nameof IQueue.length, "queue", (fun () -> IQueue.length nullQueue |> ignore)) + struct (nameof IQueue.exactlyOne, "queue", (fun () -> IQueue.exactlyOne nullQueue |> ignore)) + struct (nameof IQueue.head, "queue", (fun () -> IQueue.head nullQueue |> ignore)) + struct (nameof IQueue.last, "queue", (fun () -> IQueue.last nullQueue |> ignore)) + struct (nameof IQueue.tail, "queue", (fun () -> IQueue.tail nullQueue |> ignore)) + struct (nameof IQueue.tryExactlyOne, "queue", (fun () -> IQueue.tryExactlyOne nullQueue |> ignore)) + struct (nameof IQueue.tryHead, "queue", (fun () -> IQueue.tryHead nullQueue |> ignore)) + struct (nameof IQueue.tryLast, "queue", (fun () -> IQueue.tryLast nullQueue |> ignore)) + struct (nameof IQueue.tryTail, "queue", (fun () -> IQueue.tryTail nullQueue |> ignore)) + struct (nameof IQueue.tryUncons, "queue", (fun () -> IQueue.tryUncons nullQueue |> ignore)) + struct (nameof IQueue.uncons, "queue", (fun () -> IQueue.uncons nullQueue |> ignore)) + struct (nameof IQueue.(|Cons|_|), "queue", (fun () -> IQueue.(|Cons|_|) nullQueue |> ignore)) + struct (nameof IQueue.(|Nil|_|), "queue", (fun () -> IQueue.(|Nil|_|) nullQueue |> ignore)) + struct (nameof IQueue.contains, "source", (fun () -> IQueue.contains 1 nullQueue |> ignore)) + struct (nameof IQueue.exists, "queue", (fun () -> IQueue.exists ((=) 1) nullQueue |> ignore)) + struct (nameof IQueue.exists2, "queue1", (fun () -> IQueue.exists2 (=) nullQueue (several ()) |> ignore)) + struct (nameof IQueue.find, "queue", (fun () -> IQueue.find ((=) 1) nullQueue |> ignore)) + struct (nameof IQueue.findBack, "queue", (fun () -> IQueue.findBack ((=) 1) nullQueue |> ignore)) + struct (nameof IQueue.findIndex, "queue", (fun () -> IQueue.findIndex ((=) 1) nullQueue |> ignore)) + struct (nameof IQueue.findIndexBack, "queue", (fun () -> IQueue.findIndexBack ((=) 1) nullQueue |> ignore)) + struct (nameof IQueue.forall, "queue", (fun () -> IQueue.forall ((=) 1) nullQueue |> ignore)) + struct (nameof IQueue.forall2, "queue2", (fun () -> IQueue.forall2 (=) (several ()) nullQueue |> ignore)) + struct (nameof IQueue.pick, "queue", (fun () -> IQueue.pick ValueSome nullQueue |> ignore)) + struct (nameof IQueue.tryFind, "queue", (fun () -> IQueue.tryFind ((=) 1) nullQueue |> ignore)) + struct (nameof IQueue.tryFindBack, "queue", (fun () -> IQueue.tryFindBack ((=) 1) nullQueue |> ignore)) + struct (nameof IQueue.tryFindIndex, "queue", (fun () -> IQueue.tryFindIndex ((=) 1) nullQueue |> ignore)) + struct (nameof IQueue.tryFindIndexBack, "queue", (fun () -> IQueue.tryFindIndexBack ((=) 1) nullQueue |> ignore)) + struct (nameof IQueue.tryPick, "queue", (fun () -> IQueue.tryPick ValueSome nullQueue |> ignore)) + struct (nameof IQueue.allPairs, "queue2", (fun () -> IQueue.allPairs (several ()) nullQueue |> ignore)) + struct (nameof IQueue.append, "queue1", (fun () -> IQueue.append nullQueue (several ()) |> ignore)) + struct (nameof IQueue.choose, "queue", (fun () -> IQueue.choose ValueSome nullQueue |> ignore)) + struct (nameof IQueue.chunkBySize, "queue", (fun () -> IQueue.chunkBySize 2 nullQueue |> ignore)) + struct (nameof IQueue.collect, "queue", (fun () -> IQueue.collect (fun _ -> (several ())) nullQueue |> ignore)) + struct (nameof IQueue.concat, "queues", (fun () -> IQueue.concat [| (several ()); nullQueue |] |> ignore)) + struct (nameof IQueue.enqueue, "queue", (fun () -> IQueue.enqueue 1 nullQueue |> ignore)) + struct (nameof IQueue.enqueueMany, "queue", (fun () -> IQueue.enqueueMany [| 1 |] nullQueue |> ignore)) + struct (nameof IQueue.filter, "queue", (fun () -> IQueue.filter ((=) 1) nullQueue |> ignore)) + struct (nameof IQueue.indexed, "queue", (fun () -> IQueue.indexed nullQueue |> ignore)) + struct (nameof IQueue.map, "queue", (fun () -> IQueue.map id nullQueue |> ignore)) + struct (nameof IQueue.map2, "queue1", (fun () -> IQueue.map2 (+) nullQueue (several ()) |> ignore)) + struct (nameof IQueue.map3, + "queue2", + (fun () -> + IQueue.map3 (fun a b c -> a + b + c) (several ()) nullQueue (several ()) + |> ignore + )) + struct (nameof IQueue.mapi, "queue", (fun () -> IQueue.mapi (+) nullQueue |> ignore)) + struct (nameof IQueue.mapi2, + "queue2", + (fun () -> + IQueue.mapi2 (fun index a b -> index + a + b) (several ()) nullQueue + |> ignore + )) + struct (nameof IQueue.pairwise, "queue", (fun () -> IQueue.pairwise nullQueue |> ignore)) + struct (nameof IQueue.permute, "queue", (fun () -> IQueue.permute id nullQueue |> ignore)) + struct (nameof IQueue.rev, "queue", (fun () -> IQueue.rev nullQueue |> ignore)) + struct (nameof IQueue.transpose, "queues", (fun () -> IQueue.transpose [| nullQueue |] |> ignore)) + struct (nameof IQueue.unzip, "queue", (fun () -> IQueue.unzip pairs |> ignore)) + struct (nameof IQueue.unzip3, "queue", (fun () -> IQueue.unzip3 triples |> ignore)) + struct (nameof IQueue.where, "queue", (fun () -> IQueue.where ((=) 1) nullQueue |> ignore)) + struct (nameof IQueue.windowed, "queue", (fun () -> IQueue.windowed 2 nullQueue |> ignore)) + struct (nameof IQueue.zip, "queue2", (fun () -> IQueue.zip (several ()) nullQueue |> ignore)) + struct (nameof IQueue.zip3, "queue3", (fun () -> IQueue.zip3 (several ()) (several ()) nullQueue |> ignore)) + struct (nameof IQueue.average, "queue", (fun () -> IQueue.average floats |> ignore)) + struct (nameof IQueue.averageBy, "queue", (fun () -> IQueue.averageBy float nullQueue |> ignore)) + struct (nameof IQueue.compareWith, "queue1", (fun () -> IQueue.compareWith compare nullQueue (several ()) |> ignore)) + struct (nameof IQueue.fold, "queue", (fun () -> IQueue.fold (+) 0 nullQueue |> ignore)) + struct (nameof IQueue.fold2, + "queue1", + (fun () -> + IQueue.fold2 (fun state a b -> state + a + b) 0 nullQueue (several ()) + |> ignore + )) + struct (nameof IQueue.foldBack, "queue", (fun () -> IQueue.foldBack (+) nullQueue 0 |> ignore)) + struct (nameof IQueue.foldBack2, + "queue2", + (fun () -> + IQueue.foldBack2 (fun a b state -> state + a + b) (several ()) nullQueue 0 + |> ignore + )) + struct (nameof IQueue.iter, "queue", (fun () -> IQueue.iter ignore nullQueue)) + struct (nameof IQueue.iter2, "queue1", (fun () -> IQueue.iter2 (fun _ _ -> ()) nullQueue (several ()))) + struct (nameof IQueue.iteri, "queue", (fun () -> IQueue.iteri (fun _ _ -> ()) nullQueue)) + struct (nameof IQueue.iteri2, "queue2", (fun () -> IQueue.iteri2 (fun _ _ _ -> ()) (several ()) nullQueue)) + struct (nameof IQueue.mapFold, + "queue", + (fun () -> + IQueue.mapFold (fun state item -> struct (item, state)) 0 nullQueue + |> ignore + )) + struct (nameof IQueue.mapFoldBack, + "queue", + (fun () -> + IQueue.mapFoldBack (fun item state -> struct (item, state)) nullQueue 0 + |> ignore + )) + struct (nameof IQueue.max, "queue", (fun () -> IQueue.max nullQueue |> ignore)) + struct (nameof IQueue.maxBy, "queue", (fun () -> IQueue.maxBy id nullQueue |> ignore)) + struct (nameof IQueue.min, "queue", (fun () -> IQueue.min nullQueue |> ignore)) + struct (nameof IQueue.minBy, "queue", (fun () -> IQueue.minBy id nullQueue |> ignore)) + struct (nameof IQueue.reduce, "queue", (fun () -> IQueue.reduce (+) nullQueue |> ignore)) + struct (nameof IQueue.reduceBack, "queue", (fun () -> IQueue.reduceBack (+) nullQueue |> ignore)) + struct (nameof IQueue.scan, "queue", (fun () -> IQueue.scan (+) 0 nullQueue |> ignore)) + struct (nameof IQueue.scanBack, "queue", (fun () -> IQueue.scanBack (+) nullQueue 0 |> ignore)) + struct (nameof IQueue.sum, "queue", (fun () -> IQueue.sum nullQueue |> ignore)) + struct (nameof IQueue.sumBy, "queue", (fun () -> IQueue.sumBy id nullQueue |> ignore)) + struct (nameof IQueue.countBy, "queue", (fun () -> IQueue.countBy id nullQueue |> ignore)) + struct (nameof IQueue.distinct, "queue", (fun () -> IQueue.distinct nullQueue |> ignore)) + struct (nameof IQueue.distinctBy, "queue", (fun () -> IQueue.distinctBy id nullQueue |> ignore)) + struct (nameof IQueue.except, "queue", (fun () -> IQueue.except [| 1 |] nullQueue |> ignore)) + struct (nameof IQueue.groupBy, "queue", (fun () -> IQueue.groupBy id nullQueue |> ignore)) + struct (nameof IQueue.partition, "queue", (fun () -> IQueue.partition ((=) 1) nullQueue |> ignore)) + struct (nameof IQueue.partitionWith, "queue", (fun () -> IQueue.partitionWith Choice1Of2 nullQueue |> ignore)) + struct (nameof IQueue.skip, "queue", (fun () -> IQueue.skip 0 nullQueue |> ignore)) + struct (nameof IQueue.skipLast, "queue", (fun () -> IQueue.skipLast 0 nullQueue |> ignore)) + struct (nameof IQueue.skipWhile, "queue", (fun () -> IQueue.skipWhile ((=) 1) nullQueue |> ignore)) + struct (nameof IQueue.splitAt, "queue", (fun () -> IQueue.splitAt 0 nullQueue |> ignore)) + struct (nameof IQueue.splitInto, "queue", (fun () -> IQueue.splitInto 2 nullQueue |> ignore)) + struct (nameof IQueue.take, "queue", (fun () -> IQueue.take 0 nullQueue |> ignore)) + struct (nameof IQueue.takeLast, "queue", (fun () -> IQueue.takeLast 0 nullQueue |> ignore)) + struct (nameof IQueue.takeWhile, "queue", (fun () -> IQueue.takeWhile ((=) 1) nullQueue |> ignore)) + struct (nameof IQueue.truncate, "queue", (fun () -> IQueue.truncate 1 nullQueue |> ignore)) + struct (nameof IQueue.sort, "queue", (fun () -> IQueue.sort nullQueue |> ignore)) + struct (nameof IQueue.sortBy, "queue", (fun () -> IQueue.sortBy id nullQueue |> ignore)) + struct (nameof IQueue.sortByDescending, "queue", (fun () -> IQueue.sortByDescending id nullQueue |> ignore)) + struct (nameof IQueue.sortDescending, "queue", (fun () -> IQueue.sortDescending nullQueue |> ignore)) + struct (nameof IQueue.sortWith, "queue", (fun () -> IQueue.sortWith compare nullQueue |> ignore)) + struct (nameof IQueue.sortWithComparer, + "queue", + (fun () -> + IQueue.sortWithComparer Comparer.Default nullQueue + |> ignore + )) + struct (nameof IQueue.randomChoice, "source", (fun () -> IQueue.randomChoice nullQueue |> ignore)) + struct (nameof IQueue.randomChoiceBy, "source", (fun () -> IQueue.randomChoiceBy (fun () -> 0.5) nullQueue |> ignore)) + struct (nameof IQueue.randomChoiceWith, "source", (fun () -> IQueue.randomChoiceWith (Random 1) nullQueue |> ignore)) + struct (nameof IQueue.randomChoices, "source", (fun () -> IQueue.randomChoices 1 nullQueue |> ignore)) + struct (nameof IQueue.randomChoicesBy, + "source", + (fun () -> IQueue.randomChoicesBy (fun () -> 0.5) 1 nullQueue |> ignore)) + struct (nameof IQueue.randomChoicesWith, + "source", + (fun () -> IQueue.randomChoicesWith (Random 1) 1 nullQueue |> ignore)) + struct (nameof IQueue.randomSample, "source", (fun () -> IQueue.randomSample 1 nullQueue |> ignore)) + struct (nameof IQueue.randomSampleBy, + "source", + (fun () -> IQueue.randomSampleBy (fun () -> 0.5) 1 nullQueue |> ignore)) + struct (nameof IQueue.randomSampleWith, "source", (fun () -> IQueue.randomSampleWith (Random 1) 1 nullQueue |> ignore)) + struct (nameof IQueue.randomShuffle, "source", (fun () -> IQueue.randomShuffle nullQueue |> ignore)) + struct (nameof IQueue.randomShuffleBy, + "source", + (fun () -> IQueue.randomShuffleBy (fun () -> 0.5) nullQueue |> ignore)) + struct (nameof IQueue.randomShuffleWith, "source", (fun () -> IQueue.randomShuffleWith (Random 1) nullQueue |> ignore)) + struct (nameof IQueue.toArray, "queue", (fun () -> IQueue.toArray nullQueue |> ignore)) + struct (nameof IQueue.toList, "queue", (fun () -> IQueue.toList nullQueue |> ignore)) + struct (nameof IQueue.toSeq, "queue", (fun () -> IQueue.toSeq nullQueue |> ignore)) + struct (nameof IQueue.clear, "queue", (fun () -> IQueue.clear nullQueue |> ignore)) + |] + |> Array.map (fun struct (name, paramName, action) -> name, struct (paramName, action)) + |> Map.ofArray + + /// The functions that take a sequence, by name and input, with the parameter that names the sequence and the function on + /// the input. The inputs are a default flat list, which converts to a sequence without a check and fails with + /// InvalidOperationException when it is enumerated, a default flat list of immutable queues as a sequence of the + /// interface, and a default flat list of strings as a sequence of objects: seq is covariant, so a type test for the flat + /// list of the element type of the function does not see the last two. The rows of the test are the names. + static let defaultSequenceRejections = + let defaultList = Inputs.DefaultFlatListAsSeq() + let defaultQueues = Inputs.DefaultFlatListAsSeq>() + + let widenedQueues = + match box (Unchecked.defaultof>>) with + | :? seq> as queues -> queues + | _ -> invalidOp "A flat list of immutable queues must be a sequence of the interface" + + let widened = FlatListOracles.defaultStringsAsObjects + + [| + struct (nameof IQueue.concat, "default flat list", "queues", (fun () -> IQueue.concat defaultQueues |> ignore)) + struct (nameof IQueue.concat, + "default flat list of immutable queues", + "queues", + (fun () -> IQueue.concat widenedQueues |> ignore)) + struct (nameof IQueue.transpose, "default flat list", "queues", (fun () -> IQueue.transpose defaultQueues |> ignore)) + struct (nameof IQueue.transpose, + "default flat list of immutable queues", + "queues", + (fun () -> IQueue.transpose widenedQueues |> ignore)) + struct (nameof IQueue.enqueueMany, + "default flat list", + "values", + (fun () -> IQueue.enqueueMany defaultList (several ()) |> ignore)) + struct (nameof IQueue.enqueueMany, + "default flat list of strings as objects", + "values", + (fun () -> + IQueue.enqueueMany widened (Queue.empty :> IQueue) + |> ignore + )) + struct (nameof IQueue.except, + "default flat list", + "itemsToExclude", + (fun () -> IQueue.except defaultList (several ()) |> ignore)) + struct (nameof IQueue.except, + "default flat list and an empty queue", + "itemsToExclude", + (fun () -> + IQueue.except defaultList (Queue.empty :> IQueue) + |> ignore + )) + struct (nameof IQueue.except, + "default flat list of strings as objects", + "itemsToExclude", + (fun () -> + IQueue.except widened (Queue.empty :> IQueue) + |> ignore + )) + |] + |> Array.map (fun struct (name, input, paramName, action) -> $"%s{name} of a %s{input}", struct (paramName, action)) + |> Map.ofArray + + /// The functions that keep every element of a queue for some arguments, by name, on such arguments. The names tell + /// apart the two parts of splitAt. + static let wholeQueues : Map -> IQueue> = + Map.ofArray [| + nameof IQueue.take, IQueue.take 8 + nameof IQueue.takeLast, IQueue.takeLast 8 + nameof IQueue.takeWhile, IQueue.takeWhile (fun _ -> true) + nameof IQueue.truncate, IQueue.truncate 100 + nameof IQueue.skip, IQueue.skip 0 + nameof IQueue.skipLast, IQueue.skipLast 0 + nameof IQueue.skipWhile, IQueue.skipWhile (fun _ -> false) + ($"%s{nameof IQueue.splitAt} (first part)", + fun queue -> + let struct (whole, _) = IQueue.splitAt 8 queue + whole) + ($"%s{nameof IQueue.splitAt} (second part)", + fun queue -> + let struct (_, rest) = IQueue.splitAt 0 queue + rest) + nameof IQueue.append, (fun queue -> IQueue.append queue (IQueue.clear queue)) + |] + + /// The sorts that compare the elements or their keys, by name, with the Array function that sorts in the same order. + static let stringSorts : Map string array) * (IQueue -> IQueue))> = + Map.ofArray [| + nameof IQueue.sort, struct (Array.sort, IQueue.sort) + nameof IQueue.sortBy, struct (Array.sortBy id, IQueue.sortBy id) + nameof IQueue.sortByDescending, struct (Array.sortByDescending id, IQueue.sortByDescending id) + nameof IQueue.sortDescending, struct (Array.sortDescending, IQueue.sortDescending) + |] + + /// 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 queue, as the rows of their test. + static member NullQueueRejections = nullQueueRejections.Keys + + /// The names of the functions and the default flat lists that they reject as sequences, as the rows of their test. + static member DefaultSequenceRejections = defaultSequenceRejections.Keys + + /// The names of the cases that keep every element of a queue, as the rows of their test. + static member WholeQueues = wholeQueues.Keys + + /// The names of the sorts that compare the elements or their keys, as the rows of the test of strings. + static member StringSorts = stringSorts.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, $"IQueue.%s{name} must keep the implementation of its input") + |> ignore + + CollectionAssert.AreEqual ( + IQueue.toArray (apply (several ())), + IQueue.toArray result, + $"IQueue.%s{name} must return the same elements for every implementation" + ) + + [] + member _.``functions that combine queues or change the element type return an immutable queue`` () = + let queue = tagged () + + CollectionAssert.AreEqual ( + Array.map string Inputs.Several, + Queue.toArray (IQueue.map string queue), + "IQueue.map must map the elements of any implementation into an immutable queue" + ) + + let concatenated = IQueue.concat [| queue; several () |] + + CollectionAssert.AreEqual ( + Array.append Inputs.Several Inputs.Several, + Queue.toArray concatenated, + "IQueue.concat must concatenate queues of different implementations into an immutable queue" + ) + + [] + member _.``functions reject a null queue`` (name : string) = + let struct (paramName, action) = nullQueueRejections[name] + + let ex = + Assert.ThrowsExactly(Action action, $"IQueue.%s{name} must reject a null queue") + + Assert.AreEqual(paramName, ex.ParamName, $"IQueue.%s{name} must name the parameter of the null queue") + + [] + member _.``enqueueMany rejects null values with the name of the parameter`` () = + let ex = + Assert.ThrowsExactly( + Action (fun () -> + IQueue.enqueueMany (Unchecked.defaultof>) (several ()) + |> ignore + ), + "IQueue.enqueueMany must reject null values" + ) + + Assert.AreEqual("values", ex.ParamName, "IQueue.enqueueMany must name the values parameter") + + [] + member _.``functions reject a default flat list given as a sequence with the names of their parameters`` (name : string) = + let struct (paramName, action) = defaultSequenceRejections[name] + FlatListAssert.RejectsNull ($"IQueue.%s{name}", paramName, action) + + [] + member _.``functions take a flat list of strings that is not default as a sequence of objects`` () = + let widened = FlatListOracles.stringsAsObjects (FlatList.ofArray [| "a"; "b" |]) + let queue = Queue.ofSeq widened :> IQueue + + CollectionAssert.AreEqual ( + [| box "a"; box "b"; box "a"; box "b" |], + IQueue.toArray (IQueue.enqueueMany widened queue), + "IQueue.enqueueMany must take a flat list of strings as objects" + ) + + CollectionAssert.AreEqual ( + [| box "c" |], + IQueue.toArray (IQueue.except widened (Queue.ofArray [| "a"; "b"; "c" |] :> IQueue)), + "IQueue.except must take a flat list of strings as objects" + ) + + [] + member _.``enqueue and enqueueMany add the values after the last element`` () = + CollectionAssert.AreEqual ( + Array.append Inputs.Several [| 99 |], + IQueue.toArray (IQueue.enqueue 99 (tagged ())), + "IQueue.enqueue must add the value at the end" + ) + + CollectionAssert.AreEqual ( + Array.append Inputs.Several [| 97; 98; 99 |], + IQueue.toArray (IQueue.enqueueMany [| 97; 98; 99 |] (tagged ())), + "IQueue.enqueueMany must add the values at the end, in their order" + ) + + let queue = tagged () + Assert.AreSame (queue, IQueue.enqueueMany [||] queue, "IQueue.enqueueMany without values must return the input") + + [] + member _.``uncons throws for an empty queue as List.head does`` () = + ExceptionAssert.Same ( + QueueOps.paramName, + (fun () -> List.head List.empty |> ignore), + (fun () -> IQueue.uncons (IQueue.clear (tagged ())) |> ignore), + "IQueue.uncons of an empty queue must throw as List.head does" + ) + + [] + member _.``head, last and tail read the first and the last elements of any implementation`` () = + let queue = tagged () + Assert.AreEqual(Array.head Inputs.Several, IQueue.head queue, "IQueue.head must return the first element") + Assert.AreEqual(Array.last Inputs.Several, IQueue.last queue, "IQueue.last must return the last element") + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + IQueue.toArray (IQueue.tail queue), + "IQueue.tail must drop the first element" + ) + CollectionAssert.AreEqual (Inputs.Several, IQueue.toArray queue, "IQueue.tail must leave the input unchanged") + + [] + member _.``functions return the input itself when they keep every element`` (name : string) = + let queue = tagged () + Assert.AreSame (queue, wholeQueues[name] queue, $"IQueue.%s{name} must return the input when it keeps every element") + + [] + member _.``Cons matches the first element and the other elements`` () = + let rec sum (rest : IQueue) = + match rest with + | IQueue.Cons (struct (head, tail)) -> head + sum tail + | _ -> 0 + + Assert.AreEqual(Array.sum Inputs.Several, sum (tagged ()), "IQueue.Cons must match every element in turn") + + match tagged () with + | IQueue.Cons (struct (_, tail)) -> + Assert.IsInstanceOfType>(tail, "IQueue.Cons must keep the implementation of the tail") + |> ignore + | _ -> Assert.Fail "IQueue.Cons must match a queue with elements" + + [] + member _.``Nil matches an empty queue only`` () = + let describe (rest : IQueue) = + match rest with + | IQueue.Nil -> "empty" + | IQueue.Cons (struct (head, _)) -> $"starts with %d{head}" + | _ -> "unreachable" + + Assert.AreEqual("empty", describe (IQueue.clear (tagged ())), "IQueue.Nil must match an empty queue") + Assert.AreEqual("starts with 5", describe (several ()), "IQueue.Nil must not match a queue with elements") + + [] + member _.``contains compares in F# structural equality`` () = + let arrays : IQueue = Queue.singleton [| 1; 2 |] + + Assert.IsTrue ( + IQueue.contains [| 1; 2 |] arrays, + "IQueue.contains must compare arrays by their elements, as List.contains does" + ) + + [] + member _.``sorts order strings in F# generic comparison, which is ordinal`` (name : string) = + // A comparison under a culture puts "a" before "A" and both before "b"; F# generic comparison compares ordinally + let elements = [| "b"; "A"; "a"; "B" |] + let struct (oracle, sort) = stringSorts[name] + + CollectionAssert.AreEqual ( + oracle elements, + IQueue.toArray (sort (Queue.ofArray elements)), + $"IQueue.%s{name} must order strings in F# generic comparison, as the List function does" + ) + + [] + member _.``tryFind, tryFindIndex and tryPick read a queue of another implementation up to the first match`` (value : int) = + let elements = Inputs.Several + let queue = CountingQueue (ImmutableQueue.CreateRange elements) + let isValue item = item = value + let tenfoldIfValue item = if item = value then ValueSome (item * 10) else ValueNone + + Assert.AreEqual( + Array.tryFind isValue elements |> ValueOption.ofOption, + IQueue.tryFind isValue queue, + "IQueue.tryFind must find the first match in a queue of another implementation, as List.tryFind does" + ) + + Assert.AreEqual( + Array.tryFindIndex isValue elements |> ValueOption.ofOption, + IQueue.tryFindIndex isValue queue, + "IQueue.tryFindIndex must find the index of the first match in a queue of another implementation" + ) + + Assert.AreEqual( + Array.tryPick (tenfoldIfValue >> ValueOption.toOption) elements + |> ValueOption.ofOption, + IQueue.tryPick tenfoldIfValue queue, + "IQueue.tryPick must return the first chosen value of a queue of another implementation" + ) + + // Each search reads the elements up to the first match, or every element when none matches + let read = + match Array.tryFindIndex isValue elements with + | Some index -> index + 1 + | None -> elements.Length + + Assert.AreEqual(3 * read, queue.ElementsRead, "The searches must stop reading at the first match") + Assert.AreEqual(3, queue.Enumerators, "Each search must take one enumerator of the queue") + Assert.AreEqual(3, queue.Disposals, "Each search must dispose the enumerator that it takes") + + [] + member _.``find, findIndex and pick read a queue of another implementation up to the first match`` (value : int) = + let elements = Inputs.Several + let queue = CountingQueue (ImmutableQueue.CreateRange elements) + let isValue item = item = value + let tenfoldIfValue item = if item = value then ValueSome (item * 10) else ValueNone + + Assert.AreEqual( + Array.find isValue elements, + IQueue.find isValue queue, + "IQueue.find must find the first match in a queue of another implementation, as List.find does" + ) + + Assert.AreEqual( + Array.findIndex isValue elements, + IQueue.findIndex isValue queue, + "IQueue.findIndex must find the index of the first match in a queue of another implementation" + ) + + Assert.AreEqual( + Array.pick (tenfoldIfValue >> ValueOption.toOption) elements, + IQueue.pick tenfoldIfValue queue, + "IQueue.pick must return the first chosen value of a queue of another implementation" + ) + + Assert.AreEqual( + 3 * (Array.findIndex isValue elements + 1), + queue.ElementsRead, + "The searches must stop reading at the first match" + ) + + Assert.AreEqual(3, queue.Enumerators, "Each search must take one enumerator of the queue") + Assert.AreEqual(3, queue.Disposals, "Each search must dispose the enumerator that it takes") + + [] + member _.``take reads a queue of another implementation up to the element after the ones that it takes`` (count : int) = + let elements = Inputs.Several + let queue = CountingQueue (ImmutableQueue.CreateRange elements) + let taken = IQueue.take count queue + // Read before toArray reads the result, which is the input itself when take keeps every element + let struct (read, enumerators, disposals) = + struct (queue.ElementsRead, queue.Enumerators, queue.Disposals) + + CollectionAssert.AreEqual ( + Array.take count elements, + IQueue.toArray taken, + "IQueue.take must return the first elements of a queue of another implementation" + ) + + Assert.AreEqual( + min (count + 1) elements.Length, + read, + "IQueue.take must read the elements that it takes and the next one, which tells whether there are more" + ) + + Assert.AreEqual(1, enumerators, "IQueue.take must take one enumerator of the queue") + Assert.AreEqual(1, disposals, "IQueue.take must dispose the enumerator that it takes") + + [] + member _.``truncate reads a queue of another implementation up to the element after the ones that it keeps`` (count : int) = + let elements = Inputs.Several + let queue = CountingQueue (ImmutableQueue.CreateRange elements) + let truncated = IQueue.truncate count queue + // Read before toArray reads the result, which is the input itself when truncate keeps every element + let struct (read, enumerators, disposals) = + struct (queue.ElementsRead, queue.Enumerators, queue.Disposals) + + CollectionAssert.AreEqual ( + Array.truncate count elements, + IQueue.toArray truncated, + "IQueue.truncate must return the first elements of a queue of another implementation" + ) + + Assert.AreEqual( + min (count + 1) elements.Length, + read, + "IQueue.truncate must read the elements that it keeps and the next one, which tells whether there are more" + ) + + Assert.AreEqual(1, enumerators, "IQueue.truncate must take one enumerator of the queue") + Assert.AreEqual(1, disposals, "IQueue.truncate must dispose the enumerator that it takes") + + [] + [] + [] + [] + [] + [] + [] + [] + [] + member _.``the queue functions compile with the names of the queue operations of the BCL`` + (name : string, compiledName : string) + = + Assert.AreEqual( + name, + QueueOps.sourceName "FSharp.Collections.Immutable.IQueue" compiledName, + "IQueue must compile the function under the name of its queue operation in the BCL" + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Queue/IQueueTupleTests.fs b/tests/FSharp.Collections.Immutable.Tests/Queue/IQueueTupleTests.fs new file mode 100644 index 00000000..14bab8ee --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Queue/IQueueTupleTests.fs @@ -0,0 +1,262 @@ +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 `IQueue` module of the `Tuple` namespace. +[] +type IQueueTupleTests () = + + static let several : IQueue = Queue.ofArray Inputs.Several + + static let other : IQueue = Queue.ofArray Inputs.Other + + static let third : IQueue = Queue.ofArray Inputs.Third + + [] + member _.``tryUncons returns a value option of a reference tuple`` () = + let found : (int * IQueue) voption = IQueue.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.IQueue.tryUncons must return the first element") + + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + IQueue.toArray tail, + "Tuple.IQueue.tryUncons must return the other elements" + ) + | ValueNone -> Assert.Fail "Tuple.IQueue.tryUncons must return the elements of a queue with elements" + + [] + member _.``uncons returns a reference tuple`` () = + let (head : int), (tail : IQueue) = IQueue.uncons several + Assert.AreEqual(Array.head Inputs.Several, head, "Tuple.IQueue.uncons must return the first element") + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + IQueue.toArray tail, + "Tuple.IQueue.uncons must return the other elements" + ) + + [] + member _.``Cons is a struct partial pattern of reference tuples`` () = + let pattern : IQueue -> (int * IQueue) voption = IQueue.(|Cons|_|) + + match several with + | IQueue.Cons (head, tail) -> + Assert.AreEqual(Array.head Inputs.Several, head, "Tuple.IQueue.Cons must match the first element") + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + IQueue.toArray tail, + "Tuple.IQueue.Cons must match the other elements" + ) + | _ -> Assert.Fail "Tuple.IQueue.Cons must match a queue with elements" + + Assert.IsTrue ((pattern (IQueue.clear several)).IsNone, "Tuple.IQueue.Cons must not match an empty queue") + + [] + member _.``allPairs returns reference tuples`` () = + let pairs : Queue = IQueue.allPairs several other + + CollectionAssert.AreEqual ( + Array.allPairs Inputs.Several Inputs.Other, + Queue.toArray pairs, + "Tuple.IQueue.allPairs must pair as Array.allPairs does" + ) + + [] + member _.``indexed returns reference tuples`` () = + let pairs : Queue = IQueue.indexed several + + CollectionAssert.AreEqual ( + Array.indexed Inputs.Several, + Queue.toArray pairs, + "Tuple.IQueue.indexed must index as Array.indexed does" + ) + + [] + member _.``pairwise returns reference tuples`` () = + let pairs : Queue = IQueue.pairwise several + + CollectionAssert.AreEqual ( + Array.pairwise Inputs.Several, + Queue.toArray pairs, + "Tuple.IQueue.pairwise must pair as Array.pairwise does" + ) + + [] + member _.``unzip takes and returns reference tuples`` () = + let pairs : IQueue = IQueue.zip several other + let (first : Queue), (second : Queue) = IQueue.unzip pairs + CollectionAssert.AreEqual (Inputs.Several, Queue.toArray first, "Tuple.IQueue.unzip must return the first components") + CollectionAssert.AreEqual (Inputs.Other, Queue.toArray second, "Tuple.IQueue.unzip must return the second components") + + [] + member _.``unzip3 takes and returns reference tuples`` () = + let triples : IQueue = IQueue.zip3 several other third + let (first : Queue), (second : Queue), (last : Queue) = IQueue.unzip3 triples + CollectionAssert.AreEqual (Inputs.Several, Queue.toArray first, "Tuple.IQueue.unzip3 must return the first components") + CollectionAssert.AreEqual (Inputs.Other, Queue.toArray second, "Tuple.IQueue.unzip3 must return the second components") + CollectionAssert.AreEqual (Inputs.Third, Queue.toArray last, "Tuple.IQueue.unzip3 must return the third components") + + [] + member _.``zip returns reference tuples`` () = + let pairs : Queue = IQueue.zip several other + + CollectionAssert.AreEqual ( + Array.zip Inputs.Several Inputs.Other, + Queue.toArray pairs, + "Tuple.IQueue.zip must zip as Array.zip does" + ) + + [] + member _.``zip and zip3 throw for different lengths as the defaults do`` () = + let short : IQueue = Queue.singleton 1 + + ExceptionAssert.Same ( + id, + (fun () -> + FSharp.Collections.Immutable.IQueue.zip several short + |> ignore + ), + (fun () -> IQueue.zip several short |> ignore), + "Tuple.IQueue.zip must throw as the default zip does" + ) + + ExceptionAssert.Same ( + id, + (fun () -> + FSharp.Collections.Immutable.IQueue.zip3 several short other + |> ignore + ), + (fun () -> IQueue.zip3 several short other |> ignore), + "Tuple.IQueue.zip3 must throw as the default zip3 does" + ) + + [] + member _.``zip3 returns reference tuples`` () = + let triples : Queue = IQueue.zip3 several other third + + CollectionAssert.AreEqual ( + Array.zip3 Inputs.Several Inputs.Other Inputs.Third, + Queue.toArray triples, + "Tuple.IQueue.zip3 must zip as Array.zip3 does" + ) + + [] + member _.``mapFold takes and returns reference tuples`` () = + let (results : Queue), (state : int) = + IQueue.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, Queue.toArray results, "Tuple.IQueue.mapFold must map as Array.mapFold does") + Assert.AreEqual(expectedState, state, "Tuple.IQueue.mapFold must thread the state as Array.mapFold does") + + [] + member _.``mapFoldBack takes and returns reference tuples`` () = + let (results : Queue), (state : int) = + IQueue.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, + Queue.toArray results, + "Tuple.IQueue.mapFoldBack must map as Array.mapFoldBack does" + ) + Assert.AreEqual(expectedState, state, "Tuple.IQueue.mapFoldBack must thread the state as Array.mapFoldBack does") + + [] + member _.``countBy returns reference tuples`` () = + let counts : Queue = IQueue.countBy (fun item -> item % 3) several + + CollectionAssert.AreEqual ( + Array.countBy (fun item -> item % 3) Inputs.Several, + Queue.toArray counts, + "Tuple.IQueue.countBy must count as Array.countBy does" + ) + + [] + member _.``groupBy returns reference tuples`` () = + let groups : Queue> = IQueue.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 |> Queue.toArray |> Array.map fst, + "Tuple.IQueue.groupBy must return the keys as List.groupBy does" + ) + + CollectionAssert.AreEqual ( + Array.map snd expected, + groups + |> Queue.toArray + |> Array.map (fun (_, items) -> Queue.toArray items), + "Tuple.IQueue.groupBy must group the elements as List.groupBy does" + ) + + [] + member _.``partition returns a reference tuple`` () = + let (accepted : IQueue), (rejected : IQueue) = IQueue.partition (fun item -> item > 0) several + let expectedAccepted, expectedRejected = Array.partition (fun item -> item > 0) Inputs.Several + CollectionAssert.AreEqual ( + expectedAccepted, + IQueue.toArray accepted, + "Tuple.IQueue.partition must return the accepted elements" + ) + CollectionAssert.AreEqual ( + expectedRejected, + IQueue.toArray rejected, + "Tuple.IQueue.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 : Queue), (second : Queue) = IQueue.partitionWith partitioner several + let expectedFirst, expectedSecond = Array.partitionWith partitioner Inputs.Several + CollectionAssert.AreEqual (expectedFirst, Queue.toArray first, "Tuple.IQueue.partitionWith must return the first choices") + CollectionAssert.AreEqual ( + expectedSecond, + Queue.toArray second, + "Tuple.IQueue.partitionWith must return the second choices" + ) + + [] + member _.``splitAt returns a reference tuple`` () = + let (before : IQueue), (after : IQueue) = IQueue.splitAt 3 several + let expectedBefore, expectedAfter = Array.splitAt 3 Inputs.Several + CollectionAssert.AreEqual (expectedBefore, IQueue.toArray before, "Tuple.IQueue.splitAt must return the first part") + CollectionAssert.AreEqual (expectedAfter, IQueue.toArray after, "Tuple.IQueue.splitAt must return the second part") + + [] + member _.``the functions without a tuple keep resolving to the default module`` () = + let found : int voption = IQueue.tryFind (fun item -> item > 6) several + Assert.AreEqual(ValueSome 8, found, "IQueue.tryFind must resolve to the default module") + + [] + [] + [] + member _.``the queue functions compile with the names of the queue operations of the BCL`` + (name : string, compiledName : string) + = + Assert.AreEqual( + name, + QueueOps.sourceName "FSharp.Collections.Immutable.Tuple.IQueue" compiledName, + "Tuple.IQueue must compile the function under the name of its queue operation in the BCL" + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Queue/QueueComputationExpressionTests.fs b/tests/FSharp.Collections.Immutable.Tests/Queue/QueueComputationExpressionTests.fs new file mode 100644 index 00000000..11cb75c7 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Queue/QueueComputationExpressionTests.fs @@ -0,0 +1,218 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// The `queue { }` computation expression. +[] +type QueueComputationExpressionTests () = + + /// 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 Queue> = + Map.ofArray [| + "sequence", (fun () -> queue { yield! Unchecked.defaultof> }) + "array", (fun () -> queue { yield! Unchecked.defaultof }) + "queue", (fun () -> queue { 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 () -> queue { yield! nullSequence } |> ignore)) + "yield! (default flat list)", struct ("items", (fun () -> queue { yield! defaultSequence } |> ignore)) + "yield! (default flat list of strings as objects)", + struct ("items", (fun () -> queue { yield! widenedSequence } |> ignore)) + "for (null sequence)", + struct ("source", + (fun () -> + queue { + for item in nullSequence do + yield item + } + |> ignore + )) + "for (default flat list)", + struct ("source", + (fun () -> + queue { + for item in defaultSequence do + yield item + } + |> ignore + )) + "for (default flat list of strings as objects)", + struct ("source", + (fun () -> + queue { + for item in widenedSequence do + yield item + } + |> ignore + )) + |] + + /// The shapes of the null inputs of yield!, as the rows of their test. + static member NullYields = nullYields.Keys + + /// The constructs and the inputs that take a sequence, as the rows of the test of the sequences that they reject. + static member SequenceRejections = sequenceRejections.Keys + + [] + member _.``an expression without elements is empty`` () = + let empty : Queue = queue { () } + Assert.IsTrue (Queue.isEmpty empty, "queue { () } must be empty") + + [] + member _.``yield adds the elements in order, the first one leaves first`` () = + let built = queue { + yield 1 + yield 2 + yield 3 + } + + CollectionAssert.AreEqual ([| 1; 2; 3 |], Queue.toArray built, "queue must keep the order of yield") + Assert.AreEqual(1, Queue.head built, "queue must let the first yielded element leave first") + + [] + member _.``implicit yields add the elements in order`` () = + let built = queue { + 1 + 2 + 3 + } + + CollectionAssert.AreEqual ([| 1; 2; 3 |], Queue.toArray built, "queue must keep the order of implicit yields") + + [] + member _.``yield! adds a sequence, an array, a queue and another expression`` () = + let inner = queue { yield 7 } + + let built = queue { + yield! [ 1; 2 ] + yield! [| 3; 4 |] + yield! Queue.ofArray [| 5; 6 |] + yield! inner + } + + CollectionAssert.AreEqual ([| 1; 2; 3; 4; 5; 6; 7 |], Queue.toArray built, "queue must add the elements of yield!") + + [] + member _.``yield! rejects a null input with the name of its parameter`` (shape : string) = + let ex = + Assert.ThrowsExactly( + Action (fun () -> nullYields[shape]() |> ignore), + $"queue must reject a null %s{shape} in yield!" + ) + + Assert.AreEqual("items", ex.ParamName, $"queue 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 ($"queue with %s{name}", paramName, expression) + + [] + member _.``yield! and for take a flat list of strings that is not default as a sequence of objects`` () = + let widened = FlatListOracles.stringsAsObjects (FlatList.ofArray [| "a"; "b" |]) + + let built = queue { + yield! widened + + for item in widened do + yield item + } + + CollectionAssert.AreEqual ( + [| box "a"; box "b"; box "a"; box "b" |], + Queue.toArray built, + "queue 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 built = queue { + for item in 1..3 do + yield item * 10 + } + + CollectionAssert.AreEqual ([| 10; 20; 30 |], Queue.toArray built, "queue must run the body of for for each item") + + [] + member _.``while adds the elements while the guard holds`` () = + let mutable count = 0 + + let built = queue { + while count < 3 do + count <- count + 1 + yield count + } + + CollectionAssert.AreEqual ([| 1; 2; 3 |], Queue.toArray built, "queue must run the body of while while the guard holds") + + [] + member _.``if without else and following elements combine`` () = + let built = queue { + if false then + yield 0 + + yield 1 + + if true then + yield 2 + } + + CollectionAssert.AreEqual ([| 1; 2 |], Queue.toArray built, "queue must combine conditional and plain elements") + + [] + member _.``try with adds the elements of the handler after an exception`` () = + let built = queue { + try + yield 1 + failwith "fails" + with _ -> + yield 2 + } + + CollectionAssert.AreEqual ( + [| 1; 2 |], + Queue.toArray built, + "queue must keep the elements before the exception and add the handler's" + ) + + [] + member _.``try finally runs the compensation`` () = + let mutable compensated = false + + let built = queue { + try + yield 1 + finally + compensated <- true + } + + Assert.IsTrue (compensated, "queue must run the compensation of try finally") + CollectionAssert.AreEqual ([| 1 |], Queue.toArray built, "queue must keep the elements of the body of try finally") + + [] + member _.``use disposes the resource after the body`` () = + let mutable disposed = false + + let built = queue { + use _resource = { + new IDisposable with + member _.Dispose () = disposed <- true + } + + yield 1 + } + + Assert.IsTrue (disposed, "queue must dispose the resource of use") + CollectionAssert.AreEqual ([| 1 |], Queue.toArray built, "queue must keep the elements of the body of use") diff --git a/tests/FSharp.Collections.Immutable.Tests/Queue/QueueContractTests.fs b/tests/FSharp.Collections.Immutable.Tests/Queue/QueueContractTests.fs new file mode 100644 index 00000000..62756952 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Queue/QueueContractTests.fs @@ -0,0 +1,29 @@ +namespace FSharp.Collections.Immutable.Tests + +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// The sequence-like contract for `Queue`. +[] +type QueueSeqLikeTests () = + inherit SeqLikeContract> (QueueOps.seqLike) + +/// The creation contract for `Queue`. +[] +type QueueCreationTests () = + inherit SeqCreationContract> (QueueOps.creation) + +/// The stable sorting contract for `Queue`. +[] +type QueueStableSortTests () = + inherit StableSortContract> (QueueOps.stableSort) + +/// The sequence-like contract for `IQueue`. +[] +type IQueueSeqLikeTests () = + inherit SeqLikeContract> (IQueueOps.seqLike) + +/// The stable sorting contract for `IQueue`. +[] +type IQueueStableSortTests () = + inherit StableSortContract> (IQueueOps.stableSort) diff --git a/tests/FSharp.Collections.Immutable.Tests/Queue/QueueOps.fs b/tests/FSharp.Collections.Immutable.Tests/Queue/QueueOps.fs new file mode 100644 index 00000000..82847653 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Queue/QueueOps.fs @@ -0,0 +1,248 @@ +/// The functions of the `Queue` module for the contract tests. +[] +module FSharp.Collections.Immutable.Tests.QueueOps + +open System +open System.Reflection +open FSharp.Collections.Immutable + +/// The name that the queue modules give to a parameter that the List module names after the list. +let private queueName (name : string) = + match name with + | "list" -> "queue" + | "list1" -> "queue1" + | "list2" -> "queue2" + | "list3" -> "queue3" + | "lists" -> "queues" + // List.transpose names the list at index i "list.[i]", and the queue modules "queue.[i]" + | _ when name.StartsWith ("list.[", StringComparison.Ordinal) -> "queue" + name.Substring ("list".Length) + | _ -> name + +/// +/// Maps the name of a parameter of a List function to the name of the parameter of the queue modules, which name their +/// queues where the List module names its lists, and keep the other names. +/// +/// +/// The name of several parameters, such as "list1, list2", maps each of them. +/// +let paramName (name : string) = + name.Split (", ", StringSplitOptions.None) + |> Array.map queueName + |> String.concat ", " + +/// +/// The complete message of an exception of a List function with the names of the queue modules: the names of the lists +/// that the second line and the parameter suffix of the message mention become the names of the queues. +/// +let message (text : string) = + text + .Replace("list1", "queue1", StringComparison.Ordinal) + .Replace("list2", "queue2", StringComparison.Ordinal) + .Replace("list3", "queue3", StringComparison.Ordinal) + .Replace("list.[", "queue.[", StringComparison.Ordinal) + .Replace("'list'", "'queue'", StringComparison.Ordinal) + +/// +/// The name in F# of the function that a module of the queues 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.Queue. +/// 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 (queues : Queue>) = queues |> Queue.toArray |> Array.map Queue.toArray + +let private toArrayPair (struct (first : Queue<'T1>, second : Queue<'T2>)) = struct (Queue.toArray first, Queue.toArray second) + +/// The functions of the sequence-like facet. +let seqLike : SeqOps> = { + Name = nameof Queue + Mirror = ListMirror + ParamName = paramName + OfSeq = Queue.ofSeq + ToArray = Queue.toArray + + IsEmpty = Queue.isEmpty + Length = Queue.length + + ExactlyOne = Queue.exactlyOne + Head = Queue.head + Last = Queue.last + Tail = Queue.tail + TryExactlyOne = Queue.tryExactlyOne + TryHead = Queue.tryHead + TryLast = Queue.tryLast + TryTail = Queue.tryTail + + Contains = Queue.contains + Exists = Queue.exists + Exists2 = Queue.exists2 + Find = Queue.find + FindBack = Queue.findBack + FindIndex = Queue.findIndex + FindIndexBack = Queue.findIndexBack + Forall = Queue.forall + Forall2 = Queue.forall2 + Pick = Queue.pick + TryFind = Queue.tryFind + TryFindBack = Queue.tryFindBack + TryFindIndex = Queue.tryFindIndex + TryFindIndexBack = Queue.tryFindIndexBack + TryPick = Queue.tryPick + + AllPairs = fun queue1 queue2 -> Queue.allPairs queue1 queue2 |> Queue.toArray + Append = Queue.append + Choose = Queue.choose + ChunkBySize = fun chunkSize queue -> Queue.chunkBySize chunkSize queue |> toArrays + Collect = Queue.collect + Concat = Queue.concat + Filter = Queue.filter + Indexed = Queue.indexed >> Queue.toArray + Map = Queue.map + Map2 = Queue.map2 + Map3 = Queue.map3 + Mapi = Queue.mapi + Mapi2 = Queue.mapi2 + Pairwise = Queue.pairwise >> Queue.toArray + Permute = Queue.permute + Rev = Queue.rev + Transpose = fun queues -> Queue.transpose queues |> toArrays + Unzip = fun pairs -> Queue.unzip (Queue.ofArray pairs) |> toArrayPair + Unzip3 = + fun triples -> + let struct (first, second, third) = Queue.unzip3 (Queue.ofArray triples) + struct (Queue.toArray first, Queue.toArray second, Queue.toArray third) + Where = Queue.where + Windowed = fun windowSize queue -> Queue.windowed windowSize queue |> toArrays + Zip = fun queue1 queue2 -> Queue.zip queue1 queue2 |> Queue.toArray + Zip3 = fun queue1 queue2 queue3 -> Queue.zip3 queue1 queue2 queue3 |> Queue.toArray + + Average = fun items -> Queue.average (Queue.ofArray items) + AverageBy = Queue.averageBy + CompareWith = Queue.compareWith + Fold = Queue.fold + Fold2 = Queue.fold2 + FoldBack = Queue.foldBack + FoldBack2 = Queue.foldBack2 + Iter = Queue.iter + Iter2 = Queue.iter2 + Iteri = Queue.iteri + Iteri2 = Queue.iteri2 + MapFold = + fun mapping state queue -> + let struct (results, final) = Queue.mapFold mapping state queue + struct (Queue.toArray results, final) + MapFoldBack = + fun mapping queue state -> + let struct (results, final) = Queue.mapFoldBack mapping queue state + struct (Queue.toArray results, final) + Max = Queue.max + MaxBy = Queue.maxBy + Min = Queue.min + MinBy = Queue.minBy + Reduce = Queue.reduce + ReduceBack = Queue.reduceBack + Scan = fun folder state queue -> Queue.scan folder state queue |> Queue.toArray + ScanBack = fun folder queue state -> Queue.scanBack folder queue state |> Queue.toArray + Sum = Queue.sum + SumBy = Queue.sumBy + + CountBy = fun projection queue -> Queue.countBy projection queue |> Queue.toArray + Distinct = Queue.distinct + DistinctBy = Queue.distinctBy + Except = Queue.except + GroupBy = + fun projection queue -> + Queue.groupBy projection queue + |> Queue.toArray + |> Array.map (fun struct (key, items) -> struct (key, Queue.toArray items)) + Partition = Queue.partition + PartitionWith = fun partitioner queue -> Queue.partitionWith partitioner queue |> toArrayPair + Skip = Queue.skip + SkipLast = Queue.skipLast + SkipWhile = Queue.skipWhile + SplitAt = Queue.splitAt + SplitInto = fun count queue -> Queue.splitInto count queue |> toArrays + Take = Queue.take + TakeLast = Queue.takeLast + TakeWhile = Queue.takeWhile + Truncate = Queue.truncate + + Sort = Queue.sort + SortBy = Queue.sortBy + SortByDescending = Queue.sortByDescending + SortDescending = Queue.sortDescending + SortWith = Queue.sortWith + SortWithComparer = Queue.sortWithComparer + + RandomChoice = Queue.randomChoice + RandomChoiceBy = Queue.randomChoiceBy + RandomChoiceWith = Queue.randomChoiceWith + RandomChoices = Queue.randomChoices + RandomChoicesBy = Queue.randomChoicesBy + RandomChoicesWith = Queue.randomChoicesWith + RandomSample = Queue.randomSample + RandomSampleBy = Queue.randomSampleBy + RandomSampleWith = Queue.randomSampleWith + RandomShuffle = Queue.randomShuffle + RandomShuffleBy = Queue.randomShuffleBy + RandomShuffleWith = Queue.randomShuffleWith + + ToList = Queue.toList + ToSeq = Queue.toSeq +} + +/// The functions of the creation facet. +let creation : SeqCreationOps> = { + Name = nameof Queue + Mirror = ListMirror + ParamName = paramName + ToArray = Queue.toArray + + Empty = fun () -> Queue.empty + Init = Queue.init + OfArray = Queue.ofArray + OfList = Queue.ofList + OfReadOnlySpan = fun array -> Queue.ofReadOnlySpan (ReadOnlySpan array) + OfSeq = Queue.ofSeq + OfSpan = fun array -> Queue.ofSpan (Span array) + Replicate = Queue.replicate + Singleton = Queue.singleton + Unfold = Queue.unfold +} + +/// The functions of the stable sorting facet. +let stableSort : StableSortOps> = { + Name = nameof Queue + OfSeq = Queue.ofSeq + ToArray = Queue.toArray + + Sort = fun items -> Queue.ofArray items |> Queue.sort |> Queue.toArray + SortBy = Queue.sortBy + SortByDescending = Queue.sortByDescending + SortDescending = fun items -> Queue.ofArray items |> Queue.sortDescending |> Queue.toArray + SortWith = Queue.sortWith + SortWithComparer = Queue.sortWithComparer + + SortIntegersDescending = fun items -> Queue.ofArray items |> Queue.sortDescending |> Queue.toArray + SortIntegersByDescending = + fun items -> + Queue.ofArray items + |> Queue.sortByDescending id + |> Queue.toArray +} diff --git a/tests/FSharp.Collections.Immutable.Tests/Queue/QueueOptionTests.fs b/tests/FSharp.Collections.Immutable.Tests/Queue/QueueOptionTests.fs new file mode 100644 index 00000000..2eb2fc0d --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Queue/QueueOptionTests.fs @@ -0,0 +1,217 @@ +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 `Queue` module of the `Option` namespace. +[] +type QueueOptionTests () = + + static let several = Queue.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 : Queue = Queue.unfold generator 3 + + CollectionAssert.AreEqual ( + Array.unfold generator 3, + Queue.toArray generated, + "Option.Queue.unfold must generate as Array.unfold does" + ) + + [] + member _.``tryExactlyOne returns an option`` () = + let found : int option = Queue.tryExactlyOne (Queue.singleton 5) + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.Queue.tryExactlyOne (Queue.singleton 5), + found, + "Option.Queue.tryExactlyOne must match the default after conversion" + ) + + [] + member _.``tryHead returns an option`` () = + let found : int option = Queue.tryHead several + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.Queue.tryHead several, + found, + "Option.Queue.tryHead must match the default" + ) + + [] + member _.``tryLast returns an option`` () = + let found : int option = Queue.tryLast several + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.Queue.tryLast several, + found, + "Option.Queue.tryLast must match the default" + ) + + [] + member _.``tryTail returns an option`` () = + let found : Queue option = Queue.tryTail several + + // The equality of an option compares the elements of an array that it holds + Assert.AreEqual( + FSharp.Collections.Immutable.Queue.tryTail several + |> ValueOption.map Queue.toArray + |> ValueOption.toOption, + found |> Option.map Queue.toArray, + "Option.Queue.tryTail must match the default" + ) + + [] + member _.``tryUncons returns an option of a reference tuple`` () = + let found : (int * Queue) option = Queue.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.Queue.tryUncons must return the first element") + + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + Queue.toArray tail, + "Option.Queue.tryUncons must return the other elements" + ) + | None -> Assert.Fail "Option.Queue.tryUncons must return the elements of a queue with elements" + + Assert.IsTrue ((Queue.tryUncons Queue.empty).IsNone, "Option.Queue.tryUncons must return None for an empty queue") + + [] + member _.``Cons is a partial pattern of options`` () = + let pattern : Queue -> (int * Queue) option = Queue.(|Cons|_|) + + match several with + | Queue.Cons (head, tail) -> + Assert.AreEqual(Array.head Inputs.Several, head, "Option.Queue.Cons must match the first element") + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + Queue.toArray tail, + "Option.Queue.Cons must match the other elements" + ) + | _ -> Assert.Fail "Option.Queue.Cons must match a queue with elements" + + Assert.IsTrue ((pattern Queue.empty).IsNone, "Option.Queue.Cons must not match an empty queue") + + [] + member _.``pick takes a chooser of options`` () = + let picked : int = Queue.pick negatedIfNegative several + + Assert.AreEqual( + Array.pick negatedIfNegative Inputs.Several, + picked, + "Option.Queue.pick must pick as Array.pick does" + ) + + [] + member _.``pick throws as Array.pick does`` () = + ExceptionAssert.Same ( + QueueOps.paramName, + (fun () -> + List.pick (fun _ -> None) (List.ofArray Inputs.Several) + |> ignore + ), + (fun () -> Queue.pick (fun _ -> None) several |> ignore), + "Option.Queue.pick without a choice must throw as Array.pick does" + ) + + [] + member _.``tryFind returns an option`` () = + let found : int option = Queue.tryFind (fun item -> item > 6) several + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.Queue.tryFind (fun item -> item > 6) several, + found, + "Option.Queue.tryFind must match the default" + ) + + [] + member _.``tryFindBack returns an option`` () = + let found : int option = Queue.tryFindBack (fun item -> item > 6) several + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.Queue.tryFindBack (fun item -> item > 6) several, + found, + "Option.Queue.tryFindBack must match the default" + ) + + [] + member _.``tryFindIndex returns an option`` () = + let found : int option = Queue.tryFindIndex (fun item -> item > 6) several + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.Queue.tryFindIndex (fun item -> item > 6) several, + found, + "Option.Queue.tryFindIndex must match the default" + ) + + [] + member _.``tryFindIndexBack returns an option`` () = + let found : int option = Queue.tryFindIndexBack (fun item -> item > 6) several + + OverrideAssert.OptionMatches ( + FSharp.Collections.Immutable.Queue.tryFindIndexBack (fun item -> item > 6) several, + found, + "Option.Queue.tryFindIndexBack must match the default" + ) + + [] + member _.``tryPick takes a chooser of options and returns an option`` () = + let found : int option = Queue.tryPick negatedIfNegative several + + Assert.AreEqual( + Array.tryPick negatedIfNegative Inputs.Several, + found, + "Option.Queue.tryPick must pick as Array.tryPick does" + ) + + [] + member _.``choose takes a chooser of options`` () = + let chosen : Queue = Queue.choose negatedIfNegative several + + CollectionAssert.AreEqual ( + Array.choose negatedIfNegative Inputs.Several, + Queue.toArray chosen, + "Option.Queue.choose must choose as Array.choose does" + ) + + [] + member _.``the functions without an option keep resolving to the default module`` () = + let mapped : Queue = Queue.map (fun item -> item * 2) several + let zipped : Queue = Queue.zip several several + + CollectionAssert.AreEqual ( + Array.map (fun item -> item * 2) Inputs.Several, + Queue.toArray mapped, + "Queue.map must resolve to the default module" + ) + + CollectionAssert.AreEqual ( + Array.zip Inputs.Several Inputs.Several, + Queue.toArray zipped, + "Queue.zip must resolve to the Tuple module" + ) + + [] + [] + [] + [] + member _.``the queue functions compile with the names of the queue operations of the BCL`` + (name : string, compiledName : string) + = + Assert.AreEqual( + name, + QueueOps.sourceName "FSharp.Collections.Immutable.Option.Queue" compiledName, + "Option.Queue must compile the function under the name of its queue operation in the BCL" + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Queue/QueueTests.fs b/tests/FSharp.Collections.Immutable.Tests/Queue/QueueTests.fs new file mode 100644 index 00000000..8a30095b --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Queue/QueueTests.fs @@ -0,0 +1,853 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open System.Collections.Generic +open System.Globalization +open FSharp.Collections.Immutable +open Microsoft.VisualStudio.TestTools.UnitTesting + +/// The behaviour of `Queue` that the shared contracts do not cover: the first-in, first-out order, the extras, null inputs, +/// the results that are their inputs, the details of the semantics of the List module, the order of strings and the +/// compiled names. +[] +type QueueTests () = + + static let nullQueue = Unchecked.defaultof> + + static let several () = Queue.ofArray Inputs.Several + + /// The elements of `Inputs.Several` in both stacks of a queue. ImmutableQueue dequeues from a front stack and enqueues + /// onto a back stack, so a queue that was dequeued and then enqueued keeps elements in both. + static let mixed () = + Queue.ofArray [| 0; 5; -3; 8 |] + |> Queue.tail + |> Queue.enqueueMany [| 5; 0; -3; 12; 7 |] + + /// The exception of an action and its complete message, under the invariant culture, in which FSharp.Core writes the + /// English messages that this library copies. + static let thrown (action : unit -> unit) (message : string) = + let culture = CultureInfo.CurrentUICulture + CultureInfo.CurrentUICulture <- CultureInfo.InvariantCulture + + try + Assert.Throws(Action action, message) + finally + CultureInfo.CurrentUICulture <- culture + + /// The functions that take a queue, by name, with the parameter that names the null queue and the function on a null + /// queue. The rows of the test are the names: a function has no readable name in a row. + static let nullQueueRejections = + let pairs = Unchecked.defaultof> + let triples = Unchecked.defaultof> + let floats = Unchecked.defaultof> + + [| + struct (nameof Queue.isEmpty, "queue", (fun () -> Queue.isEmpty nullQueue |> ignore)) + struct (nameof Queue.length, "queue", (fun () -> Queue.length nullQueue |> ignore)) + struct (nameof Queue.exactlyOne, "queue", (fun () -> Queue.exactlyOne nullQueue |> ignore)) + struct (nameof Queue.head, "queue", (fun () -> Queue.head nullQueue |> ignore)) + struct (nameof Queue.last, "queue", (fun () -> Queue.last nullQueue |> ignore)) + struct (nameof Queue.tail, "queue", (fun () -> Queue.tail nullQueue |> ignore)) + struct (nameof Queue.tryExactlyOne, "queue", (fun () -> Queue.tryExactlyOne nullQueue |> ignore)) + struct (nameof Queue.tryHead, "queue", (fun () -> Queue.tryHead nullQueue |> ignore)) + struct (nameof Queue.tryLast, "queue", (fun () -> Queue.tryLast nullQueue |> ignore)) + struct (nameof Queue.tryTail, "queue", (fun () -> Queue.tryTail nullQueue |> ignore)) + struct (nameof Queue.tryUncons, "queue", (fun () -> Queue.tryUncons nullQueue |> ignore)) + struct (nameof Queue.uncons, "queue", (fun () -> Queue.uncons nullQueue |> ignore)) + struct (nameof Queue.(|Cons|_|), "queue", (fun () -> Queue.(|Cons|_|) nullQueue |> ignore)) + struct (nameof Queue.(|Nil|_|), "queue", (fun () -> Queue.(|Nil|_|) nullQueue |> ignore)) + struct (nameof Queue.contains, "source", (fun () -> Queue.contains 1 nullQueue |> ignore)) + struct (nameof Queue.exists, "queue", (fun () -> Queue.exists ((=) 1) nullQueue |> ignore)) + struct (nameof Queue.exists2, "queue2", (fun () -> Queue.exists2 (=) (several ()) nullQueue |> ignore)) + struct (nameof Queue.find, "queue", (fun () -> Queue.find ((=) 1) nullQueue |> ignore)) + struct (nameof Queue.findBack, "queue", (fun () -> Queue.findBack ((=) 1) nullQueue |> ignore)) + struct (nameof Queue.findIndex, "queue", (fun () -> Queue.findIndex ((=) 1) nullQueue |> ignore)) + struct (nameof Queue.findIndexBack, "queue", (fun () -> Queue.findIndexBack ((=) 1) nullQueue |> ignore)) + struct (nameof Queue.forall, "queue", (fun () -> Queue.forall ((=) 1) nullQueue |> ignore)) + struct (nameof Queue.forall2, "queue1", (fun () -> Queue.forall2 (=) nullQueue (several ()) |> ignore)) + struct (nameof Queue.pick, "queue", (fun () -> Queue.pick ValueSome nullQueue |> ignore)) + struct (nameof Queue.tryFind, "queue", (fun () -> Queue.tryFind ((=) 1) nullQueue |> ignore)) + struct (nameof Queue.tryFindBack, "queue", (fun () -> Queue.tryFindBack ((=) 1) nullQueue |> ignore)) + struct (nameof Queue.tryFindIndex, "queue", (fun () -> Queue.tryFindIndex ((=) 1) nullQueue |> ignore)) + struct (nameof Queue.tryFindIndexBack, "queue", (fun () -> Queue.tryFindIndexBack ((=) 1) nullQueue |> ignore)) + struct (nameof Queue.tryPick, "queue", (fun () -> Queue.tryPick ValueSome nullQueue |> ignore)) + struct (nameof Queue.allPairs, "queue1", (fun () -> Queue.allPairs nullQueue (several ()) |> ignore)) + struct (nameof Queue.append, "queue2", (fun () -> Queue.append (several ()) nullQueue |> ignore)) + struct (nameof Queue.choose, "queue", (fun () -> Queue.choose ValueSome nullQueue |> ignore)) + struct (nameof Queue.chunkBySize, "queue", (fun () -> Queue.chunkBySize 2 nullQueue |> ignore)) + struct (nameof Queue.collect, "queue", (fun () -> Queue.collect Queue.singleton nullQueue |> ignore)) + struct (nameof Queue.concat, "queues", (fun () -> Queue.concat [| (several ()); nullQueue |] |> ignore)) + struct (nameof Queue.enqueue, "queue", (fun () -> Queue.enqueue 1 nullQueue |> ignore)) + struct (nameof Queue.enqueueMany, "queue", (fun () -> Queue.enqueueMany [| 1 |] nullQueue |> ignore)) + struct (nameof Queue.filter, "queue", (fun () -> Queue.filter ((=) 1) nullQueue |> ignore)) + struct (nameof Queue.indexed, "queue", (fun () -> Queue.indexed nullQueue |> ignore)) + struct (nameof Queue.map, "queue", (fun () -> Queue.map id nullQueue |> ignore)) + struct (nameof Queue.map2, "queue2", (fun () -> Queue.map2 (+) (several ()) nullQueue |> ignore)) + struct (nameof Queue.map3, + "queue3", + (fun () -> + Queue.map3 (fun a b c -> a + b + c) (several ()) (several ()) nullQueue + |> ignore + )) + struct (nameof Queue.mapi, "queue", (fun () -> Queue.mapi (+) nullQueue |> ignore)) + struct (nameof Queue.mapi2, + "queue1", + (fun () -> + Queue.mapi2 (fun index a b -> index + a + b) nullQueue (several ()) + |> ignore + )) + struct (nameof Queue.pairwise, "queue", (fun () -> Queue.pairwise nullQueue |> ignore)) + struct (nameof Queue.permute, "queue", (fun () -> Queue.permute id nullQueue |> ignore)) + struct (nameof Queue.rev, "queue", (fun () -> Queue.rev nullQueue |> ignore)) + struct (nameof Queue.transpose, "queues", (fun () -> Queue.transpose [| nullQueue |] |> ignore)) + struct (nameof Queue.unzip, "queue", (fun () -> Queue.unzip pairs |> ignore)) + struct (nameof Queue.unzip3, "queue", (fun () -> Queue.unzip3 triples |> ignore)) + struct (nameof Queue.where, "queue", (fun () -> Queue.where ((=) 1) nullQueue |> ignore)) + struct (nameof Queue.windowed, "queue", (fun () -> Queue.windowed 2 nullQueue |> ignore)) + struct (nameof Queue.zip, "queue1", (fun () -> Queue.zip nullQueue (several ()) |> ignore)) + struct (nameof Queue.zip3, "queue2", (fun () -> Queue.zip3 (several ()) nullQueue (several ()) |> ignore)) + struct (nameof Queue.average, "queue", (fun () -> Queue.average floats |> ignore)) + struct (nameof Queue.averageBy, "queue", (fun () -> Queue.averageBy float nullQueue |> ignore)) + struct (nameof Queue.compareWith, "queue2", (fun () -> Queue.compareWith compare (several ()) nullQueue |> ignore)) + struct (nameof Queue.fold, "queue", (fun () -> Queue.fold (+) 0 nullQueue |> ignore)) + struct (nameof Queue.fold2, + "queue2", + (fun () -> + Queue.fold2 (fun state a b -> state + a + b) 0 (several ()) nullQueue + |> ignore + )) + struct (nameof Queue.foldBack, "queue", (fun () -> Queue.foldBack (+) nullQueue 0 |> ignore)) + struct (nameof Queue.foldBack2, + "queue1", + (fun () -> + Queue.foldBack2 (fun a b state -> state + a + b) nullQueue (several ()) 0 + |> ignore + )) + struct (nameof Queue.iter, "queue", (fun () -> Queue.iter ignore nullQueue)) + struct (nameof Queue.iter2, "queue2", (fun () -> Queue.iter2 (fun _ _ -> ()) (several ()) nullQueue)) + struct (nameof Queue.iteri, "queue", (fun () -> Queue.iteri (fun _ _ -> ()) nullQueue)) + struct (nameof Queue.iteri2, "queue1", (fun () -> Queue.iteri2 (fun _ _ _ -> ()) nullQueue (several ()))) + struct (nameof Queue.mapFold, + "queue", + (fun () -> + Queue.mapFold (fun state item -> struct (item, state)) 0 nullQueue + |> ignore + )) + struct (nameof Queue.mapFoldBack, + "queue", + (fun () -> + Queue.mapFoldBack (fun item state -> struct (item, state)) nullQueue 0 + |> ignore + )) + struct (nameof Queue.max, "queue", (fun () -> Queue.max nullQueue |> ignore)) + struct (nameof Queue.maxBy, "queue", (fun () -> Queue.maxBy id nullQueue |> ignore)) + struct (nameof Queue.min, "queue", (fun () -> Queue.min nullQueue |> ignore)) + struct (nameof Queue.minBy, "queue", (fun () -> Queue.minBy id nullQueue |> ignore)) + struct (nameof Queue.reduce, "queue", (fun () -> Queue.reduce (+) nullQueue |> ignore)) + struct (nameof Queue.reduceBack, "queue", (fun () -> Queue.reduceBack (+) nullQueue |> ignore)) + struct (nameof Queue.scan, "queue", (fun () -> Queue.scan (+) 0 nullQueue |> ignore)) + struct (nameof Queue.scanBack, "queue", (fun () -> Queue.scanBack (+) nullQueue 0 |> ignore)) + struct (nameof Queue.sum, "queue", (fun () -> Queue.sum nullQueue |> ignore)) + struct (nameof Queue.sumBy, "queue", (fun () -> Queue.sumBy id nullQueue |> ignore)) + struct (nameof Queue.countBy, "queue", (fun () -> Queue.countBy id nullQueue |> ignore)) + struct (nameof Queue.distinct, "queue", (fun () -> Queue.distinct nullQueue |> ignore)) + struct (nameof Queue.distinctBy, "queue", (fun () -> Queue.distinctBy id nullQueue |> ignore)) + struct (nameof Queue.except, "queue", (fun () -> Queue.except [| 1 |] nullQueue |> ignore)) + struct (nameof Queue.groupBy, "queue", (fun () -> Queue.groupBy id nullQueue |> ignore)) + struct (nameof Queue.partition, "queue", (fun () -> Queue.partition ((=) 1) nullQueue |> ignore)) + struct (nameof Queue.partitionWith, "queue", (fun () -> Queue.partitionWith Choice1Of2 nullQueue |> ignore)) + struct (nameof Queue.skip, "queue", (fun () -> Queue.skip 0 nullQueue |> ignore)) + struct (nameof Queue.skipLast, "queue", (fun () -> Queue.skipLast 0 nullQueue |> ignore)) + struct (nameof Queue.skipWhile, "queue", (fun () -> Queue.skipWhile ((=) 1) nullQueue |> ignore)) + struct (nameof Queue.splitAt, "queue", (fun () -> Queue.splitAt 0 nullQueue |> ignore)) + struct (nameof Queue.splitInto, "queue", (fun () -> Queue.splitInto 2 nullQueue |> ignore)) + struct (nameof Queue.take, "queue", (fun () -> Queue.take 0 nullQueue |> ignore)) + struct (nameof Queue.takeLast, "queue", (fun () -> Queue.takeLast 0 nullQueue |> ignore)) + struct (nameof Queue.takeWhile, "queue", (fun () -> Queue.takeWhile ((=) 1) nullQueue |> ignore)) + struct (nameof Queue.truncate, "queue", (fun () -> Queue.truncate 1 nullQueue |> ignore)) + struct (nameof Queue.sort, "queue", (fun () -> Queue.sort nullQueue |> ignore)) + struct (nameof Queue.sortBy, "queue", (fun () -> Queue.sortBy id nullQueue |> ignore)) + struct (nameof Queue.sortByDescending, "queue", (fun () -> Queue.sortByDescending id nullQueue |> ignore)) + struct (nameof Queue.sortDescending, "queue", (fun () -> Queue.sortDescending nullQueue |> ignore)) + struct (nameof Queue.sortWith, "queue", (fun () -> Queue.sortWith compare nullQueue |> ignore)) + struct (nameof Queue.sortWithComparer, + "queue", + (fun () -> + Queue.sortWithComparer Comparer.Default nullQueue + |> ignore + )) + struct (nameof Queue.randomChoice, "source", (fun () -> Queue.randomChoice nullQueue |> ignore)) + struct (nameof Queue.randomChoiceBy, "source", (fun () -> Queue.randomChoiceBy (fun () -> 0.5) nullQueue |> ignore)) + struct (nameof Queue.randomChoiceWith, "source", (fun () -> Queue.randomChoiceWith (Random 1) nullQueue |> ignore)) + struct (nameof Queue.randomChoices, "source", (fun () -> Queue.randomChoices 1 nullQueue |> ignore)) + struct (nameof Queue.randomChoicesBy, + "source", + (fun () -> Queue.randomChoicesBy (fun () -> 0.5) 1 nullQueue |> ignore)) + struct (nameof Queue.randomChoicesWith, "source", (fun () -> Queue.randomChoicesWith (Random 1) 1 nullQueue |> ignore)) + struct (nameof Queue.randomSample, "source", (fun () -> Queue.randomSample 1 nullQueue |> ignore)) + struct (nameof Queue.randomSampleBy, "source", (fun () -> Queue.randomSampleBy (fun () -> 0.5) 1 nullQueue |> ignore)) + struct (nameof Queue.randomSampleWith, "source", (fun () -> Queue.randomSampleWith (Random 1) 1 nullQueue |> ignore)) + struct (nameof Queue.randomShuffle, "source", (fun () -> Queue.randomShuffle nullQueue |> ignore)) + struct (nameof Queue.randomShuffleBy, "source", (fun () -> Queue.randomShuffleBy (fun () -> 0.5) nullQueue |> ignore)) + struct (nameof Queue.randomShuffleWith, "source", (fun () -> Queue.randomShuffleWith (Random 1) nullQueue |> ignore)) + struct (nameof Queue.toArray, "queue", (fun () -> Queue.toArray nullQueue |> ignore)) + struct (nameof Queue.toList, "queue", (fun () -> Queue.toList nullQueue |> ignore)) + struct (nameof Queue.toSeq, "queue", (fun () -> Queue.toSeq nullQueue |> ignore)) + |] + |> Array.map (fun struct (name, paramName, action) -> name, struct (paramName, action)) + |> Map.ofArray + + /// The functions that take another input that can be null, by name, with the name of that parameter and the function + /// on a null input. + static let otherNullRejections = + [| + struct (nameof Queue.ofArray, "array", (fun () -> Queue.ofArray (Unchecked.defaultof) |> ignore)) + struct (nameof Queue.ofSeq, "source", (fun () -> Queue.ofSeq (Unchecked.defaultof>) |> ignore)) + struct (nameof Queue.concat, + "queues", + (fun () -> + Queue.concat (Unchecked.defaultof>>) + |> ignore + )) + struct (nameof Queue.transpose, + "queues", + (fun () -> + Queue.transpose (Unchecked.defaultof>>) + |> ignore + )) + struct (nameof Queue.enqueueMany, + "values", + (fun () -> + Queue.enqueueMany (Unchecked.defaultof>) (several ()) + |> ignore + )) + |] + |> Array.map (fun struct (name, paramName, action) -> name, struct (paramName, action)) + |> Map.ofArray + + /// The functions that take a sequence, by name and input, with the parameter that names the sequence and the function on + /// the input. The inputs are a default flat list, which converts to a sequence without a check and fails with + /// InvalidOperationException when it is enumerated, and a default flat list of strings as a sequence of objects, which a + /// type test for the flat list of objects does not see because seq is covariant. The rows of the test are the names. + static let defaultSequenceRejections = + let defaultList = Inputs.DefaultFlatListAsSeq() + let defaultQueues = Inputs.DefaultFlatListAsSeq>() + let widened = FlatListOracles.defaultStringsAsObjects + + [| + struct (nameof Queue.ofSeq, "default flat list", "source", (fun () -> Queue.ofSeq defaultList |> ignore)) + struct (nameof Queue.ofSeq, + "default flat list of strings as objects", + "source", + (fun () -> Queue.ofSeq widened |> ignore)) + struct (nameof Queue.concat, "default flat list", "queues", (fun () -> Queue.concat defaultQueues |> ignore)) + struct (nameof Queue.transpose, "default flat list", "queues", (fun () -> Queue.transpose defaultQueues |> ignore)) + struct (nameof Queue.enqueueMany, + "default flat list", + "values", + (fun () -> Queue.enqueueMany defaultList (several ()) |> ignore)) + struct (nameof Queue.enqueueMany, + "default flat list of strings as objects", + "values", + (fun () -> Queue.enqueueMany widened Queue.empty |> ignore)) + struct (nameof Queue.except, + "default flat list", + "itemsToExclude", + (fun () -> Queue.except defaultList (several ()) |> ignore)) + struct (nameof Queue.except, + "default flat list and an empty queue", + "itemsToExclude", + (fun () -> Queue.except defaultList Queue.empty |> ignore)) + struct (nameof Queue.except, + "default flat list of strings as objects", + "itemsToExclude", + (fun () -> Queue.except widened Queue.empty |> ignore)) + |] + |> Array.map (fun struct (name, input, paramName, action) -> $"%s{name} of a %s{input}", struct (paramName, action)) + |> Map.ofArray + + /// The functions that keep every element of a queue for some arguments, by name, on such arguments. The names tell + /// apart the cases of the same function. + static let wholeQueues : Map -> Queue> = + Map.ofArray [| + nameof Queue.take, Queue.take 8 + nameof Queue.takeLast, Queue.takeLast 8 + nameof Queue.takeWhile, Queue.takeWhile (fun _ -> true) + nameof Queue.truncate, Queue.truncate 100 + $"%s{nameof Queue.skip} of a negative count", Queue.skip -1 + $"%s{nameof Queue.skip} of zero", Queue.skip 0 + nameof Queue.skipLast, Queue.skipLast 0 + nameof Queue.skipWhile, Queue.skipWhile (fun _ -> false) + ($"%s{nameof Queue.splitAt} (first part)", + fun queue -> + let struct (whole, _) = Queue.splitAt 8 queue + whole) + ($"%s{nameof Queue.splitAt} (second part)", + fun queue -> + let struct (_, rest) = Queue.splitAt 0 queue + rest) + $"%s{nameof Queue.append} of an empty second queue", (fun queue -> Queue.append queue Queue.empty) + $"%s{nameof Queue.append} of an empty first queue", (fun queue -> Queue.append Queue.empty queue) + |] + + /// The sorting functions, by name, with a projection or a comparison function that must not run on one element. + static let singleElementSorts : Map -> Queue> = + Map.ofArray [| + nameof Queue.sort, Queue.sort + nameof Queue.sortBy, Queue.sortBy (fun _ -> failwith "The projection must not run") + nameof Queue.sortByDescending, Queue.sortByDescending (fun _ -> failwith "The projection must not run") + nameof Queue.sortDescending, Queue.sortDescending + nameof Queue.sortWith, Queue.sortWith (fun _ _ -> failwith "The comparer must not run") + (nameof Queue.sortWithComparer, + Queue.sortWithComparer (Comparer.Create (fun _ _ -> failwith "The comparer must not run"))) + |] + + /// The sorts that compare the elements or their keys, by name, with the Array function that sorts in the same order. + static let stringSorts : Map string array) * (Queue -> Queue))> = + Map.ofArray [| + nameof Queue.sort, struct (Array.sort, Queue.sort) + nameof Queue.sortBy, struct (Array.sortBy id, Queue.sortBy id) + nameof Queue.sortByDescending, struct (Array.sortByDescending id, Queue.sortByDescending id) + nameof Queue.sortDescending, struct (Array.sortDescending, Queue.sortDescending) + |] + + /// The functions of two queues, 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 queue. + static let commonPrefixWalks = + let short = [| 1; 2 |] + let long = [| 10; 20; 30 |] + + Map.ofArray [| + (nameof Queue.iter2, + struct ((fun (action : int -> int -> unit) -> List.iter2 action (List.ofArray short) (List.ofArray long)), + (fun (action : int -> int -> unit) -> Queue.iter2 action (Queue.ofArray short) (Queue.ofArray long)))) + (nameof Queue.map2, + struct ((fun (action : int -> int -> unit) -> + List.map2 action (List.ofArray long) (List.ofArray short) + |> ignore + ), + (fun (action : int -> int -> unit) -> + Queue.map2 action (Queue.ofArray long) (Queue.ofArray short) + |> ignore + ))) + (nameof Queue.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) -> + Queue.fold2 (fun () a b -> action a b) () (Queue.ofArray short) (Queue.ofArray long) + ))) + (nameof Queue.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) -> + Queue.foldBack2 (fun a b () -> action a b) (Queue.ofArray short) (Queue.ofArray long) () + ))) + |] + + /// The functions whose exceptions List writes with more than one line, by name, with the List function on the same + /// input. The names tell apart two inputs of the same function. + static let completeMessages = + let one = [| 1 |] + let three = [| 1; 2; 3 |] + let four = [| 1; 2; 3; 4 |] + + Map.ofArray [| + (nameof Queue.zip3, + struct ((fun () -> + List.zip3 (List.ofArray three) (List.ofArray one) (List.ofArray four) + |> ignore + ), + (fun () -> + Queue.zip3 (Queue.ofArray three) (Queue.ofArray one) (Queue.ofArray four) + |> ignore + ))) + (nameof Queue.map3, + struct ((fun () -> + List.map3 (fun a b c -> a + b + c) (List.ofArray three) (List.ofArray one) (List.ofArray four) + |> ignore + ), + (fun () -> + Queue.map3 (fun a b c -> a + b + c) (Queue.ofArray three) (Queue.ofArray one) (Queue.ofArray four) + |> ignore + ))) + (nameof Queue.zip, + struct ((fun () -> List.zip (List.ofArray four) (List.ofArray one) |> ignore), + (fun () -> Queue.zip (Queue.ofArray four) (Queue.ofArray one) |> ignore))) + (nameof Queue.foldBack2, + struct ((fun () -> + List.foldBack2 (fun _ _ state -> state) (List.ofArray one) (List.ofArray four) 0 + |> ignore + ), + (fun () -> + Queue.foldBack2 (fun _ _ state -> state) (Queue.ofArray one) (Queue.ofArray four) 0 + |> ignore + ))) + ($"%s{nameof Queue.foldBack2} of an empty first queue", + struct ((fun () -> + List.foldBack2 (fun _ _ state -> state) List.empty (List.ofArray four) 0 + |> ignore + ), + (fun () -> + Queue.foldBack2 (fun _ _ state -> state) Queue.empty (Queue.ofArray four) 0 + |> ignore + ))) + (nameof Queue.head, + struct ((fun () -> List.head List.empty |> ignore), (fun () -> Queue.head Queue.empty |> ignore))) + (nameof Queue.take, + struct ((fun () -> List.take 5 (List.ofArray three) |> ignore), + (fun () -> Queue.take 5 (Queue.ofArray three) |> ignore))) + (nameof Queue.skip, + struct ((fun () -> List.skip 5 (List.ofArray three) |> ignore), + (fun () -> Queue.skip 5 (Queue.ofArray three) |> ignore))) + (nameof Queue.skipLast, + struct ((fun () -> List.skip 5 (List.ofArray three) |> ignore), + (fun () -> Queue.skipLast 5 (Queue.ofArray three) |> ignore))) + (nameof Queue.splitAt, + struct ((fun () -> List.splitAt 5 (List.ofArray three) |> ignore), + (fun () -> Queue.splitAt 5 (Queue.ofArray three) |> ignore))) + (nameof Queue.init, struct ((fun () -> List.init -2 id |> ignore), (fun () -> Queue.init -2 id |> ignore))) + (nameof Queue.windowed, + struct ((fun () -> List.windowed 0 (List.ofArray three) |> ignore), + (fun () -> Queue.windowed 0 (Queue.ofArray three) |> ignore))) + (nameof Queue.transpose, + struct ((fun () -> + List.transpose [| List.ofArray four; List.ofArray three; List.ofArray four |] + |> ignore + ), + (fun () -> + Queue.transpose [| Queue.ofArray four; Queue.ofArray three; Queue.ofArray four |] + |> ignore + ))) + (nameof Queue.randomChoiceBy, + struct ((fun () -> + List.randomChoiceBy (fun () -> 1.5) (List.ofArray three) + |> ignore + ), + (fun () -> + Queue.randomChoiceBy (fun () -> 1.5) (Queue.ofArray three) + |> ignore + ))) + |] + + /// The names of the functions that take a queue, as the rows of their test. + static member NullQueueRejections = nullQueueRejections.Keys + + /// The names of the functions that take another input that can be null, as the rows of their test. + static member OtherNullRejections = otherNullRejections.Keys + + /// The names of the functions and the default flat lists that they reject as sequences, as the rows of their test. + static member DefaultSequenceRejections = defaultSequenceRejections.Keys + + /// The names of the cases that keep every element of a queue, as the rows of their test. + static member WholeQueues = wholeQueues.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 sorts that compare the elements or their keys, as the rows of the test of strings. + static member StringSorts = stringSorts.Keys + + /// The names of the functions of two queues, as the rows of the test of the common prefix. + static member CommonPrefixWalks = commonPrefixWalks.Keys + + /// The names of the functions whose exceptions have several lines, as the rows of the test of the messages. + static member CompleteMessages = completeMessages.Keys + + [] + member _.``functions reject a null queue`` (name : string) = + let struct (paramName, action) = nullQueueRejections[name] + + let ex = + Assert.ThrowsExactly(Action action, $"Queue.%s{name} must reject a null queue") + + Assert.AreEqual(paramName, ex.ParamName, $"Queue.%s{name} must name the parameter of the null queue") + + [] + 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, $"Queue.%s{name} must reject a null input") + + Assert.AreEqual(paramName, ex.ParamName, $"Queue.%s{name} must name the null parameter") + + [] + member _.``functions reject a default flat list given as a sequence with the names of their parameters`` (name : string) = + let struct (paramName, action) = defaultSequenceRejections[name] + FlatListAssert.RejectsNull ($"Queue.%s{name}", paramName, action) + + [] + member _.``functions take a flat list of strings that is not default as a sequence of objects`` () = + let widened = FlatListOracles.stringsAsObjects (FlatList.ofArray [| "a"; "b" |]) + let queue = Queue.ofSeq widened + + CollectionAssert.AreEqual ( + [| box "a"; box "b" |], + Queue.toArray queue, + "Queue.ofSeq must take a flat list of strings as objects" + ) + + CollectionAssert.AreEqual ( + [| box "a"; box "b"; box "a"; box "b" |], + Queue.toArray (Queue.enqueueMany widened queue), + "Queue.enqueueMany must take a flat list of strings as objects" + ) + + CollectionAssert.AreEqual ( + [| box "c" |], + Queue.toArray (Queue.except widened (Queue.ofArray [| "a"; "b"; "c" |])), + "Queue.except must take a flat list of strings as objects" + ) + + [] + member _.``the elements leave the queue in the order in which they joined it`` () = + let joined = + Queue.empty + |> Queue.enqueue 1 + |> Queue.enqueue 2 + |> Queue.enqueue 3 + + let rec drain (rest : Queue) = + match rest with + | Queue.Cons (struct (head, tail)) -> Array.append [| head |] (drain tail) + | _ -> [||] + + Assert.AreEqual(1, Queue.head joined, "Queue.head must return the element that joined first") + Assert.AreEqual(3, Queue.last joined, "Queue.last must return the element that joined last") + CollectionAssert.AreEqual ( + [| 1; 2; 3 |], + drain joined, + "Queue.tail must remove the elements in the order in which they joined" + ) + CollectionAssert.AreEqual ( + [| 1; 2; 3 |], + List.toArray (Queue.toList joined), + "Queue.toList must list the elements in the order in which they leave" + ) + + [)>] + member _.``ofSeq is the inverse of toList`` (input : int array) = + CollectionAssert.AreEqual (input, List.toArray (Queue.toList (Queue.ofSeq input)), "Queue.toList must undo Queue.ofSeq") + + [] + member _.``ofSeq of the list of a queue in both stacks rebuilds the queue`` () = + let original = mixed () + + CollectionAssert.AreEqual ( + Queue.toArray original, + Queue.toArray (Queue.ofSeq (Queue.toList original)), + "Queue.ofSeq must undo Queue.toList" + ) + + [] + member _.``ofSeq of a queue returns it without a copy`` () = + let original = mixed () + Assert.AreSame (original, Queue.ofSeq original, "Queue.ofSeq of a queue must return it") + + [] + member _.``enqueue adds the value after the last element and leaves the input unchanged`` () = + let original = several () + let enqueued = Queue.enqueue 99 original + + CollectionAssert.AreEqual ( + Array.append Inputs.Several [| 99 |], + Queue.toArray enqueued, + "Queue.enqueue must add the value at the end" + ) + + CollectionAssert.AreEqual (Inputs.Several, Queue.toArray original, "Queue.enqueue must leave the input unchanged") + + [] + member _.``enqueueMany adds the values in their order after the last element`` () = + let original = several () + + CollectionAssert.AreEqual ( + Array.append Inputs.Several [| 97; 98; 99 |], + Queue.toArray (Queue.enqueueMany [| 97; 98; 99 |] original), + "Queue.enqueueMany must add the values at the end, in their order" + ) + + CollectionAssert.AreEqual ( + [| 1; 2 |], + Queue.toArray (Queue.enqueueMany (Seq.map id [| 1; 2 |]) Queue.empty), + "Queue.enqueueMany must fill an empty queue from a lazy sequence" + ) + + Assert.AreSame (original, Queue.enqueueMany [||] original, "Queue.enqueueMany without values must return the input") + CollectionAssert.AreEqual (Inputs.Several, Queue.toArray original, "Queue.enqueueMany must leave the input unchanged") + + [] + member _.``head and tail read the queue without changing it`` () = + let original = mixed () + + Assert.AreEqual(Array.head Inputs.Several, Queue.head original, "Queue.head must return the first element") + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + Queue.toArray (Queue.tail original), + "Queue.tail must drop the first element" + ) + CollectionAssert.AreEqual (Inputs.Several, Queue.toArray original, "Queue.tail must leave the input unchanged") + + [] + member _.``uncons returns the first element and the other elements`` () = + let struct (head, tail) = Queue.uncons (mixed ()) + Assert.AreEqual(Array.head Inputs.Several, head, "Queue.uncons must return the first element") + CollectionAssert.AreEqual (Array.tail Inputs.Several, Queue.toArray tail, "Queue.uncons must return the other elements") + + [] + member _.``uncons throws for an empty queue as List.head does`` () = + ExceptionAssert.Same ( + QueueOps.paramName, + (fun () -> List.head List.empty |> ignore), + (fun () -> Queue.uncons Queue.empty |> ignore), + "Queue.uncons of an empty queue must throw as List.head does" + ) + + [] + member _.``tryUncons returns the first element and the other elements or nothing`` () = + match Queue.tryUncons (mixed ()) with + | ValueSome (struct (head, tail)) -> + Assert.AreEqual(Array.head Inputs.Several, head, "Queue.tryUncons must return the first element") + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + Queue.toArray tail, + "Queue.tryUncons must return the other elements" + ) + | ValueNone -> Assert.Fail "Queue.tryUncons must return the elements of a queue with elements" + + Assert.IsTrue ((Queue.tryUncons Queue.empty).IsNone, "Queue.tryUncons of an empty queue must return nothing") + + [] + member _.``exactlyOne tells one element from two in different stacks`` () = + // The first enqueued element goes to the front stack of an empty queue, the second one to the back stack + let two = Queue.empty |> Queue.enqueue 1 |> Queue.enqueue 2 + let one = Queue.tail two + + Assert.AreEqual( + ValueNone, + Queue.tryExactlyOne two, + "Queue.tryExactlyOne of two elements must return nothing" + ) + Assert.AreEqual(ValueSome 2, Queue.tryExactlyOne one, "Queue.tryExactlyOne of one element must return it") + Assert.AreEqual(2, Queue.exactlyOne one, "Queue.exactlyOne of one element must return it") + + ExceptionAssert.Same ( + QueueOps.paramName, + (fun () -> List.exactlyOne [ 1; 2 ] |> ignore), + (fun () -> Queue.exactlyOne two |> ignore), + "Queue.exactlyOne of two elements must throw as List.exactlyOne does" + ) + + [] + member _.``Cons matches the first element and the other elements`` () = + let rec sum (rest : Queue) = + match rest with + | Queue.Cons (struct (head, tail)) -> head + sum tail + | _ -> 0 + + Assert.AreEqual(Array.sum Inputs.Several, sum (mixed ()), "Queue.Cons must match every element in turn") + + match Queue.empty with + | Queue.Cons _ -> Assert.Fail "Queue.Cons must not match an empty queue" + | _ -> () + + [] + member _.``Nil matches an empty queue only`` () = + let describe (rest : Queue) = + match rest with + | Queue.Nil -> "empty" + | Queue.Cons (struct (head, _)) -> $"starts with %d{head}" + | _ -> "unreachable" + + Assert.AreEqual("empty", describe Queue.empty, "Queue.Nil must match an empty queue") + Assert.AreEqual("starts with 5", describe (several ()), "Queue.Nil must not match a queue with elements") + + [] + member _.``the functions read the elements of both stacks of a queue in order`` () = + let both = mixed () + let elements = Inputs.Several + let expectedBefore, expectedAfter = Array.splitAt 3 elements + let struct (before, after) = Queue.splitAt 3 both + + CollectionAssert.AreEqual ( + elements, + List.toArray (Queue.toList both), + "Queue.toList must list the front stack and then the back stack" + ) + + CollectionAssert.AreEqual (elements, Queue.toArray both, "Queue.toArray must copy both stacks in order") + Assert.AreEqual(elements.Length, Queue.length both, "Queue.length must count both stacks") + Assert.AreEqual(Array.last elements, Queue.last both, "Queue.last must return the last element of the back stack") + CollectionAssert.AreEqual (Array.rev elements, Queue.toArray (Queue.rev both), "Queue.rev must reverse both stacks") + CollectionAssert.AreEqual ( + Array.skip 3 elements, + Queue.toArray (Queue.skip 3 both), + "Queue.skip must dequeue into the back stack" + ) + CollectionAssert.AreEqual ( + Array.take 3 elements, + Queue.toArray (Queue.take 3 both), + "Queue.take must take from the front stack" + ) + CollectionAssert.AreEqual ( + Array.skip 5 elements, + Queue.toArray (Queue.takeLast 3 both), + "Queue.takeLast must take from the back stack" + ) + CollectionAssert.AreEqual ( + Array.take 5 elements, + Queue.toArray (Queue.skipLast 3 both), + "Queue.skipLast must skip from the back stack" + ) + CollectionAssert.AreEqual (expectedBefore, Queue.toArray before, "Queue.splitAt must return the first part") + CollectionAssert.AreEqual (expectedAfter, Queue.toArray after, "Queue.splitAt must return the second part") + CollectionAssert.AreEqual (Array.sort elements, Queue.toArray (Queue.sort both), "Queue.sort must sort both stacks") + + CollectionAssert.AreEqual ( + Array.foldBack (fun item state -> Array.append state [| item |]) elements [||], + Queue.foldBack (fun item state -> Array.append state [| item |]) both [||], + "Queue.foldBack must visit both stacks from the last element" + ) + + [] + member _.``functions return the input itself when they keep every element`` (name : string) = + let original = mixed () + Assert.AreSame (original, wholeQueues[name] original, $"Queue.%s{name} must return the input when it keeps every element") + + [] + member _.``sorts return a queue of one element itself, as List does`` (name : string) = + let single = Queue.singleton 1 + Assert.AreSame (single, singleElementSorts[name] single, $"Queue.%s{name} of one element must return the input") + + [] + member _.``sorts order strings in F# generic comparison, which is ordinal`` (name : string) = + // A comparison under a culture puts "a" before "A" and both before "b"; F# generic comparison compares ordinally + let elements = [| "b"; "A"; "a"; "B" |] + let struct (oracle, sort) = stringSorts[name] + + CollectionAssert.AreEqual ( + oracle elements, + Queue.toArray (sort (Queue.ofArray elements)), + $"Queue.%s{name} must order strings in F# generic comparison, as the List function does" + ) + + [] + member _.``contains compares in F# structural equality`` () = + Assert.IsTrue ( + Queue.contains [| 1; 2 |] (Queue.singleton [| 1; 2 |]), + "Queue.contains must compare arrays by their elements, as List.contains does" + ) + + Assert.AreEqual( + List.contains Double.NaN [ Double.NaN ], + Queue.contains Double.NaN (Queue.singleton Double.NaN), + "Queue.contains must compare NaN as List.contains does" + ) + + [] + member _.``map and mapi call the mapping in the order of the elements`` () = + let original = Queue.init 100 id + let visited = ResizeArray() + Queue.map visited.Add original |> ignore + CollectionAssert.AreEqual (Array.init 100 id, visited.ToArray (), "Queue.map must call the mapping in order") + + visited.Clear () + Queue.mapi (fun _ item -> visited.Add item) original + |> ignore + CollectionAssert.AreEqual (Array.init 100 id, visited.ToArray (), "Queue.mapi must call the mapping in order") + + [] + member _.``the functions of two queues 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, + $"Queue.%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), + Queue.exists2 (=) (Queue.ofArray short) (Queue.ofArray long), + "Queue.exists2 must return true for a match before the end of the shorter queue" + ) + + Assert.AreEqual( + List.forall2 (=) (List.ofArray short) (List.ofArray long), + Queue.forall2 (=) (Queue.ofArray short) (Queue.ofArray long), + "Queue.forall2 must return false for a mismatch before the end of the shorter queue" + ) + + [] + member _.``except does not read the items to exclude for an empty queue, as List does`` () = + let items = seq { failwith "The items to exclude must not be read" } + Assert.IsEmpty (Queue.except items Queue.empty, "Queue.except of an empty queue must be empty") + + [] + member _.``transpose names the queue that ends first as List names the list`` () = + ExceptionAssert.Same ( + QueueOps.paramName, + (fun () -> List.transpose [| [ 1 ]; [ 1; 2 ] |] |> ignore), + (fun () -> + Queue.transpose [| Queue.ofArray [| 1 |]; Queue.ofArray [| 1; 2 |] |] + |> ignore + ), + "Queue.transpose of a shorter first queue must throw as List.transpose does" + ) + + [] + member _.``splitAt of an empty queue throws as List.splitAt does`` () = + ExceptionAssert.Same ( + QueueOps.paramName, + (fun () -> List.splitAt 1 List.empty |> ignore), + (fun () -> Queue.splitAt 1 Queue.empty |> ignore), + "Queue.splitAt 1 of an empty queue must throw as List.splitAt does" + ) + + [] + member _.``the exceptions have the complete messages of List with the names of the queues`` (name : string) = + let struct (oracle, actual) = completeMessages[name] + let expected = thrown oracle $"List.%s{name} must throw" + let ex = thrown actual $"Queue.%s{name} must throw" + Assert.AreEqual(expected.GetType (), ex.GetType (), $"Queue.%s{name} must throw the type of exception of List") + + Assert.AreEqual( + QueueOps.message expected.Message, + ex.Message, + $"Queue.%s{name} must throw the message of List with the names of the queues" + ) + + [] + [] + [] + [] + [] + [] + [] + [] + [] + member _.``the queue functions compile with the names of the queue operations of the BCL`` + (name : string, compiledName : string) + = + Assert.AreEqual( + name, + QueueOps.sourceName "FSharp.Collections.Immutable.Queue" compiledName, + "Queue must compile the function under the name of its queue operation in the BCL" + ) diff --git a/tests/FSharp.Collections.Immutable.Tests/Queue/QueueTupleTests.fs b/tests/FSharp.Collections.Immutable.Tests/Queue/QueueTupleTests.fs new file mode 100644 index 00000000..070af737 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Queue/QueueTupleTests.fs @@ -0,0 +1,285 @@ +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 `Queue` module of the `Tuple` namespace. +[] +type QueueTupleTests () = + + static let several = Queue.ofArray Inputs.Several + + static let other = Queue.ofArray Inputs.Other + + static let third = Queue.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 : Queue = Queue.unfold generator 3 + CollectionAssert.AreEqual ([| 3; 2; 1 |], Queue.toArray generated, "Tuple.Queue.unfold must generate the elements") + + [] + member _.``tryUncons returns a value option of a reference tuple`` () = + let found : (int * Queue) voption = Queue.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.Queue.tryUncons must return the first element") + + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + Queue.toArray tail, + "Tuple.Queue.tryUncons must return the other elements" + ) + | ValueNone -> Assert.Fail "Tuple.Queue.tryUncons must return the elements of a queue with elements" + + [] + member _.``uncons returns a reference tuple`` () = + let (head : int), (tail : Queue) = Queue.uncons several + Assert.AreEqual(Array.head Inputs.Several, head, "Tuple.Queue.uncons must return the first element") + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + Queue.toArray tail, + "Tuple.Queue.uncons must return the other elements" + ) + + [] + member _.``uncons throws as the default does`` () = + ExceptionAssert.Same ( + id, + (fun () -> + FSharp.Collections.Immutable.Queue.uncons Queue.empty + |> ignore + ), + (fun () -> Queue.uncons Queue.empty |> ignore), + "Tuple.Queue.uncons must throw as the default uncons does" + ) + + [] + member _.``Cons is a struct partial pattern of reference tuples`` () = + let pattern : Queue -> (int * Queue) voption = Queue.(|Cons|_|) + + match several with + | Queue.Cons (head, tail) -> + Assert.AreEqual(Array.head Inputs.Several, head, "Tuple.Queue.Cons must match the first element") + CollectionAssert.AreEqual ( + Array.tail Inputs.Several, + Queue.toArray tail, + "Tuple.Queue.Cons must match the other elements" + ) + | _ -> Assert.Fail "Tuple.Queue.Cons must match a queue with elements" + + Assert.IsTrue ((pattern Queue.empty).IsNone, "Tuple.Queue.Cons must not match an empty queue") + + [] + member _.``allPairs returns reference tuples`` () = + let pairs : Queue = Queue.allPairs several other + + CollectionAssert.AreEqual ( + Array.allPairs Inputs.Several Inputs.Other, + Queue.toArray pairs, + "Tuple.Queue.allPairs must pair as Array.allPairs does" + ) + + [] + member _.``indexed returns reference tuples`` () = + let pairs : Queue = Queue.indexed several + + CollectionAssert.AreEqual ( + Array.indexed Inputs.Several, + Queue.toArray pairs, + "Tuple.Queue.indexed must index as Array.indexed does" + ) + + [] + member _.``pairwise returns reference tuples`` () = + let pairs : Queue = Queue.pairwise several + + CollectionAssert.AreEqual ( + Array.pairwise Inputs.Several, + Queue.toArray pairs, + "Tuple.Queue.pairwise must pair as Array.pairwise does" + ) + + [] + member _.``unzip takes and returns reference tuples`` () = + let (first : Queue), (second : Queue) = Queue.unzip (Queue.zip several other) + CollectionAssert.AreEqual (Inputs.Several, Queue.toArray first, "Tuple.Queue.unzip must return the first components") + CollectionAssert.AreEqual (Inputs.Other, Queue.toArray second, "Tuple.Queue.unzip must return the second components") + + [] + member _.``unzip3 takes and returns reference tuples`` () = + let (first : Queue), (second : Queue), (last : Queue) = + Queue.unzip3 (Queue.zip3 several other third) + + CollectionAssert.AreEqual (Inputs.Several, Queue.toArray first, "Tuple.Queue.unzip3 must return the first components") + CollectionAssert.AreEqual (Inputs.Other, Queue.toArray second, "Tuple.Queue.unzip3 must return the second components") + CollectionAssert.AreEqual (Inputs.Third, Queue.toArray last, "Tuple.Queue.unzip3 must return the third components") + + [] + member _.``zip returns reference tuples`` () = + let pairs : Queue = Queue.zip several other + + CollectionAssert.AreEqual ( + Array.zip Inputs.Several Inputs.Other, + Queue.toArray pairs, + "Tuple.Queue.zip must zip as Array.zip does" + ) + + [] + member _.``zip and zip3 throw for different lengths as the defaults do`` () = + let short = Queue.singleton 1 + + ExceptionAssert.Same ( + id, + (fun () -> + FSharp.Collections.Immutable.Queue.zip several short + |> ignore + ), + (fun () -> Queue.zip several short |> ignore), + "Tuple.Queue.zip must throw as the default zip does" + ) + + ExceptionAssert.Same ( + id, + (fun () -> + FSharp.Collections.Immutable.Queue.zip3 several short other + |> ignore + ), + (fun () -> Queue.zip3 several short other |> ignore), + "Tuple.Queue.zip3 must throw as the default zip3 does" + ) + + [] + member _.``zip3 returns reference tuples`` () = + let triples : Queue = Queue.zip3 several other third + + CollectionAssert.AreEqual ( + Array.zip3 Inputs.Several Inputs.Other Inputs.Third, + Queue.toArray triples, + "Tuple.Queue.zip3 must zip as Array.zip3 does" + ) + + [] + member _.``mapFold takes and returns reference tuples`` () = + let (results : Queue), (state : int) = + Queue.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, Queue.toArray results, "Tuple.Queue.mapFold must map as Array.mapFold does") + Assert.AreEqual(expectedState, state, "Tuple.Queue.mapFold must thread the state as Array.mapFold does") + + [] + member _.``mapFoldBack takes and returns reference tuples`` () = + let (results : Queue), (state : int) = + Queue.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, + Queue.toArray results, + "Tuple.Queue.mapFoldBack must map as Array.mapFoldBack does" + ) + Assert.AreEqual(expectedState, state, "Tuple.Queue.mapFoldBack must thread the state as Array.mapFoldBack does") + + [] + member _.``countBy returns reference tuples`` () = + let counts : Queue = Queue.countBy (fun item -> item % 3) several + + CollectionAssert.AreEqual ( + Array.countBy (fun item -> item % 3) Inputs.Several, + Queue.toArray counts, + "Tuple.Queue.countBy must count as Array.countBy does" + ) + + [] + member _.``groupBy returns reference tuples`` () = + let groups : Queue> = Queue.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 |> Queue.toArray |> Array.map fst, + "Tuple.Queue.groupBy must return the keys as List.groupBy does" + ) + + CollectionAssert.AreEqual ( + Array.map snd expected, + groups + |> Queue.toArray + |> Array.map (fun (_, items) -> Queue.toArray items), + "Tuple.Queue.groupBy must group the elements as List.groupBy does" + ) + + [] + member _.``partition returns a reference tuple`` () = + let (accepted : Queue), (rejected : Queue) = Queue.partition (fun item -> item > 0) several + let expectedAccepted, expectedRejected = Array.partition (fun item -> item > 0) Inputs.Several + CollectionAssert.AreEqual ( + expectedAccepted, + Queue.toArray accepted, + "Tuple.Queue.partition must return the accepted elements" + ) + CollectionAssert.AreEqual ( + expectedRejected, + Queue.toArray rejected, + "Tuple.Queue.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 : Queue), (second : Queue) = Queue.partitionWith partitioner several + let expectedFirst, expectedSecond = Array.partitionWith partitioner Inputs.Several + CollectionAssert.AreEqual (expectedFirst, Queue.toArray first, "Tuple.Queue.partitionWith must return the first choices") + CollectionAssert.AreEqual ( + expectedSecond, + Queue.toArray second, + "Tuple.Queue.partitionWith must return the second choices" + ) + + [] + member _.``splitAt returns a reference tuple`` () = + let (before : Queue), (after : Queue) = Queue.splitAt 3 several + let expectedBefore, expectedAfter = Array.splitAt 3 Inputs.Several + CollectionAssert.AreEqual (expectedBefore, Queue.toArray before, "Tuple.Queue.splitAt must return the first part") + CollectionAssert.AreEqual (expectedAfter, Queue.toArray after, "Tuple.Queue.splitAt must return the second part") + + [] + member _.``the functions without a tuple keep resolving to the default module`` () = + let found : int voption = Queue.tryFind (fun item -> item > 6) several + Assert.AreEqual(ValueSome 8, found, "Queue.tryFind must resolve to the default module") + + [] + [] + [] + member _.``the queue functions compile with the names of the queue operations of the BCL`` + (name : string, compiledName : string) + = + Assert.AreEqual( + name, + QueueOps.sourceName "FSharp.Collections.Immutable.Tuple.Queue" compiledName, + "Tuple.Queue must compile the function under the name of its queue operation in the BCL" + ) From 931e95e07e399efb80bb3cc8c0bdf5d87f9d94f2 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 18 Oct 2026 14:08:25 +0200 Subject: [PATCH 3/3] docs: `Queue` and `IQueue` in the changelog and the collection table The changelog lists the new functions of `Queue`, the new `IQueue` module, the `Option` and `Tuple` modules and `queue { }`, the breaking changes of the `List` semantics, the queue type of the module and the patterns, the fixed `forall` and `averageBy`, and the removed `clear` and `ImmutableQueue` alias. The collection tables name `queue { }` for `Queue` and add `IQueue`. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ README.md | 3 ++- docsSrc/Explanations/Background.md | 3 ++- docsSrc/index.md | 3 ++- 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 029a7817..61f2434c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,22 @@ 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. +* `Queue` has every function of the FSharp.Core `List` module that applies to an immutable queue, over the elements in the + order in which they leave the queue, with its semantics and exceptions and with stable sorts, plus `enqueueMany`, + `ofReadOnlySpan`, `ofSpan`, `skipLast`, `sortWithComparer`, `takeLast`, `tryTail`, `tryUncons` and `uncons`. Its + parameters and exceptions name the queues `queue`, `queue1`, `queue2`, `queue3` and `queues` where `List` names its + lists. `enqueueMany`, `uncons`, `tryHead`, `tryTail` and `tryUncons` compile to `EnqueueMany`, `PeekDequeue`, + `TryPeek`, `TryDequeue` and `TryPeekDequeue`, in the `IQueue`, `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 the BCL would reject with `InvalidOperationException` while it enumerates it; the `IQueue` + module and the `queue { }` expression do the same. +* The `IQueue` module has the functions of `Queue` for `IImmutableQueue<'T>`, without the creation functions, plus + `clear`. Its functions keep the implementation of the input queue where the result has the element type of the input, + and return a `Queue` otherwise. `append` enqueues the second queue into the first one, so its result keeps the + implementation of the first queue, also when that queue is empty. +* The `FSharp.Collections.Immutable.Option` and `FSharp.Collections.Immutable.Tuple` namespaces have `Queue` and `IQueue` + modules that repeat the functions that use value options or struct tuples with options and reference tuples. +* `queue { }` builds a queue. ### Changed @@ -124,6 +140,19 @@ 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. +* `Queue` works on `Queue<'T>` (`ImmutableQueue<'T>`) instead of `IQueue<'T>`, and `enqueue`, `tail` and `filter` return a + `Queue<'T>`; the functions for `IImmutableQueue<'T>` are in the new `IQueue` module. +* `Queue`: `tryFind`, `tryFindIndex` and `tryPick` return `voption`, and `pick` and `tryPick` take functions that return + `voption`. The complete active pattern `Cons | Nil`, whose `Cons` case matched a reference tuple of the head and the + tail, is replaced by the struct partial active patterns `Cons`, which matches a struct tuple of the head and the tail, + and `Nil`. +* `Queue`: every function throws what the FSharp.Core `List` function of the same name throws. `head` and `tail` of an + empty queue throw `ArgumentException`, where they threw `InvalidOperationException`, and `iter2` of queues of + different lengths throws `ArgumentException`, where it stopped at the shorter queue. A default `FlatList` given as a + sequence to `ofSeq` throws `ArgumentNullException`, where it threw `InvalidOperationException`. +* `Queue.forall` and `Queue.averageBy` apply to the queue; they returned a function of a sequence before. +* `Queue`: `enqueue`, `head` and `tail` compile to `Enqueue`, `Peek` and `Dequeue`, and the other functions to + PascalCase names such as `IsEmpty`, where 1.0.0 compiled all of them under their F# names, such as `isEmpty`. ### Removed @@ -161,6 +190,7 @@ 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. +* `Queue.clear`: use `Queue.empty`, or `IQueue.clear` to keep the implementation of an interface value. ## [1.0.0] - 2020-02-23 diff --git a/README.md b/README.md index 239ef746..46712352 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ Please also join the [F# Open Source Group](http://fsharp.github.com) | `IndexedList` | `ImmutableList<'T>` | `indexedList { }` | | `IIndexedList` | `IImmutableList<'T>` | – | | `Stack` | `ImmutableStack<'T>` | – | -| `Queue` | `ImmutableQueue<'T>` | – | +| `Queue` | `ImmutableQueue<'T>` | `queue { }` | +| `IQueue` | `IImmutableQueue<'T>` | – | | `HashMap` | `ImmutableDictionary<'Key, 'Value>` | – | | `SortedMap` | `ImmutableSortedDictionary<'Key, 'Value>` | – | | `HashSet` | `ImmutableHashSet<'T>` | – | diff --git a/docsSrc/Explanations/Background.md b/docsSrc/Explanations/Background.md index 21f275db..57231e47 100644 --- a/docsSrc/Explanations/Background.md +++ b/docsSrc/Explanations/Background.md @@ -30,7 +30,8 @@ While F# has its own built-in immutable collections, System.Collections.Immutabl | `IndexedList` | `ImmutableList<'T>` | `indexedList { }` | | `IIndexedList` | `IImmutableList<'T>` | – | | `Stack` | `ImmutableStack<'T>` | – | -| `Queue` | `ImmutableQueue<'T>` | – | +| `Queue` | `ImmutableQueue<'T>` | `queue { }` | +| `IQueue` | `IImmutableQueue<'T>` | – | | `HashMap` | `ImmutableDictionary<'Key, 'Value>` | – | | `SortedMap` | `ImmutableSortedDictionary<'Key, 'Value>` | – | | `HashSet` | `ImmutableHashSet<'T>` | – | diff --git a/docsSrc/index.md b/docsSrc/index.md index 8fd0c476..6cc7d1f3 100644 --- a/docsSrc/index.md +++ b/docsSrc/index.md @@ -12,7 +12,8 @@ F# bindings for [System.Collections.Immutable](https://learn.microsoft.com/en-us | `IndexedList` | `ImmutableList<'T>` | `indexedList { }` | | `IIndexedList` | `IImmutableList<'T>` | – | | `Stack` | `ImmutableStack<'T>` | – | -| `Queue` | `ImmutableQueue<'T>` | – | +| `Queue` | `ImmutableQueue<'T>` | `queue { }` | +| `IQueue` | `IImmutableQueue<'T>` | – | | `HashMap` | `ImmutableDictionary<'Key, 'Value>` | – | | `SortedMap` | `ImmutableSortedDictionary<'Key, 'Value>` | – | | `HashSet` | `ImmutableHashSet<'T>` | – |