Search code examples
scalacollectionstuplesfor-comprehension

Use 4 (or N) collections to yield only one value at a time (1xN) (i.e. zipped for tuple4+)


scala> val a = List(1,2)
a: List[Int] = List(1, 2)

scala> val b = List(3,4)
b: List[Int] = List(3, 4)

scala> val c = List(5,6)
c: List[Int] = List(5, 6)

scala> val d = List(7,8)
d: List[Int] = List(7, 8)

scala> (a,b,c).zipped.toList
res6: List[(Int, Int, Int)] = List((1,3,5), (2,4,6))

Now:

scala> (a,b,c,d).zipped.toList
<console>:12: error: value zipped is not a member of (List[Int], List[Int], List[Int],     List[Int])
              (a,b,c,d).zipped.toList
                       ^

I've searched for this elsewhere, including this one and this one, but no conclusive answer.

I want to do the following or similar:

for((itemA,itemB,itemC,itemD) <- (something)) yield itemA + itemB + itemC + itemD

Any suggestions?


Solution

  • Short answer:

    for (List(w,x,y,z) <- List(a,b,c,d).transpose) yield (w,x,y,z)
      // List[(Int, Int, Int, Int)] = List((1,3,5,7), (2,4,6,8))
    

    Why you want them as tuples, I'm not sure, but a slightly more interesting case would be when your lists are of different types, and for example, you want to combine them into a list of objects:

    case class Person(name: String, age: Int, height: Double, weight: Double)
    val names =   List("Alf", "Betty")
    val ages =    List(22, 33)
    val heights = List(111.1, 122.2)
    val weights = List(70.1, 80.2)
    val persons: List[Person] = ???
    

    Solution 1: using transpose, as above:

    for { List(name: String, age: Int, height: Double, weight: Double) <- 
            List(names, ages, heights, weights).transpose
        } yield Person(name, age, height, weight)
    

    Here, we need the type annotations in the List extractor, because transpose gives a List[List[Any]].

    Solution 2: using iterators:

    val namesIt   = names.iterator
    val agesIt    = ages.iterator
    val heightsIt = heights.iterator
    val weightsIt = weights.iterator
    
    for { name <- names } 
      yield Person(namesIt.next, agesIt.next, heightsIt.next, weightsIt.next)
    

    Some people would avoid iterators because they involve mutable state and so are not "functional". But they're easy to understand if you come from the Java world and might be suitable if what you actually have are already iterators (input streams etc).