使用Scala中的索引进行高效迭代

时间:2011-07-26 16:39:18

标签: scala iteration

由于Scala没有带索引的旧Java样式for循环,

// does not work
val xs = Array("first", "second", "third")
for (i=0; i<xs.length; i++) {
  println("String #" + i + " is " + xs(i))
}

我们如何有效地迭代,而不使用var

你可以这样做

val xs = Array("first", "second", "third")
val indexed = xs zipWithIndex
for (x <- indexed) println("String #" + x._2 + " is " + x._1)

但是列表遍历了两次 - 效率不高。

12 个答案:

答案 0 :(得分:117)

比遍历两次更糟糕的是,它创建了一对中间数组。 您可以使用view。当您执行collection.view时,您可以将后续调用视为在迭代期间懒惰地执行。如果您想要恢复正确完全实现的集合,请在最后调用force。这将是无用的和昂贵的。所以将代码更改为

for((x,i) <- xs.view.zipWithIndex) println("String #" + i + " is " + x)

答案 1 :(得分:64)

有人提到Scala 具有for循环的语法:

for (i <- 0 until xs.length) ...

或只是

for (i <- xs.indices) ...

但是,你也要求效率。事实证明,Scala for语法实际上是高阶方法的语法糖,例如mapforeach等。因此,在某些情况下,这些循环可能效率低下,例如: How to optimize for-comprehensions and loops in Scala?

(好消息是Scala团队正在努力改进这一点。以下是错误跟踪器中的问题:https://issues.scala-lang.org/browse/SI-4633

为了获得最高效率,可以使用while循环,或者如果您坚持删除var的使用,则可以使用尾递归:

import scala.annotation.tailrec

@tailrec def printArray(i: Int, xs: Array[String]) {
  if (i < xs.length) {
    println("String #" + i + " is " + xs(i))
    printArray(i+1, xs)
  }
}
printArray(0, Array("first", "second", "third"))

请注意,可选 @tailrec注释对于确保该方法实际上是尾递归非常有用。 Scala编译器将尾递归调用转换为等效于while循环的字节代码。

答案 2 :(得分:17)

还有一种方法:

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

答案 3 :(得分:13)

实际上,scala具有索引的旧式Java循环:

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

其中0 until xs.length0.until(xs.length)RichInt方法,返回适合循环的Range

此外,您可以尝试使用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

答案 4 :(得分:6)

这个怎么样?

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

输出:

0: One
1: Two
2: Three

答案 5 :(得分:4)

在scala中循环非常简单。 为ex。

创建您选择的任何数组
val myArray = new Array[String](3)
myArray(0)="0";
myArray(1)="1";
myArray(2)="2";

循环类型,

for(data <- myArray)println(data)

for (i <- 0 until myArray.size)
println(i + ": " + myArray(i))

答案 6 :(得分:4)

确实,在集合上调用zipWithIndex将遍历它,并为对创建一个新集合。为避免这种情况,您只需在集合的迭代器上调用zipWithIndex即可。这将只返回一个新的迭代器,它在迭代时跟踪索引,因此无需创建额外的集合或额外的遍历。

这是scala.collection.Iterator.zipWithIndex目前在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
    }
  }

这甚至比在集合上创建视图更有效。

答案 7 :(得分:3)

stdlib中没有任何内容可以在不创建元组垃圾的情况下为您完成,但编写自己的垃圾并不难。不幸的是,我从来没有费心去弄清楚如何做正确的CanBuildFrom隐式raindance来使这些东西在他们应用的集合类型中是通用的,但是如果可能的话,我相信有人会启发我们。 :)

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

答案 8 :(得分:3)

更多迭代方法:

scala>  xs.foreach (println) 
first
second
third

foreach和类似的map,它会返回一些东西(函数的结果,对于println,Unit,所以单位列表)

scala> val lens = for (x <- xs) yield (x.length) 
lens: Array[Int] = Array(5, 6, 5)

使用元素,而不是索引

scala> ("" /: xs) (_ + _) 
res21: java.lang.String = firstsecondthird

折叠

for(int i=0, j=0; i+j<100; i+=j*2, j+=i+2) {...}
     

可以通过递归完成:

def ijIter (i: Int = 0, j: Int = 0, carry: Int = 0) : Int =
  if (i + j >= 100) carry else 
    ijIter (i+2*j, j+i+2, carry / 3 + 2 * i - 4 * j + 10) 

进位部分只是一些例子,用i和j做点什么。它不一定是Int。

对于更简单的东西,更接近通常的for循环:

scala> (1 until 4)
res43: scala.collection.immutable.Range with scala.collection.immutable.Range.ByOne = Range(1, 2, 3)

scala> (0 to 8 by 2)   
res44: scala.collection.immutable.Range = Range(0, 2, 4, 6, 8)

scala> (26 to 13 by -3)
res45: scala.collection.immutable.Range = Range(26, 23, 20, 17, 14)

或没有订单:

List (1, 3, 2, 5, 9, 7).foreach (print) 

答案 9 :(得分:3)

我有以下方法

object HelloV2 {

   def main(args: Array[String]) {

     //Efficient iteration with index in Scala

     //Approach #1
     var msg = "";

     for (i <- args.indices)
     {
       msg+=(args(i));
     }
     var msg1="";

     //Approach #2
     for (i <- 0 until args.length) 
     {
       msg1 += (args(i));
     }

     //Approach #3
     var msg3=""
     args.foreach{
       arg =>
        msg3 += (arg)
     }


      println("msg= " + msg);

      println("msg1= " + msg1);

      println("msg3= " + msg3);

   }
}

答案 10 :(得分:2)

一种简单而有效的方式,受益于SeqLike.scala

transform的实施
    var i = 0
    xs foreach { el =>
      println("String #" + i + " is " + xs(i))
      i += 1
    }

答案 11 :(得分:0)

所提出的解决方案遭受以下事实:它们要么明确地迭代集合,要么将集合填充到函数中。坚持使用Scala的常用习惯并将索引放在通常的map或foreach方法中更为自然。这可以使用memoizing完成。生成的代码可能看起来像

myIterable map (doIndexed(someFunction))

这是实现此目的的一种方法。请考虑以下实用程序:

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

这已经是你所需要的了。您可以按如下方式应用此项:

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

导致列表

List(97, 99, 101)

这样,您可以使用通常的Traversable函数,但代价是包装有效函数。享受!