2009-08-06 18 views
111

Existen varias formas de crear una lista inmutable en Scala (consulte el código de ejemplo artificial a continuación). Puede usar un ListBuffer mutable, crear una lista var y modificarla, usar un método tail recursive y probablemente otros que no conozca.Forma preferida para crear una lista de Scala

Instintivamente, uso ListBuffer, pero no tengo una buena razón para hacerlo. ¿Existe un método preferido o idiomático para crear una lista, o hay situaciones que son mejores para un método que para otro?

import scala.collection.mutable.ListBuffer 

// THESE are all the same as: 0 to 3 toList. 
def listTestA() ={ 
    var list:List[Int] = Nil 

    for(i <- 0 to 3) 
     list = list ::: List(i) 
    list 
} 


def listTestB() ={ 
    val list = new ListBuffer[Int]() 

    for (i <- 0 to 3) 
     list += i 
    list.toList 
} 


def listTestC() ={ 
    def _add(l:List[Int], i:Int):List[Int] = i match { 
     case 3 => l ::: List(3) 
     case _ => _add(l ::: List(i), i +1) 
    } 
    _add(Nil, 0) 
} 

Respuesta

104

ListBuffer es una lista mutable que tiene append de constante de tiempo, y la conversión de constante de tiempo en un List.

List es inmutable y tiene apendice de tiempo constante y apendice de tiempo lineal.

Cómo construyes tu lista depende del algoritmo que usarás para la lista y del orden en el que obtienes los elementos para crearla.

Por ejemplo, si obtiene los elementos en el orden inverso de cuándo se van a usar, entonces puede simplemente usar un List y hacerlos antes. Ya sea que lo haga con una función recursiva de cola, foldLeft, o alguna otra cosa no es realmente relevante.

Si obtiene los elementos en el mismo orden en que los usa, entonces es muy probable que elija ListBuffer, si el rendimiento es crítico.

Pero, si usted no está en una ruta crítica y la entrada es lo suficientemente baja, siempre se puede reverse la lista más adelante, o simplemente foldRight o reverse la entrada, que es en tiempo lineal.

Lo que DO NO hacer es usar un List y anexarlo. Esto le proporcionará un rendimiento mucho peor que el de adelantarse y retroceder al final.

+0

'Lo que no hacemos es utilizar una lista y añadir a it' ¿Eso es porque se crea una ** nueva lista **? Considerando que, usando una operación de anteponer no creará una nueva lista? –

+2

@KevinMeredith Sí. Append es O (n), prepend es O (1). –

+0

@pgoggijr Eso no es verdad. Primero, no hay "cambio" en ninguna parte, porque es inmutable. Se requiere un recorrido porque todos los elementos deben copiarse, solo para que se pueda hacer una copia del último elemento apuntando a un nuevo elemento en lugar de 'Nil'. En segundo lugar, no hay copia de ningún tipo en anteponer: se crea un elemento que apunta a la lista existente, y eso es todo. –

20

Uhmm .. me parecen demasiado complejas. ¿Puedo proponer

def listTestD = (0 to 3).toList 

o

def listTestE = for (i <- (0 to 3).toList) yield i 
+0

Gracias por la respuesta, pero la pregunta es ¿qué haces en el caso no trivial. Puse un comentario en el código explicando que todos eran equivalentes a 0 to 3 toList. – agilefall

+0

Vaya, lo siento! Francamente, nunca uso ListBuffer. –

2

siempre prefiero lista y lo uso "fold/reducir el" antes "para la comprensión". Sin embargo, se prefiere "para la comprensión" si se requieren "pliegues" anidados. La recursividad es el último recurso si no puedo lograr la tarea usando "fold/reduce/for".

así por su ejemplo, lo haré:

((0 to 3) :\ List[Int]())(_ :: _) 

antes que yo:

(for (x <- 0 to 3) yield x).toList 

Nota: Yo uso "foldRight (: \)" en lugar de "foldLeft (/ :) "aquí debido al orden de" _ "s. Para una versión que no arroja StackOverflowException, use "foldLeft" en su lugar.

+18

Estoy totalmente en desacuerdo; su forma preferida solo parece ruido de línea. –

+1

Bueno, todo lo que puedo decir es que si te apegas a Scala y a la programación funcional lo suficiente, aprenderás a amarla. –

+14

¿Lo haré? Conocí a Haskell por primera vez en 1999 y he estado incursionando en Scala durante un par de años. Creo que los pliegues son geniales, pero si aplicar un pliegue en cualquier situación requiere escribir una cadena críptica de símbolos de puntuación, consideraría un enfoque diferente. –

63

Y para los casos simples:

val list = List(1,2,3) 

:)

+10

¡No se olvide del operador contras! 1 :: 2 :: 3 :: Nil –

+3

¿O debería decir "operador"? –

2

Nota: Esta respuesta está escrita para una versión antigua de Scala.

Las clases de la colección Scala se rediseñarán a partir de Scala 2.8, así que prepárese para cambiar la forma de crear listas muy pronto.

¿Cuál es la forma de crear una lista de manera avanzada? No tengo idea ya que aún no he leído los 2.8 documentos.

A PDF document describing the proposed changes of the collection classes

+2

La mayoría de los cambios están en la forma en que se implementan las cosas internamente, y en cosas avanzadas como las proyecciones. Cómo se crea una lista no se ve afectada. –

+0

Ok, eso es bueno saberlo. También se verá afectado si usa cualquier clase en el paquete collection.jcl. –

5

desea centrarse en la inmutabilidad de Scala en general, mediante la eliminación de cualquier VARs. legibilidad sigue siendo importante para su prójimo de modo:

Probar:

scala> val list = for(i <- 1 to 10) yield i 
list: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) 

Es probable que ni siquiera necesita para convertir a una lista en la mayoría de los casos :)

La SEC indexados tendrá todo lo que necesita:

eso es, ahora puede trabajar en ese IndexedSeq:

scala> list.foldLeft(0)(_+_) 
res0: Int = 55 
+0

N.B. 'Vector' ahora también es la implementación' Seq' predeterminada. –

1

Como nuevo desarrollador de Scala, escribí una pequeña prueba para verificar el tiempo de creación de la lista con los métodos sugeridos anteriormente. Parece que (para (p < - (0 a x)) rinda p) para enumerar el enfoque más rápido.

import java.util.Date 
object Listbm { 

    final val listSize = 1048576 
    final val iterationCounts = 5 
    def getCurrentTime: BigInt = (new Date) getTime 

    def createList[T] (f : Int => T)(size : Int): T = f (size) 

    // returns function time execution 
    def experiment[T] (f : Int => T) (iterations: Int) (size :Int) : Int = { 

    val start_time = getCurrentTime 
    for (p <- 0 to iterations) createList (f) (size) 
    return (getCurrentTime - start_time) toInt 

    } 

    def printResult (f: => Int) : Unit = println ("execution time " + f ) 

    def main(args : Array[String]) { 


    args(0) match { 

     case "for" => printResult (experiment (x => (for (p <- (0 to x)) yield p) toList ) (iterationCounts) (listSize)) 
     case "range" => printResult (experiment (x => (0 to x) toList) (iterationCounts) (listSize)) 
     case "::" => printResult (experiment (x => ((0 to x) :\ List[Int]())(_ :: _)) (iterationCounts) (listSize)) 
     case _ => println ("please use: for, range or ::\n") 
    } 
    } 
} 
2

Usando List.tabulate, de esta manera,

List.tabulate(3)(x => 2*x) 
res: List(0, 2, 4) 

List.tabulate(3)(_ => Math.random) 
res: List(0.935455779102479, 0.6004888906328091, 0.3425278797788426) 

List.tabulate(3)(_ => (Math.random*10).toInt) 
res: List(8, 0, 7) 
0

sólo un ejemplo que utiliza collection.breakOut

scala> val a : List[Int] = (for(x <- 1 to 10) yield x * 3)(collection.breakOut) 
a: List[Int] = List(3, 6, 9, 12, 15, 18, 21, 24, 27, 30) 

scala> val b : List[Int] = (1 to 10).map(_ * 3)(collection.breakOut) 
b: List[Int] = List(3, 6, 9, 12, 15, 18, 21, 24, 27, 30) 
Cuestiones relacionadas