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:
- Pop the first operand from the stack.
- Pop the second operand from the stack.
- Compute the result of the operation.
- 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¶
- Make a class
Stack
using a list as object data attribute encapsulating the list operations with the properpush
andpop
operations. Use thisStack
in the evaluation of a postfix expression. - 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.
- 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.
- Make an iterative version of
enumbits.py
(discussed in Lecture 11), using a stack.