Introduction to Computation and
Programming Using Python
Spring 2018 Edition
John V. GuttagIntroduction to
Computation and
Programming Using Python
(Spring 2013 Edition)
John V. Guttag
The MIT Press.
Cambridge, Massachusetts
London, England© 2013 Massachusetts Institute of Technology
Allrights reserved. No part of this book may be reproduced in any form by any
electronic or mechanical means (including photocopying, recording, or information
storage and retrieval) without permission in writing from the publisher.
MIT Press books may be purchased at special quantity discounts for business or
sales promotional use. For information, please email
special
[email protected] or write to Special Sales Department, The MIT
Press, 55 Hayward Street, Cambridge, MA 02142.
Printed and bound in the United States of America,
ISBN: 978-0-262-51963-2
Library of Congress Control Number: 2012953
0987654321
Cover photograph: Antelope Canyon, courtesy of Frédo Durand.To my family:
Olga
David
Andrea
Michael
Mark
AddieCONTENTS
PREFACE,
SPRING 2013 EDITION
ACKNOWLEDGMENTS,
1 GETTING STARTED
2 INTRODUCTION TO PYTHON
2.1 The Basic Element of Python,
2.1.1 Objects, expressions, and numer
al types
2.1.2 Variables and assignment
2.13 IDLE
2.2 Branching Programs
2.3 Type Strand Input.
2.3.1 Input.
2.4 Iteration,
3 SOME SIMPLE NUMERICAL PROGRAMS
3.1 Exhaustive enumeration
3.2. For Loops.
3.3 Approximate Solutions and Bisection Search
3.4 A Few Words About Using Floats
3.5 Newton-Raphson
4 FUNCTIONS AND ABSTRACTION BY SPECIFICATION.
4.1. Functions and Scoping
4.1.1 Punetion definitions
4.1.2 Keyword Arguments and Default Values
4.1.3 Scoping
4.2 Specifications
4.3. Recursion
4.3.1. Fibonacci numbers.
4.3.2. Palindromes and divide-and-conquer,
44 Global Variables
4.5. Modules and Import
46 Files
xi
oa 45 STRUCTURED TYPES, MUTABILITY, AND HIGHER-ORDER FUNCTIONS,
5.1 Tuples
5.1.1 Sequences and Multiple Assignment,
5.2 _ Lists and Mutability.
5.2.1 Cloning
5.2.2. List comprehension
5.3. Functions as Objects
5.4 Strings, tuples, and lists
5.5. Dictionaries
6 TESTING AND DEBUGGING.
6.1 Testing.
6.1.1 Black-box testing
6.1.2 Glass-box testing,
6.1.3 Conducting Tests
6.2 Debugging
6.2.1 Learning to Debug
6.2.2 Designing the Experiment
6.2.3 When the Going Gets Tough
6.2.4 And When You Have Found “The” Bug.
7 EXCEPTIONS AND ASSERTIONS
7.1 Handling Exceptions.
7.2. Exceptions as a Control Flow Mechanism
7.3. Assertions.
& CLASSES AND OBJECT-ORIENTED PROGRAMMING
8.1 Abstract Data Types and Classes
8.1.1 Designing Programs Using Abstract Data Types.
8.1.2 Using Classes to Keep Track of Students and Faculty,
8.2. Inheritance
8.2.1 Multiple Levels of Inheritance.
8.2.2 The Substitution Principle
8.3 Encapsulation and Information Hiding,
8.3.1 User-Defined Iterators
8.4 Mortgages, an Extended Example
85.
35
56
37
62
62
63
oa
66
70
70
nm
73
7”
76
78
79
al
82
83
aa
86
88
90
90
94
94
96
90
100
101
104
1069. A SIMPLISTIC INTRODUCTION TO ALGORITHMIC COMPLEXITY
9.1. Thinking About Computational Complexity
9.2 Asymptotic Notation
9.3 Some Important Complexity Classes.
9.3.1 Constant complexity.
9.3.2 Logarithmic complexity
9.3.3 Linear Complexit
9.3.4 Log-linear Complexity.
9.3.5 Polynomial Complexity
9.3.6 Exponential Complexity,
9.3.7 Comparisons of Complexity Classes,
10 SOME SIMPLE ALGORITHMS AND DATA STRUCTURES
10.1. Search Algorithms
10.1.1 Linear Search and Using indirection to Access Elements
10.1.2 Binary Search and Exploiting Assumptions
10.2. Sorting Algorithms
10.2.1 Merge Sort.
10.2.2 Exploiting Functions as Parameters,
10.2.8 Sorting in Python
10.3 Hash Tables.
11 PLOTTING AND MORE ABOUT CLASSES
11.1 Plotting Using PyLab
11.2 Plotting Mortgages, an Bxtended Example.
12 STOCHASTIC PROGRAMS, PROBABILITY, AND STATISTICS.
12.1 Stochastic Programs
12.2 Inferential Statistics and Simulation
12.3 Distributions
12.3.1 Normal Distributions and Confidence Levels:
12.3.2 Uniform Distributions
12.3.8 Exponential and Geometric Distributions
12.3.4 Renford’s Distribution
124 How Often Does the Better Team Win?,
12.5 Hashing and Collisions13 RANDOM WALKS AND MORE ABOUT DATA VISUALIZATION
13.1
13.2
13.3
‘The Drunkard’s Walk
Biased Random Walks
Treacherous Fields.
14 MONTE CARLO SIMULATION.
wa
142
43
144
145
Paseal's Problem
Pass or Don't Pass?
Using Table Lookup to Improve Performance
Finding x
Some Closing Remarks About Simulation Models,
15 UNDERSTANDING EXPERIMENTAL DATA
15.1
‘The Behavior of Springs
15.1.1 Using Linear Regression to Pind a Pit
15.2
‘The Behavior of Projectiles
19.2.1 Coefficient of Determination
15.2.2 Using a Compuational Model
183
154
15.5
Not All Close Fits are Good Fits.
Fitting Exponential Distributions:
When Theory is Missing.
16 LIES, DAMNED LIES, AND STATISTICS
161
16.2
16.3
16.4
16.5
16.6
16.7
16.8
16.9
Garbage In Garbage Out (GIGO).
Pictures Can Be Deceiving
Cum Hoc Ergo Propter Hoc
Statistical Measures Don't Tell the Whole Story.
Sampling Bias.
Context Matters
Beware of Extrapolation
The Texas Sharpshooter Fallacy
Percentages Can Confuse.
16.10 Just Beware,
17 KNAPSACK AND GRAPH OPTIMIZATION PROBLEMS,
17a
Knapsack Problems
17.1.1 Greedy Algorithms
177
177
183
189
191
102
193
196
197
201
204
204
207
au
212
213
214
219
2a1
222
222
228
224
225
227
208
208
220
281
282
233
288
23417.1.2. Exponential Solution to the 0/1 Knapsack Problem.
17.2. Graph Optimization Problems
17.2.1 Some Classic Graph Theoretic Problems.
17.2.2 The Spread of Disease and Min Cut
17.2.8. Shortest Path, Depth-first search, and Breadth-first Search
18 DYNAMIC PROGRAMMING
18.1 Fibonacci Sequences, Revisited
18.2 Dynamic Programming and the 0/1 Knapsack Problem,
18.3 Dynamic Programming and Divide-and-Conquer
PYTHON 2.7 QUICK REFERENCEPREFACE
‘The book is based on an MIT course that has been offered twice a year since
2006. The course is aimed at students with little or no prior programming
experience, but who have a need (or at least a desire) to understand
computational approaches to problem solving. Bach year, a few of the students
in the class use the course as a stepping stone to more advanced computer
science courses. But for most of the students it will be their only computer
Because the course will be the only computer science course for most of the
students, we focus on breadth rather than depth. The goal is to provide
students with a brief introduction to many topies, so that they will have an idea
of what's possible when the time comes to think about how to use computation
to accomplish a goal. That said, it is not a “computation appreciation” course.
It is @ challenging and rigorous course in which the students spend @ lot of time
and effort Iearning to bend the computer to their will,
‘The main goal of this book is to help you, the reader, become skillful at making
productive use of computational techniques. You should leam to apply
computational modes of thoughts to frame problems and to guide the process of
extracting information from data in a computational manner. The primary
knowledge you will take away from this book is the art of computational problem
solving,
‘The book is a bit eccentric, Part 1 (Chapters 1-8) is an unconventional
introduction to programming in Python, We braid together four strands of
material:
‘+ The basics of programming,
+ The Python programming language,
‘* Concepts central to understanding computation, and
‘© Computational problem solving techniques,
We cover most of Python's features, but the emphasis is on what one can do
with a programming language, not on the language itself. For example, by the
end of Chapter 3 the book has covered only a smalll fraction of Python, but it has
already introduced the notions of exhaustive enumeration, guess and check
algorithms, bisection search, and efficient approximation algorithms. We
introduce features of Python throughout the book. Similarly, we introduce
aspects of programming methods throughout the book. ‘The idea is to help you
learn Python and how to be a good programmer in the context of using
computation to solve interesting problems,
Part 2 (Chapters 9-16) is primarily about using computation to solve problems.
Itassumes no knowledge of mathematics beyond high school algebra, but it,
does assume that the reader is comfortable with rigorous thinking and not
intimidated by mathematical concepts. It covers some of the usual topics foundin an introductory text, e.g., computational complexity and simple algorithms,
But the bulk of this part of the book is devoted to topics not found in most
introductory texts,
Part 3 (Chapters 17-18) looks at two related and slightly advanced topies—
optimization problems and dynamic programming. Experience suggests that it
is quite comfortable to fit Parts 1 and 2 of this book into a one-semester course.
When the material in Part 3 is included, the course becomes more demanding
than is comfortable for some students.
‘The book has two pervasive themes: systematic problem solving and the power
of abstraction, When you have finished this book you should have:
+ Learned a language, Python, for expressing computations,
+ Leamed a systematic approach to organizing, writing and debugging
medium-sized programs,
‘* Developed an informal understanding of computational complexity,
+ Developed some insight into the process of moving from an
ambiguous problem statement to a computational formulation of a
method for solving the problem,
* Leamed a useful set of algorithmic and problem reduction
techniques,
¢ Learned how to use randomness and simulations to shed light on
problems that dan’ easily succumb ta closed-form solutions, and
+ Leamed haw to use computational tools, including simple statistical
and visualization tools, to model and understand data,
Programming is an intrinsically difficult activity. Just as “there is no royal road
to geometry, there is no royal road to programming, It is possible to deceive
students into thinking that they have learned how to program by having them
complete a series of highly constrained “fill in the blank” programming.
problems. However, this does not prepare students for figuring out how to
harness computational thinking to solve problems.
If you really want to learn the material, reading the book will not be enough
Various versions of the course have been available on MIT's OpenCourseWare
(CCW) Web site since 2008. The site includes video recordings of lectures and a,
complete set of problem scts and exams. Since the fall of 2012, edX and MITx,
have offered an online version of this course. If you really want to learn the
material, we strongly recommend that you do the problem sets associated with
one of these online offerings.
) This was Euclid’s purported response, circa 300 BC, to King Ptolemy’s request for an
easier way to learn mathematics.SPRING 2013 EDITION
Thave been working on this hook for many years, and had originally planned to
have it ready for publication by the summer of 2013. However, in June of 2012
agreed to team up with Eric Grimson and Chris Terman to offer a Fall 2012
MITx version of the MIT course on which this book is based, That led me to
rush to complete # version of the book that could be made available to the
students taking that course,
I knew that I could not accomplish in three months everything that I had
planned to accomplish in twelve. I therefore made a decision to focus on
producing an edition with the content as close to right as possible—at the cost of
ignoring some of the other things (e.g,, careful copy editing) that go into
producing @ high-quality textbook.
‘This version is based on the Fall 2012 version. | fixed a significant number of,
typos, a few bugs, and made other small improvements. But I resisted the
temptation to make major changes.
L expect to produce a stable version of the text by summer 2013.ACKNOWLEDGMENTS
‘This book grew out of a set of lecture notes that | prepared while teaching an
undergraduate course at MIT. The course, and therefore this book, benefitted
from suggestions from faculty colleagues (especially Eric Grimson and Srinivas
Devadas), teaching assistants (especially Asiandyar Qureshi who was the
teaching assistant the first time I taught the course), and the students who took
the course.
‘The process of transforming my Iecture notes into a book proved far mare
onerous than I had expected, Fortunately, this misguided optimism lasted long
enough to keep me from giving up, The encouragement of colleagues and family
also helped keep me going,
As the Fall 2012 version of the book neared completion, Eric Grimson, Chris
‘Terman, and David Guttag provided vital help. Erie, who is MIT's Chancellor,
managed to find the time to read almost the entire book with great care. He
found numerous errors (including an embarrassing, to me, number of technical
errors) and pointed out places where necessary explanations were missing.
Chris also read parts of the manuscript and discovered errors, He also helped
me battle Microsoft Word, which we eventuslly persuaded to do most of what we
wanted, David overcame his aversion to computer science, and proofread the
later chapters. He checked my math and tuned up my verbiage.
‘The Pall 2012 version was used in the MIT course 6.00 and the MITx course
6.00x, A number of students in these courses pointed out errors. One 6.00%
student, J.C. Cabrejas, was particularly helpful, He found a large number of
typos, and more than a few technical errors. Thank you J.C.
Lowe a special debt of gratitude to Julie Sussman, P.P.A. Until I started working
with Julie, I had no idea how much difference an editor could make, I had
worked with capable copy editors on previous books, and thought that was what
Inceded for this book. I was wrong, I needed a collaborator who could read the
book with the eyes of a student, and tell me what needed to be done, what
should be done, and what could be done if | had the time and energy to do it
sJulie buried me in “suggestions* that couldn't be ignored. Her combined
command of both the English language and programming is quite remarkable, I
only wish that I had started working with Julie earlier in the process. The errors
and awkward sentences that remain in the book are an indication that I did not
allow time to take full advantage of Julie's talent,
Frédo Durand took the beautiful photograph of Antelope Canyon on the cover, 1
appreciate his allowing me to use it, Thanks also to Jason Dorfman for taking
the photo on the back cover, and to the graduate students in my group for
joining the pyramid,
Finally, thanks to my wife Olga, for pushing me to finish and for pitching in at
critical times. I should also thank her for putting up with my grumpiness
during the latter stages of this project.1 GETTING STARTED
A computer does two things, and two things only: it performs calculations and it
remembers the results of those calculations, But it dees those two things
extremely well. The typical computer that sits on a desk or in a briefcase
performs a billion or so calculations a second, It’s hard to image how truly fast,
that is. Think about holding a ball a meter above the floor, and letting it go. By
the time it reaches the floor, your computer could have executed over a billion
instructions. As for memory, # typical computer might have hundreds of
gigabytes of storage. How big is that? If a byte (the number of bits, typically
eight, required to represent one character] weighed one ounce (which it doesn’),
100 gigabytes would weigh more than 3,000,000 tons. For comparison, that’s
roughly the weight of all the coal produced in a year in the U.S.
For most of human history, computation was limited by the speed of calculation
of the human brain and the ability to record computational results with the
human hand, This meant that only the smallest problems could be attacked
computationally. Even with the speed of modern computers, there are still
problems that are beyond modern computational models (e.g., understanding
climate change), but more and more problems are proving amenable to
computational solution. It is our hape that by the time you finish this book, you
will fee! comfortable bringing computational thinking to bear on solving many of
the problems you encounter during your studies, work, and even everyday life.
What do we mean by computational thinking?
All knowledge can be thought of as either declarative or imperative. Declarative
knowledge is composed of statements of fact. Far example, “the square root of x
is a number y such that yy=x.” This isa statement of fact. Unfortunately it
doesn't tell us how to find a square root,
Imperative knowledge is “how to” knowledge, or recipes for deducing
information, Heron of Alexandria was the first to document a way to compute
the square root of a number.? His method can be summarized as:
© Start with a guess, 9,
+ If gg is close enough to x, stop and say that gis the answer
‘+ Otherwise create a new guess, by averaging g and x/g, ie, (g + x/a)/2
* Using this new guess, which we again call g, repeat the process until 9-9
is close enough to x.
? Many believe that Heron was not the inventor of this method, and incleed! there is some
evidence that it was well known to the ancient Babylonians,Chapeer 1. Getting Stared
Consider, for example, finding the square root of 25.
1. Set g to some arbitrary
2, We device that 8«
y value, e.g, 3.
Gis not close enough to 25.
Set g= [8 + 26/92 = 5.66:
We decide that 8.65:5.65
2.04 is still not close enough to 25.
Set 9 = [5.65 + 25/5.66)2 = 6.04
We decide that 6:045.04 = 26.4 is close enough, so we stop and declare 5.04
to be an aclequate approximation to the square roat of 26.
Note that the description of the method is a sequence of simple steps, together
with a flow of control that specifies when each step is to be executed. Such a
description is called an algorithm:, ‘This algorithm is an example of a guess-
and-check algorithm. It is based on the fact that it is easy to check whether or
not a guess is a good one.
A bit more formally, an algorithm is a finite list of instructions that describe a
computation that when executed on a provided set of inputs will proceed
through & set of well-defined states and eventusilly produce an output
{An algorithm is a bit like a recipe from a cookbook,
1, Put custard mixture over heat.
2. Stir.
3. Dip spoon in custard.
4, Remove spoon and run finger across back of spoon.
5. If clear path is left, remove custard from heat and let cool,
6. Otherwise repeat.
It includes some tests for deciding when the process is complete, as well as
instructions about the order in which to execute instructions, sometimes
jumping to some instruction based on a test.
‘So how does one capture this idea of a recipe in a mechanical process? One way
would be to design a machine specifically intended to compute square roots.
Odd as this may sound, in fact the earliest computing machines were fixed-
program computers, meaning they were designed to do very specific things, and
were mostly tools to solve a specific mathematical problem, ¢.g., to compute the
trajectory of an artillery shell, One of the first computers (built in 1941 by
Atanasoff and Berry] solved systems of linear equations, but could do nothing,
else. Alan Turing’s bombe machine, developed during WWII, was designed
strictly for the purpose of breaking German Enigma codes. Some very simple
computers still use this approach, For example, a four-function calculator is &
fixed-program computer. It can do basic mathematics, but it cannot be used as.
4 For simplicity, we are rounding results
$ The word “algorithm! is derived from the name of the Persian mathematician
Muhammad ibm Musa al-KhwarizmiChapter Getting Started
a word processor or to run video games. To change the program of such a
machine, one has to replace the circuitry.
‘The first truly modern computer was the Manchester Mark 1.5 It was.
distinguished from its predecessors by the fact that it was a stored-program
computer. Such a computer stores (and manipulates} a sequence of
instructions, and has a set of elements that will execute any instruction in that
sequence, By creating an instruction-set architecture and detailing the
computation as a sequence of instructions (i.c., a program), we make a highly
flexible machine, By treating those instructions in the same way as data, &
stored-program machine can easily change the program, and can do so under
program control. Indeed, the heart of the computer then becomes a program,
(called an interpreter] that can execute any legal set of instructions, and thus
can be used to compute anything that one can describe using some basic set of
instructions
Both the program and the data it manipulates reside in memory, Typically there
is @ program counter thet points to @ particular location in memory, and
computation starts by executing the instruction at that point. Most often, the
interpreter simply goes to the next instruction in the sequence, but not always.
In some cases, it performs a test, and on the basis of that test, execution may
{jump to some other point in the sequence of instructions. This is called flew of
control, and is essential to allowing us to write programs that perform complex
tasks,
Returning to the recipe metaphor, given a fixed set of ingredients a good chef
can make an unbounded number of tasty dishes by combining them in different
ways. Similarly, given a small fixed set of primitive elements a good programmer
can produce an unbounded number of useful programs. This is what makes
programming such an amazing endeavor.
‘To create recipes, or sequences of instructions, we need a programming
Janguage in which to describe these things, a way to give the computer its
marching orders,
In 1986, the British mathematician Alan Turing described a hypothetical
computing device that has come to be called a Universal Turing machine. The
machine had an unbounded memory in the form of tape on which one could
write zeros and ones and some very simple primitive instructions for moving,
reading, and writing to the tape. The Chureb-Turing thesis states that ifa
function is computable, a Turing Machine can be programmed to compute it,
‘The “if” in the Church~Turing thesis is important, Not all problems have
computational solutions. For example, Turing showed that it is impossible to
write a program that given an arbitrary program, call it P, prints true if and only
if P will run forever. This is known as the halting problem,
© This computer was built at the University of Manchester, and ran its first program in
1949, It implemented ideas previously described by John von Neumann and was
anticipated by the theoretical concept of the Universal Turing Machine described by Alan
‘Turing in 1935,Chapeer 1. Getting Stared
‘The Church-Turing thesis leads directly to the notion of Turing Completeness.
A programming language is said to be Turing complete if it can be used to
simulate @ universal Turing Machine, All moder programming languages are
‘Turing complete, Asa consequence, anything that can be programmed in one.
programming language (¢.g., Python) can be programmed in any other
programming language (¢.g,, Java). Of course, some things may be easier to
program in a particular language, but all languages are fundamentally equal
with respect to computational power.
Fortunately, no programmer has to build programs out of Turing’s primitive
instructions, Instead, modern programming languages offer @ larger, more
convenient set of primitives. However, the fundamental idea of programming as
the process of assembling a sequence of operations remains central.
Whatever set of primitives one has, and whatever methods one has for using
them, the best thing and the worst thing about programming are the same: the
computer will do exactly what you tell it to do, This is a good thing because it
means that you can make it do all sorts of fun and useful things. It is @ bad
thing because when it doesn’t do what you want it to do, you usually have
nobody to blame but yourself.
‘There are hundreds of programming languages in the world. There is no best
language (though one could nominate some candidates for worst}. Different
languages are better or worse for different kinds of applications, MATLAB, for
example, is an excellent language for manipulating vectors and matrices. C is a
good language for writing the programs that control data networks. PHP is a
good language for building Web sites. And Python is @ good general-purpose
language.
Each programming language has a set of primitive constructs, a syntax, a static
semantics, and @ semantics. By analogy with a natural language, c.g., English,
the primitive constructs are words, the syntax describes which strings of words
constitute well-formed sentences, the static semantics defines which sentences
are meaningful, and the semantics defines the meaning of those sentences, The
primitive constructs in Python include literals (c.g, the number 3.2 and the
string 'abe') and infix operators (c.g,, + and /)
‘The syntax of # language defines which strings of characters and symbols are
well formed, For example, in English the string “Cat dog boy.” is not a
syntactically valid sentence, because the syntax of English does not accept
sentences of the form
enoun>. In Python, the sequence of
primitives 3.2 + 3.2 is syntactically well formed, but the sequence 3.2 3.2 is
not.
‘The static semanties defines which syntactically valid strings have a meaning.
In English, for example, the string “I are big,” is of the form , which is a syntactically acceptable sequence, Nevertheless, it
is not valid English, because the noun “I” is singular and the verb “are” is plural,
‘This is an example of a static semantic error. In Python, the sequence
3.2/'abc" is syntactically well formed (zliteral> ), butChapter Getting Started
produces a static semantic error since it is not meaningful to divide a number by
a string of characters,
‘The semantics of a language associates a meaning with cach syntactically
correct string of symbols that has no static semantic errors. In natural
languages, the semantics of a sentence can be ambiguous. For example, the
sentence “I cannot praise this student too highly,” can be either flattering ar
damning. Programming languages are designed so that cach legal program has
exactly one meaning.
‘Though syntax errors are the most common kind of error (especially for those
learning a new programming language). they are the least dangerous kind of
error. Every serious programming language does a complete job of detecting
syntactic errors, and will not allow users to execute a program with even one
syntactic error. Furthermore, in most cases the language system gives a
sufficiently clear indication of the location of the error that it is obvious what,
needs to be done to fix it
‘The situation with respect to static scmantic errors is a bit more complex. Some
programming languages, e.g, Java, do a lot of static semantic checking before
allowing a program to be executed, Others, ¢.g., C and Python (alas), do
relatively less static semantic checking, Python does do a considerable amount
of static semantic checking while running a program. However, it does not
catch all static semantic errors. When these errors are not detected, the
behavior of a program is often unpredictable. We will see examples of this later
in the book.
One doesnt usually speak of a program as having a semantic error. Ifa
program has no syntactic errors and no static semantic errors, it has a meaning,
«., it has semantics. Of course, that isn't to say that it has the semantics that
its creator intended it to have, When a program means something other than
what its creator thinks it means, bad things can happen.
What might happen if the program has an error, and behaves in an unintended
way?
«It might crash, i.c., stop running and produce some sort of obvious
indication that it has done so. In a properly designed computing
system, when a program crashes it does not do damage to the overall
system. Of course, some very popular computer systems don't have this
nice property. Almost everyone who uses @ personal computer has run a
program that has managed to make it necessary to restart the whole
compute
‘© Orit might keep running, and running, and running, and never stop. If
one has no ides about how long the program is supposed to take to do its
job, this situation can be hard to recognize
‘+ Orit might run to completion and produce an answer that might, or
might not, be correct.Chapeer 1. Getting Stared
Each of these is bad, but the last of them is certainly the worst. When a
program appears to be doing the right thing but isn’t, bad things can follow.
Fortunes can be lost, patients can receive fatal doses of radiation therapy,
airplanes can crash, ete
Whenever possible, programs should be written in such a way that when they
don't work properly, it is self-evident. We will discuss how to da this
throughout the book,
Finger Exercise: Computers can be annoyingly literal. If you don’t tell them
exactly what you want ther to do, they are likely to do the wrong thing, Try
writing an algorithm for driving between two destinations. Write it the way you
would for a person, and then imagine what would happen if the person executed,
the algorithm exactly as written. For example, how many traffic tickets might
they get?2 INTRODUCTION TO PYTHON
‘Though each programming language is different (though not as different as the!
designers would have us believe), there are some dimensions along which they
can be related,
+ Low-level versus high-level refers to whether we program using
instructions and data objects at the level of the machine [e.g., move 64
bits of data from this location to that location) or whether we program,
using more abstract operations (e.g., pop up @ menu on the screen) that
have been provided by the language designer.
+ General versus targeted to an application domain refers to whether
the primitive operations of the programming language are widely
applicable or are fine-tuned to a domain. For example Adobe Flash is
designed to facilitate adding animation and interactivity to Web pages,
but you wouldn't want to use it build a stock portfolio analysis program,
‘+ Interpreted versus compiled refers to whether the sequence of
instructions written by the programmer is executed directly or whether it
is first converted into a sequence of machine-level primitive operations.
‘The code written by @ programmer is called sousee code, In the early
days of computers, people had to write source code in a language that
was very close to the machine eode that could be directly interpreted by
the computer hardware, There are advantages to bath approaches. It is
often easier to debug programs written in languages that are designed to
be interpreted, because the interpreter can produce error messages that
are easy to correlate with the source code. Compiled languages usually
produce programs that run mare quickly and use less space.
In this book, we use Python. However, this book is not about Python. It will
certainly help readers learn Python, and that’s a good thing. What is much,
more important, however, is that careful readers will learn something about how
to write programs that solve problems. This skill can be transferred to any
programming language.
Python is a general-purpose programming language that can be used effectively
to build almost any kind of program that does not need direct access to the
computer's hardware, Python is not optimal for programs that have high
reliability constraints (because of its weak static semantic checking] or that are
built and maintained by many people or over a long period of time (again
because of the weak static semantic checking)
However, Python does have several advantages over many other languages. It is
a relatively simple language that is easy to learn, Because Python is designed to
be interpreted, it can provide the kind of runtime feedback that is especially
helpful to novice programmers, There are also a large number of frecly available
libraries that interface to Python and provide useful extended functionality,
Several of those are used in this book.(Chapter 2. Inoredion to Python
Now we are ready to start learning some of the basic elements of Python. These
are common to almost all programming languages in concept, though not
necessarily in detail
‘The reader should be forewarned that this book is by no means a comprehensive
introduction to Python, We use Python as a vehicle to present concepts related
to computational problem solving and thinking. The language is presented in
dribs and drabs, as needed for this ulterior purpose. Python features that we
don’t need for that purpose are not presented at all, We feal comfortable about
not covering the entire language because there are excellent online resources.
describing almost every aspect of the language. When we teach the course on.
which this book is based, we suggest to the students that they rely on these free
online resources for Python reference material,
Python is @ living language. Since its introduction by Guido von Rossum in
1990, it has undergone many changes. For the first decade of its life, Python
was a little known and little used language. That changed with the arrival of
Python 2.0 in 2000, In addition to incorporating a number of important
improvements to the language itself, it marked a shift in the evolutionary path of
the language, A large number of people began developing libraries that,
interfaced seamlessly with Python, and continuing support and development of
the Python ecosystem hecame a community-based activity. Python 3.0 was
released at the end of 2008, This version of Python cleaned up many of the
inconsistencies in the design of the various releases of Python 2 (often referred
to as Python 2.x}. However, it was not backward compatible. That meant that,
most programs written for earlier versions of Python could not be run using
implementations of Python 3.0,
‘The backward incompatibility presents @ problem for this book. In our view,
Python 3.0 is clearly superior to Python 2.x. However, at the time of this
writing, some important Python libraries still do not work with Python 3. We
will, therefore, use Python 2.7 (into which many of the most important features
of Python 3 have been “back ported”) throughout this book.
2.41
The Basic Element of Python
A Python program, sometimes called a seript, is a sequence of definitions and
commands. ‘These definitions are evaluated and the commands are executed by
the Python interpreter in something called the shell. Typically, a new shell is
created whenever execution of a program begins, In most cases, a window is
associated with the shell.
We recommend that you start a Python shell now, and use it to try the examples
contained in the remainder of the chapter. And, for that matter, later in the
book as well,
A commaad, often called a statement, instructs the interpreter to do
something. For example, the statement print "Yankees rule!" instructs the
interpreter to output the string Yankees rule! to the window associated with the
shellChapier 2 Inordion to Python
‘The sequence of commands
print "Yankees rule!’
print 'But not in Boston!"
print "Yankees rule,’, "but not in Boston!"
causes the interpreter to produce the output
Yankees rule!
But not in Boston!
Yankees rule, but not in Boston!
Notice that two values were passed to print in the third statement, The print
command takes a variable number of values and prints them, separated by a
space character, in the order in which they appear.”
2.1.1 Objects, expressions, and numerical types
Objects are the core things that Python programs manipulate, Every object has
a type that defines the kinds of things that programs can do with objects of that
type.
‘Types are either scalar or non-sealar, Sealar objects are indivisible. Think of
them as the atoms of the language.’ Non-scalar objects, for example strings,
have internal structure,
Python has four types of scalar objects:
* int is used to represent integers. Literals of type int are written in the
obvious way, e.g. 3 or 10002 or ~4,
+ Float is used to represent real numbers. Literals of type Float are also
written in the obvious way, c.g. 3.0 or 3.17 or =28.72. (It is rarely uscd,
but itis also possible to write literals of type float using scientific
notation, For example, the literal 1.663 stands for 1.6-10%, ic. it is the
same as 1600) You might wonder why this type is not called real
Within the computer, values of type Float are stored in the computer as
floating point aumbers. This representation, which is used by all
modern programming languages, has many advantages. However, under
some situations it causes floating point arithmetic to behave in ways that
are slightly different from real arithmetic. We discuss this in Chapter 3.
+ boo! is used to represent the Boolean values True and False.
‘¢ None is a type with a single value, We will say more about this when we
get to variables,
Objects and operators can be combined to form expressions, cach of which
denotes an object of some type, We will refer to this as the value of the
expression, For example, the expression 3 + 2 denotes the object 5 of type int,
and the expression 3.0 + 2.0 denotes the object 5.0 of type float.
© In Python 3, print is a function rather than a command. One would therefore write
print('Yankees rule!', "but not in Boston’).
7 Yes, atoms are not truly indivisible. However, splitting them is not easy, and doing so
can have consequences that are not always desirable,10
Chapter 2. Lntraducton 0 Python
‘The == operator is used to test whether two expressions are equal, and the !=
operator is used to test if two expressions are unequal,
‘The built-in Python function type can be used to find out the type of an object,
as illustrated by the following interaction with the interpreter. The symbol >>> is
a shell prompt indicating that the interpreter is expecting the user to type some
Python code into the shell. The line below the line with the prompt is produced
when the interpreter evaluates the Python code entered at the prompt.
pos type(3)
>>> type (3.0)
‘The operators on types int and Float are listed in Figure 2.1.
* tj is the sum of i and j. If i and j are both of type int, the result is
an int. If either of them isa float, the result is a float.
* ijisi minus j. Ifj and j are both ints, the result is an int. If
either of them is a float, the result is a float.
+ i®J is the product of i and j. If i and j are both ints, the result is
an int. If either of them is a float, the result is a float.
+ i//i is integer division. For example, the value of 6//2 is the int 3
and the value of 6//4 is the int 1. The value is 1 because integer
division returns the quotient and ignores the remainder.
+ 4/4 is divided by j. In Python 2.7, when the operands are both of
type int, the result is also an int, otherwise the result is a float. In
this booie, we will use / only to divide one Float by another. We will
use // to divide one int by another. (In Python 3, the / operator,
thank goociness, always returns a float. For example, the value of
6/4 is 1.5.)
‘© i%j is the remainder when the int 4 is divided by the int j. Itis
typically pronounced “i mod j,” which is short for “i modulo j.”
+ i#4j is 1 raised to the power j. If i and j are both of type int, the
result is an int. If either of them is a float, the result is a float.
‘+ The comparison operators >, >=, <, and <= have their usual meanings.
Figure 2.1 Operators on types int and float
‘The arithmetic operators have the usual precedence. For example, * binds more
tightly than +, so the expression xty*2 is evaluated by first multiplying y by 2,
and then adding the result to x. The order of evaluation can be changed by
using parentheses to group subexpressions, e.g., (x+y)°2 first adds x and y, and
then multiplies the result by 2.
‘The operators on type boo! are:
* a and bis True if both a and b are True, and False otherwise.
‘© a or bis True if at least one of a or b is True, and False otherwise,
© not
is True if a is False, and False if a is True.(Choptr 2. Intron to Python u
2.1.2 Variables and assignment
‘Variables provide n way to associate names with objects. Consider the code
pi = 3.14159
radius = 11.2
area - pi * (radius**2)
radius = 14.3
It firet binds the names pi and radius to different objects of type float, Tt then
binds the name area to a third object of type Float. This is depicted in the left
panel of Figuse 2.2.
-- =
f _>,
pi —— 5.14159 ei 3.10159
radius J radius |
] 112
~~ [aes
\ 394,08104
(
Figure 2.2 Binding of variables to objects
Ifthe program then executes radius - 14.3, the name radius is rebound to a
different object of type Float, as shown in the right panel of Figure 2.2. Note
that this assignment has no effect on the vahie to which area is bound. It is
still bound to the object denoted by the expression 3.14159* (11.2**2).
In Python, a variable is just a mame, nothing more, Remember this—itis
important. An assignment statement associates the name to the left of the =
symbol with the object denoted by the expression to the right of the =.
Remember this too. An object can have one, more than one, or no name
associnted with it.
Pethaps we shouldn't have said, “a variable is just a name.” Despite what Juliet
said,* names matter. Programming languages let us describe computations in a
way that allows machines to execute them. This docs not mean that only
computers read programs.
‘As you will soon discover, it’s not always easy to write programs that work
correctly. Experienced programmers will confirm that they spend a great deal of
time reading programs in an attempt to understand why they behave as they do
Itis therefore of critical importance to write programs in such way that they are
# What's in a name? That which we call a rose by any other name would smell as sweet”(Chapter 2. Inoredion to Python
easy to read, Apt choice of variable names plays an important role in enhancing
readability.
Consider the two code fragments
3.14159 pi = 3.14159
41.2 dianeter = 11.2
ah(b*"2) area = pi*(diameter*¥2)
As far as Python is concerned, they are not different. When executed, they will
do the same thing, To a human reader, however, they are quite different. When
we read the fragment on the left, there is no @ priori reason to suspect that
anything is amiss, However, a quick glance at the code on the right should,
prompt us to be suspicious that something is wrong, Bither the variable should,
have been named radius rather than diameter, or there should be a division by
2.0 in the calculation of the area,
In Python, variable names can contain uppercase and lowercase letters, digits
(but they cannot start with a digit}, and the special character _. Python variable
names are case-sensitive, e.g., Julie and julie are different names. Finally, there
are a small number of reserved keywords in Python that have built-in
meanings and cannot be used as variable names. Different versions of Python
have slightly different lists of reserved words, The reserved words in Python 2.7
are and, as, assert, break, class, continue, def, del, elif, else, except, exec,
Finally, for, from, global, if, import, in, is, lambda, not, or, pass, print, raise,
return, try, with, while, and yield
Another good way to enhance the readability of code is to add comments. Text
following the symbol # is not interpreted by Python. For example, one might
write
fsubtract area of square s from area of circle ¢
areaC = pitradius**2
areaS ~ side'side
difference = areaC-areaS
Python allows multiple assignment, The statement
x yd, 3
binds x to 2 and y to 3, Alll of the expressions on the right-hand-side of the
assignment are evaluated before any bindings are changed. This is convenient
since it allows you to use multiple assignment to swap the bindings of two
variables.
For example, the code
x yn 2, 3
XY = ys x
print 'x'="y x
print "y=", y
will print
x-3
yo2Chapier 2 Inordion to Python 13
2.4.3 IDLE
‘Typing programs directly into the shell is highly inconvenient. Most
programmers prefer to use some sort of text editor that is part of an integrated
development environment (IDE),
In this book, we will use IDLE,# the IDE that comes as part of the standard
Python installation package. IDLE is an application, just like any other
application on your computer. Start it the same way you would start any other
application, ¢.g., by double-clicking on an icon
IDLE provides
‘* a text editor with syntax highlighting, autocompletion, and smart
indentation,
+ ashell with syntax highlighting, and
‘+ an integrated debugger, which you should ignore for now.
When IDLE starts it will open a shell window into which you can type Python
commands. It will also provide you with a file menu and an edit menu, 10
‘The file menu includes commands to
‘© create a new editing window into which you can type a Python program,
‘+ open a file containing an existing Python program, and
¢ save the contents of the current editing window into a file (with file
extension .py)
‘The edit menu includes standard text-editing commands (¢.g., copy, paste, and
find) plus some commands specifically designed to make it easy to edit Python
code (e.g., indent region and comment out region)
For a complete description of IDLE, see
http: //docs.python.org/library/idle.htnl
2.2
Branching Programs
‘The kinds of computations we have been looking at thus far are called straight-
Hne programs. They execute one statement after another in the order in which
they appear, and stop when they run out of statements. The kinds of
computations we can describe with straight-line programs are not very
interesting. In fact, they are downright boring.
Allegedly, the name Python was chosen as a tribute to the British comedy troupe Monty
Python, This leads one to think that the name IDLE is a pun on Bric Idle, a member of
the troupe,
1 It will also provide some other menus, which you ean safely ignore for now.14
Chapter 2. Inacio to Bytom
Branching programs are more interesting. The simplest branching statement
is a conditional. As depicted in Figure 2.2, a conditional statement has three
parts:
+ atest, ie, an expression that evaluates to either True or False;
+ ablock of code that is executed if the test evaluates to Trueand
+ an optional block of code that is executed if the test evaluates to False.
After a conditional statement is executed, execution resumes at the code
following the statement.
Block Block
code
Figure 2.3 Flow chart for conditional statement
In Python, conditional statement has the form
if bootean expression:
block of code
else:
block of code
In describing the form of Python statements we use italics to indicate
nonterminal symbols, These symbols do not appear in Python programs, but
are used to describe the kinds of code that could occur at that point in a
program. For example, the nonterminal boolean expression indicates that any
expression that evaluates to True o False can follow the keyword if, and the
nonterminal block of code indicates that any sequence of Python statements can
follow else:.
Consider the following program that prints “Even” if the value of the variable x is,
even and “Odd” otherwise:
if 02 == 0:
print “Event
else:
print ‘odd’
print "Done with conditional’Chapier 2 Inordion to Python 15
‘The expression x%2==0 evaluates to True when the remainder of x divided by 2 is
0, and False otherwise, Remember that — is used for comparison, since = is
reserved for assignment.
Indentation is semantically meaningful in Python. For example, if the last
statement in the above code were indented it would be part of the block of cade
associated with the else, rather than with the block of code following the
conditional statement.
Python is unusual in using indentation this way. Most other programming
languages use some sort of bracketing symbols to delineate blocks of code, e.g,
C encloses blocks in braces, { }. An advantage of the Python approach is that it
ensures that the visual structure of a program is an accurate representation of
the semantic structure of that program.
When either the true block or the false block of a conditional contains another
conditional, the conditional statements are said to be nested. In the code
below, there are nested conditionals in both branches of the top-level if
statement.
if 02
if 3 == 0:
print "Divisible by 2 and 3°
else:
print "Divisible by 2 and not by 3°
elif 83 == 0:
print "Divisible by 3 and not by 2°
‘The el7f in the above code stands for “else if.”
Itis often convenient to use compound Boolean expressions in the test of @
conditional, for example,
if x >> is a prompt, not something that you type):
de> 364
bo> 34a!
35> B44
Soo tata’
‘The operator + is said to be overloaded. It has different meanings depending
upon the types of the objects to which itis applied, for example, it means
addition when applied to two numbers and concatenation when applied to two
strings. ‘The operator * is also overloaded. It means what you expect it to mean
when its operands are both numbers. When applied to an int and a str, it
duplicates the str. For example, the expression 2"John" has the value
"JohnJohn". There is a logic to this. Just as the expression 2°9 is equiva
24242, the expression 3*a" is equivalent to 'a'y'a+'a".
to
Now try typing
eonateiay
Each of these lines generates an error message.
0 Unlike many program languages, Python has no type corresponding to a character,
Instead, it uses strings of length 1Chapier 2 Inordion to Python 7
‘The first line produces the message
NameError: name ‘a’ is not defined
Because a is not a literal of any type, the interpreter treats it as a name.
However, since that name is not bound to any object, attempting to use it
causes a runtime error,
‘The code 'a'*"a" produces the error message
TypeError: can't multiply sequence by non-int of type ‘str’
‘That type checking exists is a good thing. It turns careless (and sometimes
subtle] mistakes into errors that stop execution, rather than errors that lead
programs to behave in mysterious ways. The type checking in Python is not as
strong as in some other programming languages (e.g., Java). For example, it is
pretty clear what < should mean when it is used to compare two strings or two
numbers. But what should the value of '4" < 3 be? Rather arbitrarily, the
designers of Python decided that it should be False, because all numeric values.
should be less than all values of type str. The designers of some other
languages decided that since such expressions don’ have an obvious meaning,
they should generate an error message.
Strings are one of several sequence types in Python. They share the following
operations with all sequence types,
‘The length of a string can be found using the Ten function. For example, the
value of len(*abc') is 3
Indexing can be used to extract individual characters from a string. For
example, typing ‘abc’ [0] into the interpreter will cause it to display the string
ta’. Typing ‘abc’ [3] will produce the error message IndexError: string index
out of range. Since Python uses 0 to indicate the first element of a string, the
last clement of a string of length 3 is accessed using the inex 2, Negative
numbers are used to index from the end of a string, For example, the value of
"abe" [-1] is 'e”
Slicing is used to extract substrings of arbitrary length. I's isa string, the
expression s[startzend] denotes the substring of s that starts at index start
and ends at index end-1, For example, "abe" [1:3] = "be', Why does it end at
index end-1 rather than end? So that expressions such as ‘abc’ [0:Ten¢*abc")]
have the value one might expect. If the value before the colon is omitted, it
defaults to 0, If the value after the colon is omitted, it defaults to the length of
the string, Consequently, the expression ‘abc’ [:] is semantically equivalent to
the more verbose ‘abc' [0:1en¢"abe')]18 Chapter 2. Itredaction te Python
2.3.4 Input
Python 2.7 has two functions (see Chapter 4 for a discussion af functions in
Python} that can be used to get input directly from a user, input and
raw_input.!2 Bach takes a string as an argument and displays it as a prompt in
the shell, It then waits for the user to type something, followed by hitting the
enter key, For ran_input, the input line is treated as a string and becomes the
value returned by the function; input treats the typed line as a Python
expression and infers a type. In this book, we use only ram_input, which is less
likely to lead to programs that behave in unexpected ways,
Consider the cade
>>> name = raxinput("Enter your name: *)
Enter your name: George Washington
>>> print ‘Are you really", name, "2"
Are you really George Washington’?
>>> print ‘Are you really ' + name + "7"
‘Ace you really Gearge Washington?
Notice that the first print statement introduces a blank before the *?” It docs
this because when print is given multiple arguments it places a blank space
between the values associated with the arguments, The second print statement
uses concatenation to produce a string that does not contain the superfluous
blank and passes this as the only argument to print.
Now consid
boon = rawinput('Enter an int: *)
Enter an int: 3
>>> print type(n)
‘type 'ste'>
Notice that the variable n is bound to the str '3" not the int 3. So, for example,
the value of the expression n#4 is 3333" rather than 12, ‘The good news is that
whenever a string is a valid literal of some type. a type conversion can be applied
toi.
‘Type conversions (also calleci type easts) arc used often in Python code, We
use the name of a type to convert values to that type. So, for example, the value
of int(3')*4 is 12. When a float is converted to an int, the number is
truncated, e.g., the value of int(@3.9) is the int 3
2.4 Iteration
A generic iteration mechanism is depicted in Figure 2.4. Like a conditional
statement it begins with a test, If the test evaluates to True, the program
executes the loop body once, and then goes back to reevaluate the test. This
process is repeated until the test evaluates to False, after which control passes
to the code following the iteration statement.
2 Python $ has only one command, input. Somewhat confusingly, Python 3's input
hhag the same semantics as raw_input in Python 2.7. Go figure,(Choptr 2. Intron to Python 19
Loop
Body
code
Figure 2.4 Flow chart for iteration
Consider the following example:
x=3
ans = 0
itersLeft = x
while Citersteft '= 0):
jtersleft ~ itersLeft — 1
print str(x) + "*" + strG + =" + str(ans)
The code starts by binding the variable x to the integer 3. It then proceeds to
square x using repetitive addition, The following table shows the value
associated with each variable each time the test at the start of the loop is
reached, We constructed it by hand-simulating the code, ie., we pretended to
be a Python interpreter and executed the program using pencil and paper,
Using pencil and paper might seem kind of quaint, but it is an excellent way to
understand how a program behaves. '°
rest # x ans___iterstefr
1 3 0 3
2 3 3 2
2 a 6 1
4 3 9 9
The fourth time the test is reached, it evaluates to False and flow of control
proceeds to the statement following the loop.
For what values of x will this program terminate?
Ife -- 0, the initial value of itersLeft will also be 0, and the loop body will
never be executed. Ifx > 0, the initial value of itersLeft will be greater than 0,
and the loop body will be executed.
1 It ia ale possible to hand-simulate a program using pen and paper, of even a text
editor.(Chapter 2. Inoredion to Python
Each time the loop body is executed, the value of itersLeft is decreased by
exactly 1. This means that if itersLeft started out greater than 0, after some
finite number of iterations of the loop, itersLeft == 0. At this point the loop
test evaluates to False, and control procceds to the code following the while
statement.
‘Suppose the value of x is -1? Something very bad happens. Control will enter
the loop, and each itcration will move itersLeft farther from 0 rather than
closer to it, The program will therefore continue executing the loop forever (or
until something else bad, €.g., an overflow error, occurs). How might we remove
this flaw in the program? Initializing itersLeft to the absolute value of x almost
works. The loop terminates, but it prints a negative value. If the assignment
‘statement inside the loop is also changed, to ans = ans+abs(x), the code works
properly.
We have now covered pretty much everything about Python that we need to
know to start writing interesting programs that deal with numbers and strings.
We now take a short break from learning the language. In the next chapter, we
use Python to solve some simple problems,
Finger exereise: Write a program that asks the uscr to input 10 integers, and
then prints the largest odd number that was entered. If no odd number was
entered, it should print a message to that effect3.1
3 SOME SIMPLE NUMERICAL PROGRAMS
Now that we have covered some basic Python constructs, it is time to start
thinking about how we can combine those constructs to write some simple
programs. Along the way, we'll sneak in a few more language constructs and
some algorithmic techniques.
Exhaustive enumeration
‘The code in Figure 3.1 prints the integer cube root, if it exists, of an
integer. If the input is not a perfect cube, it prints a message to that
effect.
Find the cube root of a perfect cube
k= intCrawinputC’ Enter an integer: '))
ns = 0
nile ans**3 < abs(x)
ans = ans + 1
Jif anss*3 t= abs(x):
print x, 'is not a perfect cube’
leise:
if x <0
print "Cube root of ' + str(x) +’ is ' + str(ans)
Figure 3.1 Using exhaustive enumeration to find the cube root
For what values of x will this program terminate?
‘The answer is, “all integers.” This can be argued quite simply.
+ The value of the expression ans"*3 starts at 0, and gets larger each time
through the loop.
‘+ When it reaches or exceeds abs(x), the loop terminates.
‘* Since abs (x) is always positive there are only a finite number of
iterations before the loop must terminate.
Whenever you write a loop, you should think about an appropriate
decrementing function. This is a function that has the following properties:
1, It maps a set of program variables into an integer.
2. When the loop is entered, its value is nonnegative.
3. When its value is <=0, the loop terminates.
4
Its value is decreased every time through the loop."
\ Of course, one can count up instead of down,Chapeer 3. Some Siople Numerical Progra:
What is the decrementing function for the loop in Figure 8,1? It is abs(x) -
ans**3.
Now, let’s insert some errors and see what happens. First, try commenting out
the statement ans = 0. The Python interpreter prints the error message,
NameError: name ‘ans' is not defined, because the interpreter attempts to
find the value to which ans is bound before it has been bound to anything, Now,
restore the initialization of ans, replace the statement ans = ans + 1 by
ans = ans, and try finding the cube root of 8. After you get tired of waiting, enter
4c (hold down the control key and the ¢ key simultaneously), This will return
you to the user prompt in the shell.
Now, add the statement,
print "Value of the decrementing function, abs(x)-ans**3,
abs (x) - ans**3
at the start of the loop, and try running it again, (The \ at the end of the first
line of the print statement is used to indicate that the statement covers multiple
lines.)
This
ime it will print,
Value of the decrementing function, abs(x)-ans**3, =
over and over again,
‘The program would have run forever because the loop body is na longer
reducing the distance between ans**3 and abs(x). When confronted with a
program that seems not to be terminating, experienced programmers ofte:
insert print statements, such as the one here, to test whether the decrementing
function is indeed being decremented,
‘The algorithimic technique used in this program is @ variant of guess and check
called exhaustive enumeration, We enumerate all possibilities until we get to
the right answer or exhaust the space of possibilities, At first blush, this may
seem like an incredibly stupid way to solve a problem. Surprisingly, however,
exhaustive enumeration algorithms are often the most practical way to solve a
problem. They are typically easy to implement and easy to understand. And, in
many cases, they run fast enough for all practical purposes. Make sure to
remove or comment out the print statement that you inserted and reinsert the
ans = ans + 1 statement, and then try finding the cube root of 1987816251, The
program will seem to finish slmost instantaneously. Now, try
7406961012236944616.
As you can see, even if millions of guesses are required, it's not usually &
problem, Modern computers are amazingly fast. It takes on the order of one
nanosecond—one billionth of a second—to execute an instruction. It’s a bit
hard to appreciate how fast that is. For perspective, it takes slightly more than
a nanosecond for light to travel a single foot (0.3 meters). Another way to think
about this is that in the time it takes for the sound of your voice to travel a
hundred feet, a modern computer can execute millions of instructions,Chapter 3. Some Single Numerical Pregrams
Just for fun, try executing the code
ax = int(rawinput(’Enter a postive integer: '))
i=0
while 4 < max:
yaatl
print +
‘See how large an integer you need to enter before there is @ perceptible pause
before the result is printed.
Finger exercise: Write a program that asks the user to enter an integer and
prints two integers, root and pwr, such that 0 < par < 6 and root is equal to
the integer entered by the user. If no such pair of integers exists, it should print
a message to that effect.
3.2
For Loops
‘The while loops we have used in this chapter are highly stylized. Bach iterates
over a sequence of integers. Python provides a language mechanism, the for
loop, that can be used to simplify programs containing this kind of highly
stylized iteration.
‘The general form of a for statement (recall that the words in italics represent
nonterminals) is:
for variable in sequence:
code block
‘The variable following for is bound to the first value in the sequence, and the
code block is executed. The variable is then assigned the second value in the
sequence, and the code block is executed again. ‘The process continues until the
sequence is exhausted or a break statement is executed within the code block.
‘The sequel y generated using the
built-in function range, which returns a sequence containing all of the integers
ce of values bound to variable is most commonl;
from its first argument (if the first argument is omitted, it defaults to 0} to one
less that the value of its last argument. For example,
range(0,3) == range(3) == (0,1,2). Less commonly, we can specify the
sequence using literal, €.g., (0, 1, 2). In Python 2.7, range generates the
entire sequence when it is invoked. Therefore, for example, the expression
‘range(1000000) uses quite a lot of memory. This can be avoided by using the
built-in function xrange instead of range, since xrange generates the values only
as they are needed by the for loop.25
Consider the cade
x=4
for 4 in range(0, x):
print +
in Python 3, range behaves the way xrange behaves in Python 2.24
Chapeer 3. Some Siople Numerical Progra:
It prints
°
1
2
3
Now, think about the code
x=4
for i in range(O, x):
print
x=5
It raises the question of whether changing the value of x inside the loop affects
the number of iterations. It does not. The range function in the line with for is
evaluated just before the first iteration of the loop, and not reevaluated for
subsequent iterations. To see how this works, consider
x=4
for j in range(x):
for i in range(x):
print
x=2
It prints
°
1
2
3
°
1
0
1
°
1
because the range function in the outer loop is evaluated only once, but the
range function in the inner loop is evaluated each time the inner for statement
is reached.
‘The code in Figure 3.2 reimplements the exhaustive enumeration algorithm for
finding cube roots, The break statement in the for loop causes the loop to
terminate before it is run on each element in the sequence over which it is
iterating. When exccuted, a break statcment exits the innermost loop in which
it is enclosed,Chapter 3. Same Sinple Numerical Provan 25
fFind the cube root of a perfect cube
= int(raw_input(’Enter an integer: '))
ffor ans in range(O, abs(x)+1)
if ans**3 == abs(x)
break
jif ans**3. = abs(x)
print x, 'is not a perfect cube’
else:
ifx<0
print "Cube root of ' + str(x) + ' is ' + str(ans)
Figure 3.2. Using for and break statements
‘The for statement can be used to conveniently iterate over characters of a string,
For example,
total = 0.
for c in "123456789":
total = total + int(c)
print total
sums the digits in the string denoted by the literal 123456789" and prints the
total.
Finger exercise: Let 5 be a string that contains a sequence of decimal numbers
separated by commas, e.g.,§ = '1.23,2.4,3.123'. Write a program that prints
the sum of the numbers in s.
3.3
‘Approximate Solutions and Bisection Search
Imagine that someone asks you to write a program that finds the square root of
any nonnegative number. What should you do?
You should probably start by saying that you need a better problem statement,
For example, what should the program do if asked to find the square root of 2?
the square root of 2 is not a rational number. This means that there is ne way
to precisely represent its value as a finite string of digits (or as a float), so the
problem as initially stated cannot be solved.
‘The right thing to have asked for is a program that finds an approximation to
the square root—i.e., an answer that is close enough to the actual square root to
be useful. We will return to this issue in considerable detail later in the book.
But for now, let's think of “close enough” as an answer that lies within some
constant, call it epsilon, of the actual answer.
Figure 3.3 implements an algorithm that finds an approximation to a square
root. It uses an operator, +=, that we have not previously used. The code ans
step is semantically equivalent to the more verbose code ans = ans+step. The
operators -= and *= work similarly.26
Chapter 3. Some Simple Numerical Programs
= 25
epsilon = 0.01
‘tep = epsilon’#2
Jnuncuesses = 0
ns = 0.0
nile abs(ans**2 - x) >= epsilon and ans <= x
ans += step
unGuesses += 1
[print ‘nunGuesses =", nunGuesses
f abs(ans**2 - x) >= epsilon
print "Failed on square root of", x
else:
print ans, ‘is close to square root of, x
Figure 3.3 Approximating the square root using exhaustive enumeration
Once again, we are using exhaustive enumeration. Notice that this method for
finding the square root has nothing in common with the way of finding square
roots using a pencil that you might have learned in middle school. It is often the
case that the best way to solve a problem with a computer is quite different from
how one would approach the problem by hand.
When the code is run, it prints
hunGuesses = 49990
4,999 is close to square root of 25
Should we be disappointed that the program didn't figure out that 25 is a perfect
square and print 5? No. The program did what it was intended to do. It would
have been OK to print 5, but no better than printing any value close enough to
5.
What do you think will happen if we set x = 0.25? Will it find a root close to
0.5? Nope. Exhaustive enumeration is a search technique that works only if
the set of values being searched includes the answer. In this case, we are
enumerating the values between 0 and x. When x is between 0 and 1, the square
root of x does not lie in this interval. One way to fix this is to change the first
line of the while loop to
while abs(ans**2 - x) >= epsilon and an
Now, let’s think about how long the program will take to run, The number of
iterations depends upon how close the answer is to zero and on the size of the
steps. Roughly speaking, the program will execute the while loop at most
x/step times,
Let’s try the code on something
then print
ger, €.g., X = 123456, It will run for a bit, and
nunGuesses = 3513631
Failed on square root of 123456
What do you think happened? Surely there exists a floating point number that
approximates the square root of 123456 to within 0.01. Why didn’t our program
find it? The problem is that our step size was too large, and the program
skipped over all the suitable answers. Try making step equal to epsilon**3 andChapter 3. Some Single Numerical Pregrams
running the program, It will eventually find a suitable answer, but you might
not have the patience to wait for it ta do so.
Roughly how many guesses will it have to make? The step size will be 0.000001
and the square root of 123486 around 361.98. This means that the program will
have to make in the neighborhood of 381,000,000 guesses to find a satisfactory
answer. We could try to speed it up by starting closer to the answer, but that
presumes that we know the answer.
‘The time has come to look for a different way to attack the problem, Wen
choose a better algorithm rather than fine tune the current one. But before
doing so, let's look at a problem that, at first blush, appears to be completely
different from root finding,
dito
Consider the problem of discovering whether or not a sequence of letters.
appears in some hard copy dictionary of the English language. Exhaustive
enumeration would, in principle, work, You could start at the first word and
examine each word until either you found a word matching the sequence of
letters or you ran out of words to examine. If the dictionary contained N words,
it would, on average, take N/2 probes to find the word. If the word were not in
the dictionary, it would take N probes, Of course, those who have had the
pleasure of actually looking a word up in a physical (rather than online}
dictionary would never proceed in this way.
Fortunately, the folks who publish dictionaries go the trouble of putting the
words in lexicographical order, This allows us to open the book to @ page where
we think the word might lie (e.g., near the middle for words starting with the
letter m]. Ifthe sequence of letters lexicographically precedes the first word on.
the page, we know to go backwards, If the sequence of letters follows the last
word on the page, we know to go forwards. Otherwise, we check whether the
sequence of letters matches a word on the page.
Now let's take the same idea and apply it the problem of finding the square root
of x. Suppose we know that a good approximation ta the square root af x lies
somewhere between 0. and max, We can exploit the fact that numbers are totally
ordered. That is to say, for any pair of distinct numbers, nt and n2, either
nt n2, So, we can think of the square root of x as lying somewhere on
the line
0. max
and start searching that interval. Since we don’t necessarily know where to
start searching, Iet’s start in the middle.
Iga saree gues EeePC
If that is not the right answer (and it won't be most of the time}, ask whether it is
too big or too small, IFit is too big, we know that the answer must lie to the left
Ifit is too small, we know that the answer must lic to the right. We then repeat
the process on the smaller interval. Pigure 3.4 contains an implementation and
test of this algorithm.Chapter 3. Some Simple Numerical Programs
= 25
psiton = 0.01
unGuesses = 0
jou = 0.0
righ = max(1.0, x)
ns = Chigh + 1ou)/2.0
hile abs(ans**2 ~ x) >= epsilon
Tow, "high
high, ‘ans
nunGuesses +=
if ans**2 < x
Yow = ans
else
high = ans
ans = (high + 1ow)/2.0
rint "nunGuesses =", nunGuesses
rint ans, ‘is close to square root of", x
Figure 3.4 Using bisection search to approximate square root
When run, it prints,
Jow = 0.0 high = 25 ans = 12.5
Jow = 0.0 high = 12.5 ans = 6.25
Jou = 0.0 high = 6.25 ans = 3.125
Jou = 3.125 high = 6.25 ans = 4.6875
Jow = 4.6875 high = 6.25 ans = 5.46875
Jow = 4.6875 high = 5.46875 ans = 5.078125
Tow 51078125 ans = 4.828125
Jou = 4.828125 high = 5.078125 ans = 4.98046875
ow = 4.98046875 high = 5.078125 ans = 5.029296875
Jow = 4.98046875 high = 5029296875 ans = 5.0048828125
Jou = 4.98046875 high = 5.0048828125 ans = 4.9267578125
ow = 4.99267578125 high = 5.0048828125 ans
‘Tow = 4.99877929688 high = 5.0048828125 ans
nunGuesses = 13
5.00030517578 is close to square root of 25
4.99877929688
°
0
0
3
4
4
4.6875 high
4
A
4
4
4
4 500183105469
Notice that it finds a different answer than our earlier algorithm. That is
perfectly fine, since it still meets the problem statement,
More important, notice that at each iteration the size of the space to be searched
is cut in half, Because it divides the search space in half at each step, it is
called a bisection search. Bisection search is a huge improvement over our
earlier algorithm, which reduced the search space by only a small amount at
each iteration
Let us try x = 123456 again. This time the program takes only thirty guesses to
find an acceptable answer. How about x = 123456789 ? It takes only forty-five
guesses.
‘There is nothing special about the fact that we are using this algorithm to find
square roots. For example, by changing a couple of 2’s to 3's, we can use it to
find a cube root of a nonnegative number. In the next chapter we wi
a language mechanism that allows us to generalize this code to find any root
introduce
Finger exercise: What would the code in Figure 3.4 do if the statement x = 25
were replaced by x = -25?Chapter 3. Some Single Numerical Pregrams
Finger exercise: What would have to be changed to make the code in Pigure
3.4 work for finding an approximation to the cube root of both negative and.
positive numbers? (Hint: think about changing Tow to ensure that the answer
lics within the region being searched.)
3.4
A Few Words About Using Floats
Most of the time, numbers of type float provide a reasonably good
approximation to real numbers, But “most of the time” is not all of the time,
and when they don’t it can lead to surprising consequences. For example, try
running the code
x = 0.0
for + "in range(10):
x=x40.1
ifx i
print x,
else:
print x, Tis not 1.0"
Perhaps you, like most people, find it doubly surprising that it prints,
1.0 is not 1.0
10°
Why docs it get to the else clause in the first place? And if it somehow does get,
there, why is it printing such a nonsensical phrase?
‘To understand why this happens, we need to understand how floating point
numbers are represented at runtime. To understand that, we need to
understand binary number
When you first earned about decimal numbers, ivc., numbers base 10, you
learned that a decimal number is represented by a sequence of the digits
0128456789. The rightmost digit is the 10° place, the next digit towards the left,
the 10" place, etc. For example, the sequence of digits 302 represents 3100 +
10+ 24, How many different numbers can be represented by a sequence of
length n? A sequence of length one can represent any one of ten numbers (0-8).
A sequence of length two can represent one hundred different numbers (0-99)
More generally, with a sequence of length n, one can represent 10” different
numbers.
Binary numbers—numbers base 2—work similarly. A binary number is
represented by a sequence of digits each of which is either Oor 1, These digits
are often called bits. ‘The rightmost digit is the 2° place, the next digit towards
the left the 2" place, ete. For example, the sequence of binary digits 101
represents 1-4 402+ 1-15, How many different numbers can be represented by
a sequence of length n? 2"
Finger exercise: What is the decimal equivalent of the binary number
30011?Chapeer 3. Some Siople Numerical Progra:
Perhaps because most have ten fingers, people seem to like to use decimals to
represent numbers. On the other hand, all modern computer systems represent
numbers in binary, This is not because computers are born with two fingers, It
is because it is easy to build hardware switches, i.c., devices that can be in only
one of two states, on or off, That the computer uses a binary representation and
people @ decimal representation can lead to occasional cognitive dissonance,
In all modern programming languages non-integer numbers are implemented
using a representation called floating point, ‘Think of floating point as a variant
of scientific notation, In scientific notation, we represent a number as a product
of the significant digits of the number anc 10 raised to some exponent, For
example, we represent 1949 as 1.949X 10°, Floating point works the same way,
except we represent the significant digits and exponents in binary rather than
decimal and raise 2 rather than 10 to the exponent, For example, the number 8
would be represented as the pair (1.0, 11)
‘The number of significant digits determines the preeision with which numbers
can be represented. If for example, there were only 2 significant binary digits,
the decimal number 5 could not be represented exactly. It would have to be
converted to some approximation of 5. That approximation is called the
rounded value.
Fortunately, the number of significant cligits used in modern computing systems.
is so large that you need not worry about rounding when dealing with integers.
But fractions are a different stor
Fractions are represented in floating point as rational numbers—one integer
divided by another. Consider the decimal fraction 1/8 , which can also be written
8 0.125, ie,, 190°" + 2107 + 510%, It is equivalent to the binary number 0.001—
0-21 + 02% + 1-2°, Ifour floating point representation had 4 significant binary
digits (64 bits is more typical today, but there seems little point in filling the
page with leading 0's) the number 0.128 could be precisely represented in floating
point as the pair (0001, -11).
What about the decimal fraction 1/10, which we write in Python as 0.1? The best
we can do with four significant digits is (0001,-11}. This is equivalent to 1.0/8.0,
i.e,, 0.125. Ifwe had five significant binary digits, we would represent 0.1 as
(11001, -1000), which is equivalent to 25.0/256.0, i.e., 0.09765625. How many
significant digits would we need to get an exact floating point representation of
0.1? An infinite number of digits! ‘There do not exist integers sig and p such that,
sig+ 2° equals 0.1. So no matter how many bits Python (or any other language}
chooses to use to represent floating point numbers, it will only be able to
represent an approximation to 0.1, In most Python implementations, there are
53 bits of precision available for floating point numbers, so the significant digits
stored for the decimal number 0.1 will be
11001100110011001100110011001100110011001100110011001
‘This is equivalent to the decimal number
(0.1000000000000000055511151231257827021181583404541015625Chap 3. Some Simple Neral Pregrat 31
Pretty close to 1/10, but not 1/10. Returning to the original mystery, why does
x= 0.0
for i in range(10):
x=x +01
ifx =u
print
else:
print x, ‘is not 1.0"
Te 107
print
4.0 4s not 1.0
We now see that the test x == 1.0 produces the result False because the value
to which x is bound is not exactly 1.0. What gets printed if we add to the end of
the else clause the code print x == 10,040,1? It prints False because during at
Ieast one iteration of the loop Python ran out of significant digits and did some
rounding.
Finally, why does the code print x print 0.1 rather than the actual value of the
variable x? Because the designers of Python thought that would be convenient
for users if print did some automatic rounding. This is probably an accurate
assumption most of the time, However, it is important to keep in mind that
what is being displayed does not necessarily exactly match the value stored in
the machine.
By the way, if' you want to explicitly round a floating point number, use the
For example round(2**0.5, 3) will print 1.414 as an
approximation to the square root of 2
Does the difference between real and floating point numbers reslly matter?
Most of the time, mercifully, it does not. However, one thing that is almost
always worth worrying about is tests for equality. As we have seen, using == to
compare two floating point values can produce a surprising result. It is almost
always more appropriate to ask whether two floating point values are close
enough to each ather, not whether they are identical. So, for example, it is
better to write abs (x-y) < 0.0001 rather than x == y.
Another thing to worry about is the accumulation of rounding errors. Most of
the time things work out OK because sometimes the number stored in the
computer is a little bigger than intended, and sometimes it is a little smaller
than intended, However, in some programs, the errors will all be in the same
direction and accumulate over time.
3.5
Newton-Raphson
‘The most commonly used approximation algorithm is usually attributed to Isaac
Newton. Itis typically called Newton’s method, but is sometimes referred to asChapter 3. Some Simple Numerical Programs
the Newton-Raphson method.'© It can be used to find the real roots of many
functions, but we shall look at it only in the context of finding the real roots of a
polynomial with one variable. The generalization to polynomials with multiple
variables is straightforward both mathematically and algorithmically.
A polynomial with one variable (by convention, we will write the variable as x) is
either zero or the sum of a finite number of nonzero terms, ¢.g., 3x7 + 2x +3.
Each term, e.g., &x°, consists of a constant (the coefficient of the term, 9 in this
case) multiplied by the variable (x in this case) raised to a nonnegative integer
exponent (2 in this case). The exponent on a variable in a term is called the
degree of that term. The degree of a polynomial is the largest degree of any
single term. Some examples are, 3 (degree 0) , 25x +12 (degree 7), and 3x? (degree
2). In contrast, 2/ and x°° are not polynomials.
If pis a polynomial and r a real number, we will write pt) to stand for the value of
the polynomial when x=r. A root of the polynomial p is a solution to the
equation p =, i.e., an r such that p(f)=0. So, for example, the problem of finding
an approximation to the square root of 24 can be formulated as finding an x
such that x?~24=0.
Newton proved a theorem that implies that if a value, call it guess, is an
approximation to a root of a polynomial, then guess - p(guess)/p (guess), where p' is
the first derivative of p, is a better approximation.1"
For any constant k and any coefficient c, the first derivative of ex* +k is 20x. For
example, the first derivative of x°-k is 2x. Therefore, we know that we can
improve on the current guess, call it y, by choosing as our nest guess
y--W/2y. This is called suecessive approximation. Figure 3.5 contains
code illustrating how to use this idea to quickly find an approximation to the
square root.
fewton-Raphson for square root
Find x such that x**2 - 24 is within epsilon of 0
psiton = 0.01
= 24.0
ess = y/2.0
hile abs(guess*guess - y) >= epsilon:
guess = guess - ((Cguess*#2) - y)/(2*guess))
rint "Square root of", y, ‘is about’, guess
Figure 3.5 Newton-Raphson method
1 Joseph Raphson published a similar method about the same time as Newton.
17 The first derivative of a function ffx) can be thought of as expressing how the value of
{() changes with respect to changes in x. If you haven't previously encountered
derivatives, don't worry. You don't need to understand them, or for that matter
polynomials, to understand the implementation of Newton's snethod,Chapter 3. Some Single Numerical Pregrams
Finger exercise: Add some code to the implementation of Newton-Raphson that
keeps track of the number of iterations used to find the root. Use that code as
part of @ program that compares the efficiency of Newton-Raphson and bisection
search. (You should cliscover that Newton-Raphson is more efficient)4 FUNCTIONS AND ABSTRACTION BY SPECIFICATION
So far, we have introduced numbers, assignments, input/output, comparisons,
and looping constructs. How powerful is this subset of Python? In a theoretical
sense, it is as powerful as you will ever need. Such languages are called Turing
complete. This means that if a problem can be solved via computation, it can
be solved using only those statements you have already seen,
Which isn’t to say that you should use only these statements. At this point we
have covered a lot of language mechanisms, but the code has been a single
sequence of instructions, all merged together. For example, in the last chapter
we looked at the code in Figure 4.1.
= 25
fpsiton = 0.01
huncuesses = 0
flow = 0.0
high = max(1.0, x)
ns = Chigh + Tow)/2.0
hile abs(ans**2 ~ x) >= epsilon:
nunGuesses += 1
if ans**2 < x
Tow = ans
else:
high = ans
ans = (high + low)/2.0
print ‘nunGuesses =", numGuesses
print ans, "is close to square root of’, x
Figure 4.1 Using bisection search to approximate square root
This is a reasonable piece of code, but it lacks general utility. It works only for
values denoted by the variables x and epsilon. This means that if we want to
reuse it, we need to copy the code, edit the variable names, and paste it where
we want it. Because of this we cannot easily use this computation inside of
some other, more complex, computation.
Furthermore, if we want to compute cube roots rather than square roots, we
have to edit the code, If we want a program that computes both square and
cube roots (or for that matter square roots in two different places), the program.
would contain multiple chunks of almost identical code. This is a very bad
thing. The more code a program contains, the more chance there is for
something to go wrong, and the harder the code is to maintain. Imagine, for
example, that there was an error in the initial implementation of square root,
and that the error came to light when testing the program. It would be all too
easy to fix the implementation of square root in one place and forget that there
was similar code elsewhere that was also in need of repair.
Python provides several linguistic features that make it relatively easy to
generalize and reuse code, The most important is the functionChapin t Pes
nctions and Abortion by Speiaton 35
41
Functions and Scoping
We've already used a number of built-in func
4.1. The ability fo
if they were built-
jon, €.g., max and abs in Figure
r programmers to define and then use their own functions, as
. is @ qualitative leap forward in convenience.
4.1.1 Function definitions
In Python each funetion definition is of the forms
def functtontiane(FormaTParaneters):
{fnctionBody
For example, we could define the function max by the code,
def maxGx, y):
if >
return x
else:
return y
def is. keyword that tells Python that a function is about to be defined, The
function name (max in this example) is simply a name that is used to refer to the
function.
‘The sequence of names (x, y in this example} within the parentheses following
the function name are the formal parameters of the function. When the
function is used, the actual parameters (often referred to as arguments of the
fanction invocation (also referred to as a function call] are bound (as in an
assignment statement} to the formal parameters. For example, the invocation
mnax(3, 4)
binds x to 3 and y to 4
‘The function body is any piece of Python code. There is, however, a special
statement, return, that can be used only within the body of a function.
A function call is an expression, and like all expressions it has a value, That
value is the value returned by the invoked function. For example, the value of
the expression max(3,4)*nax(3,2) is 12, because the first invocation of max
returns the int 4 and the second returns the int 3, Note that execution of a
return statement terminates that invocation of the function,
To recapitulate, when a function is called
1, The expressions that make up the actual parameters ane evaluated, and
the formal parameters of the function are bound to the resulting values.
For example, the invocation max(3+4, 2) will bind the formal parameter x
to 7 and the formal parameter y to whatever value the variable z has
when the invocation is evaluated.
1 Recall that italics ig used to indicate nonterminal symbols.36
Chapter Functions aud Abstraction by Spedfeation
2. The point of execution (the next instruction to be executed) moves from
the point of invocation to the first statement in the body of the function.
3. The code in the body of the function is executed until either a return
statement is encountered, in which case the value of the expression
following the return becomes the value of the function invocation, or
there are no more statements to execute, in which case the function
returns the value None. (If no expression follows the return, the value of
the invocation is None.)
4, The value of the invocation is the returned value.
5. The point of execution is transferred back to the code immediately
following the invocation,
Parameters provide something called lambda abstraction.'° It allows
programmers to write code that manipulates not specific objects, but instead
whatever objects the caller of the function chooses to use as actual parameters.
Finger exercise: Write a function isIn that accepts two strings as arguments
and returns True if either string occurs anywhere in the other, and False
otherwise. Hint: you might want to use the built-in str operation in.
4.1.2 Keyword Arguments and Default Values
In Python, there are two ways that formal parameters get bound to actual
parameters. The most common method, which is the only one we have used
thus far, is called positional—the first formal parameter is bound to the first
actual parameter, the second formal to the second actual, ete. Python also
supports what it calls keyword arguments, in which formals are bound to
actuals using the name of the formal parameter. Consider the function
definition in Figure 4.2. The function printNane assumes that FirstName and
‘astNane are strings and that reverse is a Boolean. If reverse = True, it prints
FirstName lastNane, otherwise it prints TastNane, FirstName.
lef printName(firstNane, lastNane, reverse):
if reverse
print lastName + ', ' + FirstName
else:
print FirstName, lastName
Figure 4.2 Function that prints a name
.ch of the following is a legal invocation of printNane:
printName('Olga', 'Puchmajerova’, True)
printNane('Olga', 'Puchmajerova’, False)
printNane('Olga’, 'Puchmajerova', reverse = False)
printNane('Olga', lastName = ‘Puchnajerova’, reverse = False)
19 The name lambda abstraction is derived from some mathematics developed by Alonzo
Church in the 1930's and 1940's,Chapter + Panctions and Abrasion by Speifeation
‘Though the keyword arguments can appear in any order in the list of actual
parameters, it is not legal to follow a keyword argument with a non-keyword
argument, Therefore, an error message would be produced by,
printNane('O1ga’, TastNane = 'Puchnajerova’, False)
Keyword arguments are commonly used in conjunction with default parameter
values. We can, for example, write
def printNane(firstName, TastName, reverse = False):
if reverse:
print TastName +", ' + FirstName
else:
print firstName, lastName
Default valucs allow programmers to call a function with fewer than the
specified number of arguments. For example,
printNane¢'lga", 'Puchnajerova")
printNane('0ga", 'Puchnajerova", reverse = True)
will print
Olga Puchnajerova
Puchnajerova, Olga
4.1.3 Scoping
Let's look at another smell example,
def FC:
yeu
x=xty
print "x =", x
return x
3
2
z= FOO
print ‘2
print "x
print ty =" y
when
+z
at
n, this code prints,
x-4
za4
x=3
ye2
What is going on here? At the call of f, the formal parameter x is locally bound
to the value of the actual parameter x. It is important to note that though the
actual and formal parameters have the same name, they are not the same
variable, Each function defines a new mame space, also called a seope. The
formal parameter x and the local variable y exist only within the scope of the
definition of f. The assignment statement x = x + y within the function body
binds the local name x to the object 4. It has no effect at all on the bindings of
the names x and y that exist outside the scape of F.38
Chapter Functions aud Abstraction by Spedfeation
Here's one way to think about this:
At top level, i.e., the level of the shell, a symbol table keeps track of all
names defined at that level and their current bindings.
When a function is called, a new symbol table (sometimes called a stack
frame) is created. This table keeps tracks of all names defined within
the function (including the formal parameters) and their current
bindings. Ifa function is called from within the function body, yet
another stack frame is created.
When the function completes, its stack frame goes away.
In Python, one can always determine the scope of a name by looking at the
program text. This is called static or lexical scoping. Figure 4.3 contains a
slightly more elaborate example.
lef FOO:
def 90
x= ‘abet
print 'x
def hO
z=x
print 'z
xaxel
print ‘x
ho.
90.
print "x
return g
=3
foo
int "x
Figure 4.3 Nested scopes
‘The history of the stack frames associated with the code in Figure 4.3 is
depicted in Figure 4.4.
h b b h ni
a 8 9
a f f f € € fChapin t Pes
nctions and Abortion by Speiaton 39
Figure 4.4 Stack frames
‘The first column contains the set of names known outside the body of the
function f, i¢., the variables x and 2, and the function name f, The first
assignment statement binds x to 3
‘The assignment statement z = Fd first evaluates the expression F(x) by
invoking the function f with the value to which x is bound, When fis entered, a
stack frame is created, as shown in column 2. The names in the stack frame are
x (the formal parameter, not the x in the calling environment), g and h, The
variables g and h are bound to objects of type function. The properties of each
of these functions are given by the function definitions within f.
When h is invoked from within f, yet another stack frame is created, as shown in
column 3, This frame contains only the local variable z, Why does it not also
contain x? A name is added to the scope associated with a function only if that
name is either a formal parameter of the function or a variable that is bound to
an object within the body of the function. In the body of h, x occurs only on the
right-hand side of an assignment statement. The appearance of a name that is
not bound anywhere in the function body causes the interpreter to search the
previous stack frame associated with the scope within which the function is
defined. If the name is found, the value to which it is bound is used. [fit is not
found there, an error message is produced.
When h returns, the stack frame associated with the invocation of h goes away
(ic., it is popped off the top of the stack), as depicted in column 4, Note that we
never remove frames from the middle of the stack, but only the most recently
added frame, It is because it has this “last in first out” behavior that we refer to
its. stack (think of a stack of trays waiting to be taken in a cafeteria)
Next g is invoked, and a stack frame containing g's local variable x is added
(colurnn 5). When g returns, that frame is popped (column 6). When f returns,
the stack frame containing the names associated with f is popped, getting us
back to the original stack frame (column 7}
Notice that when f returns, even though the variable g no longer exists, the
object of type function to which that name was once bound still exists. This is
because functions are objects, and can be returned just like any other kind of
object. So, 2 can be bound to the value returned by f, and the function call 2)
can be used to invoke the function that was bound to the name g within f—even,
though the name g is not known outsice the context of f.
So, what does the code in Figure 2.1 print? It prints
abe
4
3
=
abe
‘The order in which references to a name accur is not germane, If'an object is
bound to a name anywhere in the function body (even after it occurs in an1 Speieation
expression before it appears as the left-hand-side of an assignment), it is treated.
as local to that function.
Consider, for example, the code
def FO:
print x
def 90:
print x
xel
x=3
FO
x3
90
It prints 3 when f is invoked, but an error message is printed when it
encounters the print statement in g because the assignment statement following
the print statement causes x to be local to g. And because x is local to g, it has
no value when the print statement is executed,
Confused yet? It takes most people a bit of time to get their head around scope
rules. Don't let this bother you. For now, charge ahead and start using
functions, Most of the time you will find that you only want to use variables
that are local to a function, and the subtleties of scoping will be irrelevant.
4.2
Specifications
Figure 4.5 defines a function, FindRoot, that generalizes the bisection search we
used to find square roots in Figure 4.1. Tealso contains a function,
testFindRoot, that can be used to test whether or not FindRoot works as
intended
‘The function testFindRoot is almost as long as FindRoot itself. To
inexperienced programmers, writing test funetions such as this often seems to
be a waste of effort. Experienced programmers know, however, that an
investment in writing testing code often pays big dividends, It certainly beats
typing test cases into the shell over and over again during debugging (the
process of finding out why & program does not work, and then fixing it), It also
forces us to think about which tests are likely to be most illuminating,
‘The text between the triple quotation marks is called a deestring in Python. By
convention, Python programmers use docstrings to provide specifications of
functions, ‘These doctrings can be accessed using the built-in function help.
If we enter the shell and type he1p(abs), the system will display
Help on built-in function abs in module _buiTtin_:
abs(...)
‘abs (number) -> nunber
Return the absolute value of the argument.Chapter 4. Functions and Abstraction by Speifcation 4
If the code in Figure 4.5 has been loaded into Idle, typing help(FindRoot) in the
shell will display
Help on function findRoot in module _main_;
FindRoot(x, power, epsilon)
x and epsilon’ int or float, power an int >= 1
returns a float y s.t. y**power is within epsilon of x.
If such a float does not exist, it returns None
Ifwe type fFindRoot( in either the shell or in the editor, the list of formal
parameters and the first line of the docstring will be displayed.
léef FindRoot(x, power, epsilon):
"mx and epsilon int or float, power an int,
epsilon > 0 & power >=".
returns float y such that y**power is within epsilon of x.
If such a Float does not exist, it returns None’
if x < 0 and powers? == 0:
return None
low = min(-1.0, x)
max(1.0, x)
ans = (high + 1ow)/2.0
while abs(ans**power - x) >= epsilon
if ans**power < x
‘Tow = ans
else
high = ans
ans = (high + 1ov)/2.0
return ans
lief testFindRoot():
epsilon = 0.0001
for x in (0.25, -0.25, 2, -2, 8, -8)
for power in range(L, 4)
print ‘Testing x'= ‘
+ strOo +\
and poner = ' + str(power)
res = findRoot(x, power, epsilon)
if res == None:
print ' No root*
else
print *
+ str(res**power) + 'm=" + stro
Figure 4.5 Finding an approximation to a root
A specification of a function defines a contract between the implementer of a
function and those who will be writing programs that use the function. We will
refer to the users of a function as its clients. This contract can be thought of as
containing two parts:
1, Assumptions: These describe conditions that must be met by users of
the function. ‘Typically, they describe constraints on the actual
parameters. Almost always, they specify the acceptable set of types for
each parameter, and not infrequently some constraints on the value of
one or more of the parameters. For example, the first two lines of the
docstring of FindRoot describe the assumptions that must be satisfied by
clients of FindRoot.1 Speieation
2, Guarantees: These describe conditions that must he met by the function,
provided that it has been called in a way that satisfies the assumptions,
‘The last three lines of the docstring of FindRoot describe the guarantees
that the implementation of the function must meet.
Functions are a way of creating computational elements that we can think of as
primitives. Just as we have the builtin functions max and abs, we would like to
have the equivalent of a built-in function for finding roots and for many other
complex operations. Functions facilitate this by providing decomposition and
abstraction.
Decomposition creates structure. It allows us to break a problem into modules
that are reasonably self-contained, and that may be reused in different settings.
Abstraction hides detail, It allows us to use a piece of code as if it were @ black-
box—that is, something whose interior details we cannot see, don't need to see,
and shouldn't even want to see. The essence of abstraction is preserving
information that is relevant in a given context, and forgetting information that is
irrelevant in that context. The key to using abstraction effectively in
programming is finding a notion of relevance that is appropriate for both the
builder of an abstraction and the potential clients of the abstraction. That is the
true art of programming.
Abstraction is all about forgetting. There are lots of ways to model this, for
example, the auditory apparatus of most teenagers.
Teenager say:
May [borrow the car tonight?
Parent says: Yes, but be back before midnight, and make sure that the gas tank
is ful
‘Teenager hears: Yes.
‘The teenager has ignored all of those pesky details that he or she consice:
irrelevant, Abstraction is a many-to-one process, Had the parent said Yes, but
be back before 2:00. a.m., and make sure that the car is clean, it would also have
been abstracted to Yes.
By way of analogy, imagine that you were asked to produce an introductory
computer science course containing twenty-five lectures. One way to do this
would be to recruit twenty-five professors, and ask each of them to prepare a
fifty-minute lecture on their favorite topic. Though you might get twenty-five
wonderful hours, the whole thing is likely to feel like a dramatization of
Pirandello’s “Six Characters in Search of an Author" (or that political science
course you took with fifteen guest lecturers). If each professor were working in
isolation, they would have no idea how to relate the material in their lecture to
the material covered in other lectures.
Somehow, one needs to let everyone know what everyone else is doing, without
generating so much work that nobody is willing to participate. This is where
abstraction comes in. You could write twenty-five specifications, each saying
21 “Where ignorance is bliss, "is folly to be wise."—Thomas GrayChapin t Pes
nctions and Abortion by Speiaton B
what material the students should learn in each lecture, but not giving any
detail about how that material should be taught. What you got might not be
pedagogically wonderful, but at least it might make sense.
‘This is the way organizations go about using teams of programmers to get things
done, Given a specification of a module, a programmer can work on
implementing that module without worrying unduly about what the other
programmers on the team are doing. Morcover, the other programmers can usc
the specification to start writing code that uses that module without worrying
unduly about how that module is to be implemented.
‘The specification of FindRoot is an abstraction of all the possible
implementations that meet the specification. Users of findRoot can assume that
the implementation meets the specification, but they should assume nothing
more. For example, they can assume that the call FindRoot (4.0, 2, 0.01)
returns some value whose square is between 3.99 and 4.01. The value returned
could be positive or negative, and even though 4.0, is a perfect square the value
returned might not be 2.0 or 2.0.
4.3
Recursion
You may have heard of reeursion, and in all likelihood think of it as a rather
subtle programming technique. That's an urban legend spread by computer
scientists to make people think that we are smarter than we really are.
Recursion is @ very important idea, but it’s not so subtle, and it is more than a
programming technique.
As a descriptive method recursion is widely used, even by people who would
never dream of writing a program.
Consider part of the legal code of the United States defining the notion of a
‘natural-bon” citizen. Roughly speaking, the definition is as follows
1, Any child born inside the United States,
2, Any child born in wedlock outside the United States both of whose
parents are citizens of the U.S., as long as one parent has lived in the
U.S. prior to the birth of the child, and
3. Any child born in wedlock outside the United States one of whose
parents is @ U.S. citizen who has lived at least five years in the U.S. prior
to the birth of the child, provided that at least two of those years were
alter the citizen’s fourteenth birthday.
‘The first part is simple; if you are born in the United States, you are a natural-
born citizen (such as Barack Obama}. If you are not born in the U.S., then one
has to decide if your parents are U.S. citizens (either natural born or
naturalized). To determine if someone’s parents are U.S. citizens, one may have
to look at their grandparents, and so on,
In general, a recursive definition is made up of wo parts. There is at least one
base case that directly specifies the result for a special case (case 1 in the
example above), and there is at least one reeursive (inductive) case (cases 24
Chapter Functions aud Abstraction by Spedfeation
and 3 in the example above) that defines the answer in terms of the answer to
the question on some other input, typically a simpler version of the same
problem,
‘The world’s simplest recursive definition is probably the factorial function
(typically written in mathematics using !) on natural numbers.2! The classic
inductive definition is,
n+ ten!
‘The first equation defines the base case. The second equation defines factorial
for all natural numbers, except the base case, in terms of factorial of the
previous number.
Figure 4.6 contains both an iterative (fact) and a recursive (factR)
implementation of factorial.
ict I(n):
‘Assumes th
returns n!
res = 1
while n> 1
nel
return res
nis an int> 0
lef factR(n)
"Assumes th
returns nl
ifn
return n
return n*factR(n - 1)
nis an int > 0
Figure 4.6 Iterative and recursive implementations of factorial
n
Still, the second is a more obvious translation of the original recursive definition,
hard to follow.
function is sufficiently simple that neither implementatioi
It almost seems like cheating to implement factR by calling factR from within
the body of factR. It works for the same reason that the iterative
implementation works. We know that the iteration in factI will ters
ate
because n starts out positive and each time around the loop it is reduced by 1
‘This means that it cannot be greater than 1 forever. Similarly, if FactR is called
with 1, it returns a value without making a recursive call. When it does make a
recursive call, it always does so with a value one less than the value with which
it was called. Eventually, the recursion terminates with the call factR(1).
21 The exact definition of “natural number” is subject to debate, Some define it as the
positive integers and others as the nonnegative integers. That's why we were explicit
about the possible values of n in the docstring in Figure 4.6.Chapin t Pes
nctions and Abortion by Speiaton 45
4.3.1 Fibonacci numbers
‘The Fibonacci sequence is another common mathematical function that is
usually defined recursively. “They breed like rabbits,” is often used to describe a
population that the speaker thinks is growing too quickly. In the year 1202, the
Italian mathematician Leonardo of Pisa, also known as Fibonacci, developed a
formula designed to quantify this notion, albeit with some not terribly realistic
assumptions,
‘Suppose a newly born pair of rabbits, one male and one female, are put ina pen
(or worse, released in the wild], Suppose further that the rabbits are able to
mate at the age of one month (which, astonishingly, some breeds can) and have
one-month gestation period (which, astonishingly, some breeds do). Finally,
‘suppose that these mythical rabbits never die, and that the female always
produces one new pair (one male, one female) every month from its second
month on, How many pregnant rabbits will there be at the end of six months?
(On the last day of the first month (call it month 0}, there will be one female
(ready to conceive on the first day of the next month). On the last day of the
second month, there will still be only one female (since she will not give birth,
until the first day of the next month), On the last day of the next month, there
will be two females (one pregnant and one not). On the last day of the next
month, there will be three females (two pregnant and one not), And soon. Let's
look at this progression in tabular form.
Notice that in this table, for month n> 1, females(n) =femalesin-_ [Month | Females
4) +females(n-2). This is not an accident. Each female that z a
was alive in month n-1 will still be alive in month n, In
addition, each female that was alive in month n-2 will a a
produce one new female in month n, The new females can 2 2
be added to the females alive in month m-1 to get the number -—> z
of females in month n.
7 5
‘The growth in population is described naturally by the = T
recurrence: z =
females(0) = 1
females(1) = 1
females(n + 2) = females(n#1) + females(n)
‘This definition is a little different from the recursive definition of factorial:
‘* It has two base cases, not just one. In general, you can have as many
base cases as you need, and
‘© In the recursive case, there are two recursive calls, not just one. Again
there can be as many as you need.Chapter. Functions and Abrasion by Spesifation
Figure 4.7 contains a straightforward implementation of the Fibonacci
recurrence,” along with a function that can be used to test it.
lef Fib(n):
“assumes nan int >= 0
Returns Fibonacci of n’
Oorn== 1:
return 1
else:
return fib(n-1) + Fib(n-2)
ifn
lef testFib(n):
for i in range(n+1):
print (‘ib of, i, '=', ib(i))
Figure 4.7 Recursive implementation of Fibonacei sequence
Writing the code is the easy part of solving this problem. Once we went from the
vague statement of a problem about bunnies to a set of recursive equations, the
code almost wrote itself. Finding some kind of abstract way to express a
solution to the problem at hand is very often the hardest step in building a
useful program, We will talk much more about this later in the book.
As you might guess, this is not a perfect model for the growth of rabbit,
populations in the wild. In 1859, Thomas Austin, an Australian farmer,
imported twenty-four rabbits from England, to be used as targets in hunts, Ten
years later, approximately two million rabbits were shot or trapped each year in
Australia, with no noticeable impact on the population. That's a lot of rabbits,
but not anywhere close to the 120" Fibonacci number.2°
Though the Fibonacci sequence=" does not actually provide a perfect model of
the growth of rabbit populations, it does have many interesting mathematical
properties. Fibonacci numbers are also quite common in nature.25
2 While obviously correct, this is a terribly inelficient implementation of the Fibonacci
function. Later in the beck, we present a more efficient version.
2. The damage done by the descendants of those twenty-four cute bunnies has been
estimated to be $600M/year, and they are in the process of eating many native plants
into extinction,
2° That we call this a Fibonacci sequence is an example of a Burocentrie interpretation of
history. Fibonacci’s great contribution to European mathematics was his book Liber
‘Abaci, which introduced to European mathematicians many concepts already well known
to Indian and Arabic scholars. These concepts included Hindu-Arabic numerals and the
decimal system, What we today call the Fibonacci sequence was taken from the work of
the Sanskrit mathematician Pingala,
25 Ifyou are feeling especially geeky, try writing a Fibonacci poem. This is a form of
poetry in which the number of syllables in each line is equal to the total number of
syllables in the previous two lines, Think of the first line (which has zero syllables) as a
place to take a deep breath before starting to read your poem,Chapter 4. Functions and Abstraction by Speifcation 7
Finger exercise: When the implementation of fib in Figure 4.7 is used to
compute fib(5), how many times does it compute the value fib(2)?
4.3.2 Palindromes and divide-and-conquer
Recursion is also useful for many problems that do not involve numbers. Figure
4.8 contains a function, isPalindrone, that checks whether a string reads the
same way backwards and forwards.
jef_isPalindrome(s):
ssumes s is an str
Returns True if s is a palindrome; False otherwise.
Punctuation marks, blanks, and capitalization are
ignored."
def tochars(s):
5 = s.loverO)
for ¢ ins
if ¢ in ‘abedefghijkImnopqrstuvuxyz":
return ans
def isPal(s)
if Ten(s) <= 1:
return True
else
return s[0]
s{-1] and isPal(s[1:~11)
return isPal(toChars(s))
Figure 4.8 Palindrome testing
‘The function isPalindrone contains two internal “helper” functions. This should
be of no interest to clients of the function, who should care only that
‘isPalindrone meets its specification. But you should care, because there are
things to learn by examining the implementation.
‘The helper function tochars converts all letters to lowercase and removes all
non-letters. It starts by using a built-in method on strings to generate a string
that is identical to s, except that all uppercase letters have been converted to
lowercase. We will talk a lot more about method invocation when we get to
classes. For now, think of it as a peculiar syntax for a function call. Instead of
putting the first (and in this case only) actual parameter inside parentheses
following the function name, we use dot notation to place that argument before
the function name,
‘The helper function isPal uses recursion to do the real work. The two base
cases are strings of length zero or one. This means that the recursive part of the
implementation is reached only on strings of length two or more. The conjunct
2° When two Boolean. valued expressions are connect by “and,” each expression is called
aconjunet. If they are connected by “or,” they are called disjunets.48
Chapter Functions aud Abstraction by Spedfeation
in the else clause is evaluated from left to right. The code first checks whether
the first and last characters are the same, and if they are goes on to check
whether the string minus those two characters is a palindrome. That the second
conjunct is not evaluated unless the first conjunct evaluates to True is
semantically irrelevant in this example. However, later in the book we will see
examples where this kind of short-circuit evaluation of Boolean expressions is
semantically relevant.
‘This implementation of isPalindrone is an example of a problem solving
principle known as divide-and-conquer. The idea is to conquer a hard problem
by breaking it into a set of subproblems with the properties that
+ the subproblems are easier to solve than the original problem, and
+ solutions of the subproblems can be combined to solve the original
problem,
In this case, we solve the problem by breaking the original problem into a
simpler version of the same problem (checking whether a shorter string is a
palindrome), plus some simple things we know how to do (comparing single
characters), Figure 4.9 contains some code that can be used to visualize how
this works.
ief isPalindromel(s)
"assumes s is an str
Returns True if s is a palindrome and False otherwise
Punctuation marks, blanks, and capitalization are
‘ignored.
def tochars(s)
s = s.lowerO
for c ins
if ¢ in ‘abcdefghijkImopqrstuvunyz" :
return ans
def isPal(s)
print ' isPal called with", s
if len(s) <= 1
print ' About to return True from base case’
return True
else
ans = s[0] == s{-1] and isPal(s(1:-1])
print * About to return’, ans, ‘for’, s
return ans
return isPal(tochars(s))
lef testIsPalindromel():
print ‘Try dogGod’
print isPalindromel¢' dogGod")
print ‘Try doGood"
print isPalindromel¢' doGood")
Figure 4.9 Code to visualize palindrome testingChapter + Panctions and Abrasion by Speifeation 49
When the code in Figure 4.9 is ru
Try dogGed
}sPal called with dogged
jsPal called with oggo
isPal called with 99
sPal called with
About to return True from base case
About to return True for ag
About to return True for oggo
About to return True for doggod
Teue
Tey doGood
}sPal called with dagood
sPal called with ogoo
jsPal called with go
About to return False for go
About to return False far ogoo
About to return False for dogood
False
it will print
Divide-and-conquer is a very old idea, Julius Caesar practiced what the
Romans referred to as divide et impera (divide and rule). The British practiced it
brilliantly to control the Indian subcontinent, Benjamin Franklin was well
aware of the British expertise in using this technique, prompting him to say at
the signing of the U.S, Declaration of Independence, We must all hang togethe!
or assuredly we shall all hang separately.”
44
Global Variables
If you tried calling fib with a large number, you probably noticed that it took a
very long time to run. Suppose we want to know how many recursive calls are
made? We could do a careful analysis of the code and figure it out, and later in
the book we will talk about how to do that. Another approach is to add some
code that counts the number of calls. One way to do that uses global variables.
Until now, all of the functions we have written communicate with their
environment solely through their parameters and return values. For the most
part, this is exactly as it should be. It typically leads to programs that are
relatively easy to read, test, and debug. Every once in a while, however, global
variables come in handy. Consider, the code in Figure 4.10.