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?

4 Answers 4

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

Sign up to request clarification or add additional context in comments.

3 Comments

I love reading your answers on speed stuff, but it can be so depressing sometimes... :-)
How does the speed of a.view.zipWithIndex.map{ case (s,i) => myFn(s,i) } compare to your solutions?
@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.
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)

1 Comment

"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 :-)
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;} )

Comments

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.