Questa è una domanda sorprendentemente complessa, a causa di due caratteristiche di Haskell e GHC:
- valutazione pigra
- Elenco fusione
Elenco fusione significa che in alcune situazioni, GHC può riscrivere il codice di elaborazione dell'elenco in un ciclo che non alloca le celle dell'elenco. Quindi, a seconda del contesto in cui viene utilizzato, lo stesso codice potrebbe non comportare costi aggiuntivi.
Valutazione pigra significa che se il risultato di un'operazione non viene consumato, non si paga il costo del calcolo. Così, per esempio, questo è a buon mercato, perché hai solo per costruire i primi dieci elementi della lista:
example = take 10 ([1..1000000] ++ [1000001])
Infatti, in quel codice l'take 10
possono fondersi con la lista di aggiunta, quindi è la stessa come solo [1..10]
.
Ma supponiamo solo che stiamo consumando tutti gli elementi di tutti gli elenchi che creiamo e che il compilatore non sta fondendo le operazioni delle liste. Ora alle vostre domande:
Se aggiungo un elemento a un elenco in Haskell, Haskell restituisce un (completamente?) Nuovo elenco e non manipola quello originale. Ora diciamo che ho un elenco di un milione di elementi e aggiungo un elemento alla fine. Haskell "copia" l'intera lista (1 milione di elementi) e aggiunge l'elemento a quella copia? O c'è un bel "trucco" in corso dietro le quinte per evitare di copiare l'intera lista?
Esistono trucchi per evitare di copiare l'intera lista, ma aggiungendoli alla fine li sconfiggi. La cosa da capire è che le strutture dati funzionali sono normalmente progettate in modo che le operazioni che le "modificano" sfruttino lo condivisione delle strutture per riutilizzare il più possibile la vecchia struttura. Così, per esempio, aggiungendo due liste può essere definita in questo modo:
(++) :: [a] -> [a] -> [a]
[] ++ ys = ys
(x:xs) ++ ys = x : xs ++ ys
Guardando questa definizione, si può dire che l'elenco ys
sarà riutilizzato nel risultato. Quindi, se abbiamo xs = [1..3]
, ys = [4..5]
e xs ++ ys
, tutte completamente valutato e mantenuto in memoria in una sola volta, che sarà simile a questa memoria-saggio:
+---+---+ +---+---+ +---+---+
xs = | 1 | -----> | 2 | -----> | 3 | -----> []
+---+---+ +---+---+ +---+---+
+---+---+ +---+---+
ys = | 4 | -----> | 5 | -----> []
+---+---+ +---+---+
^
|
+------------------------------------+
|
+---+---+ +---+---+ +---+---+ |
xs ++ ys = | 1 | -----> | 2 | -----> | 3 | -------+
+---+---+ +---+---+ +---+---+
Questo è il lungo modo di dire questo: se si fa xs ++ ys
, e non si fonde, e si consuma l'intera lista, quindi creerà una copia di xs
ma riutilizzerà la memoria per ys
.
Ma ora diamo un'occhiata di nuovo a questo pezzo della tua domanda:
Ora diciamo che ho una lista di un milione di elementi e aggiungo un elemento alla fine. Haskell "copia" l'intera lista (1 milione di elementi) e aggiunge l'elemento a quella copia?
Sarebbe qualcosa come [1..1000000] ++ [1000001]
e sì, copierà l'intero milione di elementi. D'altra parte, [0] ++ [1..1000000]
copierà solo lo [0]
. La regola generale è questa:
- L'aggiunta di elementi all'inizio di un elenco è la più efficiente.
- L'aggiunta di elementi alla fine di un elenco è spesso inefficace, in particolare se lo si fa ripetutamente.
Le soluzioni generali a questo tipo di problema sono:
- Modificare l'algoritmo in modo che si utilizzano gli elenchi in un modello di accesso che supportano in modo efficiente.
- Non utilizzare elenchi; utilizzare un'altra struttura di dati di sequenza che supporti in modo efficiente il tipo di accesso necessario per il problema in questione. Un'altra risposta menzionato liste differenza, ma gli altri degni di nota sono:
Io sono d'accordo a quello che dici, ma la vostra notazione O-grande non è corretto. O (500000500000) == O (1) == tempo costante (vedere http://en.wikipedia.org/wiki/Big_O_notation#Multiplication_by_a_constant). Certo, si può sostenere che se si tenta di "aggiungere un milione di elementi", viene sempre eseguito in O (1) poiché non è rimasta alcuna variabile e l'operazione "aggiungi un milione di volte" viene eseguita in tempo costante. Ma non penso che sia quello che vuoi dire. –
@ JohannesWeiß Meglio? – bheklilr
Sì, @bheklilr, grazie :) –