2011-08-22 6 views
15

Por ejemplo supongamos que tengo¿Hay alguna forma de manejar el último caso de forma diferente en un bucle de Scala for?

for (line <- myData) { 
    println("}, {") 
    } 

¿Hay una manera de conseguir la última línea para imprimir

println("}") 
+0

Como la respuesta de @ Nicolas ya lo indica, con frecuencia es mucho más fácil si puede refactorizar su comportamiento deseado para actuar de manera diferente para la * primera * iteración, en lugar de la última. Es trivial identificar la primera iteración. –

+4

+1 para usar scala –

Respuesta

18

Antes de ir más lejos, Te recomendaría evitar println en una comprensión forzosa. A veces puede ser útil para rastrear un error que ocurre en el medio de una colección, pero de lo contrario conduce a un código que es más difícil de refactorizar y probar.

De forma más general, la vida suele ser más fácil si puede restringir dónde se produce algún tipo de efecto secundario. Así que en lugar de:

for (line <- myData) { 
    println("}, {") 
} 

Se puede escribir:

val lines = for (line <- myData) yield "}, {" 
println(lines mkString "\n") 

También voy a tomar una conjetura aquí que quería el contenido de cada línea en la salida!

val lines = for (line <- myData) yield (line + "}, {") 
println(lines mkString "\n") 

A pesar de que sería mejor aún si acaba de utilizar directamente mkString - eso es para lo que sirve!

val lines = myData.mkString("{", "\n}, {", "}") 
println(lines) 

Nota cómo estamos produciendo una primera String, a continuación, imprimirlo en una sola operación. Este enfoque puede dividirse fácilmente en métodos separados y utilizarse para implementar toString en su clase, o para inspeccionar el String generado en las pruebas.

6

usted puede tomar la función del rasgo addStringTraversableOnce como un ejemplo.

def addString(b: StringBuilder, start: String, sep: String, end: String): StringBuilder = { 
    var first = true 

    b append start 
    for (x <- self) { 
    if (first) { 
     b append x 
     first = false 
    } else { 
     b append sep 
     b append x 
    } 
    } 
    b append end 

    b 
} 

En su caso, el separador es }, { y al final es }

+0

+1. Es bueno ver cómo se hace en la biblioteca estándar. –

28

¿Se puede refactorizar el código para tomar ventaja de una función de mkString?

scala> List(1, 2, 3).mkString("{", "}, {", "}") 
res1: String = {1}, {2}, {3} 
2

Si no desea utilizar la función incorporada de mkString, puede hacer algo como

for (line <- lines) 
    if (line == lines.last) println("last") 
    else println(line) 

ACTUALIZACIÓN: Comodidierd mencionado en los comentarios, esta solución está mal porque último valor puede ocurrir varias veces, él proporciona una mejor solución en su answer.

Está bien para Vectors, porque last función toma "tiempo con eficacia constante" para ellos, como para Lists, se necesita tiempo lineal, por lo que puede utilizar coincidencia de patrones

@tailrec 
def printLines[A](l: List[A]) { 
    l match { 
    case Nil => 
    case x :: Nil => println("last") 
    case x :: xs => println(x); printLines(xs) 
    } 
} 
+1

La solución recursiva está bien. El que tiene == lines.last es incorrecto a menos que sepa que el valor del último elemento no aparece en ninguna otra parte. –

+0

@didierd gracias, lo extrañé. Señalé el error en la respuesta. – 4e6

+0

Lo probé usando 'eq' en lugar de' == 'y todavía no funciona - las cadenas deben ser almacenadas en caché, lo cual es un poco molesto –

12

Estoy completamente de acuerdo con lo que se ha dicho antes sobre el uso de mkstring, y distingue la primera iteración en lugar de la última. ¿Todavía tendría que distinguir en el último, las colecciones scala tienen un método init, que devuelve todos los elementos, excepto el último. Por lo que puede hacer

for(x <- coll.init) workOnNonLast(x) 
workOnLast(coll.last) 

(init y last siendo una especie de lo contrario de la cabeza y la cola, que son la primera y y todos, excepto el primero). Sin embargo, tenga en cuenta que, dependiendo de la estructura, pueden ser costosos. En Vector, todos son rápidos. En List, mientras que la cabeza y la cola son básicamente libres, init y last son lineales en la longitud de la lista. headOption y lastOption le ayudo cuando la colección puede estar vacío, en sustitución de workOnlast por

for (x <- coll.lastOption) workOnLast(x) 
1

Otras respuestas se señalan con razón a mkString, y para una cantidad normal de datos que también habría que utilizar. Sin embargo, mkString construye (acumula) el resultado final en la memoria a través de StringBuilder. Esto no siempre es deseable, dependiendo de la cantidad de datos que tenemos.

En este caso, si todo lo que queremos es "imprimir", no es necesario construir primero el gran resultado (y tal vez incluso queremos evitar esto).

examinar la ejecución de esta función auxiliar:

def forEachIsLast[A](iterator: Iterator[A])(operation: (A, Boolean) => Unit): Unit = { 
    while(iterator.hasNext) { 
    val element = iterator.next() 
    val isLast = !iterator.hasNext // if there is no "next", this is the last one 
    operation(element, isLast) 
    } 
}  

Se itera sobre todos los elementos e invoca operation pasando cada elemento, a su vez, con un valor booleano. El valor es true si el elemento pasado es el último.

En su caso se podría utilizar como esto:

forEachIsLast(myData) { (line, isLast) => 
    if(isLast) 
    println("}") 
    else 
    println("}, {") 
} 

Tenemos las siguientes ventajas aquí:

  • Opera sobre cada elemento, uno por uno, sin acumular necesariamente el resultado de memoria (a menos que lo desee).
  • Como no es necesario cargar toda la colección en la memoria para verificar su tamaño, es suficiente preguntarle al Iterador si está agotado o no. Puede leer datos de un archivo grande, o de la red, etc.
+2

O, ligeramente más elegantemente, tal vez: 'forEachIsLast' solo puede ser' val it = seq.toIterator it.foreach {operation (_, it.hasNext)} ' –

+0

Muchas gracias @TheArchetypalPaul! Edité mi respuesta para usar una implementación basada en su sugerencia. –

Cuestiones relacionadas