Uma Stack<T> é uma pilha: você coloca itens no topo com Push e os tira do topo com Pop, então o último item a entrar é o primeiro a sair (LIFO). Só o topo é alcançável, e toda operação sobre ele leva tempo constante.
Push, Pop e Peek
Saída:
On top: green
Count: 3
Took green
On top: red
Took red
Took blue
Count: 0
green foi empilhado por último, então sai primeiro. Peek retorna o topo sem mudar a pilha, e é assim que você inspeciona o que o Pop devolveria antes de decidir retirá-lo.
A exceção da pilha vazia e o TryPop
Fazer pop ou peek em uma pilha vazia lança InvalidOperationException. Isso aparece com mais frequência em parsers e algoritmos que recebem uma entrada com mais itens de fechamento que de abertura.
Saída:
Caught InvalidOperationException
True 10
False 0
TryPop e TryPeek (.NET Core 2.0 em diante) retornam false em uma pilha vazia e definem a variável out com o valor padrão, 0 aqui. No .NET Framework, verifique Count > 0 antes.
Ordem de iteração: o topo primeiro
Enumerar uma pilha não remove nada, e vai do topo para baixo, na ordem em que o Pop devolveria os itens:
Saída:
checkout products home
checkout > products > home
True
home
checkout
A cópia invertida pega muita gente de surpresa: o construtor recebe qualquer IEnumerable<T> e empilha os itens em ordem, e uma pilha é enumerada a partir do topo, então o topo antigo acaba no fundo da cópia. Inverter a sequência antes (o Reverse() do LINQ devolve os itens a partir do fundo) gera uma cópia com o mesmo topo.
Empilhar uma lista de itens em uma pilha nova também os inverte, o que é um jeito rápido de inverter uma sequência: new Stack<char>("hello") devolve no pop o, l, l, e, h.
Exemplo: um histórico de desfazer
Editores guardam cada mudança em uma pilha. Desfazer tira a mudança mais recente e a reverte; refazer mantém uma segunda pilha com as mudanças desfeitas.
Saída:
Hello, world!
Hello, world
Hello
Hello, world
Guardar cópias inteiras do estado é a versão mais simples. Editores reais empilham pequenos objetos de comando (o que foi inserido e onde), cada um com um método para se reverter, mas as duas pilhas funcionam do mesmo jeito.
Exemplo: parênteses balanceados
Verificar se (, [ e { são fechados na ordem certa é o exercício clássico de pilha, e a mesma lógica está dentro de todo compilador e parser de JSON.
Saída:
"f(a[i], {x: 1})" -> True
"(]" -> False
"((a)" -> False
"a)b(" -> False
"" -> True
As três verificações de falha correspondem às três formas de os símbolos darem errado: um fechamento sem nada aberto (a)b(, pego por Count == 0 em vez de uma exceção do Pop), um fechamento do tipo errado ((]) e aberturas nunca fechadas (((a), pegas pela verificação final).
Outros usos
- Busca em profundidade. Troque a fila de uma busca em largura por uma pilha e o percurso vai fundo antes de ir para os lados. Uma pilha explícita também substitui a recursão quando a entrada é profunda o bastante para arriscar uma
StackOverflowException, que não pode ser capturada. - Avaliar expressões. A notação pós-fixa (
3 4 + 2 *) é avaliada empilhando números e desempilhando dois para cada operador. - Backtracking. Histórico de navegação, resolução de labirintos e estados de parser empilham uma posição e voltam para ela ao chegar a um beco sem saída.
Veja Queue para a contraparte em que o primeiro a entrar é o primeiro a sair.
Stack vs Queue vs List
Stack<T> | Queue<T> | List<T> | |
|---|---|---|---|
| Ordem de saída | O mais novo primeiro | O mais antigo primeiro | Qualquer, por índice |
| Adicionar | Push | Enqueue | Add, Insert |
| Remover | Pop (topo) | Dequeue (frente) | Remove, RemoveAt |
| Ver | Peek | Peek | list[i] |
| Variantes seguras | TryPop, TryPeek | TryDequeue, TryPeek | não são necessárias |
Para várias threads, ConcurrentStack<T> em System.Collections.Concurrent oferece Push, TryPop e TryPeek sem locks.
Erros comuns
- Fazer Pop sem verificar. Uma pilha vazia lança
InvalidOperationException; verifiqueCountou useTryPop. - Esperar que o
foreachcomece pelo primeiro item empilhado. Ele começa pelo topo. - Copiar com
new Stack<T>(stack). A cópia fica invertida. - Empilhar dentro de um
foreachsobre a mesma pilha. Lança exceção; use um laçowhile (stack.Count > 0).
Perguntas frequentes
O que é uma Stack em C#?
Stack<T>, em System.Collections.Generic, é uma coleção em que o último a entrar é o primeiro a sair (LIFO). Push coloca um item no topo, Pop remove e retorna o item do topo, e Peek retorna o item do topo sem removê-lo. Os três executam em tempo constante.
O que acontece ao fazer Pop em uma pilha vazia em C#?
Pop e Peek lançam InvalidOperationException quando a pilha está vazia. Verifique stack.Count > 0 antes, ou use TryPop(out var item) e TryPeek(out var item), que retornam false em vez de lançar exceção (.NET Core 2.0 em diante).
Em que ordem o foreach percorre uma Stack?
Do topo para baixo: o item empilhado mais recentemente vem primeiro, na mesma ordem em que o Pop os devolveria. ToArray() usa a mesma ordem. Uma consequência é que new Stack<T>(otherStack) produz uma cópia invertida, porque o construtor empilha os itens na ordem em que os enumera.
Qual a diferença entre uma Stack e uma Queue em C#?
Uma Stack<T> devolve primeiro o item mais novo (último a entrar, primeiro a sair), enquanto uma Queue<T> devolve primeiro o item mais antigo (primeiro a entrar, primeiro a sair). Use uma pilha para histórico de desfazer, estruturas aninhadas e busca em profundidade; use uma fila para processar trabalho na ordem de chegada e para busca em largura.
Como verificar se os parênteses estão balanceados em C#?
Percorra a string uma vez. Empilhe cada símbolo de abertura em uma Stack<char>. Para cada símbolo de fechamento, a pilha não pode estar vazia e o topo precisa ser o símbolo de abertura correspondente, que você então desempilha. A string está balanceada quando a leitura termina com a pilha vazia.