Stacks of Function Calls

When a recursive function is execution, the computer maintains a stack of function calls. Stacks are also used to evaluate expressions.

Stacks to Evaluate Expressions

Normally we write arithmetical expressions in the infix format: the operator is written between the operands. In the postfix format, the operator follows the operands. For example, 2 + 3 is written as 2 3 + `` in the postfix notation. Operands, like ``2 and 3 are separated by space.

The advantage of the postfix notation is that no brackets are needed to make the evaluation unambiguous. For example, 3 2 + 4 * is the same as (3 + 2) * 4 and 2 4 * 3 + is 3 + 2 * 4 in the infix notation.

This simple representation of expressions is used in stack based languages as PostScript.

In Python, we can use a list to represent a stack. We push elements on the stck with insert(0, for example:

>>> S = []
>>> S.insert(0,'2')
>>> S.insert(0,'3')
>>> S
['3', '2']
>>> S.insert(0,'+')
>>> S
['+', '3', '2']

To evaluate the expression defined in the stack, we pop from the stack:

>>> op = S.pop(0)
>>> e = S.pop(0) + op + S.pop(0)
>>> e
'3+2'
>>> eval(e)
5

Given a string which represents a postfix expression, we will use a stack to evaluate the expression. We scan the string for operands and operators:

  • If an operand, then push it to the stack.
  • If an operator, then:
    1. Pop the first operand from the stack.
    2. Pop the second operand from the stack.
    3. Compute the result of the operation.
    4. Push the result on the stack.

At the end, for a correct postfix expression we will find the value is on the top of the stack.

The function eval_postfix provides an implementation of the above algorithm.

def eval_postfix(exp):
    """
    Returns the value of the expression exp,
    given as string in postfix notation.
    An exception handler prints the stack.
    """
    opd = ''
    stk = []
    try:
        for char in exp:
            (stk, opd) = update_postfix(stk, opd, char)
        print('S =', stk)
        return stk[0]
    except:
        print('exception raised at ')
        print('c =', char, 'S =', stk)
        return 0

An example of running eval_postfix is shown below. We display the evolution of the stack, for 2 3 + 4 *, processing the string character after character:

S = []
S = ['2']
S = ['2']
S = ['3', '2']
S = ['5']
S = ['5']
S = ['5']
S = ['4', '5']
S = ['20']

We can extend the evaluation algorithm, to convert the postfix expression to its infix notation.

Consider the following prototype of a function.

def update_postfix(stk, opd, char):
    """
    Evaluates operations to numbers, via an
    update of the stack stk with a character char,
    where opd is the current operand.
    If char is an operator, then its arguments
    are popped from the stack and the result
    of the operation is pushed on the stack.
    The new stk and opd are returned as (stk, opd).
    """

Multidigit arguments are read character after character and concatenated again as strings. Also the intermediate values are stored as strings. The code for update_postfix is below.

OPERATORS = ['+', '-', '*', '/']

def update_postfix(stk, opd, char):
    """
    Evaluates operations to numbers, via an
    update of the stack stk with a character char,
    where opd is the current operand.
    If char is an operator, then its arguments
    are popped from the stack and the result
    of the operation is pushed on the stack.
    The new stk and opd are returned as (stk, opd).
    """
    if char == ' ':
        if opd != '':
            stk.insert(0, opd)
            opd = ''
        return (stk, opd)
    elif char in OPERATORS:
        exp = stk.pop(1) + char + stk.pop(0)
        value = eval(exp)
        stk.insert(0, str(value))
        return (stk, opd)
    else:
        return (stk, opd + char)

Next consider the conversion of a postfix expression in a string into another string which contains the corresponding infix expession. Consider for example the postfix expression 2 3 + 4 *. Instead of evaluating the arithmetical expresssion, the string representation of the expression is returned. For this example, the evolution of the stack is as follows.

S = []
S = ['2']
S = ['2']
S = ['3', '2']
S = ['3+2']
S = ['3+2']
S = ['3+2']
S = ['4', '3+2']
S = ['4*(3+2)']

In going from postfix to infix, we must place brackets around all expressions that are not all numerical. The function which does this is below.

def bracket(item):
    """
    Returns item with round brackets around
    it if item is not a number.
    """
    if item.isalnum():
        return item
    else:
        return '(' + item + ')'

The function to evaluate a postfix expession into a string is below.

def eval_string(stk, opd, char):
    """
    Evaluates operations to a string, via an
    update of the stack stk with a character char,
    where opd is the current operand.
    If char is an operator, then its arguments
    are popped from the stack and the result
    of the operation is pushed on the stack.
    The new stk and opd are returned as (stk, opd).
    """
    if char == ' ':
        if opd != '':
            stk.insert(0, opd)
            opd = ''
        return (stk, opd)
    elif char in OPERATORS:
        exp = bracket(stk.pop(1)) + char
        exp = exp + bracket(stk.pop(0))
        stk.insert(0, exp)
        return (stk, opd)
    else:
        return (stk, opd + char)

To prepare for the evaluation of an infix expression, consider the evolution of a stack to evaluate (4*(3+2)). Every line below shows the processing of the stack after processing one character.

S = []
S = []
S = ['*', '4']
S = ['*', '4']
S = ['*', '4']
S = ['+', '3', '*', '4']
S = ['+', '3', '*', '4']
S = ['5', '*', '4']
S = ['20']

The function update_infix() is called by eval_infix(), a function that processes expression strings character by character, accumulating multidigit operands in the string opd.

def update_infix(stk, opd, char):
    """
    Evaluates operations to numbers, via an
    update of the stack stk with a character char,
    where opd is the current operand.
    If char is a closing bracket, then two
    operands and an operator are popped
    from the stack and the result
    of the operation is pushed on the stack.
    The new stk and opd are returned as (stk, opd).
    """
    if char in OPERATORS:
        if opd != '':
            stk.insert(0, opd)
            opd = ''
        stk.insert(0, char)
        return (stk, opd)
    elif char == ')':
        if opd == '':
            opd = stk.pop(0)
        exp = stk.pop(0)
        exp = stk.pop(0) + exp + opd
        value = eval(exp)
        stk.insert(0, str(value))
        return (stk, '')
    elif char != '(':
        return (stk, opd + char)
    else:
        return (stk, opd)

Recursive Function Calls

An important application of a stack is to transform a recursive function into an interative one. Consider a recursive gcd:

def gcd(alpha, beta):
    """
    Returns greatest common divisor
    of the numbers alpha and beta.
    """
    rest = alpha % beta
    if rest == 0:
        return beta
    else:
        return gcd(beta, rest)

Our goal is to transform this function into an equivalent iterative version. The key tool in this transformation is a stack.

The script gcdstack.py prints the stack of successive arguments of the recursive gcd(). Consider for example:

$ python gcdstack.py
give a : 2146
give b : 2244
S = ['gcd(2146,2244)']
S = ['gcd(2244,2146)']
S = ['gcd(2146,98)']
S = ['gcd(98,88)']
S = ['gcd(88,10)']
S = ['gcd(10,8)']
S = ['gcd(8,2)']
gcd(2146,2244) = 2

The code for the script gcdstack.py contains the definition of the function gcdstack() which does the same calculations as the original recursive gcd() but then iteratively.

def gcdstack(alpha, beta):
    """
    Builds the stack of function calls in
    a recursive gcd for alpha and beta.
    """
    from ast import literal_eval
    stk = ['gcd(%d,%d)' % (alpha, beta)]
    while stk != []:
        print('S =', stk)
        exp = stk.pop(0)
        (alpha, beta) = literal_eval(exp[3:len(exp)])
        rest = alpha % beta
        if rest == 0:
            result = beta
        else:
            stk.insert(0, 'gcd(%d,%d)' % (beta, rest))
    return result

Another illustration is the computation of the factorial. Its recursive definition is as follows. If \(n \leq 1\), then \(n! = 1\), else \(n! = n \times (n-1)!\). THe corresponding recursive function is below.

def fac(nbr):
    """
    Returns the factorial of the number nbr.
    """
    if nbr <= 0:
        return 1
    else:
        return nbr*fac(nbr-1)

Consider the evolution of the stack in the calculation of \(5!\).

S = ['F(5)']
S = ['F(4)', 'F(5)']
S = ['F(3)', 'F(4)', 'F(5)']
S = ['F(2)', 'F(3)', 'F(4)', 'F(5)']
S = ['F(1)', 'F(2)', 'F(3)', 'F(4)', 'F(5)']
F(5) = 120

The output above is produced by the function facstack().

def facstack(nbr):
    """
    Builds the stack of function calls in
    a recursion for the factorial of nbr.
    """
    from ast import literal_eval
    stk = ['F(%d)' % nbr]
    while stk != []:
        print('S =', stk)
        exp = stk.pop(0)
        nbr = literal_eval(exp[2:len(exp)-1])
        if nbr <= 1:
            result = 1
            while stk != []:
                exp = stk.pop(0)
                nbr = literal_eval(exp[2:len(exp)-1])
                result = result * nbr
        else:
            stk.insert(0, 'F(%d)' % nbr)
            stk.insert(0, 'F(%d)' % (nbr-1))
    return result

Exercises

  1. Make a class Stack using a list as object data attribute encapsulating the list operations with the proper push and pop operations. Use this Stack in the evaluation of a postfix expression.
  2. Write Python code to store a postfix arithmetical expression in a binary tree. The data at the nodes are the operators, while the operands are at the leaves. Provide routines to write the content of the tree using prefix, infix, and postfix traversal orders.
  3. Consider a recursive definition of the Harmonic numbers \(H_n\): \(H_1 = 1\) and for \(n > 1\): \(H_{n} = H_{n-1} + 1/n\). Use a stack to write an equivalent iterative version.
  4. Make an iterative version of enumbits.py (discussed in Lecture 11), using a stack.