web-dev-qa-db-fra.com

Itération efficace avec index dans Scala

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.

76
snappy

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)
120
Didier Dupont

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.

66
Kipton Barros

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
18
missingfaktor

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

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
13
om-nom-nom

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
6
Matthew Saltz

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.

4
herman

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))
4
Prakhyat

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

   }
}
3
anish

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)
}
3
Alex Cruise

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) 
3
user unknown

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

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!

0
Matt Brasen