Menu

Stos Stack w C#: Push, Pop, Peek, cofanie i sprawdzanie nawiasów

Stack<T> to kolekcja typu last in, first out: element dodany najpóźniej wychodzi pierwszy. Poznaj Push, Pop i Peek, wyjątek pustego stosu i TryPop, powód, dla którego stos jest przeglądany w odwrotnej kolejności, oraz dwa klasyczne zastosowania: historię cofania i sprawdzanie zbalansowanych nawiasów.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Stack<T> to stos: kładziesz elementy na wierzch za pomocą Push i zdejmujesz je z wierzchu za pomocą Pop, więc ostatni włożony element wychodzi pierwszy (LIFO). Dostępny jest tylko wierzch, a każda operacja na nim działa w czasie stałym.

Push, Pop i Peek

Wynik:

On top: green
Count: 3
Took green
On top: red
Took red
Took blue
Count: 0

green został włożony jako ostatni, więc wychodzi pierwszy. Peek zwraca wierzch bez zmiany stosu; w ten sposób sprawdzasz, co dałby ci Pop, zanim zdecydujesz się to zdjąć.

Wyjątek pustego stosu i TryPop

Zdejmowanie z pustego stosu albo podglądanie go rzuca InvalidOperationException. Najczęściej zdarza się to w parserach i algorytmach, które dostają dane z większą liczbą elementów zamykających niż otwierających.

Wynik:

Caught InvalidOperationException
True 10
False 0

TryPop i TryPeek (.NET Core 2.0 i nowsze) zwracają false na pustym stosie i ustawiają zmienną out na wartość domyślną, tutaj 0. W .NET Framework najpierw sprawdź Count > 0.

Kolejność iteracji: najpierw wierzch

Przeglądanie stosu niczego nie usuwa i przebiega od wierzchu w dół, w kolejności, w jakiej zwracałby elementy Pop:

Wynik:

checkout products home 
checkout > products > home
True
home
checkout

Odwrócona kopia potrafi zaskoczyć: konstruktor przyjmuje dowolne IEnumerable<T> i wkłada jego elementy po kolei, a stos jest przeglądany od wierzchu, więc stary wierzch ląduje na dnie kopii. Wcześniejsze odwrócenie sekwencji (Reverse() z LINQ zwraca elementy od dna) daje kopię z tym samym wierzchem.

Włożenie listy elementów na nowy stos również je odwraca, co jest szybkim sposobem na odwrócenie sekwencji: new Stack<char>("hello") oddaje przy zdejmowaniu o, l, l, e, h.

Przykład: historia cofania

Edytory trzymają każdą zmianę na stosie. Cofnięcie zdejmuje najnowszą zmianę i ją odwraca; ponowienie korzysta z drugiego stosu cofniętych zmian.

Wynik:

Hello, world!
Hello, world
Hello
Hello, world

Zapisywanie całych migawek to najprostsza wersja. Prawdziwe edytory wkładają zamiast tego małe obiekty poleceń (co wstawiono i gdzie), z których każdy ma metodę odwracającą samego siebie, ale oba stosy działają tak samo.

Przykład: zbalansowane nawiasy

Sprawdzenie, czy (, [ i { są zamykane we właściwej kolejności, to standardowe ćwiczenie ze stosem, a ta sama logika siedzi w każdym kompilatorze i parserze JSON.

Wynik:

"f(a[i], {x: 1})" -> True
"(]" -> False
"((a)" -> False
"a)b(" -> False
"" -> True

Trzy sprawdzenia błędów odpowiadają trzem sposobom, w jakie nawiasy mogą się zepsuć: nawias zamykający bez otwartego (a)b(, wyłapany przez Count == 0 zamiast wyjątku z Pop), nawias zamykający złego rodzaju ((]) oraz nawiasy otwierające, których nigdy nie zamknięto (((a), wyłapane przez końcowe sprawdzenie).

Inne zastosowania

  • Przeszukiwanie w głąb. Zamień kolejkę w przeszukiwaniu wszerz na stos, a przejście najpierw pójdzie w głąb, a dopiero potem wszerz. Jawny stos zastępuje też rekurencję, gdy dane są na tyle głębokie, że grożą StackOverflowException, którego nie da się przechwycić.
  • Obliczanie wyrażeń. Notację postfiksową (3 4 + 2 *) oblicza się, wkładając liczby na stos i zdejmując dwie przy każdym operatorze.
  • Nawroty (backtracking). Historia nawigacji, rozwiązywanie labiryntów i stany parsera wkładają pozycję na stos i wracają do niej w ślepym zaułku.

Odpowiednik typu first in, first out opisuje strona o Queue.

Stack a Queue a List

Stack<T>Queue<T>List<T>
Kolejność wyjściaNajnowszy najpierwNajstarszy najpierwDowolna, po indeksie
DodawaniePushEnqueueAdd, Insert
UsuwaniePop (wierzch)Dequeue (przód)Remove, RemoveAt
PodglądPeekPeeklist[i]
Bezpieczne wariantyTryPop, TryPeekTryDequeue, TryPeekniepotrzebne

Dla wielu wątków ConcurrentStack<T> z System.Collections.Concurrent oferuje Push, TryPop i TryPeek bez blokad.

Typowe błędy

  • Zdejmowanie bez sprawdzenia. Pusty stos rzuca InvalidOperationException; sprawdź Count albo użyj TryPop.
  • Oczekiwanie, że foreach zacznie od pierwszego włożonego elementu. Zaczyna od wierzchu.
  • Kopiowanie przez new Stack<T>(stack). Kopia jest odwrócona.
  • Wkładanie elementów wewnątrz foreach po tym samym stosie. Rzuca wyjątek; użyj pętli while (stack.Count > 0).

Najczęściej zadawane pytania

Czym jest Stack w C#?

Stack<T> z System.Collections.Generic to kolekcja typu last in, first out (LIFO). Push kładzie element na wierzch, Pop usuwa i zwraca element z wierzchu, a Peek zwraca element z wierzchu bez usuwania go. Wszystkie trzy działają w czasie stałym.

Co się dzieje po wywołaniu Pop na pustym stosie w C#?

Pop i Peek rzucają InvalidOperationException, gdy stos jest pusty. Najpierw sprawdź stack.Count > 0 albo użyj TryPop(out var item) i TryPeek(out var item), które zamiast rzucać wyjątek zwracają false (.NET Core 2.0 i nowsze).

W jakiej kolejności foreach przechodzi przez Stack?

Od wierzchu w dół: najpierw element dodany najpóźniej, w tej samej kolejności, w jakiej zwracałby je Pop. ToArray() używa tej samej kolejności. W konsekwencji new Stack<T>(otherStack) tworzy odwróconą kopię, bo konstruktor wkłada elementy w kolejności, w jakiej je przegląda.

Jaka jest różnica między Stack a Queue w C#?

Stack<T> zwraca najpierw najnowszy element (last in, first out), a Queue<T> najpierw najstarszy (first in, first out). Stosu używaj do historii cofania, zagnieżdżonych struktur i przeszukiwania w głąb; kolejki do przetwarzania pracy w kolejności przybycia i przeszukiwania wszerz.

Jak sprawdzić zbalansowanie nawiasów w C#?

Przejdź string jeden raz. Każdy nawias otwierający włóż na Stack<char>. Przy każdym nawiasie zamykającym stos musi być niepusty, a na wierzchu musi leżeć pasujący nawias otwierający, który wtedy zdejmujesz. String jest zbalansowany, gdy po przejściu stos jest pusty.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ