Skip to content

Commit

Permalink
Implement max|min, maxBy|minBy and maxByAsync|minByAsync
Browse files Browse the repository at this point in the history
  • Loading branch information
abelbraaksma committed Dec 22, 2023
1 parent 06bc268 commit 23e9039
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 1 deletion.
6 changes: 6 additions & 0 deletions src/FSharp.Control.TaskSeq/TaskSeq.fs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ type TaskSeq private () =
// Utility functions
//

static member max source = Internal.maxMin max source
static member min source = Internal.maxMin max source
static member maxBy projection source = Internal.maxMinBy (<) projection source // looks like 'less than', is 'greater than'
static member minBy projection source = Internal.maxMinBy (>) projection source
static member maxByAsync projection source = Internal.maxMinByAsync (<) projection source // looks like 'less than', is 'greater than'
static member minByAsync projection source = Internal.maxMinByAsync (>) projection source
static member length source = Internal.lengthBy None source
static member lengthOrMax max source = Internal.lengthBeforeMax max source
static member lengthBy predicate source = Internal.lengthBy (Some(Predicate predicate)) source
Expand Down
76 changes: 76 additions & 0 deletions src/FSharp.Control.TaskSeq/TaskSeq.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,82 @@ type TaskSeq =
/// <exception cref="T:ArgumentNullException">Thrown when the input task sequence is null.</exception>
static member lengthByAsync: predicate: ('T -> #Task<bool>) -> source: TaskSeq<'T> -> Task<int>

/// <summary>
/// Returns the greatest of all elements of the sequence, compared via <see cref="Operators.max" />.
/// </summary>
///
/// <param name="source">The input task sequence.</param>
/// <returns>The largest element of the sequence.</returns>
/// <exception cref="T:ArgumentNullException">Thrown when the input task sequence is null.</exception>
/// <exception cref="T:ArgumentException">Thrown when the input task sequence is empty.</exception>
static member max: source: TaskSeq<'T> -> Task<'T> when 'T: comparison

/// <summary>
/// Returns the smallest of all elements of the sequence, compared via <see cref="Operators.max" />.
/// </summary>
///
/// <param name="source">The input task sequence.</param>
/// <returns>The smallest element of the sequence.</returns>
/// <exception cref="T:ArgumentNullException">Thrown when the input task sequence is null.</exception>
/// <exception cref="T:ArgumentException">Thrown when the input task sequence is empty.</exception>
static member min: source: TaskSeq<'T> -> Task<'T> when 'T: comparison

/// <summary>
/// Returns the greatest of all elements of the task sequence, compared via <see cref="Operators.max" />
/// on the result of applying the function <paramref name="projection" /> to each element.
///
/// If <paramref name="projection" /> is asynchronous, use <see cref="TaskSeq.maxByAsync" />.
/// </summary>
///
/// <param name="projection">A function to transform items from the input sequence into comparable keys.</param>
/// <param name="source">The input sequence.</param>
/// <returns>The largest element of the sequence.</returns>
/// <exception cref="T:ArgumentNullException">Thrown when the input sequence is null.</exception>
/// <exception cref="T:ArgumentException">Thrown when the input sequence is empty.</exception>
static member maxBy: projection: ('T -> 'U) -> source: TaskSeq<'T> -> Task<'T> when 'U: comparison

/// <summary>
/// Returns the smallest of all elements of the task sequence, compared via <see cref="Operators.min" />
/// on the result of applying the function <paramref name="projection" /> to each element.
///
/// If <paramref name="projection" /> is asynchronous, use <see cref="TaskSeq.minByAsync" />.
/// </summary>
///
/// <param name="projection">A function to transform items from the input sequence into comparable keys.</param>
/// <param name="source">The input sequence.</param>
/// <returns>The smallest element of the sequence.</returns>
/// <exception cref="T:ArgumentNullException">Thrown when the input sequence is null.</exception>
/// <exception cref="T:ArgumentException">Thrown when the input sequence is empty.</exception>
static member minBy: projection: ('T -> 'U) -> source: TaskSeq<'T> -> Task<'T> when 'U: comparison

/// <summary>
/// Returns the greatest of all elements of the task sequence, compared via <see cref="Operators.max" />
/// on the result of applying the function <paramref name="projection" /> to each element.
///
/// If <paramref name="projection" /> is synchronous, use <see cref="TaskSeq.maxBy" />.
/// </summary>
///
/// <param name="projection">A function to transform items from the input sequence into comparable keys.</param>
/// <param name="source">The input sequence.</param>
/// <returns>The largest element of the sequence.</returns>
/// <exception cref="T:ArgumentNullException">Thrown when the input sequence is null.</exception>
/// <exception cref="T:ArgumentException">Thrown when the input sequence is empty.</exception>
static member maxByAsync: projection: ('T -> #Task<'U>) -> source: TaskSeq<'T> -> Task<'T> when 'U: comparison

/// <summary>
/// Returns the smallest of all elements of the task sequence, compared via <see cref="Operators.min" />
/// on the result of applying the function <paramref name="projection" /> to each element.
///
/// If <paramref name="projection" /> is synchronous, use <see cref="TaskSeq.minBy" />.
/// </summary>
///
/// <param name="projection">A function to transform items from the input sequence into comparable keys.</param>
/// <param name="source">The input sequence.</param>
/// <returns>The smallest element of the sequence.</returns>
/// <exception cref="T:ArgumentNullException">Thrown when the input sequence is null.</exception>
/// <exception cref="T:ArgumentException">Thrown when the input sequence is empty.</exception>
static member minByAsync: projection: ('T -> #Task<'U>) -> source: TaskSeq<'T> -> Task<'T> when 'U: comparison

/// <summary>
/// Returns a task sequence that is given by the delayed specification of a task sequence.
/// </summary>
Expand Down
71 changes: 70 additions & 1 deletion src/FSharp.Control.TaskSeq/TaskSeqInternal.fs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,6 @@ module internal TaskSeqInternal =
checkNonNull (nameof source) source

task {

use e = source.GetAsyncEnumerator CancellationToken.None
let mutable go = true
let mutable i = 0
Expand All @@ -178,6 +177,76 @@ module internal TaskSeqInternal =
return i
}

let inline maxMin ([<InlineIfLambda>] maxOrMin) (source: TaskSeq<_>) =
checkNonNull (nameof source) source

task {
use e = source.GetAsyncEnumerator CancellationToken.None
let! nonEmpty = e.MoveNextAsync()

if not nonEmpty then
raiseEmptySeq ()

let mutable acc = e.Current

while! e.MoveNextAsync() do
acc <- maxOrMin e.Current acc

return acc
}

let inline maxMinBy ([<InlineIfLambda>] compare) ([<InlineIfLambda>] projection) (source: TaskSeq<_>) =
checkNonNull (nameof source) source

task {
use e = source.GetAsyncEnumerator CancellationToken.None
let! nonEmpty = e.MoveNextAsync()

if not nonEmpty then
raiseEmptySeq ()

let value = e.Current
let mutable accProjection = projection value
let mutable accValue = value

while! e.MoveNextAsync() do
let value = e.Current
let currentProjection = projection value

if compare currentProjection accProjection then
accProjection <- currentProjection
accValue <- value

return accValue
}


let inline maxMinByAsync ([<InlineIfLambda>] compare) ([<InlineIfLambda>] projectionAsync) (source: TaskSeq<_>) =
checkNonNull (nameof source) source

task {
use e = source.GetAsyncEnumerator CancellationToken.None
let! nonEmpty = e.MoveNextAsync()

if not nonEmpty then
raiseEmptySeq ()

let value = e.Current
let! projValue = projectionAsync value
let mutable accProjection = projValue
let mutable accValue = value

while! e.MoveNextAsync() do
let value = e.Current
let! currentProjection = projectionAsync value

if compare currentProjection accProjection then
accProjection <- currentProjection
accValue <- value

return accValue
}

let tryExactlyOne (source: TaskSeq<_>) =
checkNonNull (nameof source) source

Expand Down

0 comments on commit 23e9039

Please sign in to comment.