Menu

Stack em C#: Push, Pop, Peek, desfazer e balanceamento de parênteses

Stack<T> é uma coleção em que o último a entrar é o primeiro a sair: o item adicionado mais recentemente sai primeiro. Veja Push, Pop e Peek, a exceção da pilha vazia e o TryPop, por que uma pilha é enumerada ao contrário e dois usos clássicos: um histórico de desfazer e a verificação de parênteses balanceados.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

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ídaO mais novo primeiroO mais antigo primeiroQualquer, por índice
AdicionarPushEnqueueAdd, Insert
RemoverPop (topo)Dequeue (frente)Remove, RemoveAt
VerPeekPeeklist[i]
Variantes segurasTryPop, TryPeekTryDequeue, TryPeeknã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; verifique Count ou use TryPop.
  • Esperar que o foreach comece pelo primeiro item empilhado. Ele começa pelo topo.
  • Copiar com new Stack<T>(stack). A cópia fica invertida.
  • Empilhar dentro de um foreach sobre a mesma pilha. Lança exceção; use um laço while (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.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR