Forma preferida de crear una lista Scala

117

Hay varias formas de construir una lista inmutable en Scala (vea el código de ejemplo artificial a continuación). Puede usar un ListBuffer mutable, crear una varlista y modificarla, usar un método recursivo de cola y probablemente otros que no conozco.

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 sobre 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)
}
caída ágil
fuente

Respuestas:

108

ListBufferes una lista mutable que tiene un agregado de tiempo constante y una conversión de tiempo constante en un List.

List es inmutable y tiene un prefijo de tiempo constante y un anexo de tiempo lineal.

La forma en que construya su lista depende del algoritmo para el que usará la lista y del orden en que obtiene los elementos para crearla.

Por ejemplo, si obtiene los elementos en el orden opuesto de cuándo se van a usar, entonces puede usar a Listy hacer prepends. Si lo hará con una función recursiva de cola foldLeft, o con otra cosa, no es realmente relevante.

Si obtiene los elementos en el mismo orden en que los usa, ListBufferlo más probable es que a sea una opción preferible, si el rendimiento es crítico.

Pero, si no se encuentra en una ruta crítica y la entrada es lo suficientemente baja, siempre puede consultar reversela lista más tarde, o simplemente foldRight, o reversela entrada, que es de tiempo lineal.

Lo que NO DEBE hacer es usar un Listy anexarlo. Esto le dará un rendimiento mucho peor que simplemente anteponer y revertir al final.

Daniel C. Sobral
fuente
What you DON'T do is use a List and append to it¿Es porque se crea una nueva lista ? Considerando que, el uso de una operación de prefijo no creará una nueva lista?
Kevin Meredith
2
@KevinMeredith Sí. Agregar es O (n), anteponer es O (1).
Daniel C. Sobral
@pgoggijr Eso no es cierto. Primero, no hay "cambios" 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 al anteponer: se crea un elemento apuntando a la lista existente, y eso es todo.
Daniel C. Sobral
65

Y para casos sencillos:

val list = List(1,2,3) 

:)

Tomasz Nurkiewicz
fuente
10
¡No te olvides del operador de contras! 1 :: 2 :: 3 :: Nil
André Laszlo
22

Uhmm ... me parecen demasiado complejos. Puedo proponer

def listTestD = (0 to 3).toList

o

def listTestE = for (i <- (0 to 3).toList) yield i
Alejandro Azarov
fuente
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 a 3 toList.
Agilefall
¡Vaya, lo siento entonces! Francamente, nunca uso ListBuffer.
Alexander Azarov
5

Desea centrarse en la inmutabilidad en Scala generalmente eliminando cualquier var. La legibilidad sigue siendo importante para su prójimo, así que:

Tratar:

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)

Probablemente ni siquiera necesite convertir a una lista en la mayoría de los casos :)

La secuencia indexada tendrá todo lo que necesita:

Es decir, ahora puede trabajar en ese IndexedSeq:

scala> list.foldLeft(0)(_+_)
res0: Int = 55
JasonG
fuente
NB Vectorahora también es la Seqimplementación predeterminada .
Connor Doyle
2

Siempre prefiero List y uso "plegar / reducir" antes que "para comprensión". Sin embargo, se prefiere "para la comprensión" si se requieren "pliegues" anidados. La recursividad es el último recurso si no puedo realizar la tarea usando "plegar / reducir / para".

así que para tu ejemplo, haré:

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

antes que yo:

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

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

Walter Chang
fuente
18
Estoy totalmente en desacuerdo; su forma preferida simplemente parece ruido de línea.
Matt R
14
Lo haré? Aprendí 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 una situación dada requiere escribir una cadena críptica de símbolos de puntuación, consideraría un enfoque diferente.
Matt R
11
@Matt R: Estoy de acuerdo. Existe tal cosa como exagerar, y esta es una de ellas.
ryeguy
8
@WalterChang Me gusta el aspecto de todos esos emoticonos. Espera un minuto, ¿es ese código? : P
David J.
4
¿Es justo llamar ((0 to 3) :\ List[Int]())(_ :: _)emoticode?
David J.
2

Usando List.tabulate, así,

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)
olmo
fuente
2

Nota: esta respuesta está escrita para una versión anterior de Scala.

Las clases de la colección de Scala se rediseñarán a partir de Scala 2.8, así que esté preparado para cambiar la forma en que crea listas muy pronto.

¿Cuál es la forma compatible con versiones posteriores de crear una lista? No tengo ni idea ya que todavía no he leído los documentos 2.8.

Un documento PDF que describe los cambios propuestos de las clases de colección.

André Laszlo
fuente
2
La mayoría de los cambios se encuentran en la forma en que las cosas se implementan internamente y en cosas avanzadas como las proyecciones. La forma en que crea una lista no se ve afectada.
Marcus Downing
Ok, es bueno saberlo. También se verá afectado si usa cualquier clase en el paquete collection.jcl.
André Laszlo
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 (para (p <- (0 ax)) rendimiento 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")
    }
  }
}
Yuli Reiri
fuente
0

solo un ejemplo que usa 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)
Arne
fuente
0

Para crear una lista de cadenas, use lo siguiente:

val l = List("is", "am", "are", "if")
KayV
fuente
1
Al responder una pregunta tan antigua (10 años), y con tantas respuestas existentes (9), es una buena práctica explicar por qué su respuesta es diferente a todas las demás. Tal como está, parece que no entendió la pregunta.
jwvh