Depuis Scala n'a pas d'anciennes boucles Java style for
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 efficacement et sans utiliser var
?
Tu pourrais faire ça
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.
Bien pire que de traverser deux fois, cela crée un tableau intermédiaire de paires. Vous pouvez utiliser view
. Quand vous faites collection.view
, vous pouvez imaginer que les appels suivants agissent paresseusement pendant l’itération. Si vous voulez récupérer une collection entièrement réalisée, vous appelez force
à la fin. Ici, cela serait inutile et coûteux. Alors changez votre code en
for((x,i) <- xs.view.zipWithIndex) println("String #" + i + " is " + x)
Il a été mentionné que Scala ) a une syntaxe pour les boucles for
:
for (i <- 0 until xs.length) ...
ou simplement
for (i <- xs.indices) ...
Cependant, vous avez également demandé l'efficacité. Il se trouve que la syntaxe Scala for
est en fait un sucre syntaxique pour les méthodes d'ordre supérieur telles que map
, foreach
, etc. , dans certains cas, ces boucles peuvent être inefficaces, par exemple Comment optimiser les compréhensions et les boucles dans Scala?
(La bonne nouvelle, c’est que l’équipe Scala travaille à l’améliorer. Voici le problème dans le traqueur de bogues: https://issues.scala-lang.org/browse/ SI-46 )
Pour une efficacité maximale, vous pouvez utiliser une boucle while
ou, si vous insistez pour supprimer les utilisations de var
, une 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 facultatif @tailrec
annotation est utile pour s'assurer que la méthode est réellement récursive. Le compilateur Scala) convertit les appels inter-récursifs en l'équivalent en code octet de boucles while.
Une autre façon:
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
En fait, scala a de vieilles 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
Où 0 until xs.length
Ou 0.until(xs.length)
est une méthode RichInt
qui renvoie Range
appropriée pour la mise en boucle.
Aussi, vous pouvez 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
Que dis-tu de ça?
val a = Array("One", "Two", "Three")
a.foldLeft(0) ((i, x) => {println(i + ": " + x); i + 1;} )
Sortie:
0: One
1: Two
2: Three
En effet, appeler zipWithIndex
sur une collection la traversera et créera également une nouvelle collection pour les paires. Pour éviter cela, vous pouvez simplement appeler zipWithIndex
sur l'itérateur de la collection. Cela retournera simplement un nouvel itérateur qui gardera une trace de l'index lors de l'itération, donc sans créer de collection supplémentaire ni de parcours supplémentaire.
C'est ainsi scala.collection.Iterator.zipWithIndex
est actuellement implémenté en 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
}
}
Cela devrait même être un peu plus efficace que de créer une vue sur la collection.
Boucler dans scala est assez simple. Créez n'importe quel tableau de votre choix pour ex.
val myArray = new Array[String](3)
myArray(0)="0";
myArray(1)="1";
myArray(2)="2";
Types de boucles,
for(data <- myArray)println(data)
for (i <- 0 until myArray.size)
println(i + ": " + myArray(i))
J'ai les approches suivantes
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);
}
}
Il n'y a rien dans stdlib qui le fasse pour vous sans créer Tuple garbage, mais ce n'est pas trop difficile d'écrire les vôtres. Malheureusement, je n'ai jamais pris la peine de trouver le moyen de créer le type de collecte implicite dans CanBuildFrom afin de rendre ces éléments génériques, mais si cela 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)
}
Quelques autres moyens d'itérer:
scala> xs.foreach (println)
first
second
third
foreach, et similaire, map, qui renverrait quelque chose (les résultats de la fonction, qui est, pour println, Unit, donc une liste d'unités)
scala> val lens = for (x <- xs) yield (x.length)
lens: Array[Int] = Array(5, 6, 5)
travailler avec les éléments, pas l'index
scala> ("" /: xs) (_ + _)
res21: Java.lang.String = firstsecondthird
pliant
for(int i=0, j=0; i+j<100; i+=j*2, j+=i+2) {...}
peut être fait avec récursivité:
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)
La partie reportée n'est qu'un exemple, faire quelque chose avec i et j. Ce ne doit pas être un Int.
pour des choses plus simples, plus proches des boucles habituelles:
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)
ou sans commande:
List (1, 3, 2, 5, 9, 7).foreach (print)
Un moyen simple et efficace, inspiré de la mise en oeuvre de transform
dans SeqLike.scala
var i = 0
xs foreach { el =>
println("String #" + i + " is " + xs(i))
i += 1
}
Les solutions proposées souffrent du fait qu’elles itéralisent explicitement sur une collection ou la fargent en une fonction. Il est plus naturel de s'en tenir aux idiomes habituels de Scala et de placer l'index dans les méthodes habituelles map ou foreach. Cela peut être fait en utilisant memoizing. Le code résultant pourrait ressembler à
myIterable map (doIndexed(someFunction))
Voici un moyen d'atteindre cet objectif. Considérez l'utilitaire suivant:
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)
}
}
C'est déjà tout ce dont vous avez besoin. Vous pouvez appliquer cela par exemple comme suit:
import TraversableUtil._
List('a','b','c').map(doIndexed((i, char) => char + i))
qui résulte dans la liste
List(97, 99, 101)
De cette façon, vous pouvez utiliser les fonctions habituelles de Traversable au détriment de l'encapsulation de votre fonction effective. Prendre plaisir!