Menu

C# Stack: מחסנית עם Push, Pop, Peek, ביטול פעולות ואיזון סוגריים

Stack<T> הוא אוסף מסוג "אחרון נכנס, ראשון יוצא": האיבר שנוסף אחרון יוצא ראשון. למדו את Push, Pop ו-Peek, את החריגה של מחסנית ריקה ואת TryPop, למה מחסנית עוברת על האיברים בסדר הפוך, ושני שימושים קלאסיים: היסטוריית ביטול ובדיקת איזון סוגריים.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

Stack<T> הוא ערימה: שמים איברים בראש עם Push ולוקחים אותם מהראש עם Pop, כך שהאיבר האחרון שנכנס הוא הראשון שיוצא (LIFO). רק הראש נגיש, וכל פעולה עליו לוקחת זמן קבוע.

Push, Pop ו-Peek

פלט:

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

green נדחף אחרון, ולכן הוא יוצא ראשון. Peek מחזיר את הראש בלי לשנות את המחסנית, וכך בודקים מה Pop ייתן לפני שמחליטים לקחת אותו.

החריגה של מחסנית ריקה ו-TryPop

Pop או Peek על מחסנית ריקה זורקים InvalidOperationException. זה קורה הכי הרבה ב-parsers ובאלגוריתמים שמקבלים קלט עם יותר פריטים סוגרים מפותחים.

פלט:

Caught InvalidOperationException
True 10
False 0

TryPop ו-TryPeek (.NET Core 2.0 ואילך) מחזירים false על מחסנית ריקה ומציבים במשתנה ה-out את ערך ברירת המחדל, כאן 0. ב-.NET Framework, בדקו קודם Count > 0.

סדר המעבר: הראש קודם

מעבר על מחסנית לא מסיר כלום, והוא הולך מלמעלה למטה, בסדר ש-Pop היה מחזיר את האיברים:

פלט:

checkout products home 
checkout > products > home
True
home
checkout

העותק ההפוך מפיל אנשים בפח: הבנאי מקבל כל IEnumerable<T> ודוחף את האיברים שלו לפי הסדר, ומחסנית עוברת על האיברים מהראש, כך שהראש הישן מגיע לתחתית העותק. היפוך הרצף קודם (Reverse() של LINQ מחזיר את האיברים מהתחתית) נותן עותק עם אותו ראש.

גם דחיפה של רשימת איברים למחסנית חדשה הופכת אותם, וזו דרך מהירה להפוך רצף: new Stack<char>("hello") מוציא בחזרה o, l, l, e, h.

דוגמה: היסטוריית ביטול

עורכי טקסט שומרים כל שינוי במחסנית. ביטול (undo) מוציא את השינוי האחרון ומחזיר אותו לאחור; ביצוע מחדש (redo) שומר מחסנית שנייה של שינויים שבוטלו.

פלט:

Hello, world!
Hello, world
Hello
Hello, world

שמירה של תמונות מצב שלמות היא הגרסה הפשוטה ביותר. עורכים אמיתיים דוחפים במקום זאת אובייקטי פקודה קטנים (מה הוכנס, ואיפה), שלכל אחד מהם יש מתודה לבטל את עצמו, אבל שתי המחסניות עובדות באותה צורה.

דוגמה: איזון סוגריים

בדיקה ש-(, [ ו-{ נסגרים בסדר הנכון היא התרגיל הקלאסי על מחסניות, ואותה לוגיקה יושבת בתוך כל קומפיילר ו-parser של JSON.

פלט:

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

שלוש בדיקות הכישלון מתאימות לשלוש הדרכים שבהן סוגריים משתבשים: סוגר בלי שום דבר פתוח (a)b(, שנתפס על ידי Count == 0 במקום חריגה מ-Pop), סוגר מהסוג הלא נכון ((]), ופותחים שאף פעם לא נסגרו (((a), שנתפסים בבדיקה הסופית).

שימושים נוספים

  • חיפוש לעומק. החליפו את התור בחיפוש לרוחב במחסנית והמעבר ילך לעומק לפני שהוא הולך לרוחב. מחסנית מפורשת גם מחליפה רקורסיה כשהקלט עמוק מספיק כדי להסתכן ב-StackOverflowException, שאי אפשר לתפוס.
  • חישוב ביטויים. כתיב postfix (3 4 + 2 *) מחושב על ידי דחיפת מספרים והוצאת שניים לכל אופרטור.
  • חזרה לאחור (backtracking). היסטוריית ניווט, פתרון מבוכים ומצבי parser דוחפים מיקום וחוזרים אליו במבוי סתום.

ראו את Queue למקבילה מסוג "ראשון נכנס, ראשון יוצא".

Stack מול Queue מול List

Stack<T>Queue<T>List<T>
סדר היציאההחדש ביותר קודםהוותיק ביותר קודםכל סדר, לפי אינדקס
הוספהPushEnqueueAdd, Insert
הסרהPop (ראש)Dequeue (חזית)Remove, RemoveAt
הצצהPeekPeeklist[i]
גרסאות בטוחותTryPop, TryPeekTryDequeue, TryPeekלא נחוצות

לכמה תהליכונים, ConcurrentStack<T> ב-System.Collections.Concurrent מציע Push, TryPop ו-TryPeek בלי נעילות.

טעויות נפוצות

  • הוצאה בלי בדיקה. מחסנית ריקה זורקת InvalidOperationException; בדקו את Count או השתמשו ב-TryPop.
  • ציפייה ש-foreach יתחיל מהאיבר הראשון שנדחף. הוא מתחיל מהראש.
  • העתקה עם new Stack<T>(stack). העותק הפוך.
  • דחיפה בתוך foreach על אותה מחסנית. זורק חריגה; השתמשו בלולאת while (stack.Count > 0).

שאלות נפוצות

מה זה Stack ב-C#?

Stack<T> ב-System.Collections.Generic הוא אוסף מסוג "אחרון נכנס, ראשון יוצא" (LIFO). Push שם איבר בראש, Pop מסיר ומחזיר את האיבר שבראש, ו-Peek מחזיר את האיבר שבראש בלי להסיר אותו. שלושתם רצים בזמן קבוע.

מה קורה כשמבצעים Pop על מחסנית ריקה ב-C#?

Pop ו-Peek זורקים InvalidOperationException כשהמחסנית ריקה. בדקו קודם stack.Count > 0, או השתמשו ב-TryPop(out var item) וב-TryPeek(out var item), שמחזירים false במקום לזרוק חריגה (.NET Core 2.0 ואילך).

באיזה סדר foreach עובר על Stack?

מלמעלה למטה: האיבר שנדחף אחרון בא ראשון, באותו סדר ש-Pop היה מחזיר אותם. ToArray() משתמש באותו סדר. אחת התוצאות היא ש-new Stack<T>(otherStack) יוצר עותק הפוך, כי הבנאי דוחף את האיברים בסדר שבו הוא עובר עליהם.

מה ההבדל בין Stack ל-Queue ב-C#?

Stack<T> מחזיר קודם את האיבר החדש ביותר (אחרון נכנס, ראשון יוצא), ואילו Queue<T> מחזיר קודם את הוותיק ביותר (ראשון נכנס, ראשון יוצא). השתמשו במחסנית להיסטוריית ביטול, למבנים מקוננים ולחיפוש לעומק; השתמשו בתור לעיבוד עבודה לפי סדר ההגעה ולחיפוש לרוחב.

איך בודקים איזון סוגריים ב-C#?

סרקו את המחרוזת פעם אחת. דחפו כל סוגר פותח ל-Stack<char>. בכל סוגר סוגר, המחסנית חייבת להיות לא ריקה והאיבר שבראשה חייב להיות הסוגר הפותח המתאים, ואז מוציאים אותו. המחרוזת מאוזנת אם הסריקה מסתיימת כשהמחסנית ריקה.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל