Itération efficace avec index en Scala

puisque Scala n'a pas de vieux 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 efficacement, et sans utiliser var 's?

Vous pouvez faire cela

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.

71
demandé sur snappy 2011-07-26 20:39:18

12 réponses

bien pire que traverser deux fois, il crée un réseau intermédiaire de paires. Vous pouvez utiliser view . Quand vous faites collection.view , vous pouvez penser aux appels suivants comme agissant paresseusement, pendant l'itération. Si vous voulez récupérer une collection complète, vous appelez force à la fin. Ici, ce serait inutile et coûteux. Alors changez votre code en

for((x,i) <- xs.view.zipWithIndex) println("String #" + i + " is " + x)
106
répondu Didier Dupont 2011-07-26 16:59:43

il a été mentionné que Scala does ont une syntaxe pour for loops:

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

ou simplement

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

cependant, vous avez aussi demandé de l'efficacité. Il s'avère que la syntaxe Scala for est en fait du sucre syntaxique pour les méthodes d'ordre supérieur telles que map , foreach , etc. Ainsi, dans certains cas, ces boucles peuvent être inefficaces, par exemple, Comment optimiser pour-des compréhensions et des boucles en Scala?

(la bonne nouvelle, C'est que L'équipe de Scala travaille à l'améliorer. Voici le problème dans le bug tracker: https://issues.scala-lang.org/browse/SI-4633 )

pour une efficacité maximale, on peut utiliser une boucle while ou, si vous insistez pour supprimer les utilisations de var , la recursion de la 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"))

noter que le facultatif @tailrec l'annotation est utile pour s'assurer que la méthode est réellement récursive de queue. Le compilateur Scala traduit les appels récursifs de queue en code octet équivalent des boucles while.

63
répondu Kipton Barros 2017-05-23 12:02:42

Une façon de plus:

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
17
répondu missingfaktor 2011-07-26 18:02:03

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 retourne Range appropriée pour la boucle.

aussi, vous pouvez essayer 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
11
répondu om-nom-nom 2011-07-26 17:08:17

et ç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
répondu Matthew Saltz 2013-05-30 19:37:27

il n'y a rien dans le stdlib qui le fera pour vous sans créer des déchets de tuples, mais il n'est pas trop difficile d'écrire les vôtres. Malheureusement, je n'ai jamais pris la peine de trouver comment faire le bon Canbuild de raindance implicite pour rendre de telles choses génériques dans le type de collection à laquelle ils s'appliquent, mais si c'est possible, je suis sûr que quelqu'un va nous éclairer. :)

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
répondu Alex Cruise 2011-07-26 17:52:22

quelques autres façons d'itérer:

scala>  xs.foreach (println) 
first
second
third

foreach, et similaires, carte, qui permettrait le retour de 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

pliable

for(int i=0, j=0; i+j<100; i+=j*2, j+=i+2) {...}

peut être fait avec récursion:

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) 

le carry-part n'est qu'un exemple, pour faire quelque chose avec i et J. Ça ne doit pas être un Int.

pour des trucs plus simple, plus proche d'habitude pour des boucles:

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
répondu user unknown 2011-07-27 02:06:25

en effet, appeler zipWithIndex sur une collection la traverse et crée également une nouvelle collection pour les paires. Pour éviter cela, vous pouvez simplement appeler zipWithIndex sur l'itérateur pour la collection. Cela va juste retourner un nouvel itérateur qui garde la trace de l'index tout en itérant, donc sans créer une collection supplémentaire ou traversée supplémentaire.

c'est ainsi que scala.collection.Iterator.zipWithIndex est actuellement mis en œuvre 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
    }
  }

This devrait même être un peu plus efficace que de créer une vue sur la collection.

3
répondu herman 2013-11-14 21:17:07

un moyen simple et efficace, inspiré de la mise en œuvre de transform dans SeqLike.scala

    var i = 0
    xs foreach { el =>
      println("String #" + i + " is " + xs(i))
      i += 1
    }
2
répondu Adrian 2012-01-03 21:53:43

en boucle à scala est assez simple. Créer un 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))
2
répondu Prakhyat 2013-07-01 10:16:47

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

   }
}
1
répondu anish 2014-03-16 06:16:25

les solutions proposées souffrent du fait qu'elles itèrent explicitement au-dessus d'une collection ou remplissent la collection dans une fonction. Il est plus naturel de s'en tenir aux idiomes usuels de Scala et de mettre l'index à l'intérieur des méthodes habituelles de 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érons l'utilité suivante:

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 que vous devez. Vous pouvez l'appliquer par exemple comme suit:

import TraversableUtil._
List('a','b','c').map(doIndexed((i, char) => char + i))

qui aboutit à la liste

List(97, 99, 101)

de Cette façon, vous pouvez utiliser l'habituel Traversable-fonctions au détriment de l'emballage de votre fonction. Profitez-en!

0
répondu Matt Brasen 2012-05-08 15:53:28