Uno Stack<T> è una pila: metti gli elementi in cima con Push e li togli dalla cima con Pop, quindi l'ultimo elemento entrato è il primo a uscire (LIFO). Si può raggiungere solo la cima, e ogni operazione su di essa richiede tempo costante.
Push, Pop e Peek
Output:
On top: green
Count: 3
Took green
On top: red
Took red
Took blue
Count: 0
green è stato inserito per ultimo, quindi esce per primo. Peek restituisce la cima senza modificare la pila, ed è così che controlli cosa ti darebbe Pop prima di decidere di prenderlo.
L'eccezione della pila vuota e TryPop
Estrarre o guardare la cima di una pila vuota lancia InvalidOperationException. Succede soprattutto nei parser e negli algoritmi che ricevono un input con più elementi di chiusura che di apertura.
Output:
Caught InvalidOperationException
True 10
False 0
TryPop e TryPeek (.NET Core 2.0 e successivi) restituiscono false su una pila vuota e impostano la variabile out al valore predefinito, qui 0. Su .NET Framework, controlla prima Count > 0.
Ordine di iterazione: prima la cima
Enumerare una pila non rimuove nulla, e procede dalla cima verso il basso, nell'ordine in cui Pop restituirebbe gli elementi:
Output:
checkout products home
checkout > products > home
True
home
checkout
La copia invertita coglie molti di sorpresa: il costruttore accetta qualsiasi IEnumerable<T> e ne inserisce gli elementi in ordine, e una pila si enumera partendo dalla cima, quindi la vecchia cima finisce in fondo alla copia. Invertire prima la sequenza (il Reverse() di LINQ restituisce gli elementi partendo dal fondo) dà una copia con la stessa cima.
Anche inserire una lista di elementi in una nuova pila li inverte, il che è un modo rapido per invertire una sequenza: new Stack<char>("hello") restituisce con Pop o, l, l, e, h.
Esempio: una cronologia di annullamento
Gli editor tengono ogni modifica in una pila. Annulla estrae la modifica più recente e la ripristina; ripeti usa una seconda pila con le modifiche annullate.
Output:
Hello, world!
Hello, world
Hello
Hello, world
Memorizzare istantanee complete è la versione più semplice. Gli editor veri inseriscono invece piccoli oggetti comando (cosa è stato inserito, e dove), ognuno con un metodo per annullare se stesso, ma le due pile funzionano allo stesso modo.
Esempio: parentesi bilanciate
Verificare che (, [ e { vengano chiuse nell'ordine giusto è l'esercizio standard sulle pile, e la stessa logica si trova dentro ogni compilatore e parser JSON.
Output:
"f(a[i], {x: 1})" -> True
"(]" -> False
"((a)" -> False
"a)b(" -> False
"" -> True
I tre controlli di fallimento corrispondono ai tre modi in cui le parentesi possono essere sbagliate: una chiusura senza nulla di aperto (a)b(, intercettata da Count == 0 invece che da un'eccezione di Pop), una chiusura del tipo sbagliato ((]) e aperture mai chiuse (((a), intercettate dal controllo finale).
Altri usi
- Ricerca in profondità. Sostituisci la coda di una ricerca in ampiezza con una pila e la visita va in profondità prima che in larghezza. Una pila esplicita sostituisce anche la ricorsione quando l'input è abbastanza profondo da rischiare una
StackOverflowException, che non si può catturare. - Valutare espressioni. La notazione postfissa (
3 4 + 2 *) si valuta inserendo i numeri ed estraendone due per ogni operatore. - Backtracking. La cronologia di navigazione, la risoluzione di labirinti e gli stati di un parser inseriscono una posizione e tornano indietro a essa quando arrivano a un vicolo cieco.
Vedi Queue per la controparte first in, first out.
Stack, Queue e List a confronto
Stack<T> | Queue<T> | List<T> | |
|---|---|---|---|
| Ordine di uscita | Prima il più recente | Prima il più vecchio | Qualsiasi, per indice |
| Aggiungere | Push | Enqueue | Add, Insert |
| Rimuovere | Pop (cima) | Dequeue (testa) | Remove, RemoveAt |
| Guardare | Peek | Peek | list[i] |
| Varianti sicure | TryPop, TryPeek | TryDequeue, TryPeek | non necessarie |
Per più thread, ConcurrentStack<T> di System.Collections.Concurrent offre Push, TryPop e TryPeek senza lock.
Errori comuni
- Estrarre senza controllare. Una pila vuota lancia
InvalidOperationException; controllaCounto usaTryPop. - Aspettarsi che
foreachparta dal primo elemento inserito. Parte dalla cima. - Copiare con
new Stack<T>(stack). La copia è invertita. - Inserire dentro un
foreachsulla stessa pila. Lancia un'eccezione; usa un ciclowhile (stack.Count > 0).
Domande frequenti
Cos'è uno Stack in C#?
Stack<T> in System.Collections.Generic è una collezione last in, first out (LIFO). Push mette un elemento in cima, Pop rimuove e restituisce l'elemento in cima, e Peek restituisce l'elemento in cima senza rimuoverlo. Tutte e tre vengono eseguite in tempo costante.
Cosa succede se fai Pop su uno stack vuoto in C#?
Pop e Peek lanciano InvalidOperationException quando lo stack è vuoto. Controlla prima stack.Count > 0, oppure usa TryPop(out var item) e TryPeek(out var item), che restituiscono false invece di lanciare un'eccezione (.NET Core 2.0 e successivi).
In che ordine foreach percorre uno Stack?
Dalla cima verso il basso: per primo viene l'elemento inserito più di recente, nello stesso ordine in cui li restituirebbe Pop. ToArray() usa lo stesso ordine. Una conseguenza è che new Stack<T>(otherStack) produce una copia invertita, perché il costruttore inserisce gli elementi nell'ordine in cui li enumera.
Che differenza c'è tra uno Stack e una Queue in C#?
Uno Stack<T> restituisce per primo l'elemento più recente (last in, first out), mentre una Queue<T> restituisce per primo quello più vecchio (first in, first out). Usa uno stack per la cronologia di annullamento, le strutture annidate e la ricerca in profondità; usa una coda per elaborare il lavoro in ordine di arrivo e per la ricerca in ampiezza.
Come controllo se le parentesi sono bilanciate in C#?
Scorri la stringa una volta. Inserisci ogni parentesi di apertura in uno Stack<char>. Per ogni parentesi di chiusura, lo stack non deve essere vuoto e la sua cima deve essere la parentesi di apertura corrispondente, che a quel punto estrai. La stringa è bilanciata quando la scansione termina con lo stack vuoto.