Skip to content

Commit 29d1c0c

Browse files
github-actions[bot]Repo AssistCopilot
authored
[Repo Assist] Add AsyncSeq.sumBy, sumByAsync, average, averageBy, averageByAsync (#245)
* Add AsyncSeq.sumBy, sumByAsync, average, averageBy, averageByAsync These mirror the corresponding Seq module functions and complement the existing AsyncSeq.sum combinator: - sumBy / sumByAsync: map-then-sum using sync/async projection - average: compute mean of a numeric async sequence - averageBy / averageByAsync: map-then-average using sync/async projection All raise InvalidArgumentException on empty sequences (average variants). 6 new tests added; all 200 tests pass. Co-authored-by: Copilot <[email protected]> * ci: trigger CI checks --------- Co-authored-by: Repo Assist <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 44f16f0 commit 29d1c0c

3 files changed

Lines changed: 104 additions & 0 deletions

File tree

src/FSharp.Control.AsyncSeq/AsyncSeq.fs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,6 +1151,33 @@ module AsyncSeq =
11511151
let inline sum (source : AsyncSeq<'T>) : Async<'T> =
11521152
(LanguagePrimitives.GenericZero, source) ||> fold (+)
11531153

1154+
let inline sumBy (projection : 'T -> ^U) (source : AsyncSeq<'T>) : Async<^U> =
1155+
fold (fun s x -> s + projection x) LanguagePrimitives.GenericZero source
1156+
1157+
let inline sumByAsync (projection : 'T -> Async<^U>) (source : AsyncSeq<'T>) : Async<^U> =
1158+
foldAsync (fun s x -> async { let! v = projection x in return s + v }) LanguagePrimitives.GenericZero source
1159+
1160+
let inline average (source : AsyncSeq<^T>) : Async<^T> =
1161+
async {
1162+
let! sum, count = fold (fun (s, n) x -> (s + x, n + 1)) (LanguagePrimitives.GenericZero, 0) source
1163+
if count = 0 then invalidArg "source" "The input sequence was empty."
1164+
return LanguagePrimitives.DivideByInt sum count
1165+
}
1166+
1167+
let inline averageBy (projection : 'T -> ^U) (source : AsyncSeq<'T>) : Async<^U> =
1168+
async {
1169+
let! sum, count = fold (fun (s, n) x -> (s + projection x, n + 1)) (LanguagePrimitives.GenericZero, 0) source
1170+
if count = 0 then invalidArg "source" "The input sequence was empty."
1171+
return LanguagePrimitives.DivideByInt sum count
1172+
}
1173+
1174+
let inline averageByAsync (projection : 'T -> Async<^U>) (source : AsyncSeq<'T>) : Async<^U> =
1175+
async {
1176+
let! sum, count = foldAsync (fun (s, n) x -> async { let! v = projection x in return (s + v, n + 1) }) (LanguagePrimitives.GenericZero, 0) source
1177+
if count = 0 then invalidArg "source" "The input sequence was empty."
1178+
return LanguagePrimitives.DivideByInt sum count
1179+
}
1180+
11541181
let scan f (state:'State) (source : AsyncSeq<'T>) =
11551182
scanAsync (fun st v -> f st v |> async.Return) state source
11561183

src/FSharp.Control.AsyncSeq/AsyncSeq.fsi

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,37 @@ module AsyncSeq =
211211
when ^T : (static member ( + ) : ^T * ^T -> ^T)
212212
and ^T : (static member Zero : ^T)
213213

214+
/// Asynchronously sum the mapped elements of an asynchronous sequence using a synchronous projection.
215+
val inline sumBy : projection:('T -> ^U) -> source:AsyncSeq<'T> -> Async< ^U>
216+
when ^U : (static member ( + ) : ^U * ^U -> ^U)
217+
and ^U : (static member Zero : ^U)
218+
219+
/// Asynchronously sum the mapped elements of an asynchronous sequence using an asynchronous projection.
220+
val inline sumByAsync : projection:('T -> Async< ^U>) -> source:AsyncSeq<'T> -> Async< ^U>
221+
when ^U : (static member ( + ) : ^U * ^U -> ^U)
222+
and ^U : (static member Zero : ^U)
223+
224+
/// Asynchronously compute the average of the elements of the input asynchronous sequence.
225+
/// Raises InvalidArgumentException if the sequence is empty.
226+
val inline average : source:AsyncSeq< ^T> -> Async< ^T>
227+
when ^T : (static member ( + ) : ^T * ^T -> ^T)
228+
and ^T : (static member DivideByInt : ^T * int -> ^T)
229+
and ^T : (static member Zero : ^T)
230+
231+
/// Asynchronously compute the average of the mapped elements of an asynchronous sequence using a synchronous projection.
232+
/// Raises InvalidArgumentException if the sequence is empty.
233+
val inline averageBy : projection:('T -> ^U) -> source:AsyncSeq<'T> -> Async< ^U>
234+
when ^U : (static member ( + ) : ^U * ^U -> ^U)
235+
and ^U : (static member DivideByInt : ^U * int -> ^U)
236+
and ^U : (static member Zero : ^U)
237+
238+
/// Asynchronously compute the average of the mapped elements of an asynchronous sequence using an asynchronous projection.
239+
/// Raises InvalidArgumentException if the sequence is empty.
240+
val inline averageByAsync : projection:('T -> Async< ^U>) -> source:AsyncSeq<'T> -> Async< ^U>
241+
when ^U : (static member ( + ) : ^U * ^U -> ^U)
242+
and ^U : (static member DivideByInt : ^U * int -> ^U)
243+
and ^U : (static member Zero : ^U)
244+
214245
/// Asynchronously determine if the sequence contains the given value
215246
val contains : value:'T -> source:AsyncSeq<'T> -> Async<bool> when 'T : equality
216247

tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,52 @@ let ``AsyncSeq.sum works``() =
211211
let expected = ls |> List.sum
212212
Assert.True((expected = actual))
213213

214+
[<Test>]
215+
let ``AsyncSeq.sumBy works``() =
216+
for i in 0 .. 10 do
217+
let ls = [ 1 .. i ]
218+
let actual = AsyncSeq.ofSeq ls |> AsyncSeq.sumBy float |> Async.RunSynchronously
219+
let expected = ls |> List.sumBy float
220+
Assert.AreEqual(expected, actual)
221+
222+
[<Test>]
223+
let ``AsyncSeq.sumByAsync works``() =
224+
for i in 0 .. 10 do
225+
let ls = [ 1 .. i ]
226+
let actual = AsyncSeq.ofSeq ls |> AsyncSeq.sumByAsync (float >> async.Return) |> Async.RunSynchronously
227+
let expected = ls |> List.sumBy float
228+
Assert.AreEqual(expected, actual)
229+
230+
[<Test>]
231+
let ``AsyncSeq.average works``() =
232+
for i in 1 .. 10 do
233+
let ls = [ 1.0 .. float i ]
234+
let actual = AsyncSeq.ofSeq ls |> AsyncSeq.average |> Async.RunSynchronously
235+
let expected = ls |> List.average
236+
Assert.AreEqual(expected, actual)
237+
238+
[<Test>]
239+
let ``AsyncSeq.average raises on empty sequence``() =
240+
Assert.Throws<System.ArgumentException>(fun () ->
241+
AsyncSeq.empty<float> |> AsyncSeq.average |> Async.RunSynchronously |> ignore
242+
) |> ignore
243+
244+
[<Test>]
245+
let ``AsyncSeq.averageBy works``() =
246+
for i in 1 .. 10 do
247+
let ls = [ 1 .. i ]
248+
let actual = AsyncSeq.ofSeq ls |> AsyncSeq.averageBy float |> Async.RunSynchronously
249+
let expected = ls |> List.averageBy float
250+
Assert.AreEqual(expected, actual)
251+
252+
[<Test>]
253+
let ``AsyncSeq.averageByAsync works``() =
254+
for i in 1 .. 10 do
255+
let ls = [ 1 .. i ]
256+
let actual = AsyncSeq.ofSeq ls |> AsyncSeq.averageByAsync (float >> async.Return) |> Async.RunSynchronously
257+
let expected = ls |> List.averageBy float
258+
Assert.AreEqual(expected, actual)
259+
214260

215261
[<Test>]
216262
let ``AsyncSeq.length works``() =

0 commit comments

Comments
 (0)