ciclo - Eficiente iteración con índice en Scala
ciclo for scala (12)
¿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
Como Scala no tiene el estilo antiguo de Java for
bucles 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 cruza dos veces, no es muy eficiente.
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, so a List of Units)
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 recursión:
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)
El carry-part es solo un ejemplo, para hacer algo con i y j. No necesita ser un Int.
para cosas más simples, más cerca de los bucles for:
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)
De hecho, al llamar a zipWithIndex
en una colección lo atravesará y también creará una nueva colección para los pares. Para evitar esto, simplemente puede llamar a zipWithIndex
en el iterador para la colección. Esto solo devolverá un nuevo iterador que realiza un seguimiento del índice mientras itera, por lo que no crea una colección adicional ni un recorrido adicional.
Así es como scala.collection.Iterator.zipWithIndex
se 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 incluso debería ser un poco más eficiente que crear una vista en la colección.
En realidad, Scala tiene viejos bucles 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.length
o 0.until(xs.length)
es un método de RichInt
que devuelve el Range
adecuado para el bucle.
Además, puedes 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
Las soluciones propuestas adolecen del hecho de que iteran explícitamente sobre una colección o completan la colección en una función. Es más natural seguir con los modismos habituales de Scala y poner el índice dentro de los métodos habituales de mapa o foreach. Esto se puede hacer mediante la memorización. El código resultante podría verse como
myIterable map (doIndexed(someFunction))
Aquí hay una manera 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 Traversables habituales a expensas de envolver su función efectiva. ¡Disfrutar!
Looping en scala es bastante simple. Crea cualquier matriz de tu 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))
Mucho peor que atravesar dos veces, crea una matriz intermedia de pares. Puedes usar view
. Cuando haces collection.view
, puedes pensar en llamadas posteriores como actuando perezosamente, durante la iteración. Si desea recuperar una colección propia completa, llame a force
al final. Aquí eso sería inútil y costoso. Así que cambie su código a
for((x,i) <- xs.view.zipWithIndex) println("String #" + i + " is " + x)
No hay nada en el archivo stdlib que lo haga por usted sin crear basura tupla, pero no es demasiado difícil escribir el suyo. Desafortunadamente, nunca me he molestado en averiguar cómo hacer el CanBuildFrom apropiado de raindance implícito para hacer que esas cosas sean genéricas en el tipo de colección a la que se aplican, pero si es posible, estoy seguro de que alguien nos iluminará. :)
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)
}
Se ha mencionado que Scala tiene sintaxis for
bucles for
:
for (i <- 0 until xs.length) ...
o simplemente
for (i <- xs.indices) ...
Sin embargo, también pediste eficiencia. Resulta que la sintaxis de Scala es en realidad azúcar sintáctica para métodos de orden superior como map
, foreach
, etc. Como tal, en algunos casos estos bucles pueden ser ineficientes, por ejemplo, ¿cómo optimizar las 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 obtener la máxima eficiencia, uno puede usar un ciclo while o, si insiste en eliminar usos de var
, recursión final:
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 @tailrec
opcional es útil para garantizar que el método sea realmente recursivo de cola. El compilador de Scala traduce las llamadas recursivas de cola en el código de bytes equivalente de los ciclos while.
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);
}
}
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
Una forma simple y eficiente, inspirada en la implementación de transform
en SeqLike.scala
var i = 0
xs foreach { el =>
println("String #" + i + " is " + xs(i))
i += 1
}