diff --git a/src/FSharp.Control.AsyncSeq/AsyncSeq.fs b/src/FSharp.Control.AsyncSeq/AsyncSeq.fs index 60fcd984..efa553fb 100644 --- a/src/FSharp.Control.AsyncSeq/AsyncSeq.fs +++ b/src/FSharp.Control.AsyncSeq/AsyncSeq.fs @@ -1185,6 +1185,46 @@ module AsyncSeq = let inline sum (source : AsyncSeq<'T>) : Async<'T> = (LanguagePrimitives.GenericZero, source) ||> fold (+) + let minByAsync (projection: 'T -> Async<'Key>) (source: AsyncSeq<'T>) : Async<'T> = + async { + let! result = + source |> foldAsync (fun (acc: ('T * 'Key) option) v -> + async { + let! k = projection v + match acc with + | None -> return Some (v, k) + | Some (_, ak) -> return if k < ak then Some (v, k) else acc + }) None + match result with + | None -> return raise (System.InvalidOperationException("The input sequence was empty.")) + | Some (v, _) -> return v } + + let minBy (projection: 'T -> 'Key) (source: AsyncSeq<'T>) : Async<'T> = + minByAsync (projection >> async.Return) source + + let maxByAsync (projection: 'T -> Async<'Key>) (source: AsyncSeq<'T>) : Async<'T> = + async { + let! result = + source |> foldAsync (fun (acc: ('T * 'Key) option) v -> + async { + let! k = projection v + match acc with + | None -> return Some (v, k) + | Some (_, ak) -> return if k > ak then Some (v, k) else acc + }) None + match result with + | None -> return raise (System.InvalidOperationException("The input sequence was empty.")) + | Some (v, _) -> return v } + + let maxBy (projection: 'T -> 'Key) (source: AsyncSeq<'T>) : Async<'T> = + maxByAsync (projection >> async.Return) source + + let min (source: AsyncSeq<'T>) : Async<'T> = + minBy id source + + let max (source: AsyncSeq<'T>) : Async<'T> = + maxBy id source + let inline sumBy (projection : 'T -> ^U) (source : AsyncSeq<'T>) : Async<^U> = fold (fun s x -> s + projection x) LanguagePrimitives.GenericZero source @@ -1747,7 +1787,7 @@ module AsyncSeq = | Some rem -> async.Return rem | None -> Async.StartChildAsTask(ie.MoveNext()) let t = Stopwatch.GetTimestamp() - let! time = Async.StartChildAsTask(Async.Sleep (max 0 rt)) + let! time = Async.StartChildAsTask(Async.Sleep (Operators.max 0 rt)) let! moveOr = Async.chooseTasks move time let delta = int ((Stopwatch.GetTimestamp() - t) * 1000L / Stopwatch.Frequency) match moveOr with diff --git a/src/FSharp.Control.AsyncSeq/AsyncSeq.fsi b/src/FSharp.Control.AsyncSeq/AsyncSeq.fsi index 98db77b1..2c6f0220 100644 --- a/src/FSharp.Control.AsyncSeq/AsyncSeq.fsi +++ b/src/FSharp.Control.AsyncSeq/AsyncSeq.fsi @@ -227,6 +227,24 @@ module AsyncSeq = when ^T : (static member ( + ) : ^T * ^T -> ^T) and ^T : (static member Zero : ^T) + /// Asynchronously find the element with the minimum projected value. Raises InvalidOperationException if the sequence is empty. + val minByAsync : projection:('T -> Async<'Key>) -> source:AsyncSeq<'T> -> Async<'T> when 'Key : comparison + + /// Asynchronously find the element with the minimum projected value. Raises InvalidOperationException if the sequence is empty. + val minBy : projection:('T -> 'Key) -> source:AsyncSeq<'T> -> Async<'T> when 'Key : comparison + + /// Asynchronously find the element with the maximum projected value. Raises InvalidOperationException if the sequence is empty. + val maxByAsync : projection:('T -> Async<'Key>) -> source:AsyncSeq<'T> -> Async<'T> when 'Key : comparison + + /// Asynchronously find the element with the maximum projected value. Raises InvalidOperationException if the sequence is empty. + val maxBy : projection:('T -> 'Key) -> source:AsyncSeq<'T> -> Async<'T> when 'Key : comparison + + /// Asynchronously find the minimum element. Raises InvalidOperationException if the sequence is empty. + val min : source:AsyncSeq<'T> -> Async<'T> when 'T : comparison + + /// Asynchronously find the maximum element. Raises InvalidOperationException if the sequence is empty. + val max : source:AsyncSeq<'T> -> Async<'T> when 'T : comparison + /// Asynchronously sum the mapped elements of an asynchronous sequence using a synchronous projection. val inline sumBy : projection:('T -> ^U) -> source:AsyncSeq<'T> -> Async< ^U> when ^U : (static member ( + ) : ^U * ^U -> ^U) diff --git a/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs b/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs index cba1b125..f26b0dd6 100644 --- a/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs +++ b/tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs @@ -211,6 +211,54 @@ let ``AsyncSeq.sum works``() = let expected = ls |> List.sum Assert.True((expected = actual)) +[] +let ``AsyncSeq.min returns minimum element``() = + for i in 1 .. 10 do + let ls = [ 1 .. i ] |> List.rev + let actual = AsyncSeq.ofSeq ls |> AsyncSeq.min |> Async.RunSynchronously + Assert.AreEqual(1, actual) + +[] +let ``AsyncSeq.min raises on empty sequence``() = + Assert.Throws(fun () -> + AsyncSeq.empty |> AsyncSeq.min |> Async.RunSynchronously |> ignore) |> ignore + +[] +let ``AsyncSeq.max returns maximum element``() = + for i in 1 .. 10 do + let ls = [ 1 .. i ] + let actual = AsyncSeq.ofSeq ls |> AsyncSeq.max |> Async.RunSynchronously + Assert.AreEqual(i, actual) + +[] +let ``AsyncSeq.max raises on empty sequence``() = + Assert.Throws(fun () -> + AsyncSeq.empty |> AsyncSeq.max |> Async.RunSynchronously |> ignore) |> ignore + +[] +let ``AsyncSeq.minBy returns element with minimum projected value``() = + let ls = [ ("b", 2); ("a", 1); ("c", 3) ] + let actual = AsyncSeq.ofSeq ls |> AsyncSeq.minBy snd |> Async.RunSynchronously + Assert.AreEqual(("a", 1), actual) + +[] +let ``AsyncSeq.maxBy returns element with maximum projected value``() = + let ls = [ ("b", 2); ("a", 1); ("c", 3) ] + let actual = AsyncSeq.ofSeq ls |> AsyncSeq.maxBy snd |> Async.RunSynchronously + Assert.AreEqual(("c", 3), actual) + +[] +let ``AsyncSeq.minByAsync uses async projection``() = + let ls = [ 3; 1; 4; 1; 5; 9 ] + let actual = AsyncSeq.ofSeq ls |> AsyncSeq.minByAsync (fun x -> async.Return x) |> Async.RunSynchronously + Assert.AreEqual(1, actual) + +[] +let ``AsyncSeq.maxByAsync uses async projection``() = + let ls = [ 3; 1; 4; 1; 5; 9 ] + let actual = AsyncSeq.ofSeq ls |> AsyncSeq.maxByAsync (fun x -> async.Return x) |> Async.RunSynchronously + Assert.AreEqual(9, actual) + [] let ``AsyncSeq.sumBy works``() = for i in 0 .. 10 do