Stacks, Queues
Data Structures in Practice
Chapter 4 ยท Stacks, Queues
Stacks and queues are not new containers so much as rules about which end you use. A stack takes and gives from the same end. A queue takes at one end and gives from the other. Choosing the right underlying structure (Data Structures in Practice 1 and 2) is what makes those rules cheap.
Stack: Last In, First Out
The most recently added item is the first one out, like a pile of plates. A Python list is a fine stack: append pushes and pop() pops, both at the end, which Data Structures in Practice 2 showed to be cheap (amortised constant).
Queue: First In, First Out
The oldest item leaves first, like a line at a counter. A list is a poor queue because pop(0) shifts every remaining item. collections.deque is built for both ends: append and popleft are both constant cost.
Same three items in, opposite order out. Popping an empty stack or queue raises IndexError, so test for emptiness first (if stack:) or catch the error.
| Need | Good choice | Avoid |
|---|---|---|
| Stack | list (append, pop) or deque | Inserting or removing at the front of a list |
| Queue | deque (append, popleft) | list.pop(0): shifts everything |
| Queue shared between threads | queue.Queue | A bare list or deque with hand-made locking |
| Keep only the last N items | deque(maxlen=N) | Slicing a list after every append |
Stack in Action: Matching Brackets
Anything that must be closed in the reverse order it was opened is a stack problem. Push each opener; on a closer, the top of the stack must be its partner.
Three failure modes are covered: a wrong partner ("(]"), a closer with nothing open ("))", caught by not st), and openers left over at the end ("((", caught by the final not st). The same pattern underlies the call stack, expression evaluation and back-tracking.
Two Stacks: Undo and Redo
Undo history is a stack of earlier states. Redo needs a second stack that catches whatever undo removes. A new edit invalidates the redo history, so it is cleared.
In the last line the redo did nothing: typing "!" after an undo cleared the redo stack, which is how editors behave. (This stores whole snapshots for clarity; a real editor would store small changes.)
Deque: Both Ends, and a Size Limit
With maxlen, a deque silently drops from the opposite end when full. That is a ready-made "most recent N" buffer. rotate moves items around the ring, which suits round-robin turns.
The queue Module: Task Queues
queue.Queue wraps the same idea with locking, so several threads can safely put and get. It offers put, get, qsize, empty, an optional maxsize (a full queue makes put wait, or raise queue.Full with put_nowait), and queue.LifoQueue for a thread-safe stack.
The classic use is producer and worker: one side puts tasks, a worker thread gets and processes them, and a None acts as a "stop" signal.
Because there is one worker and a first-in-first-out queue, results arrive in submission order. With several workers the order of results would not be guaranteed. For a single-threaded program, a plain deque is simpler and avoids the locking overhead.
Challenges
Write rpn(expr) that evaluates a postfix expression such as "3 4 + 2 *" using a stack: push numbers, and on +, - or * pop two values (the second pop is the left operand) and push the result. Check that "3 4 + 2 *" gives 14 and "5 1 2 + 4 * + 3 -" gives 14.
Write last_n(lines, n) that returns the last n items of any iterable using deque(maxlen=n), so it never holds more than n at once. Test it on range(10) with n=3, on a list shorter than n, and with n=1. Then say what happens with list.pop(0) as a queue on a very long list and why deque avoids it.
Write hot_potato(names, k) using a deque: repeatedly rotate so the k-th person is at the front, remove them, and record the order of removal until one remains. Return the list of removed names and the survivor. For ["Ann", "Bob", "Cy", "Dee", "Eve"] with k=3, work out the answer by hand first, then check it.
Quick Reference
- Stack (LIFO):
list.appendandlist.pop() - Queue (FIFO):
deque.appendanddeque.popleft(); avoidlist.pop(0) - Empty pop raises
IndexError; check first - deque(maxlen=N): keeps the latest N, drops from the other end
- rotate(k): shifts the ring, handy for round-robin
- queue.Queue / LifoQueue: thread-safe, with
maxsize,queue.Emptyandqueue.Full - Undo and redo: two stacks; a new edit clears redo