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ścia | Najnowszy najpierw | Najstarszy najpierw | Dowolna, po indeksie |
| Dodawanie | Push | Enqueue | Add, Insert |
| Usuwanie | Pop (wierzch) | Dequeue (przód) | Remove, RemoveAt |
| Podgląd | Peek | Peek | list[i] |
| Bezpieczne warianty | TryPop, TryPeek | TryDequeue, TryPeek | niepotrzebne |
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źCountalbo użyjTryPop. - Oczekiwanie, że
foreachzacznie 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
foreachpo tym samym stosie. Rzuca wyjątek; użyj pętliwhile (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.