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