Skip to content

Commit 191415e

Browse files
authored
Merge branch 'main' into repo-assist/improve-isempty-tryhead-except-2471744-646c6897389cdfcb
2 parents cf04298 + 2561add commit 191415e

4 files changed

Lines changed: 155 additions & 0 deletions

File tree

RELEASE_NOTES.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
### 4.6.0
22

3+
<<<<<<< repo-assist/improve-isempty-tryhead-except-2471744-646c6897389cdfcb
34
* Added `AsyncSeq.isEmpty` — returns `true` if the sequence contains no elements; short-circuits after the first element, mirroring `Seq.isEmpty`.
45
* Added `AsyncSeq.tryHead` — returns the first element as `option`, or `None` if the sequence is empty, mirroring `Seq.tryHead` (equivalent to the existing `AsyncSeq.tryFirst`).
56
* Added `AsyncSeq.except` — returns a new sequence excluding all elements present in a given collection, mirroring `Seq.except`.
7+
=======
8+
* Added `AsyncSeq.findIndex` — returns the index of the first element satisfying a predicate; raises `KeyNotFoundException` if no match, mirroring `Seq.findIndex`.
9+
* Added `AsyncSeq.tryFindIndex` — returns the index of the first element satisfying a predicate as `option`, or `None` if not found, mirroring `Seq.tryFindIndex`.
10+
* Added `AsyncSeq.findIndexAsync` — async-predicate variant of `AsyncSeq.findIndex`; raises `KeyNotFoundException` if no match.
11+
* Added `AsyncSeq.tryFindIndexAsync` — async-predicate variant of `AsyncSeq.tryFindIndex`; returns `option`.
12+
* Added `AsyncSeq.sortWith` — sorts the sequence using a custom comparison function, returning an array, mirroring `Seq.sortWith`.
13+
>>>>>>> main
614
715
### 4.5.0
816

src/FSharp.Control.AsyncSeq/AsyncSeq.fs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,6 +1216,46 @@ module AsyncSeq =
12161216
let findAsync f (source : AsyncSeq<'T>) =
12171217
source |> pickAsync (fun v -> async { let! b = f v in return if b then Some v else None })
12181218

1219+
let tryFindIndex f (source : AsyncSeq<'T>) = async {
1220+
use ie = source.GetEnumerator()
1221+
let! first = ie.MoveNext()
1222+
let b = ref first
1223+
let i = ref 0
1224+
while b.Value.IsSome && not (f b.Value.Value) do
1225+
let! next = ie.MoveNext()
1226+
b := next
1227+
i := !i + 1
1228+
return if b.Value.IsSome then Some !i else None }
1229+
1230+
let tryFindIndexAsync f (source : AsyncSeq<'T>) = async {
1231+
use ie = source.GetEnumerator()
1232+
let! first = ie.MoveNext()
1233+
let b = ref first
1234+
let i = ref 0
1235+
let mutable keepGoing = b.Value.IsSome
1236+
while keepGoing do
1237+
let! matches = f b.Value.Value
1238+
if matches then
1239+
keepGoing <- false
1240+
else
1241+
let! next = ie.MoveNext()
1242+
b := next
1243+
if b.Value.IsSome then i := !i + 1
1244+
else keepGoing <- false
1245+
return if b.Value.IsSome then Some !i else None }
1246+
1247+
let findIndex f (source : AsyncSeq<'T>) = async {
1248+
let! result = tryFindIndex f source
1249+
match result with
1250+
| None -> return raise (System.Collections.Generic.KeyNotFoundException("An element satisfying the predicate was not found in the collection."))
1251+
| Some i -> return i }
1252+
1253+
let findIndexAsync f (source : AsyncSeq<'T>) = async {
1254+
let! result = tryFindIndexAsync f source
1255+
match result with
1256+
| None -> return raise (System.Collections.Generic.KeyNotFoundException("An element satisfying the predicate was not found in the collection."))
1257+
| Some i -> return i }
1258+
12191259
let exists f (source : AsyncSeq<'T>) =
12201260
source |> tryFind f |> Async.map Option.isSome
12211261

@@ -1896,6 +1936,9 @@ module AsyncSeq =
18961936

18971937
let sortByDescending (projection:'T -> 'Key) (source:AsyncSeq<'T>) : array<'T> when 'Key : comparison =
18981938
toSortedSeq (Array.sortByDescending projection) source
1939+
1940+
let sortWith (comparer:'T -> 'T -> int) (source:AsyncSeq<'T>) : array<'T> =
1941+
toSortedSeq (Array.sortWith comparer) source
18991942
#endif
19001943

19011944
#if !FABLE_COMPILER

src/FSharp.Control.AsyncSeq/AsyncSeq.fsi

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,22 @@ module AsyncSeq =
351351
/// Raises KeyNotFoundException if no matching element is found.
352352
val findAsync : predicate:('T -> Async<bool>) -> source:AsyncSeq<'T> -> Async<'T>
353353

354+
/// Asynchronously find the index of the first value in a sequence for which the predicate returns true.
355+
/// Returns None if no matching element is found.
356+
val tryFindIndex : predicate:('T -> bool) -> source:AsyncSeq<'T> -> Async<int option>
357+
358+
/// Asynchronously find the index of the first value in a sequence for which the async predicate returns true.
359+
/// Returns None if no matching element is found.
360+
val tryFindIndexAsync : predicate:('T -> Async<bool>) -> source:AsyncSeq<'T> -> Async<int option>
361+
362+
/// Asynchronously find the index of the first value in a sequence for which the predicate returns true.
363+
/// Raises KeyNotFoundException if no matching element is found.
364+
val findIndex : predicate:('T -> bool) -> source:AsyncSeq<'T> -> Async<int>
365+
366+
/// Asynchronously find the index of the first value in a sequence for which the async predicate returns true.
367+
/// Raises KeyNotFoundException if no matching element is found.
368+
val findIndexAsync : predicate:('T -> Async<bool>) -> source:AsyncSeq<'T> -> Async<int>
369+
354370
/// Asynchronously determine if there is a value in the sequence for which the predicate returns true
355371
val exists : predicate:('T -> bool) -> source:AsyncSeq<'T> -> Async<bool>
356372

@@ -614,6 +630,12 @@ module AsyncSeq =
614630
/// that sequence is iterated. As a result this function should not be used with
615631
/// large or infinite sequences.
616632
val sortByDescending : projection:('T -> 'Key) -> source:AsyncSeq<'T> -> array<'T> when 'Key : comparison
633+
634+
/// Sorts the given async sequence using the given comparison function and returns an array.
635+
/// This function returns an array that digests the whole initial sequence as soon as
636+
/// that sequence is iterated. As a result this function should not be used with
637+
/// large or infinite sequences.
638+
val sortWith : comparer:('T -> 'T -> int) -> source:AsyncSeq<'T> -> array<'T>
617639
#endif
618640

619641
/// Interleaves two async sequences of the same type into a resulting sequence. The provided

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

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3329,4 +3329,86 @@ let ``AsyncSeq.except with all excluded returns empty sequence`` () =
33293329
[<Test>]
33303330
let ``AsyncSeq.except on empty source returns empty`` () =
33313331
let result = AsyncSeq.except [1; 2] AsyncSeq.empty<int> |> AsyncSeq.toArrayAsync |> Async.RunSynchronously
3332+
3333+
// ===== findIndex / tryFindIndex / findIndexAsync / tryFindIndexAsync =====
3334+
3335+
[<Test>]
3336+
let ``AsyncSeq.findIndex returns index of first matching element`` () =
3337+
for i in 0 .. 9 do
3338+
let ls = [ 1 .. 10 ]
3339+
let result = AsyncSeq.ofSeq ls |> AsyncSeq.findIndex (fun x -> x = i + 1) |> Async.RunSynchronously
3340+
Assert.AreEqual(i, result)
3341+
3342+
[<Test>]
3343+
let ``AsyncSeq.findIndex raises KeyNotFoundException when no match`` () =
3344+
Assert.Throws<System.Collections.Generic.KeyNotFoundException>(fun () ->
3345+
AsyncSeq.ofSeq [ 1; 2; 3 ] |> AsyncSeq.findIndex (fun x -> x = 99) |> Async.RunSynchronously |> ignore)
3346+
|> ignore
3347+
3348+
[<Test>]
3349+
let ``AsyncSeq.tryFindIndex returns Some index when found`` () =
3350+
let ls = [ 10; 20; 30; 40 ]
3351+
let result = AsyncSeq.ofSeq ls |> AsyncSeq.tryFindIndex (fun x -> x = 30) |> Async.RunSynchronously
3352+
Assert.AreEqual(Some 2, result)
3353+
3354+
[<Test>]
3355+
let ``AsyncSeq.tryFindIndex returns None when not found`` () =
3356+
let result = AsyncSeq.ofSeq [ 1; 2; 3 ] |> AsyncSeq.tryFindIndex (fun x -> x = 99) |> Async.RunSynchronously
3357+
Assert.AreEqual(None, result)
3358+
3359+
[<Test>]
3360+
let ``AsyncSeq.tryFindIndex returns Some 0 for first element`` () =
3361+
let result = AsyncSeq.ofSeq [ 5; 6; 7 ] |> AsyncSeq.tryFindIndex (fun x -> x = 5) |> Async.RunSynchronously
3362+
Assert.AreEqual(Some 0, result)
3363+
3364+
[<Test>]
3365+
let ``AsyncSeq.findIndexAsync returns index of first matching element`` () =
3366+
let ls = [ 1; 2; 3; 4; 5 ]
3367+
let result =
3368+
AsyncSeq.ofSeq ls
3369+
|> AsyncSeq.findIndexAsync (fun x -> async { return x = 3 })
3370+
|> Async.RunSynchronously
3371+
Assert.AreEqual(2, result)
3372+
3373+
[<Test>]
3374+
let ``AsyncSeq.findIndexAsync raises KeyNotFoundException when no match`` () =
3375+
Assert.Throws<System.Collections.Generic.KeyNotFoundException>(fun () ->
3376+
AsyncSeq.ofSeq [ 1; 2; 3 ]
3377+
|> AsyncSeq.findIndexAsync (fun x -> async { return x = 99 })
3378+
|> Async.RunSynchronously |> ignore)
3379+
|> ignore
3380+
3381+
[<Test>]
3382+
let ``AsyncSeq.tryFindIndexAsync returns Some index when found`` () =
3383+
let result =
3384+
AsyncSeq.ofSeq [ 10; 20; 30 ]
3385+
|> AsyncSeq.tryFindIndexAsync (fun x -> async { return x = 20 })
3386+
|> Async.RunSynchronously
3387+
Assert.AreEqual(Some 1, result)
3388+
3389+
[<Test>]
3390+
let ``AsyncSeq.tryFindIndexAsync returns None when not found`` () =
3391+
let result =
3392+
AsyncSeq.ofSeq [ 1; 2; 3 ]
3393+
|> AsyncSeq.tryFindIndexAsync (fun x -> async { return x = 99 })
3394+
|> Async.RunSynchronously
3395+
Assert.AreEqual(None, result)
3396+
3397+
// ===== sortWith =====
3398+
3399+
[<Test>]
3400+
let ``AsyncSeq.sortWith sorts using custom comparer`` () =
3401+
let source = asyncSeq { yield 3; yield 1; yield 4; yield 1; yield 5 }
3402+
let result = AsyncSeq.sortWith compare source
3403+
Assert.AreEqual([| 1; 1; 3; 4; 5 |], result)
3404+
3405+
[<Test>]
3406+
let ``AsyncSeq.sortWith sorts descending with negated comparer`` () =
3407+
let source = asyncSeq { yield 3; yield 1; yield 4; yield 1; yield 5 }
3408+
let result = AsyncSeq.sortWith (fun a b -> compare b a) source
3409+
Assert.AreEqual([| 5; 4; 3; 1; 1 |], result)
3410+
3411+
[<Test>]
3412+
let ``AsyncSeq.sortWith returns empty array for empty sequence`` () =
3413+
let result = AsyncSeq.sortWith compare AsyncSeq.empty<int>
33323414
Assert.AreEqual([||], result)

0 commit comments

Comments
 (0)