Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Zip multiple sequences

Tags:

tuples

scala

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?

like image 997
Tomasz Nurkiewicz Avatar asked Mar 09 '12 09:03

Tomasz Nurkiewicz


7 Answers

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.

like image 175
Jesper Avatar answered Sep 25 '22 05:09

Jesper


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))**
like image 35
igx Avatar answered Sep 25 '22 05:09

igx


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).

like image 38
kiritsuku Avatar answered Sep 24 '22 05:09

kiritsuku


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.]

like image 35
Debilski Avatar answered Sep 26 '22 05:09

Debilski


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.

like image 34
ziggystar Avatar answered Sep 24 '22 05:09

ziggystar


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)}
like image 33
Shirish Namdeo Avatar answered Sep 23 '22 05:09

Shirish Namdeo


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.

like image 20
Mark Lister Avatar answered Sep 26 '22 05:09

Mark Lister