Skip to content

Commit ced9d2e

Browse files
github-actions[bot]Copilotdsyme
authored
[Repo Assist] Add AsyncSeq.splitAt — split at index, returning first N as array and remainder as AsyncSeq (#267)
* Add AsyncSeq.splitAt — splits at index, returning first N elements as array and remainder as AsyncSeq Mirrors Seq.splitAt. Source is enumerated once; remainder is produced lazily. 6 new tests; 293/293 total pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ci: trigger checks --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Don Syme <dsyme@users.noreply.github.com>
1 parent efa9e4c commit ced9d2e

File tree

4 files changed

+79
-0
lines changed

4 files changed

+79
-0
lines changed

RELEASE_NOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
### 4.7.0
22

3+
* Added `AsyncSeq.splitAt` — splits a sequence at the given index, returning the first `count` elements as an array and the remaining elements as a new `AsyncSeq`. Mirrors `Seq.splitAt`. The source is enumerated once.
34
* Added `AsyncSeq.removeAt` — returns a new sequence with the element at the specified index removed, mirroring `Seq.removeAt`.
45
* Added `AsyncSeq.updateAt` — returns a new sequence with the element at the specified index replaced by a given value, mirroring `Seq.updateAt`.
56
* Added `AsyncSeq.insertAt` — returns a new sequence with a value inserted before the element at the specified index (or appended if the index equals the sequence length), mirroring `Seq.insertAt`.

src/FSharp.Control.AsyncSeq/AsyncSeq.fs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1841,6 +1841,35 @@ module AsyncSeq =
18411841

18421842
let tail (source : AsyncSeq<'T>) : AsyncSeq<'T> = skip 1 source
18431843

1844+
/// Splits an async sequence at the given index, returning the first `count` elements as an array
1845+
/// and the remaining elements as a new AsyncSeq. The source is enumerated once.
1846+
let splitAt (count: int) (source: AsyncSeq<'T>) : Async<'T array * AsyncSeq<'T>> = async {
1847+
if count < 0 then invalidArg "count" "must be non-negative"
1848+
let ie = source.GetEnumerator()
1849+
let ra = ResizeArray<'T>()
1850+
let! m = ie.MoveNext()
1851+
let b = ref m
1852+
while b.Value.IsSome && ra.Count < count do
1853+
ra.Add b.Value.Value
1854+
let! next = ie.MoveNext()
1855+
b := next
1856+
let first = ra.ToArray()
1857+
let rest =
1858+
if b.Value.IsNone then
1859+
ie.Dispose()
1860+
empty<'T>
1861+
else
1862+
let cur = ref b.Value
1863+
asyncSeq {
1864+
try
1865+
while cur.Value.IsSome do
1866+
yield cur.Value.Value
1867+
let! next = ie.MoveNext()
1868+
cur := next
1869+
finally
1870+
ie.Dispose() }
1871+
return first, rest }
1872+
18441873
let toArrayAsync (source : AsyncSeq<'T>) : Async<'T[]> = async {
18451874
let ra = (new ResizeArray<_>())
18461875
use ie = source.GetEnumerator()

src/FSharp.Control.AsyncSeq/AsyncSeq.fsi

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,11 @@ module AsyncSeq =
599599
/// Returns an empty sequence if the source is empty.
600600
val tail : source:AsyncSeq<'T> -> AsyncSeq<'T>
601601

602+
/// Splits an async sequence at the given index. Returns an async computation that yields
603+
/// the first `count` elements as an array and the remaining elements as a new AsyncSeq.
604+
/// The source is enumerated once; the returned AsyncSeq lazily produces the remainder.
605+
val splitAt : count:int -> source:AsyncSeq<'T> -> Async<'T array * AsyncSeq<'T>>
606+
602607
/// Creates an async computation which iterates the AsyncSeq and collects the output into an array.
603608
val toArrayAsync : source:AsyncSeq<'T> -> Async<'T []>
604609

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

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3409,6 +3409,50 @@ let ``AsyncSeq.sortWith returns empty array for empty sequence`` () =
34093409
let result = AsyncSeq.sortWith compare AsyncSeq.empty<int>
34103410
Assert.AreEqual([||], result)
34113411

3412+
[<Test>]
3413+
let ``AsyncSeq.splitAt splits a sequence at the given index`` () =
3414+
let source = asyncSeq { yield 1; yield 2; yield 3; yield 4; yield 5 }
3415+
let first, rest = AsyncSeq.splitAt 3 source |> Async.RunSynchronously
3416+
let restArr = rest |> AsyncSeq.toArrayAsync |> Async.RunSynchronously
3417+
Assert.AreEqual([| 1; 2; 3 |], first)
3418+
Assert.AreEqual([| 4; 5 |], restArr)
3419+
3420+
[<Test>]
3421+
let ``AsyncSeq.splitAt with count=0 returns empty array and full rest`` () =
3422+
let source = asyncSeq { yield 10; yield 20 }
3423+
let first, rest = AsyncSeq.splitAt 0 source |> Async.RunSynchronously
3424+
let restArr = rest |> AsyncSeq.toArrayAsync |> Async.RunSynchronously
3425+
Assert.AreEqual([||], first)
3426+
Assert.AreEqual([| 10; 20 |], restArr)
3427+
3428+
[<Test>]
3429+
let ``AsyncSeq.splitAt with count >= length returns all elements in first and empty rest`` () =
3430+
let source = asyncSeq { yield 1; yield 2; yield 3 }
3431+
let first, rest = AsyncSeq.splitAt 10 source |> Async.RunSynchronously
3432+
let restArr = rest |> AsyncSeq.toArrayAsync |> Async.RunSynchronously
3433+
Assert.AreEqual([| 1; 2; 3 |], first)
3434+
Assert.AreEqual([||], restArr)
3435+
3436+
[<Test>]
3437+
let ``AsyncSeq.splitAt on empty sequence returns empty first and empty rest`` () =
3438+
let first, rest = AsyncSeq.splitAt 3 AsyncSeq.empty<int> |> Async.RunSynchronously
3439+
let restArr = rest |> AsyncSeq.toArrayAsync |> Async.RunSynchronously
3440+
Assert.AreEqual([||], first)
3441+
Assert.AreEqual([||], restArr)
3442+
3443+
[<Test>]
3444+
let ``AsyncSeq.splitAt with count equal to length returns all in first and empty rest`` () =
3445+
let source = asyncSeq { yield 7; yield 8; yield 9 }
3446+
let first, rest = AsyncSeq.splitAt 3 source |> Async.RunSynchronously
3447+
let restArr = rest |> AsyncSeq.toArrayAsync |> Async.RunSynchronously
3448+
Assert.AreEqual([| 7; 8; 9 |], first)
3449+
Assert.AreEqual([||], restArr)
3450+
3451+
[<Test>]
3452+
let ``AsyncSeq.splitAt with negative count throws ArgumentException`` () =
3453+
Assert.Throws<System.ArgumentException>(fun () ->
3454+
AsyncSeq.splitAt -1 AsyncSeq.empty<int> |> Async.RunSynchronously |> ignore) |> ignore
3455+
34123456
// ===== removeAt =====
34133457

34143458
[<Test>]

0 commit comments

Comments
 (0)