116

Is there any List/Sequence built-in that behaves like map and provides the element's index as well?

Geo
  • 93,257
  • 117
  • 344
  • 520

8 Answers8

167

I believe you're looking for zipWithIndex?

scala> val ls = List("Mary", "had", "a", "little", "lamb")
scala> ls.zipWithIndex.foreach{ case (e, i) => println(i+" "+e) }
0 Mary
1 had
2 a
3 little
4 lamb

From: http://www.artima.com/forums/flat.jsp?forum=283&thread=243570

You also have variations like:

for((e,i) <- List("Mary", "had", "a", "little", "lamb").zipWithIndex) println(i+" "+e)

or:

List("Mary", "had", "a", "little", "lamb").zipWithIndex.foreach( (t) => println(t._2+" "+t._1) )
Viktor Klang
  • 26,479
  • 7
  • 51
  • 68
  • 4
    Well, I meant using the `zipWithIndex` method to get the index inside a loop/map/whatever. – ziggystar Mar 02 '12 at 16:46
  • For example comparing to a `while` loop, which is probably among the fastest options. – ziggystar Mar 02 '12 at 19:35
  • An Array and a while loop is probably as fast as it can get. – Viktor Klang Mar 03 '12 at 15:11
  • 2
    @ziggystar If you're looking for performance you need to trade off some immutability. Look inside the zipWithIndex function. It just uses a var to build a new collection of pairs. You could use the same method of incrementing a var without building the new collection, just as you would do in Java. But it's not functional style. Think if you actually need it. – Cristian Vrabie Mar 12 '12 at 10:52
  • So it seems even zipWithIndex itself is not functional style. Anyway I think it should be mentioned that using `view` you should be able to prevent creating and traversing an extra List. – herman Dec 08 '13 at 22:50
  • Don't use view, use ``iterator``, views are on the way out. – Viktor Klang Dec 09 '13 at 14:31
  • If you are concerned with efficiency see: http://stackoverflow.com/questions/9137644/how-to-get-the-element-index-when-mapping-an-array-in-scala – Meekohi Oct 20 '14 at 14:57
  • Is it possible to use zipWithIndex and get the indices as Int, instead of Long, without using further mapping? And about spark, wouldn't zipWithIndex be more efficient because of the distribution of data? – Savvas Parastatidis Dec 20 '15 at 20:39
76

Use .map in .zipWithIndex

val myList = List("a", "b", "c")

myList.zipWithIndex.map { case (element, index) => 
   println(element, index) 
   s"${element}(${index})"
}

Result:

List("a(0)", "b(1)", "c(2)")
Paulo Cheque
  • 2,797
  • 21
  • 22
  • 13
    This is the first decent example I've seen that used a `map` as requested instead of just printing inside a `foreach`. – Greg Chabala Mar 28 '17 at 16:29
12

The proposed solutions suffer from the fact that they create intermediate collections or introduce variables which are not strictly necessary. For ultimately all you need to do is to keep track of the number of steps of an iteration. This can be done using memoizing. The resulting code might look like

myIterable map (doIndexed(someFunction))

The doIndexed-Function wraps the interior function which receives both an index an the elements of myIterable. This might be familiar to you from JavaScript.

Here is a way to achieve this purpose. Consider the following utility:

object TraversableUtil {
    class IndexMemoizingFunction[A, B](f: (Int, A) => B) extends Function1[A, B] {
        private var index = 0
        override def apply(a: A): B = {
            val ret = f(index, a)
            index += 1
            ret
        }
    }

    def doIndexed[A, B](f: (Int, A) => B): A => B = {
        new IndexMemoizingFunction(f)
    }
}

This is already all you need. You can apply this for instance as follows:

import TraversableUtil._
List('a','b','c').map(doIndexed((i, char) => char + i))

which results in the list

List(97, 99, 101)

This way, you can use the usual Traversable-functions at the expense of wrapping your effective function. The overhead is the creation of the memoizing object and the counter therein. Otherwise this solution is as good (or bad) in terms of memory or performance as using unindexed map. Enjoy!

Matt Brasen
  • 121
  • 1
  • 2
  • 1
    This is a very elegant solution to the problem. +1 for not creating a temporary collection. Won't work in a parallel collection, but still a very nice solution. – fbl Sep 14 '12 at 13:18
  • 6
    If you don't want to create a temporary collection, just use `coll.view.zipWithIndex` instead of `coll.zipWithIndex` – tsuna Oct 21 '13 at 08:43
6

Or, assuming your collection has constant access time, you could map the list of indexes instead of the actual collection:

val ls = List("a","b","c")
0.until(ls.length).map( i => doStuffWithElem(i,ls(i)) )
Cristian Vrabie
  • 3,972
  • 5
  • 30
  • 49
  • 3
    A more elegant way to do this is simply: `ls.indices.map(i => doStuffWithElem(i, ls(i))` – Assil Ksiksi Sep 16 '16 at 02:22
  • 3
    @aksiksi Well, seeing that [`indices` is implemented as `0 until length`](https://github.com/scala/scala/blob/2.12.x/src/library/scala/collection/SeqLike.scala#L668) it's pretty much the same thing :P – Cristian Vrabie Sep 16 '16 at 13:14
  • 1
    downvote because of complexity - iterating with ls(i) takes O(n^2) – Moshe Bixenshpaner Jul 12 '17 at 15:55
  • 2
    @MosheBixenshpaner Fair enough. My example of `List` was indeed poor. I did mention however that this is suitable if your collection has constant access time. Should have picked `Array`. – Cristian Vrabie Jul 12 '17 at 21:12
5

There is CountedIterator in 2.7.x (which you can get from a normal iterator with .counted). I believe it's been deprecated (or simply removed) in 2.8, but it's easy enough to roll your own. You do need to be able to name the iterator:

val ci = List("These","are","words").elements.counted
scala> ci map (i => i+"=#"+ci.count) toList
res0: List[java.lang.String] = List(These=#0,are=#1,words=#2)
Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
5

There are two ways of doing this.

ZipWithIndex: Creates a counter automatically starting with 0.

  // zipWithIndex with a map.
  val days = List("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat")
  days.zipWithIndex.map {
        case (day, count) => println(s"$count is $day")
  }
  // Or use it simply with a for.
  for ((day, count) <- days.zipWithIndex) {
        println(s"$count is $day")
  }

Output of both code will be:

0 is Sun
1 is Mon
2 is Tue
3 is Wed
4 is Thu
5 is Fri
6 is Sat

Zip: Use zip method with a Stream to create a counter. This gives you a way to control the starting value.

for ((day, count) <- days.zip(Stream from 1)) {
  println(s"$count is $day")
}

Result:

1 is Sun
2 is Mon
3 is Tue
4 is Wed
5 is Thu
6 is Fri
7 is Sat
Madstuffs
  • 374
  • 4
  • 13
3

Use .map in .zipWithIndex with Map data structure

val sampleMap = Map("a" -> "hello", "b" -> "world", "c" -> "again")

val result = sampleMap.zipWithIndex.map { case ((key, value), index) => 
    s"Key: $key - Value: $value with Index: $index"
}

Results

 List(
       Key: a - Value: hello with Index: 0, 
       Key: b - Value: world with Index: 1, 
       Key: c - Value: again with Index: 2
     )
fgfernandez0321
  • 197
  • 1
  • 5
0

If you require searching the map values as well (like I had to):

val ls = List("a","b","c")
val ls_index_map = ls.zipWithIndex.toMap 
Yair Beer
  • 101
  • 2
  • 8