Iteración eficiente con índice en Scala


83

Dado que Scala no tiene forbucles de estilo Java antiguos con índice,

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

¿Cómo podemos iterar de manera eficiente y sin usar var's?

Podrías hacer esto

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

pero la lista se recorre dos veces, no muy eficiente.


Todas estas son buenas respuestas. Lo que me falta de los bucles 'for' de Java es la capacidad de tener múltiples inicializadores y la capacidad de "iterar" usando más que solo incrementos / decrementos. Esta es una instancia en la que Java puede ser más conciso que Scala.
rápido

... "iterar" usando más que solo incrementos / decrementos ... En scala es posible iterar con paso, o iterar con condición "if" en el encabezado del ciclo. ¿O estás buscando algo más?
om-nom-nom

1
/ * Java * / for (int i = 0, j = 0; i + j <100; i + = j * 2, j + = i + 2) {...} ¿Cómo se puede hacer esto en 1 línea en Scala?
rápido

3
@snappy: En mi opinión, la traducción más natural a Scala sería un whilebucle. Según recuerdo, hace algunos años hubo un debate sobre si Scala debería heredar el for(;;)bucle de Java , y se decidió que el beneficio no era suficiente para justificar la complejidad añadida.
Kipton Barros

Respuestas:


130

Mucho peor que atravesar dos veces, crea una matriz intermedia de pares. Puede utilizar view. Cuando lo haga collection.view, puede pensar que las llamadas posteriores actúan de manera perezosa durante la iteración. Si desea recuperar una colección adecuada y completamente realizada, llame forceal final. Aquí eso sería inútil y costoso. Así que cambia tu código a

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

6
Buena idea, solo un recorrido, pero también crea n pares, incluso si no crea una nueva colección propiamente dicha.
rápido

2
Muy bien. Bueno, puede haber una vaga esperanza de que la JVM pueda optimizar esa creación, pero no contaría con eso. No veo una solución que no se base en iterar en índices entonces.
Didier Dupont

1
@snappy ¡Esta debería haber sido elegida como respuesta! El acceso a los elementos por índice, que se sugirió en la mayoría de las otras respuestas, viola la naturaleza funcional de Scala y se desempeña mal en las listas vinculadas (como Listla colección más utilizada en Scala), y no solo en ellas. Mira la applyoperación aquí . En una colección similar a una lista vinculada, cada acceso a un elemento por índice da como resultado un recorrido de la lista.
Nikita Volkov

Aquí se muestran enfoques bastante diferentes: stackoverflow.com/questions/6821194/…
Neil

¿Por qué es tan eficiente? está creando un nuevo objeto de matriz y utiliza una función adicional ('vista'), por lo que me resulta difícil ver por qué esto es eficiente para el desarrollador ni para la máquina, además de sentirse inteligentemente idiomático.
matanster

70

Se ha mencionado que la Scala hace tener sintaxis para forlos bucles:

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

o simplemente

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

Sin embargo, también pediste eficiencia. Resulta que la Scala forsintaxis es en realidad azúcar sintáctico para los métodos de orden superior, tales como map, foreach, etc. Por lo tanto, en algunos casos, estos bucles pueden ser ineficientes, por ejemplo, cómo optimizar para-comprensiones y bucles en Scala?

(La buena noticia es que el equipo de Scala está trabajando para mejorar esto. Aquí está el problema en el rastreador de errores: https://issues.scala-lang.org/browse/SI-4633 )

Para una máxima eficiencia, se puede usar un whilebucle o, si insiste en eliminar los usos de var, la recursividad de cola:

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

Tenga en cuenta que la anotación opcional @tailrec es útil para garantizar que el método sea realmente recursivo. El compilador de Scala traduce las llamadas recursivas de cola en el código de bytes equivalente a los bucles while.


+1 por mencionar el método / función de índices, ya que lo encuentro preferible debido a que prácticamente elimina un conjunto completo de errores de programación uno por uno.
chaotic3quilibrium

1
Debe tenerse en cuenta aquí que si xses de cualquier tipo de lista enlazada (como la ampliamente utilizada List), acceder a sus elementos por índice como xs(i)será lineal y, por lo tanto, el for (i <- xs.indices) println(i + " : " + xs(i))rendimiento será peor que incluso for((x, i) <- xs.zipWithIndex) println(i + " : " + x), ya que resultará en mucho más que dos travesías bajo el capó. Por lo tanto, la respuesta de @didierd sugiriendo usar vistas debe aceptarse como la más general y la más idiomática, IMO.
Nikita Volkov

1
Si se necesita la máxima eficiencia (por ejemplo, en computación numérica), es más rápido indexar matrices que recorrer una lista enlazada. Los nodos de una lista vinculada se asignan al montón por separado, y saltar entre diferentes ubicaciones de memoria no funciona bien con la memoria caché de la CPU. Si viewse usa a, este nivel incluso alto de abstracción ejercerá más presión sobre el montón y GC. En mi experiencia, a menudo se obtiene un factor de 10 en el rendimiento al evitar la asignación de montones en código numérico.
Kipton Barros

20

Una forma más:

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

5
Realmente me gusta que señales el método / función de índices. Reduce la complejidad y elimina virtualmente un conjunto completo de errores "uno a uno", que es el error / error de programación más común en toda la ingeniería de software.
chaotic3quilibrium

14

En realidad, scala tiene bucles antiguos de estilo Java con índice:

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

Donde 0 until xs.lengtho 0.until(xs.length)es un RichIntmétodo que devuelve Rangeadecuado para bucles.

Además, puede probar el bucle con 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

5
xs(i)en listas eleva la complejidad a O (n ^ 2)
Vadzim

@Vadzim Eso es cierto, pero ese también sería el caso en Java en el que utilizó un bucle for en los índices con una LinkedList
francoisr

1
En el caso de xs (i) en Arrays, el código anterior es O (n), ¿verdad? ¿Dado que las matrices en scala ofrecen acceso aleatorio en tiempo casi constante?
dhfromkorea

2
@dhfromkorea sí, debería ser rápido para matrices (de hecho, O (n))
om-nom-nom

6

¿Qué tal esto?

val a = Array("One", "Two", "Three")
a.foldLeft(0) ((i, x) => {println(i + ": " + x); i + 1;} )

Salida:

0: One
1: Two
2: Three

4

Hacer un bucle en Scala es bastante simple. Cree cualquier matriz de su elección, por ej.

val myArray = new Array[String](3)
myArray(0)="0";
myArray(1)="1";
myArray(2)="2";

Tipos de bucles,

for(data <- myArray)println(data)

for (i <- 0 until myArray.size)
println(i + ": " + myArray(i))

4

De hecho, llamar zipWithIndexa una colección la atravesará y también creará una nueva colección para los pares. Para evitar esto, puede simplemente llamar zipWithIndexal iterador para la colección. Esto solo devolverá un nuevo iterador que realiza un seguimiento del índice mientras se itera, por lo que sin crear una colección adicional o un recorrido adicional.

Así es como scala.collection.Iterator.zipWithIndexse implementa actualmente 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
    }
  }

Esto debería ser incluso un poco más eficaz que crear una vista de la colección.


3

No hay nada en stdlib que lo haga por usted sin crear basura de tupla, pero no es demasiado difícil escribir la suya propia. Desafortunadamente, nunca me he molestado en averiguar cómo hacer el CanBuildFrom implícito de la danza de lluvia adecuada para hacer esas cosas genéricas en el tipo de colección a la que se aplican, pero si es posible, estoy seguro de que alguien nos aclarará. :)

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

Esto es algo que definitivamente tendrá sentido agregar a la biblioteca estándar.
rápido

1
No estoy tan seguro, ya que si desea ajustarse a las API foreach / map habituales, de todos modos está atrapado con tuplas.
Alex Cruise

3

Algunas formas más de iterar:

scala>  xs.foreach (println) 
first
second
third

foreach, y similar, map, que devolvería algo (los resultados de la función, que es, para println, Unit, por lo que una lista de unidades)

scala> val lens = for (x <- xs) yield (x.length) 
lens: Array[Int] = Array(5, 6, 5)

trabajar con los elementos, no con el índice

scala> ("" /: xs) (_ + _) 
res21: java.lang.String = firstsecondthird

plegable

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

se puede hacer con recursividad:

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 parte de acarreo es solo un ejemplo, para hacer algo con i y j. No tiene por qué ser un Int.

para cosas más simples, más cercanas a los bucles for habituales:

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)

o sin orden:

List (1, 3, 2, 5, 9, 7).foreach (print) 

3

Tengo los siguientes enfoques

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

   }
}

2

Una forma sencilla y eficiente, inspirada en la implementación de transformen SeqLike.scala

    var i = 0
    xs foreach { el =>
      println("String #" + i + " is " + xs(i))
      i += 1
    }

0

Las soluciones propuestas adolecen del hecho de que iteran explícitamente sobre una colección o rellenan la colección en una función. Es más natural ceñirse a los modismos habituales de Scala y poner el índice dentro de los métodos usuales map o foreach. Esto se puede hacer mediante la memorización. El código resultante podría verse así

myIterable map (doIndexed(someFunction))

He aquí una forma de lograr este propósito. Considere la siguiente utilidad:

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

Esto ya es todo lo que necesitas. Puede aplicar esto, por ejemplo, de la siguiente manera:

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

que resulta en la lista

List(97, 99, 101)

De esta manera, puede usar las funciones habituales de Traversable a expensas de envolver su función efectiva. ¡Disfrutar!

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.