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 :math:`n \leq 1`, then :math:`n! = 1`, else :math:`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 :math:`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 :math:`H_n`: :math:`H_1 = 1` and for :math:`n > 1`: :math:`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.