Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FS-1042] Implement transpose on Seq, List and Array #4020

Merged
merged 5 commits into from
Dec 18, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,31 @@ type ArrayModule2() =

()

[<Test>]
member this.Transpose() =
// integer array
Assert.AreEqual([|[|1;4|]; [|2;5|]; [|3;6|]|], Array.transpose (seq [[|1..3|]; [|4..6|]]))
Assert.AreEqual([|[|1|]; [|2|]; [|3|]|], Array.transpose [|[|1..3|]|])
Assert.AreEqual([|[|1..2|]|], Array.transpose [|[|1|]; [|2|]|])

// string array
Assert.AreEqual([|[|"a";"d"|]; [|"b";"e"|]; [|"c";"f"|]|], Array.transpose (seq [[|"a";"b";"c"|]; [|"d";"e";"f"|]]))

// empty array
Assert.AreEqual([| |], Array.transpose [| |])

// array of empty arrays - m x 0 array transposes to 0 x m (i.e. empty)
Assert.AreEqual([| |], Array.transpose [| [||] |])
Assert.AreEqual([| |], Array.transpose [| [||]; [||] |])

// null array
let nullArr = null: string[][]
CheckThrowsArgumentNullException (fun () -> nullArr |> Array.transpose |> ignore)

// jagged arrays
CheckThrowsArgumentException (fun () -> Array.transpose [| [|1; 2|]; [|3|] |] |> ignore)
CheckThrowsArgumentException (fun () -> Array.transpose [| [|1|]; [|2; 3|] |] |> ignore)

[<Test>]
member this.Truncate() =
// integer array
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,27 @@ type ListModule02() =

()

[<Test>]
member this.Transpose() =
// integer list
Assert.AreEqual([[1; 4]; [2; 5]; [3; 6]], List.transpose (seq [[1..3]; [4..6]]))
Assert.AreEqual([[1]; [2]; [3]], List.transpose [[1..3]])
Assert.AreEqual([[1..2]], List.transpose [[1]; [2]])

// string list
Assert.AreEqual([["a";"d"]; ["b";"e"]; ["c";"f"]], List.transpose (seq [["a";"b";"c"]; ["d";"e";"f"]]))

// empty list
Assert.AreEqual([], List.transpose [])

// list of empty lists - m x 0 list transposes to 0 x m (i.e. empty)
Assert.AreEqual([], List.transpose [[]])
Assert.AreEqual([], List.transpose [[]; []])

// jagged lists
CheckThrowsArgumentException (fun () -> List.transpose [[1; 2]; [3]] |> ignore)
CheckThrowsArgumentException (fun () -> List.transpose [[1]; [2; 3]] |> ignore)

[<Test>]
member this.Truncate() =
// integer list
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1522,7 +1522,32 @@ type SeqModule2() =
// null Seq
CheckThrowsArgumentNullException(fun() -> Seq.toList null |> ignore)
()


[<Test>]
member this.Transpose() =
// integer seq
VerifySeqsEqual [seq [1; 4]; seq [2; 5]; seq [3; 6]] <| Seq.transpose (seq [seq {1..3}; seq {4..6}])
VerifySeqsEqual [seq [1]; seq [2]; seq [3]] <| Seq.transpose (seq [seq {1..3}])
VerifySeqsEqual [seq {1..2}] <| Seq.transpose (seq [seq [1]; seq [2]])

// string seq
VerifySeqsEqual [seq ["a";"d"]; seq ["b";"e"]; seq ["c";"f"]] <| Seq.transpose (seq [seq ["a";"b";"c"]; seq ["d";"e";"f"]])

// empty seq
VerifySeqsEqual Seq.empty <| Seq.transpose Seq.empty

// seq of empty seqs - m x 0 seq transposes to 0 x m (i.e. empty)
VerifySeqsEqual Seq.empty <| Seq.transpose (seq [Seq.empty])
VerifySeqsEqual Seq.empty <| Seq.transpose (seq [Seq.empty; Seq.empty])

// null seq
let nullSeq = null : seq<seq<string>>
CheckThrowsArgumentNullException (fun () -> Seq.transpose nullSeq |> ignore)

// sequences of lists
VerifySeqsEqual [seq ["a";"c"]; seq ["b";"d"]] <| Seq.transpose [["a";"b"]; ["c";"d"]]
VerifySeqsEqual [seq ["a";"c"]; seq ["b";"d"]] <| Seq.transpose (seq { yield ["a";"b"]; yield ["c";"d"] })

[<Test>]
member this.Truncate() =
// integer Seq
Expand Down
3 changes: 3 additions & 0 deletions src/fsharp/FSharp.Core.Unittests/SurfaceArea.coreclr.fs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ Microsoft.FSharp.Collections.ArrayModule: T[] Where[T](Microsoft.FSharp.Core.FSh
Microsoft.FSharp.Collections.ArrayModule: T[] ZeroCreate[T](Int32)
Microsoft.FSharp.Collections.ArrayModule: T[][] ChunkBySize[T](Int32, T[])
Microsoft.FSharp.Collections.ArrayModule: T[][] SplitInto[T](Int32, T[])
Microsoft.FSharp.Collections.ArrayModule: T[][] Transpose[T](System.Collections.Generic.IEnumerable`1[T[]])
Microsoft.FSharp.Collections.ArrayModule: T[][] Windowed[T](Int32, T[])
Microsoft.FSharp.Collections.ArrayModule: Void CopyTo[T](T[], Int32, T[], Int32, Int32)
Microsoft.FSharp.Collections.ArrayModule: Void Fill[T](T[], Int32, Int32, T)
Expand Down Expand Up @@ -307,6 +308,7 @@ Microsoft.FSharp.Collections.ListModule: Int32 GetHashCode()
Microsoft.FSharp.Collections.ListModule: Int32 Length[T](Microsoft.FSharp.Collections.FSharpList`1[T])
Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[Microsoft.FSharp.Collections.FSharpList`1[T]] ChunkBySize[T](Int32, Microsoft.FSharp.Collections.FSharpList`1[T])
Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[Microsoft.FSharp.Collections.FSharpList`1[T]] SplitInto[T](Int32, Microsoft.FSharp.Collections.FSharpList`1[T])
Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[Microsoft.FSharp.Collections.FSharpList`1[T]] Transpose[T](System.Collections.Generic.IEnumerable`1[Microsoft.FSharp.Collections.FSharpList`1[T]])
Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[Microsoft.FSharp.Collections.FSharpList`1[T]] Windowed[T](Int32, Microsoft.FSharp.Collections.FSharpList`1[T])
Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[System.Tuple`2[System.Int32,T]] Indexed[T](Microsoft.FSharp.Collections.FSharpList`1[T])
Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[System.Tuple`2[T,T]] Pairwise[T](Microsoft.FSharp.Collections.FSharpList`1[T])
Expand Down Expand Up @@ -446,6 +448,7 @@ Microsoft.FSharp.Collections.SeqModule: Microsoft.FSharp.Core.FSharpOption`1[T]
Microsoft.FSharp.Collections.SeqModule: Microsoft.FSharp.Core.FSharpOption`1[T] TryHead[T](System.Collections.Generic.IEnumerable`1[T])
Microsoft.FSharp.Collections.SeqModule: Microsoft.FSharp.Core.FSharpOption`1[T] TryItem[T](Int32, System.Collections.Generic.IEnumerable`1[T])
Microsoft.FSharp.Collections.SeqModule: Microsoft.FSharp.Core.FSharpOption`1[T] TryLast[T](System.Collections.Generic.IEnumerable`1[T])
Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[System.Collections.Generic.IEnumerable`1[T]] Transpose[TCollection,T](System.Collections.Generic.IEnumerable`1[TCollection])
Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[System.Tuple`2[System.Int32,T]] Indexed[T](System.Collections.Generic.IEnumerable`1[T])
Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[System.Tuple`2[T,T]] Pairwise[T](System.Collections.Generic.IEnumerable`1[T])
Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[System.Tuple`2[T1,T2]] AllPairs[T1,T2](System.Collections.Generic.IEnumerable`1[T1], System.Collections.Generic.IEnumerable`1[T2])
Expand Down
3 changes: 3 additions & 0 deletions src/fsharp/FSharp.Core.Unittests/SurfaceArea.net40.fs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ Microsoft.FSharp.Collections.ArrayModule: T[] Where[T](Microsoft.FSharp.Core.FSh
Microsoft.FSharp.Collections.ArrayModule: T[] ZeroCreate[T](Int32)
Microsoft.FSharp.Collections.ArrayModule: T[][] ChunkBySize[T](Int32, T[])
Microsoft.FSharp.Collections.ArrayModule: T[][] SplitInto[T](Int32, T[])
Microsoft.FSharp.Collections.ArrayModule: T[][] Transpose[T](System.Collections.Generic.IEnumerable`1[T[]])
Microsoft.FSharp.Collections.ArrayModule: T[][] Windowed[T](Int32, T[])
Microsoft.FSharp.Collections.ArrayModule: Void CopyTo[T](T[], Int32, T[], Int32, Int32)
Microsoft.FSharp.Collections.ArrayModule: Void Fill[T](T[], Int32, Int32, T)
Expand Down Expand Up @@ -294,6 +295,7 @@ Microsoft.FSharp.Collections.ListModule: Int32 GetHashCode()
Microsoft.FSharp.Collections.ListModule: Int32 Length[T](Microsoft.FSharp.Collections.FSharpList`1[T])
Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[Microsoft.FSharp.Collections.FSharpList`1[T]] ChunkBySize[T](Int32, Microsoft.FSharp.Collections.FSharpList`1[T])
Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[Microsoft.FSharp.Collections.FSharpList`1[T]] SplitInto[T](Int32, Microsoft.FSharp.Collections.FSharpList`1[T])
Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[Microsoft.FSharp.Collections.FSharpList`1[T]] Transpose[T](System.Collections.Generic.IEnumerable`1[Microsoft.FSharp.Collections.FSharpList`1[T]])
Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[Microsoft.FSharp.Collections.FSharpList`1[T]] Windowed[T](Int32, Microsoft.FSharp.Collections.FSharpList`1[T])
Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[System.Tuple`2[System.Int32,T]] Indexed[T](Microsoft.FSharp.Collections.FSharpList`1[T])
Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[System.Tuple`2[T,T]] Pairwise[T](Microsoft.FSharp.Collections.FSharpList`1[T])
Expand Down Expand Up @@ -433,6 +435,7 @@ Microsoft.FSharp.Collections.SeqModule: Microsoft.FSharp.Core.FSharpOption`1[T]
Microsoft.FSharp.Collections.SeqModule: Microsoft.FSharp.Core.FSharpOption`1[T] TryHead[T](System.Collections.Generic.IEnumerable`1[T])
Microsoft.FSharp.Collections.SeqModule: Microsoft.FSharp.Core.FSharpOption`1[T] TryItem[T](Int32, System.Collections.Generic.IEnumerable`1[T])
Microsoft.FSharp.Collections.SeqModule: Microsoft.FSharp.Core.FSharpOption`1[T] TryLast[T](System.Collections.Generic.IEnumerable`1[T])
Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[System.Collections.Generic.IEnumerable`1[T]] Transpose[TCollection,T](System.Collections.Generic.IEnumerable`1[TCollection])
Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[System.Tuple`2[System.Int32,T]] Indexed[T](System.Collections.Generic.IEnumerable`1[T])
Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[System.Tuple`2[T,T]] Pairwise[T](System.Collections.Generic.IEnumerable`1[T])
Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[System.Tuple`2[T1,T2]] AllPairs[T1,T2](System.Collections.Generic.IEnumerable`1[T1], System.Collections.Generic.IEnumerable`1[T2])
Expand Down
23 changes: 23 additions & 0 deletions src/fsharp/FSharp.Core/array.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1238,6 +1238,29 @@ namespace Microsoft.FSharp.Collections
elif array.Length = 0 then invalidArg "array" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString
else invalidArg "array" (SR.GetString(SR.inputSequenceTooLong))

let transposeArrays (array:'T[][]) =
let len = array.Length
if len = 0 then Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked 0 else
let lenInner = array.[0].Length

for j in 1..len-1 do
if lenInner <> array.[j].Length then
invalidArgDifferentArrayLength "array.[0]" lenInner (String.Format("array.[{0}]", j)) array.[j].Length

let result : 'T[][] = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked lenInner
for i in 0..lenInner-1 do
result.[i] <- Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked len
for j in 0..len-1 do
result.[i].[j] <- array.[j].[i]
result

[<CompiledName("Transpose")>]
let transpose (arrays:seq<'T[]>) =
checkNonNull "arrays" arrays
match arrays with
| :? ('T[][]) as ts -> ts |> transposeArrays // avoid a clone, since we only read the array
| _ -> arrays |> Seq.toArray |> transposeArrays

[<CompiledName("Truncate")>]
let truncate count (array:'T[]) =
checkNonNull "array" array
Expand Down
8 changes: 8 additions & 0 deletions src/fsharp/FSharp.Core/array.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,14 @@ namespace Microsoft.FSharp.Collections
[<CompiledName("ToSeq")>]
val toSeq: array:'T[] -> seq<'T>

/// <summary>Returns the transpose of the given sequence of arrays.</summary>
/// <param name="arrays">The input sequence of arrays.</param>
/// <returns>The transposed array.</returns>
/// <exception cref="System.ArgumentNullException">Thrown when the input sequence is null.</exception>
/// <exception cref="System.ArgumentException">Thrown when the input arrays differ in length.</exception>
[<CompiledName("Transpose")>]
val transpose: arrays:seq<'T[]> -> 'T[][]

/// <summary>Returns at most N elements in a new array.</summary>
/// <param name="count">The maximum number of items to return.</param>
/// <param name="array">The input array.</param>
Expand Down
15 changes: 11 additions & 4 deletions src/fsharp/FSharp.Core/list.fs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ namespace Microsoft.FSharp.Collections
[<RequireQualifiedAccess>]
module List =

let inline checkNonNull argName arg =
match box arg with
| null -> nullArg argName
| _ -> ()

let inline indexNotFound() = raise (KeyNotFoundException(SR.GetString(SR.keyNotFoundAlt)))

[<CompiledName("Length")>]
Expand Down Expand Up @@ -412,10 +417,7 @@ namespace Microsoft.FSharp.Collections

[<CompiledName("Except")>]
let except itemsToExclude list =
match box itemsToExclude with
| null -> nullArg "itemsToExclude"
| _ -> ()

checkNonNull "itemsToExclude" itemsToExclude
match list with
| [] -> list
| _ ->
Expand Down Expand Up @@ -666,6 +668,11 @@ namespace Microsoft.FSharp.Collections
| [] -> invalidArg "source" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString
| _ -> invalidArg "source" (SR.GetString(SR.inputSequenceTooLong))

[<CompiledName("Transpose")>]
let transpose (lists : seq<'T list>) =
checkNonNull "lists" lists
Microsoft.FSharp.Primitives.Basics.List.transpose (ofSeq lists)

[<CompiledName("Truncate")>]
let truncate count list = Microsoft.FSharp.Primitives.Basics.List.truncate count list

Expand Down
8 changes: 8 additions & 0 deletions src/fsharp/FSharp.Core/list.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,14 @@ namespace Microsoft.FSharp.Collections
[<CompiledName("TryHead")>]
val tryHead: list:'T list -> 'T option

/// <summary>Returns the transpose of the given sequence of lists.</summary>
/// <param name="lists">The input sequence of list.</param>
/// <returns>The transposed list.</returns>
/// <exception cref="System.ArgumentNullException">Thrown when the input sequence is null.</exception>
/// <exception cref="System.ArgumentException">Thrown when the input lists differ in length.</exception>
[<CompiledName("Transpose")>]
val transpose: lists:seq<'T list> -> 'T list list

/// <summary>Returns at most N elements in a new list.</summary>
/// <param name="count">The maximum number of items to return.</param>
/// <param name="array">The input list.</param>
Expand Down
65 changes: 65 additions & 0 deletions src/fsharp/FSharp.Core/local.fs
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,71 @@ module internal List =
then cons, (partitionToFreshConsTailLeft cons predicate t)
else (partitionToFreshConsTailRight cons predicate t), cons

let rec transposeGetHeadsFreshConsTail headsCons tailsCons list headCount =
match list with
| [] ->
setFreshConsTail headsCons []
setFreshConsTail tailsCons []
headCount
| head::tail ->
match head with
| [] ->
setFreshConsTail headsCons []
setFreshConsTail tailsCons []
headCount
| h::t ->
let headsCons2 = freshConsNoTail h
setFreshConsTail headsCons headsCons2
let tailsCons2 = freshConsNoTail t
setFreshConsTail tailsCons tailsCons2
transposeGetHeadsFreshConsTail headsCons2 tailsCons2 tail (headCount + 1)

/// Split off the heads of the lists
let transposeGetHeads list =
match list with
| [] -> [],[],0
| head::tail ->
match head with
| [] ->
let mutable j = 0
for t in tail do
j <- j + 1
if not t.IsEmpty then
invalidArgDifferentListLength "list.[0]" (System.String.Format("list.[{0}]", j)) t.Length
[],[],0
| h::t ->
let headsCons = freshConsNoTail h
let tailsCons = freshConsNoTail t
let headCount = transposeGetHeadsFreshConsTail headsCons tailsCons tail 1
headsCons, tailsCons, headCount

/// Append the next element to the transposed list
let rec transposeToFreshConsTail cons list expectedCount =
match list with
| [] -> setFreshConsTail cons []
| _ ->
match transposeGetHeads list with
| [],_,_ ->
setFreshConsTail cons []
| heads,tails,headCount ->
if headCount < expectedCount then
invalidArgDifferentListLength (System.String.Format("list.[{0}]", headCount)) "list.[0]" <| tails.[0].Length + 1
let cons2 = freshConsNoTail heads
setFreshConsTail cons cons2
transposeToFreshConsTail cons2 tails expectedCount

/// Build the transposed list
let transpose (list: 'T list list) =
match list with
| [] -> list
| [[]] -> []
| _ ->
let heads, tails, headCount = transposeGetHeads list
if headCount = 0 then [] else
let cons = freshConsNoTail heads
transposeToFreshConsTail cons tails headCount
cons

let rec truncateToFreshConsTail cons count list =
if count = 0 then setFreshConsTail cons [] else
match list with
Expand Down
1 change: 1 addition & 0 deletions src/fsharp/FSharp.Core/local.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ module internal List =
val toArray : 'T list -> 'T[]
val inline ofSeq : seq<'T> -> 'T List
val splitAt : int -> 'T list -> ('T list * 'T list)
val transpose : 'T list list -> 'T list list
val truncate : int -> 'T list -> 'T list

module internal Array =
Expand Down
9 changes: 8 additions & 1 deletion src/fsharp/FSharp.Core/seq.fs
Original file line number Diff line number Diff line change
Expand Up @@ -854,7 +854,6 @@ namespace Microsoft.FSharp.Collections
[<CompiledName("Singleton")>]
let singleton value = mkSeq (fun () -> IEnumerator.Singleton value)


[<CompiledName("Truncate")>]
let truncate count (source: seq<'T>) =
checkNonNull "source" source
Expand Down Expand Up @@ -1079,6 +1078,14 @@ namespace Microsoft.FSharp.Collections
then mkDelayedSeq (fun () -> groupByValueType projection source)
else mkDelayedSeq (fun () -> groupByRefType projection source)

[<CompiledName("Transpose")>]
let transpose (source: seq<#seq<'T>>) =
checkNonNull "source" source
source
|> collect indexed
|> groupBy fst
|> map (snd >> (map snd))

[<CompiledName("Distinct")>]
let distinct source =
checkNonNull "source" source
Expand Down
10 changes: 10 additions & 0 deletions src/fsharp/FSharp.Core/seq.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -1276,6 +1276,16 @@ namespace Microsoft.FSharp.Collections
[<CompiledName("TryPick")>]
val tryPick: chooser:('T -> 'U option) -> source:seq<'T> -> 'U option

/// <summary>Returns the transpose of the given sequence of sequences.</summary>
/// <remarks>This function returns a sequence that digests the whole initial sequence as soon as
/// that sequence is iterated. As a result this function should not be used with
/// large or infinite sequences.</remarks>
/// <param name="source">The input sequence.</param>
/// <returns>The transposed sequence.</returns>
/// <exception cref="System.ArgumentNullException">Thrown when the input sequence is null.</exception>
[<CompiledName("Transpose")>]
val transpose: source:seq<'Collection> -> seq<seq<'T>> when 'Collection :> seq<'T>

/// <summary>Returns a sequence that when enumerated returns at most N elements.</summary>
///
/// <param name="count">The maximum number of items to enumerate.</param>
Expand Down