Search code examples
scalafor-comprehension

For comprehension with two generators


I want to display an arbitrary number of strings in a GUI-application in a vertical list.

Consider the following example:

val names = Seq[String]("Boris", "Anna", "Markus", "Simone")

for(a <- 1 to names.size + 1; name <- names) yield new ListItem(
  name,
  x = 20
  y = 128 * a
  size = 128
)

Now of course this would create a "cross-product" and print:

Boris Anna Markus Simone
Boris Anna Markus Simone
Boris Anna Markus Simone
Boris Anna Markus Simone

while the wanted result would be

Boris
Anna
Markus
Simone

But how else can I bind the index to the actual variable? I mean if I'd do a counter like:

for(a <- 0 to 1; b <- 0 to 1)
yield(a,b)

I'd get

Vector((0,0), (0,1), (1,0), (1,1))

but if I'd want to I could to (for a <- 0 to 1; b <- 0 to a) yield(a,b) and get

Vector((0,0), (1,0), (1,1))

I have no idea how to do that with my example tho.


Solution

  • You want zipWithIndex, which will create a new Seq containing each element from the original Seq, tupled with it's index within the Seq.

    scala> names.zipWithIndex
    res2: Seq[(String, Int)] = List((Boris,0), (Anna,1), (Markus,2), (Simone,3))
    

    Which you can then use:

    val names = Seq[String]("Boris", "Anna", "Markus", "Simone")
    class ListItem(name: String, x: Int, y: Int, size: Int)
    
    scala> for ((name, index) <- names.zipWithIndex) 
               yield new ListItem(name, 20, 128 * index, 128)
    res0: Seq[ListItem] = List(ListItem@5956b37d, ListItem@4b22015d, ListItem@2587a734, ListItem@6cf25a2b)
    

    Which can also be written as a map:

    names.zipWithIndex.map { case (name, index) =>
        new ListItem(name, 20, 128 * index, 128)
    }