Zip multiple sequences

21,142

Solution 1

Here's one way to solve your example, but this is not for an arbitrary number of sequences.

val ints = List(1,2,3)
val chars = List('a', 'b', 'c')
val strings = List("Alpha", "Beta", "Gamma")
val bools = List(true, false, false)

val input = ints zip chars zip strings zip bools

// Flattens a tuple ((A,B),C) into (A,B,C)
def f2[A,B,C](t: ((A,B),C)) = (t._1._1, t._1._2, t._2)

// Flattens a tuple ((A,B),C,D) into (A,B,C,D)
def f3[A,B,C,D](t: ((A,B),C,D)) = (t._1._1, t._1._2, t._2, t._3)

input map f2 map f3

I don't think it is possible to do it generically for tuples of arbitrary length, at least not with this kind of solution. Tuples are strongly-typed, and the type system doesn't allow you to specify a variable number of type parameters, as far as I know, which makes it impossible to make a generalized version of f2 and f3 that takes a tuple of arbitrary length ((A,B),C,D,...) (that would return a tuple (A,B,C,D,...)).

If there were a way to specify a variable number of type parameters, we wouldn't need traits Tuple1, Tuple2, ... Tuple22 in Scala's standard library.

Solution 2

I think pattern matching is a good option

val ints = List(1,2,3)
val chars = List('a', 'b', 'c')
val strings = List("Alpha", "Beta", "Gamma")
val bools = List(true, false, false)
(ints zip chars zip strings zip bools) map { case (((i,c),s),b) => (i,c,s,b)}

**res1: List[(Int, Char, java.lang.String, Boolean)] = List((1,a,Alpha,true), (2,b,Beta,false), (3,c,Gamma,false))**

or you can add type as well

(ints zip chars zip strings zip bools) map {case (((i:Int,c:Char),s:String),b:Boolean) => (i,c,s,b)}

**res2: List[(Int, Char, java.lang.String, Boolean)] = List((1,a,Alpha,true), (2,b,Beta,false), (3,c,Gamma,false))**

Solution 3

I would create a class which represents the data sets:

case class DataSet(int: Int, char: Char, string: String, bool: Boolean)

This brings nicer names for accessing the values instead of _N we have in tuples. If the lists can have different sizes the shortest should be chosen:

val min = List(ints, chars, strings, bools).map(_.size).min

Now it is possible to extract the data:

val dataSets = (0 until min) map { i => DataSet(ints(i), chars(i), strings(i), bools(i)) }

When the original lists can contain a lot of values it is better to make them to a IndexedSeq so that the access time is O(1).

Solution 4

Using shapeless, you could do:

import shapeless.Tuples._

val ints = (1, 2, 3)
val chars = ('a', 'b', 'c')

val megatuple = (ints, chars)

val megahlist = (megatuple hlisted) map hlisted

val transposed = (mhlist transpose) map tupled tupled

scala> transposed
res: ((Int, Char), (Int, Char), (Int, Char)) = ((1,a),(2,b),(3,c))

(not sure, if there are more implicts defined which lets you avoid the map and back-and-forth conversions)

[Edit: This part is not true anymore.

Note that the shapeless docs say, only conversions up to Tuple4 are currently supported. You’d have to manually create the HLists then.]

Solution 5

Here is another solution which would work for your problem.

ints zip chars zip strings zip bools map{ case (((a, b), c), d) => (a,b,c,d)}
Share:
21,142
Tomasz Nurkiewicz
Author by

Tomasz Nurkiewicz

Contact me: nurkiewicz (at gmail) How does accepting an answer work? What have you tried? Stack Overflow and personal emails

Updated on November 12, 2021

Comments

  • Tomasz Nurkiewicz
    Tomasz Nurkiewicz over 2 years

    I am trying to zip multiple sequences to form a long tuple:

    val ints = List(1,2,3)
    val chars = List('a', 'b', 'c')
    val strings = List("Alpha", "Beta", "Gamma")
    val bools = List(true, false, false)
    
    ints zip chars zip strings zip bools
    

    What I get:

    List[(((Int, Char), String), Boolean)] =
      List((((1,a),Alpha),true), (((2,b),Beta),false), (((3,c),Gamma),false))
    

    However I would like to get a sequence of flat tuples:

    List[(Int, Char, String, Boolean)] = 
      List((1,a,Alpha,true), (2,b,Beta,false), (3,c,Gamma,false))
    

    I now I can do:

    List(ints, chars, strings, bools).transpose
    

    But it returns weakly typed List[List[Any]]. Also I can do (ints, chars, strings).zipped, but zipped works only on 2-tuples and 3-tuples.

    Is there a way to zip (arbitrary) number of equal-length sequences easily?

  • Tomasz Nurkiewicz
    Tomasz Nurkiewicz over 12 years
    +1, thanks. I am currently using the map approach, but t._1._1, t._1._2, t._2, t._3 isn't very readable - and in my case I need a 5-tuple, which makes matters even worse. I don't really need to support arbitrary length lists, but long enough. I though maybe there are some specialized methods that return strongly-typed tuples of correct length, but I get your point about Tuple1-Tuple22 problem.
  • Jesper
    Jesper over 12 years
    With pattern matching you can get rid of the unreadable ._1, ._2 etc. syntax: def f2[A,B,C](t: ((A,B),C)) = t match { case ((a, b), c) => (a, b, c) }
  • Miles Sabin
    Miles Sabin over 12 years
    Actually shapeless handles all arities up to 22.
  • Debilski
    Debilski over 12 years
    Ah, ok. I was going by the comment in conversions.scala without actually checking it.
  • Miles Sabin
    Miles Sabin over 12 years
    Oops ... out of date comment. Fixed now. Thanks for the heads up.