30

Let's consider a simple mapping example:


  val a = Array("One", "Two", "Three")
  val b = a.map(s => myFn(s))

What I need is to use not myFn(s: String): String here, but myFn(s: String, n: Int): String, where n would be the index of s in a. In this particular case myFn would expect the second argument to be 0 for s == "One", 1 for s == "Two" and 2 for s == "Three". How can I achieve this?

Ivan
  • 63,011
  • 101
  • 250
  • 382

4 Answers4

77

Depends whether you want convenience or speed.

Slow:

a.zipWithIndex.map{ case (s,i) => myFn(s,i) }

Faster:

for (i <- a.indices) yield myFn(a(i),i)

{ var i = -1; a.map{ s => i += 1; myFn(s,i) } }

Possibly fastest:

Array.tabulate(a.length){ i => myFn(a(i),i) }

If not, this surely is:

val b = new Array[Whatever](a.length)
var i = 0
while (i < a.length) {
  b(i) = myFn(a(i),i)
  i += 1
}

(In Scala 2.10.1 with Java 1.6u37, if "possibly fastest" is declared to take 1x time for a trivial string operation (truncation of a long string to a few characters), then "slow" takes 2x longer, "faster" each take 1.3x longer, and "surely" takes only 0.5x the time.)

Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
  • 18
    I love reading your answers on speed stuff, but it can be so depressing sometimes... :-) – Daniel C. Sobral Feb 04 '12 at 01:43
  • How does the speed of `a.view.zipWithIndex.map{ case (s,i) => myFn(s,i) }` compare to your solutions? – gzm0 May 31 '13 at 00:20
  • @gzm0 - Even slower (by 2-5x) than the slowest if you actually need all the values. If you only need a few, then the view is better. – Rex Kerr May 31 '13 at 05:44
5

What about this? I think it should be fast and it's pretty. But I'm no expert on Scala speed...

a.foldLeft(0) ((i, x) => {myFn(x, i); i + 1;} )
Matthew Saltz
  • 385
  • 4
  • 8
5

A general tip: Use .iterator method liberally, to avoid creation of intermediate collections, and thus speed up your computation. (Only when performance requirements demand it. Or else don't.)

scala> def myFun(s: String, i: Int) = s + i
myFun: (s: String, i: Int)java.lang.String

scala> Array("nami", "zoro", "usopp")
res17: Array[java.lang.String] = Array(nami, zoro, usopp)

scala> res17.iterator.zipWithIndex
res19: java.lang.Object with Iterator[(java.lang.String, Int)]{def idx: Int; def idx_=(x$1: Int): Unit} = non-empty iterator

scala> res19 map { case (k, v) => myFun(k, v) }
res22: Iterator[java.lang.String] = non-empty iterator

scala> res22.toArray
res23: Array[java.lang.String] = Array(nami0, zoro1, usopp2)

Keep in mind that iterators are mutable, and hence once consumed cannot be used again.


An aside: The map call above involves de-tupling and then function application. This forces use of some local variables. You can avoid that using some higher order sorcery - convert a regular function to the one accepting tuple, and then pass it to map.

scala> Array("nami", "zoro", "usopp").zipWithIndex.map(Function.tupled(myFun))
res24: Array[java.lang.String] = Array(nami0, zoro1, usopp2)
missingfaktor
  • 90,905
  • 62
  • 285
  • 365
  • 1
    "iterators are mutable, and hence once consumed cannot be used again" - I've once spent some time debugging why doesn't a program work to find an iterator to get empty once used :-) – Ivan Feb 05 '12 at 02:23
0

Index can also be accessed via the second element of tuples generated by the zipWithIndex method:

val a = Array("One", "Two", "Three")
val b = a.zipWithIndex.map(s => myFn(s._1, s._2))
suat
  • 4,239
  • 3
  • 28
  • 51