79 votes

Itération efficace avec index en Scala

Comme Scala n'a pas l'ancien style Java for boucles avec index,

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

Comment pouvons-nous itérer de manière efficace, et sans utiliser var 's ?

Vous pourriez faire ceci

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

mais la liste est parcourue deux fois - pas très efficace.

122voto

Didier Dupont Points 18256

Bien pire que de traverser deux fois, cela crée un tableau intermédiaire de paires. Vous pouvez utiliser view . Lorsque vous le faites collection.view vous pouvez considérer les appels ultérieurs comme agissant paresseusement, pendant l'itération. Si vous voulez récupérer une collection entièrement réalisée, vous appelez force à la fin. Ici, ce serait inutile et coûteux. Changez donc votre code en

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

67voto

Kipton Barros Points 12445

Il a été mentionné que Scala hace ont une syntaxe pour for boucles :

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

ou simplement

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

Cependant, vous avez également demandé l'efficacité. Il s'avère que la méthode Scala for est en fait un sucre syntaxique pour des méthodes d'ordre supérieur telles que map , foreach etc. En tant que telles, dans certains cas, ces boucles peuvent être inefficaces, par ex. Comment optimiser les for-comprehensions et les boucles en Scala ?

(La bonne nouvelle est que l'équipe de Scala travaille à l'amélioration de ce point. Voici le problème dans le bug tracker : https://issues.scala-lang.org/browse/SI-4633 )

Pour une efficacité maximale, on peut utiliser un while ou, si vous insistez pour supprimer les utilisations de var , récursion de queue :

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

Notez que le en option @tailrec L'annotation est utile pour s'assurer que la méthode est effectivement récursive à la queue. Le compilateur Scala traduit les appels récursifs de queue en l'équivalent en byte code des boucles while.

18voto

missingfaktor Points 44003

Encore un moyen :

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

14voto

om-nom-nom Points 33691

En fait, Scala a des boucles de style Java avec 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

0 until xs.length o 0.until(xs.length) est un RichInt qui renvoie la méthode Range convient pour le bouclage.

Vous pouvez également essayer la boucle avec 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

3voto

Alex Cruise Points 4809

Il n'y a rien dans la stdlib qui puisse le faire pour vous sans créer de déchets de tuple, mais il n'est pas trop difficile d'écrire le vôtre. Malheureusement, je n'ai jamais cherché à comprendre comment faire le raindance implicite CanBuildFrom approprié pour rendre de telles choses génériques dans le type de collection auquel elles sont appliquées, mais si c'est possible, je suis sûr que quelqu'un nous éclairera :)

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X