liveBook · Manning

Stack

As explained in Fabulous Adventures in Data Structures and Algorithms

A stack is a last-in, first-out data structure supporting insertion and removal at one end.

A stack is a last-in, first-out collection: push work onto one end, then pop the most recently added item. That makes it useful when an algorithm must finish newly discovered subproblems before returning to older ones. An explicit work stack can replace recursive calls when deeply nested input might exhaust a thread’s fixed call stack. section 9.3.3

Consider checking whether a pretty-printing document fits in the space left on a line. Start a Stack<Doc> with the document. Pop one doc at a time: add text length to the used width, or push a child that still needs checking. A concatenation requires its left child before its right; because the stack is last-in, first-out, push the right child first. Return false once used exceeds the available width, and true when no work remains. section 9.3.3

Listing 9.3 A nonrecursive Fits method
abstract record class Doc
{
    // […]
    private bool Fits(int width)
    {
        var docs = new Stack<Doc>(); #1
        docs.Push(this); #2
        int used = 0; #3
        while (true) 
        {
            if (used > width) #4
                return false;
            if (docs.Count == 0) #5
                return true;
            Doc doc = docs.Pop();
            switch (doc)
            {
                case TextDoc td:
                    used += td.text.Length;
                    break;
                case GroupDoc gd:
                    docs.Push(gd.doc);
                    break;
                case IndentDoc id:
                    docs.Push(id.doc); #6
                    break;
                case OptBreakDoc bd:
                    used += bd.text.Length; #7
                    break;
                case ConcatDoc cd:
                    docs.Push(cd.right); #8
                    docs.Push(cd.left);
                    break;
            }
        }
    }
    // […]

The Fits listing makes the replacement for recursion visible: a loop repeatedly pops a Doc, accounts for its width, and pushes unfinished child work. section 9.3.3

The same idea handles state that a recursive call might otherwise carry implicitly. Pretty uses a stack of (bool, int, Doc) tuples. Each item records whether optional breaks print as text, the indentation to use after a line break, and the doc to print. Group decisions can then change the state attached to subsequent work without relying on nested calls. section 9.3.4

Listing 9.4 A nonrecursive Pretty method
using System.Text;
abstract record class Doc
{
    // […]
    public string Pretty(int width)
    {
        var sb = new StringBuilder();
        int used = 0; #1
        var docs = new Stack<(bool, int, Doc)>(); #2
        docs.Push((true, 0, new GroupDoc(this))); #3
        while (docs.Count > 0) #4
        {
            (bool fits, int indent, Doc doc) = docs.Pop(); #5
            switch (doc)
            {
                case TextDoc td:
                    sb.Append(td.text); #6
                    used += td.text.Length;
                    break;
                case GroupDoc gd: #7
                    docs.Push((gd.doc.Fits(width - used), indent, gd.doc)); 
                    break;
                case IndentDoc id: #8
                    docs.Push((fits, indent + IndentSpaces, id.doc));
                    break;
                case OptBreakDoc bd:
                    if (fits) #9
                    {
                        sb.Append(bd.text);
                        used += bd.text.Length;
                    }
                    else #10
                    {
                        sb.AppendLine();
                        sb.Append(' ', indent);
                        used = indent;
                    }
                    break;
                case ConcatDoc cd: #11
                    docs.Push((fits, indent, cd.right));
                    docs.Push((fits, indent, cd.left));
                    break;

            }
        }
        return sb.ToString();
    } }

The Pretty listing shows why the stack holds tuples rather than docs alone: each pending task carries its line-break decision and indentation. section 9.3.4

The trade-off is not that the work disappears; it moves from recursive calls to an explicit, growable data structure. That matters for document graphs that can be much deeper than their parse trees. The stack keeps traversal order and pending state under the algorithm’s control while avoiding deep call-stack use. section 9.3.3 section 9.5

How Fabulous Adventures in Data Structures and Algorithms covers it

How deep it goes
Works through two stack-based traversals, including loop termination, width checks, and per-task state; does not implement a stack collection.
Examples
Uses C# Fits and Pretty methods to show stacks of documents and tuples carrying formatting state.
What it assumes
Assumes familiarity with C#-style code and tree-shaped data while explaining how an explicit stack tracks pending work.
What you can do after
Build it from scratch as a nonrecursive document traversal using an existing Stack collection.
How it connects
Connects stacks to recursion, call-stack overflow, greedy pretty printing, document graphs, and indentation.

Also explained in

Compare how 14 books explain stack →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Fabulous Adventures in Data Structures and Algorithms. The section links point to the parts of the book it draws on.