Perché l'aggiunta a un elenco in Scala presenta una complessità temporale O (n)?


13

Ho appena letto che il tempo di esecuzione dell'operazione append per un List(: +) cresce linearmente con la dimensione del file List.

L'aggiunta a una Listsembra un'operazione piuttosto comune. Perché il modo idiomatico per farlo è anteporre i componenti e quindi invertire l'elenco? Non può inoltre essere un errore di progettazione in quanto l'implementazione potrebbe essere modificata in qualsiasi momento.

Dal mio punto di vista, sia anteporre che aggiungere dovrebbe essere O (1).

C'è qualche motivo legittimo per questo?


2
Dipende dalla tua definizione di "legittimo". Scala è fortemente coinvolta in strutture di dati immutabili, liste anonime onnipresenti, composizione funzionale, ecc. L'implementazione dell'elenco predefinito (senza un puntatore mutabile extra alla coda dell'elenco) funziona bene per quello stile. Se hai bisogno di un elenco più potente, almeno è molto facile scrivere il tuo contenitore che è quasi indistinguibile da quelli standard.
Kilian Foth,

1
Relativo al sito incrociato - Aggiunta di un elemento alla fine di un elenco in Scala - C'è un po 'di là sulla natura di esso. E sembra che una lista in scala è immutabile, quindi è necessario copiare, il che è O (N).

È possibile utilizzare una delle molte strutture di dati mutabili disponibili o strutture di dati immutabili con O (1) tempo di aggiunta (Vector) fornito da Scala. List[T]presume che tu lo stia usando come in un linguaggio funzionale puro - in genere lavorando dalla testa con decostruzione e antepone.
KChaloux,

3
Prepending inserirà il puntatore del nodo successivo della nuova testa nell'elenco immutabile esistente, che non può cambiare. Quello è O (1).

1
Per il lavoro e l'analisi seminali sull'argomento generale delle misurazioni della complessità della struttura dei dati in FP puro si legge la tesi di Okasaki che è stata successivamente pubblicata come libro. È una lettura molto apprezzata e molto buona per chiunque impari alcuni FP per capire come pensare all'organizzazione dei dati in FP. È anche ben scritto e facile da leggere e seguire così del tutto un testo di qualità.
Jimmy Hoffa,

Risposte:


24

Espanderò un po 'il mio commento. La List[T]struttura dei dati, da scala.collection.immutableè ottimizzata per funzionare come funziona un elenco immutabile in un linguaggio di programmazione più puramente funzionale. Ha tempi di prepend molto rapidi e si presume che lavorerai sulla testa per quasi tutto il tuo accesso.

Gli elenchi immutabili ottengono tempi di prepend molto rapidi a causa del fatto che modellano i loro elenchi collegati come una serie di "celle contro". La cella definisce un singolo valore e un puntatore alla cella successiva (stile classico dell'elenco a link singolo):

Cell [Value| -> Nil]

Quando anteponi a un elenco, stai davvero creando una singola nuova cella, con il resto dell'elenco esistente indicato:

Cell [NewValue| -> [Cell[Value| -> Nil]]

Poiché l'elenco è immutabile, puoi farlo senza alcuna copia effettiva . Non c'è pericolo che la vecchia lista cambi e causi l'invalidità di tutti i valori nella tua nuova lista. Tuttavia, si perde la possibilità di disporre di un puntatore modificabile alla fine dell'elenco come compromesso.

Questo si presta molto bene a lavorare ricorsivamente su liste. Supponiamo che tu abbia definito la tua versione di filter:

def deleteIf[T](list : List[T])(f : T => Boolean): List[T] = list match {
  case Nil => Nil
  case (x::xs) => f(x) match {
    case true => deleteIf(xs)(f)
    case false => x :: deleteIf(xs)(f)
  }
}

Questa è una funzione ricorsiva che funziona esclusivamente dalla testa dell'elenco e sfrutta la corrispondenza dei pattern tramite il :: extractor. Questo è qualcosa che vedi molto in lingue come Haskell.

Se vuoi davvero aggiungere rapidamente, Scala offre molte strutture di dati mutabili e immutabili tra cui scegliere. Sul lato mutevole, potresti esaminare ListBuffer. In alternativa, Vectorda scala.collection.immutableha un tempo di aggiunta rapido.


ora capisco! Ha perfettamente senso.
DPM,

Non conosco nessun Scala, ma non è elseun ciclo infinito? Penso che dovrebbe essere qualcosa di simile x::deleteIf(xs)(f).
svick

@svick Uh ... si. Sì. L'ho scritto rapidamente e non ho verificato il mio codice, perché avevo una riunione a cui andare: p (Dovrebbe essere corretto ora!)
KChaloux

@Jubbat causa heade taill'accesso con questo tipo di lista è molto veloce - più veloce rispetto all'utilizzo di qualsiasi mappa di hash-based o array - è un tipo eccellente per funzioni ricorsive. Questo è uno dei motivi per cui gli elenchi sono un tipo di base nella maggior parte dei linguaggi funzionali (ad esempio Haskell o Scheme)
itsbruce

Risposta eccellente. Vorrei forse aggiungere un TL; DR che dice semplicemente "perché dovresti anteporre, non aggiungere" (potrebbe aiutare a chiarire le ipotesi di base che la maggior parte degli sviluppatori ha su se Listaggiungere e anteporre).
Daniel B,
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.