Skip to content

Commit 619713e

Browse files
authored
Merge pull request #325 from fsprojects/repo-assist/improve-findIndexBack-20260505-0babe087728a3488
[Repo Assist] Add AsyncSeq.tryFindIndexBack, findIndexBack, tryFindIndexBackAsync, findIndexBackAsync
2 parents cab8f81 + bd5d7ca commit 619713e

4 files changed

Lines changed: 121 additions & 0 deletions

File tree

RELEASE_NOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
* Performance: Optimised `AsyncSeq.pairwise` to use a `hasPrev` flag and a direct `mutable` field instead of wrapping the previous element in `Some`. Previously, each iteration allocated a new `'T option` object on the heap; the new implementation eliminates that allocation entirely, reducing GC pressure for long sequences.
44
* Bug fix: `AsyncSeq.splitAt` and `AsyncSeq.tryTail` now correctly dispose the underlying enumerator when an exception or cancellation occurs during the initial `MoveNext` call. Previously the enumerator could leak if the source sequence threw during the first few steps.
5+
* Added `AsyncSeq.tryFindIndexBack` and `AsyncSeq.findIndexBack` — return the index of the last element satisfying a predicate. Mirrors `Array.tryFindIndexBack` / `Array.findIndexBack`. Async-predicate variants `tryFindIndexBackAsync` and `findIndexBackAsync` are also included.
56

67
### 4.16.0
78

src/FSharp.Control.AsyncSeq/AsyncSeq.fs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,6 +1460,45 @@ module AsyncSeq =
14601460
| None -> return raise (System.Collections.Generic.KeyNotFoundException("An element satisfying the predicate was not found in the collection."))
14611461
| Some i -> return i }
14621462

1463+
let tryFindIndexBack f (source : AsyncSeq<'T>) = async {
1464+
use ie = source.GetEnumerator()
1465+
let! v = ie.MoveNext()
1466+
let mutable b = v
1467+
let mutable i = 0
1468+
let mutable res = None
1469+
while b.IsSome do
1470+
if f b.Value then res <- Some i
1471+
let! next = ie.MoveNext()
1472+
b <- next
1473+
i <- i + 1
1474+
return res }
1475+
1476+
let tryFindIndexBackAsync f (source : AsyncSeq<'T>) = async {
1477+
use ie = source.GetEnumerator()
1478+
let! v = ie.MoveNext()
1479+
let mutable b = v
1480+
let mutable i = 0
1481+
let mutable res = None
1482+
while b.IsSome do
1483+
let! matches = f b.Value
1484+
if matches then res <- Some i
1485+
let! next = ie.MoveNext()
1486+
b <- next
1487+
i <- i + 1
1488+
return res }
1489+
1490+
let findIndexBack f (source : AsyncSeq<'T>) = async {
1491+
let! result = tryFindIndexBack f source
1492+
match result with
1493+
| None -> return raise (System.Collections.Generic.KeyNotFoundException("An element satisfying the predicate was not found in the collection."))
1494+
| Some i -> return i }
1495+
1496+
let findIndexBackAsync f (source : AsyncSeq<'T>) = async {
1497+
let! result = tryFindIndexBackAsync f source
1498+
match result with
1499+
| None -> return raise (System.Collections.Generic.KeyNotFoundException("An element satisfying the predicate was not found in the collection."))
1500+
| Some i -> return i }
1501+
14631502
let exists f (source : AsyncSeq<'T>) =
14641503
source |> tryFind f |> Async.map Option.isSome
14651504

src/FSharp.Control.AsyncSeq/AsyncSeq.fsi

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,22 @@ module AsyncSeq =
414414
/// Raises KeyNotFoundException if no matching element is found.
415415
val findIndexAsync : predicate:('T -> Async<bool>) -> source:AsyncSeq<'T> -> Async<int>
416416

417+
/// Asynchronously find the index of the last value in a sequence for which the predicate returns true.
418+
/// Returns None if no matching element is found.
419+
val tryFindIndexBack : predicate:('T -> bool) -> source:AsyncSeq<'T> -> Async<int option>
420+
421+
/// Asynchronously find the index of the last value in a sequence for which the async predicate returns true.
422+
/// Returns None if no matching element is found.
423+
val tryFindIndexBackAsync : predicate:('T -> Async<bool>) -> source:AsyncSeq<'T> -> Async<int option>
424+
425+
/// Asynchronously find the index of the last value in a sequence for which the predicate returns true.
426+
/// Raises KeyNotFoundException if no matching element is found.
427+
val findIndexBack : predicate:('T -> bool) -> source:AsyncSeq<'T> -> Async<int>
428+
429+
/// Asynchronously find the index of the last value in a sequence for which the async predicate returns true.
430+
/// Raises KeyNotFoundException if no matching element is found.
431+
val findIndexBackAsync : predicate:('T -> Async<bool>) -> source:AsyncSeq<'T> -> Async<int>
432+
417433
/// Asynchronously determine if there is a value in the sequence for which the predicate returns true
418434
val exists : predicate:('T -> bool) -> source:AsyncSeq<'T> -> Async<bool>
419435

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

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3491,6 +3491,71 @@ let ``AsyncSeq.tryFindIndexAsync returns None when not found`` () =
34913491
|> Async.RunSynchronously
34923492
Assert.AreEqual(None, result)
34933493

3494+
// ===== tryFindIndexBack / findIndexBack / tryFindIndexBackAsync / findIndexBackAsync =====
3495+
3496+
[<Test>]
3497+
let ``AsyncSeq.tryFindIndexBack returns index of last matching element`` () =
3498+
let result = AsyncSeq.ofSeq [ 1; 2; 3; 2; 1 ] |> AsyncSeq.tryFindIndexBack (fun x -> x = 2) |> Async.RunSynchronously
3499+
Assert.AreEqual(Some 3, result)
3500+
3501+
[<Test>]
3502+
let ``AsyncSeq.tryFindIndexBack returns None when no match`` () =
3503+
let result = AsyncSeq.ofSeq [ 1; 2; 3 ] |> AsyncSeq.tryFindIndexBack (fun x -> x = 99) |> Async.RunSynchronously
3504+
Assert.AreEqual(None, result)
3505+
3506+
[<Test>]
3507+
let ``AsyncSeq.tryFindIndexBack returns None for empty sequence`` () =
3508+
let result = AsyncSeq.empty<int> |> AsyncSeq.tryFindIndexBack (fun _ -> true) |> Async.RunSynchronously
3509+
Assert.AreEqual(None, result)
3510+
3511+
[<Test>]
3512+
let ``AsyncSeq.tryFindIndexBack returns index of last element when all match`` () =
3513+
let result = AsyncSeq.ofSeq [ 1; 2; 3 ] |> AsyncSeq.tryFindIndexBack (fun _ -> true) |> Async.RunSynchronously
3514+
Assert.AreEqual(Some 2, result)
3515+
3516+
[<Test>]
3517+
let ``AsyncSeq.findIndexBack returns index of last matching element`` () =
3518+
let result = AsyncSeq.ofSeq [ 10; 20; 30; 20; 10 ] |> AsyncSeq.findIndexBack (fun x -> x = 20) |> Async.RunSynchronously
3519+
Assert.AreEqual(3, result)
3520+
3521+
[<Test>]
3522+
let ``AsyncSeq.findIndexBack raises KeyNotFoundException when no match`` () =
3523+
Assert.Throws<System.Collections.Generic.KeyNotFoundException>(fun () ->
3524+
AsyncSeq.ofSeq [ 1; 2; 3 ] |> AsyncSeq.findIndexBack (fun x -> x = 99) |> Async.RunSynchronously |> ignore)
3525+
|> ignore
3526+
3527+
[<Test>]
3528+
let ``AsyncSeq.tryFindIndexBackAsync returns index of last matching element`` () =
3529+
let result =
3530+
AsyncSeq.ofSeq [ 1; 2; 3; 2; 1 ]
3531+
|> AsyncSeq.tryFindIndexBackAsync (fun x -> async { return x = 2 })
3532+
|> Async.RunSynchronously
3533+
Assert.AreEqual(Some 3, result)
3534+
3535+
[<Test>]
3536+
let ``AsyncSeq.tryFindIndexBackAsync returns None when no match`` () =
3537+
let result =
3538+
AsyncSeq.ofSeq [ 1; 2; 3 ]
3539+
|> AsyncSeq.tryFindIndexBackAsync (fun x -> async { return x = 99 })
3540+
|> Async.RunSynchronously
3541+
Assert.AreEqual(None, result)
3542+
3543+
[<Test>]
3544+
let ``AsyncSeq.findIndexBackAsync returns index of last matching element`` () =
3545+
let result =
3546+
AsyncSeq.ofSeq [ 5; 4; 3; 4; 5 ]
3547+
|> AsyncSeq.findIndexBackAsync (fun x -> async { return x = 4 })
3548+
|> Async.RunSynchronously
3549+
Assert.AreEqual(3, result)
3550+
3551+
[<Test>]
3552+
let ``AsyncSeq.findIndexBackAsync raises KeyNotFoundException when no match`` () =
3553+
Assert.Throws<System.Collections.Generic.KeyNotFoundException>(fun () ->
3554+
AsyncSeq.ofSeq [ 1; 2; 3 ]
3555+
|> AsyncSeq.findIndexBackAsync (fun x -> async { return x = 99 })
3556+
|> Async.RunSynchronously |> ignore)
3557+
|> ignore
3558+
34943559
// ===== tryFindBack / findBack / tryFindBackAsync / findBackAsync =====
34953560

34963561
[<Test>]

0 commit comments

Comments
 (0)