在Scala中有索引的高效迭代

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

但列表遍历两次 – 不是很有效。

比遍历两次更糟,它创build了一个中间数组对。 你可以使用view 。 当您执行collection.view ,您可以将后续调用看作是在迭代期间懒惰地执行。 如果你想要找回适当的完全实现的收集,你最后呼吁force 。 这将是无用和昂贵的。 所以改变你的代码

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

有人提到,Scala 确实for循环的语法:

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

或干脆

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

但是,你也要求效率。 事实certificate,Scala for语法实际上是高阶方法(如mapforeach等)的语法糖。因此,在某些情况下,这些循环可能是低效的,例如如何优化Scala中的理解和循环?

(好消息是Scala团队正在努力改善这个问题。这个bug跟踪器的问题是: https : //issues.scala-lang.org/browse/SI-4633 )

为了达到最高效率,可以使用while循环,或者,如果你坚持删除var ,tailrecursion的使用:

 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注释对于确保该方法实际上是尾recursion是有用的。 Scala编译器将尾recursion调用转换为while循环的字节码等价物。

还有一种方法:

 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 

实际上,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 

这个怎么样?

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

输出:

 0: One 1: Two 2: Three 

在stdlib中没有任何东西可以为你创build元组垃圾,但是编写你自己的代码并不难。 不幸的是,我从来没有想过如何做适当的CanBuildFrom隐式的raindance使这样的东西generics的集合types,但如果可能的话,我敢肯定有人会启发我们。 🙂

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

一些更多的迭代方法:

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

可以用recursion完成:

 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) 

事实上,在集合上调用zipWithIndex将遍历它,并为这些对创build一个新的集合。 为了避免这种情况,可以在集合的迭代器上调用zipWithIndex 。 这只会返回一个新的迭代器,它在迭代时跟踪索引,所以不会创build额外的集合或额外的遍历。

这是如何在2.10.3中实现scala.collection.Iterator.zipWithIndex

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

这甚至比在集合上创build视图更高效。

一个简单而有效的方式,启发了SeqLike.scala实现transform

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

在scala中循环很简单。 创build任何您select的arrays前。

 val myArray = new Array[String](3) myArray(0)="0"; myArray(1)="1"; myArray(2)="2"; 

循环的types,

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

我有以下的方法

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

所提出的解决scheme遭受这样的事实,即它们明确地迭代集合或将集合填充到函数中。 坚持Scala惯用的习惯用法是比较自然的,并将这个索引放在通常的map或foreach方法中。 这可以通过记忆来完成。 生成的代码可能看起来像

 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函数来代替包装你的有效函数。 请享用!