diff --git a/README.md b/README.md
index 654caea..7fe7a35 100644
--- a/README.md
+++ b/README.md
@@ -342,18 +342,18 @@ This is what has been implemented so far, is planned or skipped:
| ✅ [#236][]| `removeAt` | `removeAt` | | |
| ✅ [#236][]| `removeManyAt` | `removeManyAt` | | |
| ✅ | `replicate` | `replicate` | | |
-| ❓ | `rev` | | | [note #1](#note1 "These functions require a form of pre-materializing through 'TaskSeq.cache', similar to the approach taken in the corresponding 'Seq' functions. It doesn't make much sense to have a cached async sequence. However, 'AsyncSeq' does implement these, so we'll probably do so eventually as well.") |
+| ✅ | `rev` | `rev` | | |
| ✅ [#296][] | `scan` | `scan` | `scanAsync` | |
| 🚫 | `scanBack` | | | [note #2](#note2 "Because of the async nature of TaskSeq sequences, iterating from the back would be bad practice. Instead, materialize the sequence to a list or array and then apply the 'Back' iterators.") |
| ✅ [#90][] | `singleton` | `singleton` | | |
| ✅ [#209][]| `skip` | `skip` | | |
| ✅ [#219][]| `skipWhile` | `skipWhile` | `skipWhileAsync` | |
| ✅ [#219][]| | `skipWhileInclusive` | `skipWhileInclusiveAsync` | |
-| ❓ | `sort` | | | [note #1](#note1 "These functions require a form of pre-materializing through 'TaskSeq.cache', similar to the approach taken in the corresponding 'Seq' functions. It doesn't make much sense to have a cached async sequence. However, 'AsyncSeq' does implement these, so we'll probably do so eventually as well.") |
-| ❓ | `sortBy` | | | [note #1](#note1 "These functions require a form of pre-materializing through 'TaskSeq.cache', similar to the approach taken in the corresponding 'Seq' functions. It doesn't make much sense to have a cached async sequence. However, 'AsyncSeq' does implement these, so we'll probably do so eventually as well.") |
-| ❓ | `sortByAscending` | | | [note #1](#note1 "These functions require a form of pre-materializing through 'TaskSeq.cache', similar to the approach taken in the corresponding 'Seq' functions. It doesn't make much sense to have a cached async sequence. However, 'AsyncSeq' does implement these, so we'll probably do so eventually as well.") |
-| ❓ | `sortByDescending` | | | [note #1](#note1 "These functions require a form of pre-materializing through 'TaskSeq.cache', similar to the approach taken in the corresponding 'Seq' functions. It doesn't make much sense to have a cached async sequence. However, 'AsyncSeq' does implement these, so we'll probably do so eventually as well.") |
-| ❓ | `sortWith` | | | [note #1](#note1 "These functions require a form of pre-materializing through 'TaskSeq.cache', similar to the approach taken in the corresponding 'Seq' functions. It doesn't make much sense to have a cached async sequence. However, 'AsyncSeq' does implement these, so we'll probably do so eventually as well.") |
+| ✅ | `sort` | `sort` | `sortDescending` | |
+| ✅ | `sortBy` | `sortBy` | `sortByAsync` | |
+| ✅ | `sortByAscending` | | | (use `sortBy`) |
+| ✅ | `sortByDescending` | `sortByDescending` | `sortByDescendingAsync` | |
+| ✅ | `sortWith` | `sortWith` | | |
| | `splitInto` | `splitInto` | | |
| | `sum` | `sum` | | |
| | `sumBy` | `sumBy` | `sumByAsync` | |
diff --git a/release-notes.txt b/release-notes.txt
index 8629e32..b2a4a1f 100644
--- a/release-notes.txt
+++ b/release-notes.txt
@@ -19,6 +19,7 @@ Release notes:
- adds TaskSeq.unfold and TaskSeq.unfoldAsync, #289
- adds TaskSeq.chunkBySize (closes #258) and TaskSeq.windowed, #289
- fixes: CancellationToken passed to GetAsyncEnumerator is now honored in MoveNextAsync, #179
+ - adds TaskSeq.rev, sort, sortDescending, sortBy, sortByDescending, sortWith, sortByAsync, sortByDescendingAsync
0.5.0
- update engineering to .NET 9/10
diff --git a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj
index 8ea06c5..e10feb2 100644
--- a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj
+++ b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj
@@ -61,6 +61,7 @@
+
diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Sort.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Sort.Tests.fs
new file mode 100644
index 0000000..e15b80e
--- /dev/null
+++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Sort.Tests.fs
@@ -0,0 +1,342 @@
+module TaskSeq.Tests.Sort
+
+open Xunit
+open FsUnit.Xunit
+
+open FSharp.Control
+
+//
+// TaskSeq.rev
+// TaskSeq.sort / sortDescending
+// TaskSeq.sortBy / sortByDescending / sortByAsync / sortByDescendingAsync
+// TaskSeq.sortWith
+//
+
+module RevEmpty =
+ []
+ let ``TaskSeq-rev with null source raises`` () = assertNullArg <| fun () -> TaskSeq.rev null
+
+ [)>]
+ let ``TaskSeq-rev on empty returns empty`` variant = Gen.getEmptyVariant variant |> TaskSeq.rev |> verifyEmpty
+
+ []
+ let ``TaskSeq-rev on singleton returns singleton`` () = task {
+ let! result = taskSeq { yield 42 } |> TaskSeq.rev |> TaskSeq.toListAsync
+ result |> should equal [ 42 ]
+ }
+
+module RevImmutable =
+ []
+ let ``TaskSeq-rev reverses a simple list`` () = task {
+ let! result =
+ taskSeq { yield! [ 1..5 ] }
+ |> TaskSeq.rev
+ |> TaskSeq.toListAsync
+
+ result |> should equal [ 5; 4; 3; 2; 1 ]
+ }
+
+ []
+ let ``TaskSeq-rev on two elements swaps them`` () = task {
+ let! result =
+ taskSeq { yield! [ 10; 20 ] }
+ |> TaskSeq.rev
+ |> TaskSeq.toListAsync
+
+ result |> should equal [ 20; 10 ]
+ }
+
+ []
+ let ``TaskSeq-rev is idempotent when applied twice`` () = task {
+ let original = [ 1..7 ]
+
+ let! result =
+ taskSeq { yield! original }
+ |> TaskSeq.rev
+ |> TaskSeq.rev
+ |> TaskSeq.toListAsync
+
+ result |> should equal original
+ }
+
+ [)>]
+ let ``TaskSeq-rev all variants yields elements in reverse order`` variant = task {
+ let! result =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.rev
+ |> TaskSeq.toListAsync
+
+ result |> should equal [ 10; 9; 8; 7; 6; 5; 4; 3; 2; 1 ]
+ }
+
+module SortEmpty =
+ []
+ let ``TaskSeq-sort with null source raises`` () = assertNullArg <| fun () -> TaskSeq.sort null
+
+ [)>]
+ let ``TaskSeq-sort on empty returns empty`` variant = Gen.getEmptyVariant variant |> TaskSeq.sort |> verifyEmpty
+
+ []
+ let ``TaskSeq-sortDescending with null source raises`` () = assertNullArg <| fun () -> TaskSeq.sortDescending null
+
+ [)>]
+ let ``TaskSeq-sortDescending on empty returns empty`` variant =
+ Gen.getEmptyVariant variant
+ |> TaskSeq.sortDescending
+ |> verifyEmpty
+
+module SortImmutable =
+ []
+ let ``TaskSeq-sort already-sorted sequence`` () = task {
+ let! result =
+ taskSeq { yield! [ 1..5 ] }
+ |> TaskSeq.sort
+ |> TaskSeq.toListAsync
+
+ result |> should equal [ 1; 2; 3; 4; 5 ]
+ }
+
+ []
+ let ``TaskSeq-sort unsorted sequence`` () = task {
+ let! result =
+ taskSeq { yield! [ 3; 1; 4; 1; 5; 9; 2; 6 ] }
+ |> TaskSeq.sort
+ |> TaskSeq.toListAsync
+
+ result |> should equal [ 1; 1; 2; 3; 4; 5; 6; 9 ]
+ }
+
+ []
+ let ``TaskSeq-sort reverse-sorted sequence`` () = task {
+ let! result =
+ taskSeq { yield! [ 5; 4; 3; 2; 1 ] }
+ |> TaskSeq.sort
+ |> TaskSeq.toListAsync
+
+ result |> should equal [ 1; 2; 3; 4; 5 ]
+ }
+
+ []
+ let ``TaskSeq-sort strings`` () = task {
+ let! result =
+ taskSeq { yield! [ "banana"; "apple"; "cherry" ] }
+ |> TaskSeq.sort
+ |> TaskSeq.toListAsync
+
+ result |> should equal [ "apple"; "banana"; "cherry" ]
+ }
+
+ []
+ let ``TaskSeq-sortDescending unsorted sequence`` () = task {
+ let! result =
+ taskSeq { yield! [ 3; 1; 4; 1; 5; 9; 2; 6 ] }
+ |> TaskSeq.sortDescending
+ |> TaskSeq.toListAsync
+
+ result |> should equal [ 9; 6; 5; 4; 3; 2; 1; 1 ]
+ }
+
+ []
+ let ``TaskSeq-sortDescending is inverse of sort`` () = task {
+ let! asc =
+ taskSeq { yield! [ 5; 1; 3; 2; 4 ] }
+ |> TaskSeq.sort
+ |> TaskSeq.toListAsync
+
+ let! desc =
+ taskSeq { yield! [ 5; 1; 3; 2; 4 ] }
+ |> TaskSeq.sortDescending
+ |> TaskSeq.toListAsync
+
+ desc |> List.rev |> should equal asc
+ }
+
+module SortByEmpty =
+ []
+ let ``TaskSeq-sortBy with null source raises`` () = assertNullArg <| fun () -> TaskSeq.sortBy id null
+
+ [)>]
+ let ``TaskSeq-sortBy on empty returns empty`` variant =
+ Gen.getEmptyVariant variant
+ |> TaskSeq.sortBy id
+ |> verifyEmpty
+
+ []
+ let ``TaskSeq-sortByDescending with null source raises`` () = assertNullArg <| fun () -> TaskSeq.sortByDescending id null
+
+ [)>]
+ let ``TaskSeq-sortByDescending on empty returns empty`` variant =
+ Gen.getEmptyVariant variant
+ |> TaskSeq.sortByDescending id
+ |> verifyEmpty
+
+ []
+ let ``TaskSeq-sortByAsync with null source raises`` () =
+ assertNullArg
+ <| fun () -> TaskSeq.sortByAsync (fun x -> task { return x }) null
+
+ [)>]
+ let ``TaskSeq-sortByAsync on empty returns empty`` variant =
+ Gen.getEmptyVariant variant
+ |> TaskSeq.sortByAsync (fun x -> task { return x })
+ |> verifyEmpty
+
+ []
+ let ``TaskSeq-sortByDescendingAsync with null source raises`` () =
+ assertNullArg
+ <| fun () -> TaskSeq.sortByDescendingAsync (fun x -> task { return x }) null
+
+ [)>]
+ let ``TaskSeq-sortByDescendingAsync on empty returns empty`` variant =
+ Gen.getEmptyVariant variant
+ |> TaskSeq.sortByDescendingAsync (fun x -> task { return x })
+ |> verifyEmpty
+
+module SortByImmutable =
+ []
+ let ``TaskSeq-sortBy ascending by negative key reverses order`` () = task {
+ let! result =
+ taskSeq { yield! [ 1..5 ] }
+ |> TaskSeq.sortBy (fun x -> -x)
+ |> TaskSeq.toListAsync
+
+ result |> should equal [ 5; 4; 3; 2; 1 ]
+ }
+
+ []
+ let ``TaskSeq-sortBy sorts record fields correctly`` () = task {
+ let items = [ {| Name = "Charlie"; Age = 30 |}; {| Name = "Alice"; Age = 25 |}; {| Name = "Bob"; Age = 35 |} ]
+
+ let! byName =
+ taskSeq { yield! items }
+ |> TaskSeq.sortBy (fun x -> x.Name)
+ |> TaskSeq.toListAsync
+
+ let! byAge =
+ taskSeq { yield! items }
+ |> TaskSeq.sortBy (fun x -> x.Age)
+ |> TaskSeq.toListAsync
+
+ byName
+ |> List.map (fun x -> x.Name)
+ |> should equal [ "Alice"; "Bob"; "Charlie" ]
+
+ byAge
+ |> List.map (fun x -> x.Age)
+ |> should equal [ 25; 30; 35 ]
+ }
+
+ []
+ let ``TaskSeq-sortByDescending ascending by negative key gives ascending order`` () = task {
+ let! result =
+ taskSeq { yield! [ 3; 1; 4; 1; 5 ] }
+ |> TaskSeq.sortByDescending (fun x -> -x)
+ |> TaskSeq.toListAsync
+
+ result |> should equal [ 1; 1; 3; 4; 5 ]
+ }
+
+ []
+ let ``TaskSeq-sortByDescending sorts in descending order by projected key`` () = task {
+ let! result =
+ taskSeq { yield! [ "bb"; "aaa"; "c" ] }
+ |> TaskSeq.sortByDescending (fun s -> s.Length)
+ |> TaskSeq.toListAsync
+
+ result
+ |> List.map (fun s -> s.Length)
+ |> should equal [ 3; 2; 1 ]
+ }
+
+ []
+ let ``TaskSeq-sortByAsync yields same result as sortBy for identity async`` () = task {
+ let input = [ 5; 3; 1; 4; 2 ]
+
+ let! sync =
+ taskSeq { yield! input }
+ |> TaskSeq.sortBy id
+ |> TaskSeq.toListAsync
+
+ let! async' =
+ taskSeq { yield! input }
+ |> TaskSeq.sortByAsync (fun x -> task { return x })
+ |> TaskSeq.toListAsync
+
+ async' |> should equal sync
+ }
+
+ []
+ let ``TaskSeq-sortByDescendingAsync yields same result as sortByDescending for identity async`` () = task {
+ let input = [ 5; 3; 1; 4; 2 ]
+
+ let! sync =
+ taskSeq { yield! input }
+ |> TaskSeq.sortByDescending id
+ |> TaskSeq.toListAsync
+
+ let! asyncDesc =
+ taskSeq { yield! input }
+ |> TaskSeq.sortByDescendingAsync (fun x -> task { return x })
+ |> TaskSeq.toListAsync
+
+ asyncDesc |> should equal sync
+ }
+
+ []
+ let ``TaskSeq-sortByAsync evaluates projection exactly once per element`` () = task {
+ let mutable callCount = 0
+
+ let! result =
+ taskSeq { yield! [ 3; 1; 2 ] }
+ |> TaskSeq.sortByAsync (fun x -> task {
+ callCount <- callCount + 1
+ return x
+ })
+ |> TaskSeq.toListAsync
+
+ callCount |> should equal 3
+ result |> should equal [ 1; 2; 3 ]
+ }
+
+module SortWithEmpty =
+ []
+ let ``TaskSeq-sortWith with null source raises`` () = assertNullArg <| fun () -> TaskSeq.sortWith compare null
+
+ [)>]
+ let ``TaskSeq-sortWith on empty returns empty`` variant =
+ Gen.getEmptyVariant variant
+ |> TaskSeq.sortWith compare
+ |> verifyEmpty
+
+module SortWithImmutable =
+ []
+ let ``TaskSeq-sortWith ascending with standard compare`` () = task {
+ let! result =
+ taskSeq { yield! [ 3; 1; 4; 1; 5; 9; 2; 6 ] }
+ |> TaskSeq.sortWith compare
+ |> TaskSeq.toListAsync
+
+ result |> should equal [ 1; 1; 2; 3; 4; 5; 6; 9 ]
+ }
+
+ []
+ let ``TaskSeq-sortWith descending with reversed compare`` () = task {
+ let! result =
+ taskSeq { yield! [ 3; 1; 4; 1; 5; 9; 2; 6 ] }
+ |> TaskSeq.sortWith (fun a b -> compare b a)
+ |> TaskSeq.toListAsync
+
+ result |> should equal [ 9; 6; 5; 4; 3; 2; 1; 1 ]
+ }
+
+ []
+ let ``TaskSeq-sortWith by string length`` () = task {
+ let! result =
+ taskSeq { yield! [ "banana"; "kiwi"; "cherry"; "fig" ] }
+ |> TaskSeq.sortWith (fun a b -> compare a.Length b.Length)
+ |> TaskSeq.toListAsync
+
+ result
+ |> List.map (fun s -> s.Length)
+ |> should equal [ 3; 4; 6; 6 ]
+ }
diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fs b/src/FSharp.Control.TaskSeq/TaskSeq.fs
index 046cca2..f3127ef 100644
--- a/src/FSharp.Control.TaskSeq/TaskSeq.fs
+++ b/src/FSharp.Control.TaskSeq/TaskSeq.fs
@@ -533,3 +533,16 @@ type TaskSeq private () =
static member partitionAsync predicate source = Internal.partition (PredicateAsync predicate) source
static member mapFold mapping state source = Internal.mapFold (MapFolderAction mapping) state source
static member mapFoldAsync mapping state source = Internal.mapFold (AsyncMapFolderAction mapping) state source
+
+ //
+ // rev/sort functions
+ //
+
+ static member rev source = Internal.rev source
+ static member sort source = Internal.sort source
+ static member sortDescending source = Internal.sortDescending source
+ static member sortBy projection source = Internal.sortBy projection source
+ static member sortByDescending projection source = Internal.sortByDescending projection source
+ static member sortWith comparer source = Internal.sortWith comparer source
+ static member sortByAsync projection source = Internal.sortByAsync projection source
+ static member sortByDescendingAsync projection source = Internal.sortByDescendingAsync projection source
diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fsi b/src/FSharp.Control.TaskSeq/TaskSeq.fsi
index cd47d20..778e027 100644
--- a/src/FSharp.Control.TaskSeq/TaskSeq.fsi
+++ b/src/FSharp.Control.TaskSeq/TaskSeq.fsi
@@ -1530,6 +1530,103 @@ type TaskSeq =
/// Thrown when is not positive.
static member windowed: windowSize: int -> source: TaskSeq<'T> -> TaskSeq<'T[]>
+ ///
+ /// Returns a new task sequence with the elements in the reverse order. This function materializes the source sequence.
+ ///
+ ///
+ /// The input task sequence.
+ /// The reversed task sequence.
+ /// Thrown when the input task sequence is null.
+ static member rev: source: TaskSeq<'T> -> TaskSeq<'T>
+
+ ///
+ /// Sorts the elements of the task sequence in ascending order. This function materializes the source sequence.
+ /// The elements must support comparison (i.e. implement ).
+ /// For a descending sort, use .
+ /// For sorting by a projection, use or .
+ ///
+ ///
+ /// The input task sequence.
+ /// The sorted task sequence.
+ /// Thrown when the input task sequence is null.
+ static member sort: source: TaskSeq<'T> -> TaskSeq<'T> when 'T: comparison
+
+ ///
+ /// Sorts the elements of the task sequence in descending order. This function materializes the source sequence.
+ /// The elements must support comparison (i.e. implement ).
+ /// For an ascending sort, use .
+ ///
+ ///
+ /// The input task sequence.
+ /// The sorted task sequence in descending order.
+ /// Thrown when the input task sequence is null.
+ static member sortDescending: source: TaskSeq<'T> -> TaskSeq<'T> when 'T: comparison
+
+ ///
+ /// Sorts the elements of the task sequence in ascending order using the given
+ /// to produce the sort key. This function materializes the source sequence.
+ /// The projected key must support comparison.
+ /// If is asynchronous, consider using .
+ ///
+ ///
+ /// A function to transform elements into the sort key.
+ /// The input task sequence.
+ /// The sorted task sequence.
+ /// Thrown when the input task sequence is null.
+ static member sortBy: projection: ('T -> 'Key) -> source: TaskSeq<'T> -> TaskSeq<'T> when 'Key: comparison
+
+ ///
+ /// Sorts the elements of the task sequence in descending order using the given
+ /// to produce the sort key. This function materializes the source sequence.
+ /// The projected key must support comparison.
+ /// If is asynchronous, consider using .
+ ///
+ ///
+ /// A function to transform elements into the sort key.
+ /// The input task sequence.
+ /// The sorted task sequence in descending order.
+ /// Thrown when the input task sequence is null.
+ static member sortByDescending: projection: ('T -> 'Key) -> source: TaskSeq<'T> -> TaskSeq<'T> when 'Key: comparison
+
+ ///
+ /// Sorts the elements of the task sequence using the given pairwise comparison .
+ /// This function materializes the source sequence.
+ ///
+ ///
+ /// A function that returns a negative integer, zero, or a positive integer to indicate ordering.
+ /// The input task sequence.
+ /// The sorted task sequence.
+ /// Thrown when the input task sequence is null.
+ static member sortWith: comparer: ('T -> 'T -> int) -> source: TaskSeq<'T> -> TaskSeq<'T>
+
+ ///
+ /// Sorts the elements of the task sequence in ascending order using an asynchronous
+ /// to produce the sort key. This function materializes the source sequence and evaluates each projection once.
+ /// The projected key must support comparison.
+ /// If is synchronous, consider using .
+ ///
+ ///
+ /// An async function to transform elements into the sort key.
+ /// The input task sequence.
+ /// The sorted task sequence.
+ /// Thrown when the input task sequence is null.
+ static member sortByAsync:
+ projection: ('T -> #Task<'Key>) -> source: TaskSeq<'T> -> TaskSeq<'T> when 'Key: comparison
+
+ ///
+ /// Sorts the elements of the task sequence in descending order using an asynchronous
+ /// to produce the sort key. This function materializes the source sequence and evaluates each projection once.
+ /// The projected key must support comparison.
+ /// If is synchronous, consider using .
+ ///
+ ///
+ /// An async function to transform elements into the sort key.
+ /// The input task sequence.
+ /// The sorted task sequence in descending order.
+ /// Thrown when the input task sequence is null.
+ static member sortByDescendingAsync:
+ projection: ('T -> #Task<'Key>) -> source: TaskSeq<'T> -> TaskSeq<'T> when 'Key: comparison
+
///
/// Combines the two task sequences into a new task sequence of pairs. The two sequences need not have equal lengths:
/// when one sequence is exhausted any remaining elements in the other sequence are ignored.
diff --git a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs
index 91937ec..8be436c 100644
--- a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs
+++ b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs
@@ -1551,3 +1551,144 @@ module internal TaskSeqInternal =
yield result
}
+
+ let rev (source: TaskSeq<'T>) : TaskSeq<'T> =
+ checkNonNull (nameof source) source
+
+ taskSeq {
+ let res = ResizeArray<'T>()
+
+ for item in source do
+ res.Add item
+
+ let arr = res.ToArray()
+
+ for i in arr.Length - 1 .. -1 .. 0 do
+ yield arr[i]
+ }
+
+ let sort (source: TaskSeq<'T>) : TaskSeq<'T> =
+ checkNonNull (nameof source) source
+
+ taskSeq {
+ let res = ResizeArray<'T>()
+
+ for item in source do
+ res.Add item
+
+ let arr = res.ToArray()
+ Array.sortInPlace arr
+
+ for item in arr do
+ yield item
+ }
+
+ let sortDescending (source: TaskSeq<'T>) : TaskSeq<'T> =
+ checkNonNull (nameof source) source
+
+ taskSeq {
+ let res = ResizeArray<'T>()
+
+ for item in source do
+ res.Add item
+
+ let arr = res.ToArray()
+ Array.sortInPlaceWith (fun a b -> compare b a) arr
+
+ for item in arr do
+ yield item
+ }
+
+ let sortBy (projection: 'T -> 'Key) (source: TaskSeq<'T>) : TaskSeq<'T> =
+ checkNonNull (nameof source) source
+
+ taskSeq {
+ let res = ResizeArray<'T>()
+
+ for item in source do
+ res.Add item
+
+ let arr = res.ToArray()
+ Array.sortInPlaceBy projection arr
+
+ for item in arr do
+ yield item
+ }
+
+ let sortByDescending (projection: 'T -> 'Key) (source: TaskSeq<'T>) : TaskSeq<'T> =
+ checkNonNull (nameof source) source
+
+ taskSeq {
+ let res = ResizeArray<'T>()
+
+ for item in source do
+ res.Add item
+
+ let arr = res.ToArray()
+ Array.sortInPlaceWith (fun a b -> compare (projection b) (projection a)) arr
+
+ for item in arr do
+ yield item
+ }
+
+ let sortWith (comparer: 'T -> 'T -> int) (source: TaskSeq<'T>) : TaskSeq<'T> =
+ checkNonNull (nameof source) source
+
+ taskSeq {
+ let res = ResizeArray<'T>()
+
+ for item in source do
+ res.Add item
+
+ let arr = res.ToArray()
+ Array.sortInPlaceWith comparer arr
+
+ for item in arr do
+ yield item
+ }
+
+ let sortByAsync (projection: 'T -> #Task<'Key>) (source: TaskSeq<'T>) : TaskSeq<'T> =
+ checkNonNull (nameof source) source
+
+ taskSeq {
+ let items = ResizeArray<'T>()
+
+ for item in source do
+ items.Add item
+
+ let count = items.Count
+ let keys = Array.zeroCreate<'Key> count
+
+ for i in 0 .. count - 1 do
+ let! k = projection items[i]
+ keys[i] <- k
+
+ let indices = Array.init count id
+ Array.sortInPlaceWith (fun i j -> compare keys[i] keys[j]) indices
+
+ for idx in indices do
+ yield items[idx]
+ }
+
+ let sortByDescendingAsync (projection: 'T -> #Task<'Key>) (source: TaskSeq<'T>) : TaskSeq<'T> =
+ checkNonNull (nameof source) source
+
+ taskSeq {
+ let items = ResizeArray<'T>()
+
+ for item in source do
+ items.Add item
+
+ let count = items.Count
+ let keys = Array.zeroCreate<'Key> count
+
+ for i in 0 .. count - 1 do
+ let! k = projection items[i]
+ keys[i] <- k
+
+ let indices = Array.init count id
+ Array.sortInPlaceWith (fun i j -> compare keys[j] keys[i]) indices
+
+ for idx in indices do
+ yield items[idx]
+ }