Modo preferito per creare un elenco Scala


117

Esistono diversi modi per costruire un elenco immutabile in Scala (vedere il codice di esempio inventato di seguito). Puoi utilizzare un ListBuffer mutabile, creare un varelenco e modificarlo, utilizzare un metodo ricorsivo di coda e probabilmente altri di cui non sono a conoscenza.

Istintivamente, utilizzo ListBuffer, ma non ho una buona ragione per farlo. Esiste un metodo preferito o idiomatico per creare un elenco o ci sono situazioni che sono migliori per un metodo rispetto a un altro?

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

Risposte:


108

ListBufferè un elenco modificabile che ha l'aggiunta in tempo costante e una conversione in tempo costante in un file List.

List è immutabile e ha un prefisso in tempo costante e un append in tempo lineare.

Il modo in cui costruisci la tua lista dipende dall'algoritmo per cui utilizzerai la lista e dall'ordine in cui ottieni gli elementi per crearla.

Ad esempio, se ottieni gli elementi nell'ordine opposto di quando verranno utilizzati, puoi semplicemente usare a Liste fare anteponi. Che lo farai con una funzione ricorsiva di coda foldLefto qualcos'altro non è realmente rilevante.

Se ottieni gli elementi nello stesso ordine in cui li usi, allora a ListBufferè molto probabilmente una scelta preferibile, se le prestazioni sono critiche.

Ma, se non sei su un percorso critico e l'input è abbastanza basso, puoi sempre reversel'elenco in seguito, o solo foldRight, o reversel'input, che è tempo lineare.

Quello che NON fai è usare Liste aggiungervi. Questo ti darà prestazioni molto peggiori rispetto alla semplice anticipazione e inversione alla fine.


What you DON'T do is use a List and append to itÈ perché viene creato un nuovo elenco ? Considerando che l'uso di un'operazione di anteponi non creerà un nuovo elenco?
Kevin Meredith

2
@KevinMeredith Sì. Append è O (n), anteporre è O (1).
Daniel C. Sobral

@pgoggijr Questo non è vero. Primo, non c'è "cambiamento" da nessuna parte, perché è immutabile. È necessario un attraversamento perché tutti gli elementi devono essere copiati, solo così si può fare una copia dell'ultimo elemento puntando a un nuovo elemento invece di Nil. Secondo, non c'è nessuna copia di alcun tipo su anteponi: viene creato un elemento che punta all'elenco esistente, e il gioco è fatto.
Daniel C. Sobral


22

Uhmm .. questi sembrano troppo complessi per me. Posso proporre

def listTestD = (0 to 3).toList

o

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

Grazie per la risposta, ma la domanda è cosa fai nel caso non banale. Ho inserito un commento nel codice spiegando che erano tutti equivalenti da 0 a 3 toList.
agilefall

Ops, scusa allora! Francamente, non uso mai ListBuffer.
Alexander Azarov

5

Vuoi concentrarti sull'immutabilità in Scala in generale eliminando qualsiasi variabile. La leggibilità è ancora importante per i tuoi simili, quindi:

Provare:

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)

Probabilmente non è nemmeno necessario convertire in un elenco nella maggior parte dei casi :)

Il seq indicizzato avrà tutto ciò di cui hai bisogno:

Cioè, ora puoi lavorare su quel IndexedSeq:

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

NB Vectorora è anche l' Seqimplementazione predefinita .
Connor Doyle

2

Preferisco sempre List e uso "piega / riduci" prima di "per comprensione". Tuttavia, "per la comprensione" è preferito se sono richieste "pieghe" annidate. La ricorsione è l'ultima risorsa se non riesco a portare a termine il compito usando "fold / reduce / for".

quindi per il tuo esempio, farò:

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

prima di me:

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

Nota: qui uso "foldRight (: \)" invece di "foldLeft (/ :)" a causa dell'ordine di "_". Per una versione che non genera StackOverflowException, utilizza invece "foldLeft".


18
Sono fortemente in disaccordo; la tua forma preferita sembra solo rumore di linea.
Matt R

14
Lo farò? Ho imparato Haskell per la prima volta nel 1999 e da un paio d'anni mi diletto in Scala. Penso che le pieghe siano fantastiche, ma se l'applicazione di una piega in una determinata situazione richiede la scrittura di una stringa criptica di simboli di punteggiatura, prenderei in considerazione un approccio diverso.
Matt R

11
@ Matt R: sono d'accordo. C'è una cosa come esagerare, e questo è uno di loro.
Ryeguy

8
@WalterChang Mi piace l'aspetto di tutte quelle emoticon. Aspetta un attimo, è quel codice? : P
David J.

4
È giusto chiamare ((0 to 3) :\ List[Int]())(_ :: _)emoticode?
David J.

2

Usando List.tabulate, in questo modo,

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)

2

Nota: questa risposta è scritta per una vecchia versione di Scala.

Le classi della collezione Scala saranno ridisegnate a partire da Scala 2.8, quindi preparati a cambiare il modo in cui crei le liste molto presto.

Qual è il modo compatibile con il futuro per creare un elenco? Non ne ho idea dato che non ho ancora letto i documenti 2.8.

Un documento PDF che descrive le modifiche proposte delle classi di raccolta


2
La maggior parte dei cambiamenti riguarda il modo in cui le cose vengono implementate internamente e in cose avanzate come le proiezioni. Il modo in cui crei un elenco non viene influenzato.
Marcus Downing

Ok, buono a sapersi. Sarai influenzato anche se usi una classe nel pacchetto collection.jcl.
André Laszlo

1

Come nuovo sviluppatore scala ho scritto un piccolo test per controllare il tempo di creazione della lista con i metodi suggeriti sopra. Sembra che (per (p <- (da 0 a x)) rendimento p) Elenca l'approccio più veloce.

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

0

solo un esempio che utilizza 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)

0

Per creare un elenco di stringhe, utilizzare quanto segue:

val l = List("is", "am", "are", "if")

1
Quando si risponde a una domanda così vecchia (10 anni) e con così tante risposte esistenti (9), è una buona pratica spiegare perché la tua risposta è diversa da tutte le altre. Così com'è, sembra che tu non abbia capito la domanda.
jwvh
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.