Эффективная итерация с индексом в Scala - PullRequest
75 голосов
/ 26 июля 2011

Поскольку в 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 ]

119 голосов
/ 26 июля 2011

Гораздо хуже, чем обход дважды, он создает промежуточный массив пар. Вы можете использовать view. Когда вы делаете collection.view, вы можете думать о последующих вызовах как о ленивых действиях во время итерации. Если вы хотите вернуть надлежащую полностью реализованную коллекцию, вы звоните force в конце. Вот это было бы бесполезно и дорого. Поэтому измените свой код на

for((x,i) <- xs.view.zipWithIndex) println("String #" + i + " is " + x)
66 голосов
/ 26 июля 2011

Было упомянуто, что Scala имеет синтаксис для циклов for:

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

или просто

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

Однако вы также просили об эффективности. Оказывается, что синтаксис Scala for фактически является синтаксическим сахаром для методов более высокого порядка, таких как map, foreach и т. Д. Таким образом, в некоторых случаях эти циклы могут быть неэффективными, например, Как оптимизировать для-понимания и циклов в 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"))

Обратите внимание, что необязательная аннотация *1028* @tailrec полезна для гарантии того, что метод на самом деле является хвостовой рекурсивной. Компилятор Scala преобразует хвостовые рекурсивные вызовы в байтовый код, эквивалентный циклам while.

18 голосов
/ 26 июля 2011

Еще один способ:

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
13 голосов
/ 26 июля 2011

На самом деле в 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
6 голосов
/ 30 мая 2013

Как насчет этого?

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

Выход:

0: One
1: Two
2: Three
4 голосов
/ 15 ноября 2013

Действительно, вызов 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
    }
  }

Это даже немного эффективнее, чем создание представления для коллекции.

4 голосов
/ 01 июля 2013

Зацикливание в Scala довольно просто.Создайте любой массив по вашему выбору, например

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))
3 голосов
/ 16 марта 2014

У меня есть следующие подходы

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

   }
}
3 голосов
/ 27 июля 2011

Еще несколько способов итерации:

scala>  xs.foreach (println) 
first
second
third

foreach и аналогичная карта, которая должна что-то возвращать (результаты функции, которая для println, Unit, так и List of Units)

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) 
3 голосов
/ 26 июля 2011

В stdlib нет ничего, что сделало бы это для вас без создания кортежа мусора, но не так уж сложно написать свой собственный. К сожалению, я никогда не удосужился выяснить, как сделать правильное неявное ограничение CanBuildFrom, чтобы сделать такие вещи общими для типа коллекции, к которой они применяются, но если это возможно, я уверен, что кто-то нас просветит. :)

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