2009-08-06 16 views
111

Esistono diversi modi per costruire un elenco immutabile in Scala (vedere il codice di esempio forzato di seguito). Puoi utilizzare un ListBuffer mutabile, creare un elenco var e modificarlo, utilizzare un metodo tail recursive e probabilmente altri che non conosco.Modo preferito per creare un elenco Scala

Istintivamente, utilizzo ListBuffer, ma non ho una buona ragione per farlo. Esiste un metodo preferito o idiomatico per la creazione di un elenco o esistono situazioni 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) 
} 

risposta

104

ListBuffer è una lista mutevole che ha tempo costante accodamento, e la conversione costante di tempo in un List.

List è immutabile e ha un valore di riferimento costante e un'appendice di tempo lineare.

Come si costruisce l'elenco dipende dall'algoritmo che si utilizzerà l'elenco e dall'ordine in cui si ottengono gli elementi per crearlo.

Ad esempio, se si ottengono gli elementi nell'ordine inverso rispetto a quando saranno utilizzati, sarà sufficiente utilizzare uno e fare le prepazioni. Se lo farai con una funzione ricorsiva in coda, foldLeft, o qualcos'altro non è veramente rilevante.

Se si ottengono gli elementi nello stesso ordine in cui li si utilizza, quindi uno ListBuffer è probabilmente una scelta preferibile, se le prestazioni sono fondamentali.

Ma, se non siete su un percorso critico e l'ingresso è abbastanza basso, si può sempre reverse la lista più tardi, o semplicemente foldRight, o reverse l'ingresso, che è lineare in tempo.

Cosa si DO do è utilizzare un List e aggiungere ad esso. Ciò ti darà prestazioni molto peggiori rispetto alla sola preposizione e inversione alla fine.

+0

'Cosa non ti fare è utilizzare una lista e aggiungere alla Liechtenstein attua È perché viene creato un ** nuovo elenco **? Considerando che, utilizzando un'operazione di antefatto non verrà creato un nuovo elenco? –

+2

@KevinMeredith Sì. Aggiungi è O (n), antepone è O (1). –

+0

@pgoggijr Questo non è vero. Innanzitutto, non c'è "cambiamento" da nessuna parte, perché è immutabile. È richiesto un attraversamento perché tutti gli elementi devono essere copiati, solo così una copia dell'ultimo elemento può essere fatta puntare a un nuovo elemento invece di 'Nil'. In secondo luogo, non ci sono copie di alcun tipo su ante: viene creato un elemento che punta all'elenco esistente e il gioco è fatto. –

20

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 
+0

Grazie per la risposta, ma la domanda è cosa fai nel caso non banale. Ho inserito un commento nel codice per spiegare che erano tutti equivalenti a 0 a 3 nell'elenco. – agilefall

+0

Oops, scusa allora! Francamente, non uso mai ListBuffer. –

2

preferisco sempre List e io uso "fold/ridurre" prima "per la comprensione". Tuttavia, "per comprensione" è preferito se sono richieste "pieghe" annidate. La ricorsione è l'ultima risorsa se non riesco a svolgere il compito utilizzando "fold/reduce/per".

così per il tuo esempio, lo farò:

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

prima di me:

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

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

+18

Sono fortemente in disaccordo; la tua forma preferita assomiglia al rumore della linea. –

+1

Bene, tutto quello che posso dire è che se ti infili con Scala e la programmazione funzionale abbastanza a lungo, imparerai ad amarla. –

+14

Will I? Ho conosciuto Haskell per la prima volta nel 1999 e mi sono dilettato in Scala per un paio d'anni. Penso che le pieghe siano grandi, ma se si applica una piega in una data situazione richiede la scrittura di una stringa criptica di simboli di punteggiatura, prenderei in considerazione un approccio diverso. –

63

E per i casi semplici:

val list = List(1,2,3) 

:)

+10

Non dimenticare l'operatore cons! 1 :: 2 :: 3 :: Nil –

+3

O dovrei dire "operatore"? –

2

Nota: Questa risposta è scritto per una vecchia versione di Scala.

Le classi di raccolta Scala verranno ridisegnate a partire da Scala 2.8, quindi preparatevi a cambiare il modo in cui create le liste molto presto.

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

A PDF document describing the proposed changes of the collection classes

+2

La maggior parte dei cambiamenti riguarda il modo in cui le cose sono implementate internamente e in cose avanzate come le proiezioni. Come si crea un elenco non è interessato. –

+0

Ok, questo è bello sapere. Verrai influenzato anche se utilizzi qualsiasi classe nel pacchetto collection.jcl. –

5

Si vuole mettere a fuoco immutabilità a Scala in generale, eliminando eventuali Vars. La leggibilità è ancora importante per il vostro prossimo così:

Prova:

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 hanno nemmeno bisogno di convertire in una lista in molti casi :)

SEQ indicizzato avrà tutto ciò che serve:

Cioè, si può ora lavorare su questo IndexedSeq:

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

N.B. 'Vector' ora è anche l'implementazione di default' Seq'. –

1

Come un nuovo sviluppatore di scala ho scritto un piccolo test per controllare il tempo di creazione della lista con i metodi suggeriti sopra. Sembra (per (p < - (da 0 a x)) resa p) per visualizzare 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") 
    } 
    } 
} 
2

Utilizzando 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) 
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) 
Problemi correlati