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?
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.
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))**
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).
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.]
I share Jesper's opinion that this is not possible in general, since each tuple arity is represented as separate class in source code, so you have to write separate code to access them unless using a code generator.
But I want to add another possible solution. If you want to preserve the typing of your tuple entries, but are otherwise interested in a more collection-like type, maybe HLists (heterogenous lists) are for you. You can google hlist scala
for implementations and explanations.
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)}
Using product-collections
scala> ints flatZip chars flatZip strings flatZip bools
res0: org.catch22.collections.immutable.CollSeq4[Int,Char,String,Boolean] =
CollSeq((1,a,Alpha,true),
(2,b,Beta,false),
(3,c,Gamma,false))
This currently works for arity 1 - 22. As you can see the types are preserved.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With