63

Suppose I have two arrays:

val ar1 = Array[String]("1", "2", "3")
val ar2 = Array[String]("1", "2", "3", "4")

Now for each element of ar1, I want to first concatenate that element with the corresponding element of ar2, and then print the result. One way to do would be something like:

List.range(0, ar1.size).foreach(i => println(ar1(i)+ar2(i)))

It would have been nicer if there was a foreach variant that would allow me to work directly with the indices of ar1 instead of first constructing the integer list.

Perhaps there is a better way?

Jus12
  • 17,824
  • 28
  • 99
  • 157
  • See also http://stackoverflow.com/questions/6833501/efficient-iteration-with-index-in-scala – Vadzim Nov 19 '13 at 21:40

4 Answers4

105

One very convenient way to do this is with the zipped method on tuples. Put two collections in, get out two arguments to a function!

(ar1,ar2).zipped.foreach((x,y) => println(x+y))

This is both convenient to write and fast, since you don't need to build a tuple to store each pair (as you would with (ar1 zip ar2)) which you then have to take apart again. Both forms of zip stop when the shorter of the two collections is exhausted.

If you have something more complicated (e.g. you need to do math on the index), the canonical solution is to zip in the index:

ar1.zipWithIndex.foreach{ case(x,i) => println(x+ar2(i)) }

The method you are using is more rapidly and compactly done as follows, an can be useful:

ar1.indices.foreach(i => println(ar1(i)+ar2(i)))

although this only works if the first collection is no longer than the second. You can also specify your ranges explcitly:

(0 until (ar1.size min ar2.size)).foreach(i => println(ar1(i)+ar2(i)))

to get around this problem. (You can see why zip and zipped are preferred unless what you're doing is too complicated for this to work easily.)

If it is not a parallel collection (and usually it is not unless you call .par), it's also possible, though not recommended, to keep track with a mutable variable:

{ var i=-1; ar1.foreach{ x => i += 1; println(x+ar2(i)) } }

There are a very limited number of cases where this is necessary (e.g. if you may want to skip or backtrack on some of the other collection(s)); if you can avoid having to do this, you'll usually end up with code that's easier to reason about.

Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
  • 1
    I doesn't answer the question, since the question asks explicitly about `Traversable`, and `Traversable` doesn't have `zipWithIndex` - which is *annoying*. – Wilfred Springer Oct 12 '12 at 15:09
  • @WilfredSpringer - The question does _not_ ask about `Traversable`, it just suggests wanting to use `foreach` at some point. – Rex Kerr Oct 12 '12 at 15:50
  • Don't get me wrong. I will vote up your answer. I'm just saying the question is: "Get index of current element in a foreach method of *Traversable*?" So it seems the starting point is having a `Traversable`; at least that's how *I* read the question. – Wilfred Springer Oct 13 '12 at 19:23
  • @WilfredSpringer - Well, fair enough; the question does mention `Traversable` even though the question itself suggests that you can assume you have a `Seq`. Note that you have to cope with _two_ sequences which is inherently not possible with `Traversable` alone unless you run each in separate threads and block--which is an absurdly slow and complicated solution for a straightforward problem--or with `O(n^2)` performance, which is dreadful for an inherently `O(n)` problem. (The real issue is that `foreach` is not a very flexible operation.) – Rex Kerr Oct 13 '12 at 20:48
41

This is how you loop with an index in idiomatic Scala:

scala> List("A", "B", "C").zipWithIndex foreach { case(el, i) =>
     |   println(i + ": " + el)
     | }
0: A
1: B
2: C

And here is the idiomatic Scala way to do what you are trying to achieve in your code:

scala> val arr1 = Array("1", "2", "3")
arr1: Array[java.lang.String] = Array(1, 2, 3)

scala> val arr2 = Array("1", "2", "3", "4")
arr2: Array[java.lang.String] = Array(1, 2, 3, 4)

scala> (arr1, arr2).zipped.map(_ + _) foreach println
11
22
33
missingfaktor
  • 90,905
  • 62
  • 285
  • 365
4

I did not had the opportunity to test it, but this should do the trick:

ar1.zip(ar2).foreach(x => println(x._1 + x._2))
Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
gizmo
  • 11,819
  • 6
  • 44
  • 61
3

zip will do it:

ar1 zip ar2 foreach { p => println(p._1 + p._2) }

This will yield:

11
22
33

Note that you don't need [String] generic type, will be infered by the compiler:

val ar1 = Array("1", "2", "3")
val ar2 = Array("1", "2", "3", "4")
Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674