Skip to content

Commit 0f99b96

Browse files
Add AsyncSeq.transpose — mirrors Seq.transpose
Transposes a sequence of sequences: each element of the result is an array of the i-th elements of all inner sequences. All inner sequences must have the same length; the entire source is buffered before yielding. Available on all targets except Fable. Closes no specific issue; mirrors Seq.transpose. Co-authored-by: Copilot <[email protected]>
1 parent 9fd94c7 commit 0f99b96

4 files changed

Lines changed: 62 additions & 0 deletions

File tree

RELEASE_NOTES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
### 4.9.0
2+
3+
* Added `AsyncSeq.transpose` — transposes a sequence of sequences so each element of the result is an array of the i-th elements of all inner sequences; all inner sequences must have the same length and the entire source is buffered, mirroring `Seq.transpose`.
4+
15
### 4.8.0
26

37
* Added `AsyncSeq.mapFoldAsync` — maps each element using an asynchronous folder that also threads an accumulator state, returning both the array of results and the final state; mirrors `Seq.mapFold`.

src/FSharp.Control.AsyncSeq/AsyncSeq.fs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2035,6 +2035,17 @@ module AsyncSeq =
20352035
let! arr = toArrayAsync source
20362036
for i in arr.Length - 1 .. -1 .. 0 do
20372037
yield arr.[i] }
2038+
2039+
let transpose (source: AsyncSeq<#seq<'T>>) : AsyncSeq<'T[]> = asyncSeq {
2040+
let! rows = toArrayAsync source
2041+
if rows.Length > 0 then
2042+
let rowArrays = rows |> Array.map (fun r -> (r :> seq<'T>) |> Seq.toArray)
2043+
let colCount = rowArrays.[0].Length
2044+
for row in rowArrays do
2045+
if row.Length <> colCount then
2046+
invalidArg "source" "All inner sequences must have the same length."
2047+
for c in 0 .. colCount - 1 do
2048+
yield [| for row in rowArrays -> row.[c] |] }
20382049
#endif
20392050

20402051
#if !FABLE_COMPILER

src/FSharp.Control.AsyncSeq/AsyncSeq.fsi

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,12 @@ module AsyncSeq =
673673
/// sequence is buffered before yielding any elements, mirroring Seq.rev.
674674
/// This function should not be used with large or infinite sequences.
675675
val rev : source:AsyncSeq<'T> -> AsyncSeq<'T>
676+
677+
/// Transposes a sequence of sequences: each element of the result is an array
678+
/// of the i-th elements of all inner sequences, mirroring Seq.transpose.
679+
/// All inner sequences must have the same length; the entire source is buffered.
680+
/// This function should not be used with large or infinite sequences.
681+
val transpose : source:AsyncSeq<#seq<'T>> -> AsyncSeq<'T[]>
676682
#endif
677683

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

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3662,3 +3662,44 @@ let ``AsyncSeq.insertAt raises ArgumentException when index exceeds length`` ()
36623662
|> AsyncSeq.toArrayAsync
36633663
|> Async.RunSynchronously |> ignore)
36643664
|> ignore
3665+
3666+
// ── AsyncSeq.transpose ────────────────────────────────────────────────────────
3667+
3668+
[<Test>]
3669+
let ``AsyncSeq.transpose transposes rows and columns`` () =
3670+
let source = asyncSeq { yield [1; 2; 3]; yield [4; 5; 6] }
3671+
let result = AsyncSeq.transpose source |> AsyncSeq.toArrayAsync |> Async.RunSynchronously
3672+
Assert.AreEqual([| [|1;4|]; [|2;5|]; [|3;6|] |], result)
3673+
3674+
[<Test>]
3675+
let ``AsyncSeq.transpose single row`` () =
3676+
let source = asyncSeq { yield [10; 20; 30] }
3677+
let result = AsyncSeq.transpose source |> AsyncSeq.toArrayAsync |> Async.RunSynchronously
3678+
Assert.AreEqual([| [|10|]; [|20|]; [|30|] |], result)
3679+
3680+
[<Test>]
3681+
let ``AsyncSeq.transpose empty source returns empty`` () =
3682+
let result = AsyncSeq.transpose AsyncSeq.empty<int[]> |> AsyncSeq.toArrayAsync |> Async.RunSynchronously
3683+
Assert.AreEqual([||], result)
3684+
3685+
[<Test>]
3686+
let ``AsyncSeq.transpose single column`` () =
3687+
let source = asyncSeq { yield [1]; yield [2]; yield [3] }
3688+
let result = AsyncSeq.transpose source |> AsyncSeq.toArrayAsync |> Async.RunSynchronously
3689+
Assert.AreEqual([| [|1;2;3|] |], result)
3690+
3691+
[<Test>]
3692+
let ``AsyncSeq.transpose square matrix`` () =
3693+
let source = asyncSeq { yield [1; 2]; yield [3; 4] }
3694+
let result = AsyncSeq.transpose source |> AsyncSeq.toArrayAsync |> Async.RunSynchronously
3695+
Assert.AreEqual([| [|1;3|]; [|2;4|] |], result)
3696+
3697+
[<Test>]
3698+
let ``AsyncSeq.transpose raises on mismatched row lengths`` () =
3699+
Assert.Throws<System.ArgumentException>(fun () ->
3700+
asyncSeq { yield [1; 2]; yield [3] }
3701+
|> AsyncSeq.transpose
3702+
|> AsyncSeq.toArrayAsync
3703+
|> Async.RunSynchronously
3704+
|> ignore)
3705+
|> ignore

0 commit comments

Comments
 (0)