Stacks

A stack is a pile. You put things on the top and take things off the top, so the last thing in is the first thing out. Undo in an editor is a stack: each change goes on top, and undo takes the most recent one off.

Python has no separate stack type because a list already is one. append puts a value on top, pop() takes the top value off and returns it.

history = []
history.append("add bread")
history.append("add milk")
history.append("remove bread")
print(history)

last = history.pop()
print("undo:", last)
print(history)

Both operations work at the end of the list, which is fast however long the list gets. That end is "the top".

Looking without taking

history[-1] shows the top without removing it. Before popping or peeking, check the stack is not empty: pop on an empty list is an error.

history = ["add bread"]
print(history[-1])
print(len(history) > 0)

history.pop()
print(len(history) == 0)

An empty list counts as False in an if, so if history: means "if the stack has anything on it" and while history: keeps going until it is empty.

Reversing with a stack

Push everything on, pop everything off, and it comes out backwards. This is the simplest thing a stack does, and the shape of every stack loop: push in one loop, pop in another.

word = "bread"
stack = []
for ch in word:
    stack.append(ch)

reversed_word = ""
while stack:
    reversed_word = reversed_word + stack.pop()
print(reversed_word)

Matching brackets

The classic stack problem. Reading ( ( ) ) left to right, every ( is an open question that the next ) answers, and the most recent open question must be answered first. Push on (, pop on ), and the text is balanced if nothing is left at the end and you never tried to pop from empty.

text = "(()())"
stack = []
ok = True
for ch in text:
    if ch == "(":
        stack.append(ch)
    elif ch == ")":
        if not stack:
            ok = False
            break
        stack.pop()
print(ok and len(stack) == 0)

Try "(()" and "())" to see both ways it can fail.

Try it

Undo the last change

history is a list of changes, most recent last. Write undo(history) that removes the most recent change and returns it. If there is nothing to undo, return None and leave the list alone.

undo(["add bread", "add milk"])  ->  "add milk"    (history is now ["add bread"])
undo([])                         ->  None
def undo(history):
    ...

Reverse with a stack

Write reverse_items(items) that returns a new list with the items in reverse order, by pushing every item onto a stack and then popping them all off. Leave items unchanged. Slicing or reverse would also work; the point here is to practise the stack.

reverse_items(["apple", "bread", "milk"])  ->  ["milk", "bread", "apple"]
reverse_items([])                          ->  []
def reverse_items(items):
    stack = []
    for item in items:
        ...
    result = []
    while stack:
        ...
    return result

Balanced parentheses

Write balanced(text) that returns True if every ( in text is closed by a later ) in the right order, and False otherwise. Only round brackets count; every other character, including [ and {, is ignored.

balanced("(a + b) * (c)")  ->  True
balanced("(()")            ->  False
balanced("())(")           ->  False
balanced("")               ->  True
def balanced(text):
    stack = []
    for ch in text:
        ...
    return ...

Next: Queues.