在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
语法实际上是高阶方法(如map
, foreach
等)的语法糖。因此,在某些情况下,这些循环可能是低效的,例如如何优化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.length
或0.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函数来代替包装你的有效函数。 请享用!