6

Let's say I have a sequence of sequences, e.g.

{1, 2, 3}, {1, 2, 3}, {1, 2, 3}

What is the best way to pivot or zip this sequence so I instead have,

{1, 1, 1}, {2, 2, 2}, {3, 3, 3}

Is there a comprehensible way of doing so without resorting to manipulation of the underlying IEnumerator<_> type?

To clarify, these are seq<seq<int>> objects. Each sequences (both internal and external) can have any number of items.

Ramon Snir
  • 7,520
  • 3
  • 43
  • 61
GregRos
  • 8,667
  • 3
  • 37
  • 63
  • So do you want to do a matrix transposition or group by an element - this is not very clear. An example for a longer input would be good. – John Palmer Oct 07 '12 at 10:36
  • 1
    possible duplicate of [How do I write a ZipN-like function in F#?](http://stackoverflow.com/questions/11770441/how-do-i-write-a-zipn-like-function-in-f) – Daniel Oct 07 '12 at 15:19

4 Answers4

4

If you're going for a solution which is semantically Seq, you're going to have to stay lazy all the time.

let zip seq = seq
            |> Seq.collect(fun s -> s |> Seq.mapi(fun i e -> (i, e))) //wrap with index
            |> Seq.groupBy(fst) //group by index
            |> Seq.map(fun (i, s) -> s |> Seq.map snd) //unwrap

Test:

let seq =  Enumerable.Repeat((seq [1; 2; 3]), 3) //don't want to while(true) yield. bleh.
printfn "%A" (zip seq)

Output:

seq [seq [1; 1; 1]; seq [2; 2; 2]; seq [3; 3; 3]]
Asti
  • 12,447
  • 29
  • 38
  • 1
    while this will work, Seq.groupBy is not lazy and will fully evaluate its input sequences once the first element is requested from Seq.groupBy – Johannes Rudolph Nov 05 '14 at 16:16
  • Is `Seq.mapi (fun i e -> (i, e))` the same as `Seq.indexed` ? (https://fsharp.github.io/fsharp-core-docs/reference/fsharp-collections-seqmodule.html#indexed) -- EDIT: yes - see Joshua answer – Romain Deneau Jul 16 '21 at 18:40
1

This seems very inelegant but it gets the right answer:

(seq [(1, 2, 3); (1, 2, 3); (1, 2, 3);]) 
|> Seq.fold (fun (sa,sb,sc) (a,b,c) ->a::sa,b::sb,c::sc) ([],[],[]) 
|> fun (a,b,c) -> a::b::c::[]
John Palmer
  • 25,356
  • 3
  • 48
  • 67
1

It looks like matrix transposition.

let data =
    seq [
        seq [1; 2; 3]
        seq [1; 2; 3]
        seq [1; 2; 3]
    ]

let rec transpose = function
    | (_::_)::_ as M -> List.map List.head M :: transpose (List.map List.tail M)
    | _ -> []

// I don't claim it is very elegant, but no doubt it is readable
let result =
    data
    |> List.ofSeq
    |> List.map List.ofSeq
    |> transpose
    |> Seq.ofList
    |> Seq.map Seq.ofList

Alternatively, you may adopt the same method for seq, thanks to this answer for an elegant Active pattern:

let (|SeqEmpty|SeqCons|) (xs: 'a seq) =
  if Seq.isEmpty xs then SeqEmpty
  else SeqCons(Seq.head xs, Seq.skip 1 xs)

let rec transposeSeq = function
    | SeqCons(SeqCons(_,_),_) as M ->
        Seq.append
            (Seq.singleton (Seq.map Seq.head M))
            (transposeSeq (Seq.map (Seq.skip 1) M))
    | _ -> Seq.empty

let resultSeq = data |> transposeSeq

See also this answer for technical details and two references: to PowerPack's Microsoft.FSharp.Math.Matrix and yet another method involving mutable data.

Community
  • 1
  • 1
Be Brave Be Like Ukraine
  • 7,596
  • 3
  • 42
  • 66
  • This evaluates all the sequences eagerly though. `Seq.ofList` does nothing but cast the list to `seq<'t>`, so there isn't much meaning in having the signature as `seq>`. – Asti Oct 07 '12 at 10:01
1

This is the same answer as @Asti, just cleaned up a little:

[[1;2;3]; [1;2;3]; [1;2;3]] 
    |> Seq.collect Seq.indexed 
    |> Seq.groupBy fst 
    |> Seq.map (snd >> Seq.map snd);;
Joshua
  • 4,099
  • 25
  • 37