Quando dovrei scegliere Vector in Scala?


200

Sembra che Vectorfosse in ritardo alla festa delle collezioni Scala e che tutti i post sul blog influenti fossero già andati via.

In Java ArrayListè la raccolta predefinita - potrei usare, LinkedListma solo quando ho pensato attraverso un algoritmo e mi sono preoccupato abbastanza per ottimizzare. In Scala dovrei usare Vectorcome predefinito Seqo provare a capire quando Listè effettivamente più appropriato?


1
Immagino che ciò che intendo qui sia che in Java List<String> l = new ArrayList<String>()creerei i blog di Scala in cui vorresti farti credere che tutti usassero List per ottenere una bontà persistente di raccolta - ma Vector è abbastanza generico per cui dovremmo usarlo al posto di List?
Duncan McGregor,

9
@Debilski: mi chiedo cosa intendi con questo. Ottengo un messaggio Listquando Seq()scrivo su REPL.
missingfaktor il

1
Hmm, beh, lo dice nei documenti. Forse questo è vero solo per IndexedSeq.
Debilski,

1
Il commento sul tipo di calcestruzzo predefinito Seqè di oltre tre anni. A partire da Scala 2.11.4 (e precedenti), il tipo concreto predefinito di Seqè List.
Mark Canlas,

3
Per l'accesso casuale, il vettore è migliore. Per la testa, l'accesso alla coda, l'elenco è migliore. Per operazioni in blocco, come mappa, filtro, vettore è preferito poiché il vettore è organizzato con 32 elementi come blocco mentre l'elenco ha organizzato gli elementi con puntatori l'uno con l'altro, non c'è garanzia che questi elementi siano vicini tra loro.
johnsam,

Risposte:


280

Come regola generale, impostazione predefinita per l'utilizzo Vector. È più veloce che Listper quasi tutto e più efficiente in termini di memoria per sequenze di dimensioni più che banali. Vedi questa documentazione delle prestazioni relative di Vector rispetto alle altre raccolte. Ci sono alcuni aspetti negativi con cui andare Vector. In particolare:

  • Gli aggiornamenti alla testa sono più lenti di List(anche se non di quanto potresti pensare)

Un altro aspetto negativo prima di Scala 2.10 era che il supporto per la corrispondenza dei modelli era migliore List, ma questo è stato corretto in 2.10 con generalizzatori +:ed :+estrattori.

C'è anche un modo più astratto e algebrico di affrontare questa domanda: che tipo di sequenza hai concettualmente ? Inoltre, cosa ci fai concettualmente ? Se vedo una funzione che restituisce un Option[A], so che la funzione ha alcuni buchi nel suo dominio (ed è quindi parziale). Possiamo applicare questa stessa logica alle raccolte.

Se ho una sequenza di tipo List[A], sto effettivamente affermando due cose. Innanzitutto, il mio algoritmo (e i miei dati) è interamente strutturato in stack. In secondo luogo, sto affermando che le uniche cose che farò con questa raccolta sono piene, O (n) traversals. Questi due vanno davvero di pari passo. Al contrario, se ho qualcosa di tipo Vector[A], l' unica cosa che sto affermando è che i miei dati hanno un ordine ben definito e una lunghezza finita. Pertanto, le asserzioni sono più deboli Vectore questo porta ad una sua maggiore flessibilità.


2
2.10 è uscito da un po 'di tempo, la corrispondenza del modello Elenco è ancora migliore di Vector?
Tim Gautier

3
La corrispondenza del modello elenco non è più migliore. In effetti, è piuttosto il contrario. Ad esempio, per ottenere testa e coda si può fare case head +: tailo case tail :+ head. Per abbinare contro vuoto, puoi fare case Seq()e così via. Tutto ciò che serve è lì nel API, che è più versatile di List's
Kai Sellgren

Listè implementato con un elenco collegato singolarmente. Vectorè implementato qualcosa come Java ArrayList.
Josiah Yoder,

6
@JosiahYoder Non è implementato niente come ArrayList. ArrayList racchiude un array che ridimensiona in modo dinamico. Vector è un trie , in cui le chiavi sono gli indici dei valori.
John Colanduoni,

1
Chiedo scusa. Stavo andando su una fonte web che era vaga sui dettagli. Devo correggere la mia precedente dichiarazione? O è quella cattiva forma?
Josiah Yoder,

93

Bene, a Listpuò essere incredibilmente veloce se l'algoritmo può essere implementato esclusivamente con ::, heade tail. Di recente ho avuto una lezione sull'oggetto, quando ho battuto Java splitgenerando un Listinvece di un Array, e non ho potuto batterlo con nient'altro.

Tuttavia, Listha un problema fondamentale: non funziona con algoritmi paralleli. Non posso dividere una Listin più segmenti o concatenarla in modo efficiente.

Esistono altri tipi di raccolte che possono gestire molto meglio il parallelismo - ed Vectorè una di queste. Vectorha anche un'ottima località - che Listnon lo fa - che può essere un vero vantaggio per alcuni algoritmi.

Quindi, tutto sommato, Vectorè la scelta migliore a meno che tu non abbia considerazioni specifiche che rendono preferibile una delle altre raccolte - ad esempio, potresti scegliere Streamse vuoi una valutazione e memorizzazione nella cache pigre ( Iteratorè più veloce ma non memorizza nella cache), o Listse l'algoritmo è naturalmente implementato con le operazioni che ho citato.

Tra l'altro, è preferibile utilizzare Seqo IndexedSeqmeno che non vogliate una parte specifica di API (ad esempio List's ::), o anche GenSeqo GenIndexedSeqse il vostro algoritmo può essere eseguito in parallelo.


3
Grazie per la risposta. Cosa intendi con "ha una grande località"?
Ngoc Dao,

10
@ngocdaothanh Significa che i dati sono raggruppati strettamente nella memoria, migliorando la possibilità che i dati siano nella cache quando ne hai bisogno.
Daniel C. Sobral,

1
@ user247077 Sì, gli elenchi possono battere i vettori nelle prestazioni dati i particolari che ho citato. E non tutte le azioni dei vettori sono ammortizzate O (1). In effetti, su strutture di dati immutabili (che è il caso), l'inserzione / eliminazione alternata alle due estremità non si ammortizzerà affatto. In tal caso, la cache è inutile perché si copia sempre il vettore.
Daniel C. Sobral,

1
@ user247077 Forse non sei consapevole che Vectoresiste una struttura di dati immutabile in Scala?
Daniel C. Sobral,

1
@ user247077 È molto più complicato di così, tra cui alcune cose mutabili internamente per rendere l'appendice più economica, ma quando lo si utilizza come uno stack, che è uno scenario ottimale di elenco immutabile, si finisce per avere le stesse caratteristiche di memoria di un elenco collegato, ma con un profilo di allocazione della memoria molto più grande.
Daniel C. Sobral,

29

Alcune delle affermazioni qui sono confuse o addirittura sbagliate, in particolare l'idea che immutabile. Il vettore in Scala è qualcosa di simile a una ArrayList. List e Vector sono strutture di dati sia immutabili che persistenti (ovvero "economico per ottenere una copia modificata"). Non esiste una scelta predefinita ragionevole in quanto potrebbero essere per strutture di dati mutabili, ma dipende piuttosto da ciò che sta facendo l'algoritmo. Elenco è un elenco collegato singolarmente, mentre Vector è un trie intero base-32, ovvero è una specie di albero di ricerca con nodi di grado 32. Utilizzando questa struttura, Vector può fornire le operazioni più comuni ragionevolmente veloci, cioè in O (log_32 ( n)). Funziona per anteporre, aggiungere, aggiornare, accesso casuale, decomposizione in testa / coda. L'iterazione in ordine sequenziale è lineare. L'elenco invece fornisce solo iterazione lineare e prependenza a tempo costante, decomposizione in testa / coda.

Questo potrebbe sembrare come se Vector fosse un buon sostituto di List in quasi tutti i casi, ma antepone, decomposizione e iterazione sono spesso le operazioni cruciali sulle sequenze in un programma funzionale e le costanti di queste operazioni sono (molto) più alte per il vettore dovuto alla sua struttura più complicata. Ho fatto alcune misurazioni, quindi l'iterazione è circa il doppio più veloce per l'elenco, anteporre è circa 100 volte più veloce sugli elenchi, la decomposizione in testa / coda è circa 10 volte più veloce sugli elenchi e la generazione da un attraversabile è circa 2 volte più veloce per i vettori. (Ciò è probabilmente dovuto al fatto che Vector può allocare array di 32 elementi contemporaneamente quando lo costruisci usando un builder invece di anteporre o aggiungere elementi uno alla volta).

Quindi quale struttura di dati dovremmo usare? Fondamentalmente, ci sono quattro casi comuni:

  • Dobbiamo solo trasformare le sequenze con operazioni come map, filter, fold etc: in pratica non importa, dovremmo programmare il nostro algoritmo genericamente e potremmo anche trarre vantaggio dall'accettare sequenze parallele. Per le operazioni sequenziali, l'elenco è probabilmente un po 'più veloce. Ma dovresti confrontarlo se devi ottimizzare.
  • Abbiamo bisogno di un sacco di accesso casuale e diversi aggiornamenti, quindi dovremmo usare il vettore, l'elenco sarà proibitivamente lento.
  • Operiamo su liste in un modo funzionale classico, costruendole anteponendo e iterando per decomposizione ricorsiva: usa la lista, il vettore sarà più lento di un fattore 10-100 o più.
  • Abbiamo un algoritmo critico per le prestazioni che è fondamentalmente imperativo e fa un sacco di accesso casuale su un elenco, qualcosa come un rapido ordinamento sul posto: usa una struttura di dati imperativa, ad esempio ArrayBuffer, localmente e copia i tuoi dati da e verso di esso.

24

Per le raccolte immutabili, se si desidera una sequenza, la decisione principale è se utilizzare a IndexedSeqo a LinearSeq, che offrono diverse garanzie di prestazione. Un IndexedSeq fornisce un rapido accesso casuale degli elementi e un'operazione di lunghezza rapida. Un LinearSeq fornisce un accesso rapido solo al primo elemento tramite head, ma ha anche un'operazione veloce tail. (Tratto dalla documentazione Seq.)

Per un IndexedSeqnormalmente sceglieresti a Vector. Ranges e WrappedStrings sono anche IndexedSeqs.

Per un LinearSeq, normalmente sceglieresti un Listsuo equivalente pigro Stream. Altri esempi sono Queues e Stacks.

Quindi in termini Java, ArrayListusato in modo simile a quello di Scala Vectore in LinkedListmodo simile a quello di Scala List. Ma in Scala tenderei ad usare List più spesso di Vector, perché Scala ha un supporto molto migliore per funzioni che includono l'attraversamento della sequenza, come mappatura, piegatura, iterazione ecc. Tenderete ad usare queste funzioni per manipolare l'elenco come intero, piuttosto che accedere in modo casuale ai singoli elementi.


Ma se l'iterazione di Vector è più veloce di quella di List, e posso mappare anche fold etc, quindi a parte alcuni casi specializzati (essenzialmente tutti quegli algoritmi FP specializzati in List) sembra che List sia essenzialmente legacy.
Duncan McGregor,

@Duncan dove hai sentito che l'iterazione di Vector è più veloce? Per cominciare, è necessario tenere traccia e aggiornare l'indice corrente, che non è necessario con un elenco collegato. Non definirei le funzioni dell'elenco "casi specializzati": sono il pane e il burro della programmazione funzionale. Non usarli sarebbe come provare a programmare Java senza cicli for o while.
Luigi Plinge,

2
Sono abbastanza sicuro che Vectorl'iterazione sia più veloce, ma qualcuno deve confrontarlo per essere sicuro.
Daniel Spiewak

Penso (?) Elementi in Vector esistano fisicamente insieme su RAM in gruppi di 32, che si adattano meglio alla cache della CPU ... quindi c'è meno miss cache
richizy

2

In situazioni che comportano molto accesso casuale e mutazione casuale, un Vector(o - come dicono i documenti - a Seq) sembra essere un buon compromesso. Questo è anche ciò che suggeriscono le caratteristiche prestazionali .

Inoltre, la Vectorclasse sembra giocare bene in ambienti distribuiti senza troppa duplicazione dei dati perché non è necessario eseguire una copia su scrittura per l'oggetto completo. (Vedi: http://akka.io/docs/akka/1.1.3/scala/stm.html#persistent-datastructures )


1
Tanto da imparare ... Cosa significa Vector come Seq predefinito? Se scrivo Seq (1, 2, 3) ottengo Elenco [Int] non Vector [Int].
Duncan McGregor,

2
Se si dispone di accesso casuale, utilizzare un IndexedSeq. Che è anche Vector, ma questa è un'altra questione.
Daniel C. Sobral,

@DuncanMcGregor: Vector è l'impostazione predefinita IndexedSeqche implementa Seq. Seq(1, 2, 3)è un LinearSeqche viene implementato usando List.
pathikrit,

0

Se stai programmando immutabilmente e hai bisogno di un accesso casuale, Seq è la strada da percorrere (a meno che tu non voglia un Set, cosa che spesso fai effettivamente). Altrimenti Elenco funziona bene, tranne per il fatto che non è possibile parallelizzare le operazioni.

Se non hai bisogno di strutture di dati immutabili, mantieni ArrayBuffer poiché è l'equivalente Scala di ArrayList.


Mi attengo al regno delle collezioni immutabili e persistenti. Il mio punto è che anche se non ho bisogno di un accesso casuale, Vector ha effettivamente sostituito l'elenco?
Duncan McGregor,

2
Dipende un po 'dal caso d'uso. I vettori sono più bilanciati. L'iterazione è più veloce dell'elenco e l'accesso casuale è molto più veloce. Gli aggiornamenti sono più lenti dal momento che non è solo una lista anteposta, a meno che non sia un aggiornamento in blocco da una piega che può essere fatto con un builder. Detto questo, penso che Vector sia la migliore scelta predefinita poiché è così versatile.
Joshua Hartman,

Il fatto che penso sia al centro della mia domanda: i vettori sono così buoni che possiamo anche usarli dove gli esempi di solito mostrano Elenco.
Duncan McGregor,
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.