Efficient iteration with index in Scala

前端 未结 12 535
后悔当初
后悔当初 2020-12-12 11:57

Since Scala does not have old Java style for loops with index,

// does not work
val xs = Array(\"first\", \"second\", \"third\")
for (i=0; i<         


        
相关标签:
12条回答
  • 2020-12-12 12:03

    One more way:

    scala> val xs = Array("first", "second", "third")
    xs: Array[java.lang.String] = Array(first, second, third)
    
    scala> for (i <- xs.indices)
         |   println(i + ": " + xs(i))
    0: first
    1: second
    2: third
    
    0 讨论(0)
  • 2020-12-12 12:03

    Indeed, calling zipWithIndex on a collection will traverse it and also create a new collection for the pairs. To avoid this, you can just call zipWithIndex on the iterator for the collection. This will just return a new iterator that keeps track of the index while iterating, so without creating an extra collection or additional traversing.

    This is how scala.collection.Iterator.zipWithIndex is currently implemented in 2.10.3:

      def zipWithIndex: Iterator[(A, Int)] = new AbstractIterator[(A, Int)] {
        var idx = 0
        def hasNext = self.hasNext
        def next = {
          val ret = (self.next, idx)
          idx += 1
          ret
        }
      }
    

    This should even be a bit more efficient than creating a view on the collection.

    0 讨论(0)
  • 2020-12-12 12:08

    Actually, scala has old Java-style loops with index:

    scala> val xs = Array("first","second","third")
    xs: Array[java.lang.String] = Array(first, second, third)
    
    scala> for (i <- 0 until xs.length)
         | println("String # " + i + " is "+ xs(i))
    
    String # 0 is first
    String # 1 is second
    String # 2 is third
    

    Where 0 until xs.length or 0.until(xs.length) is a RichInt method which returns Range suitable for looping.

    Also, you can try loop with to:

    scala> for (i <- 0 to xs.length-1)
         | println("String # " + i + " is "+ xs(i))
    String # 0 is first
    String # 1 is second
    String # 2 is third
    
    0 讨论(0)
  • 2020-12-12 12:11

    There's nothing in the stdlib that will do it for you without creating tuple garbage, but it's not too hard to write your own. Unfortunately I've never bothered to figure out how to do the proper CanBuildFrom implicit raindance to make such things generic in the type of collection they're applied to, but if it's possible, I'm sure someone will enlighten us. :)

    def foreachWithIndex[A](as: Traversable[A])(f: (Int,A) => Unit) {
      var i = 0
      for (a <- as) {
        f(i, a)
        i += 1
      }
    }
    
    def mapWithIndex[A,B](in: List[A])(f: (Int,A) => B): List[B] = {
      def mapWithIndex0(in: List[A], gotSoFar: List[B], i: Int): List[B] = {
        in match {
          case Nil         => gotSoFar.reverse
          case one :: more => mapWithIndex0(more, f(i, one) :: gotSoFar, i+1)
        }
      }
      mapWithIndex0(in, Nil, 0)
    }
    
    // Tests....
    
    @Test
    def testForeachWithIndex() {
      var out = List[Int]()
      ScalaUtils.foreachWithIndex(List(1,2,3,4)) { (i, num) =>
        out :+= i * num
      }
      assertEquals(List(0,2,6,12),out)
    }
    
    @Test
    def testMapWithIndex() {
      val out = ScalaUtils.mapWithIndex(List(4,3,2,1)) { (i, num) =>
        i * num
      }
    
      assertEquals(List(0,3,4,3),out)
    }
    
    0 讨论(0)
  • 2020-12-12 12:12

    Much worse than traversing twice, it creates an intermediary array of pairs. You can use view. When you do collection.view, you can think of subsequent calls as acting lazily, during the iteration. If you want to get back a proper fully realized collection, you call force at the end. Here that would be useless and costly. So change your code to

    for((x,i) <- xs.view.zipWithIndex) println("String #" + i + " is " + x)
    
    0 讨论(0)
  • 2020-12-12 12:13

    How about this?

    val a = Array("One", "Two", "Three")
    a.foldLeft(0) ((i, x) => {println(i + ": " + x); i + 1;} )
    

    Output:

    0: One
    1: Two
    2: Three
    
    0 讨论(0)
提交回复
热议问题