<<

Python Programming for the Mathematically Literate

A Brief Introduction

S. R. Doty

Department of and Statistics Loyola University Chicago Chicago, IL 60660 USA

c 2013 All rights reserved

September 9, 2013 Contents

1 Preliminaries 1 1.1 WhatisPython? ...... 1 1.1.1 Freeandopen-sourcesoftware ...... 2 1.1.2 Libraries...... 2 1.1.3 Sage ...... 2 1.2 Installationanddocumentation ...... 3 1.3 Python2versusPython3 ...... 3

2 Getting Started 4 2.1 RunningPythoninaterminal ...... 4 2.1.1 Integerdivision ...... 6 2.1.2 Variablesandassignment...... 6 2.1.3 Themagicunderscorevariable...... 7 2.1.4 Compoundcommands ...... 7 2.2 RunningPythoninIDLE ...... 8 2.3 Quittingtheinterpreter ...... 9 2.4 Loadingcommandsfromthelibrary...... 9 2.4.1 Importingfunctions...... 9 2.4.2 Importingmodules ...... 10 2.5 Definingfunctions...... 10 2.5.1 Reservedwords ...... 12 2.5.2 Lambdafunctions...... 12 2.6 Files ...... 12

ii CONTENTS iii

2.6.1 RunninginaTerminal ...... 13 2.6.2 Importingfromthecommandline ...... 13 2.6.3 UsingIDLE ...... 14 2.7 Thedevelopmentcycle ...... 14 2.7.1 Syntaxerrors ...... 15 2.7.2 Logicalerrors ...... 15 2.7.3 Exceptions...... 15 2.7.4 Testing...... 16 2.8 Scripts...... 16

3 Python Commands 18 3.1 Commentsanddocstrings ...... 18 3.2 Numbersandotherdatatypes...... 19 3.2.1 The type ...... 19 3.2.2 Strings...... 20 3.2.3 Listsandtuples...... 21 3.2.4 The range function...... 21 3.2.5 Booleanvalues ...... 23 3.3 Expressions ...... 23 3.3.1 Impliedcontinuation ...... 24 3.4 Operators ...... 24 3.4.1 Arithmeticoperators ...... 25 3.4.2 Comparisonoperators ...... 26 3.4.3 Membershiptesting...... 26 3.5 Variablesandassignment...... 27 3.5.1 Variablenamingconventions ...... 27 3.5.2 Assignmentstatements ...... 27 3.6 Decisions...... 29 3.7 Loops ...... 30 3.7.1 for loop...... 30 3.7.2 while loop...... 31 iv CONTENTS

3.7.3 break, continue, and pass ...... 31 3.7.4 else inloops ...... 33 3.8 Lists ...... 33 3.8.1 Lengthofalist;theemptylist...... 34 3.8.2 Sublists(slicing) ...... 35 3.8.3 Joiningtwolists...... 36 3.8.4 Multiplyingalistbyanumber...... 36 3.8.5 Listmethods ...... 37 3.8.6 Listcomprehensions ...... 38 3.9 Strings...... 40

4 Developing Functions 42 4.1 Whyfunctions? ...... 43 4.2 Listproblems ...... 45 4.2.1 Maximumofalist ...... 45 4.2.2 Searchingalist ...... 46 4.2.3 Findingthefirstandlastmaximum ...... 46 4.2.4 Reversingalist ...... 47 4.2.5 Meanandstandarddeviation ...... 48 4.2.6 Sortingalist ...... 49 4.2.7 Binarysearch ...... 50 4.3 ...... 51 4.3.1 Computingfactorials ...... 51 4.3.2 Binaryrepresentation...... 52 4.3.3 Decimalexpansionsoffractions ...... 53 4.4 Calculus ...... 54 4.4.1 Derivatives ...... 54 4.4.2 Trapezoidalrule...... 56 4.4.3 Simpson’srule...... 56 4.4.4 Approximating e ...... 57 4.5 Numbertheory ...... 60 CONTENTS v

4.5.1 Pythagoreantriples...... 60 4.5.2 Dayoftheweekfromthedate...... 60 4.5.3 Greatestcommondivisor...... 61 4.5.4 ExtendedEuclideanalgorithm ...... 62 4.5.5 Smallestfactorofanumber ...... 64 4.5.6 Primefactorizationofanumber ...... 67

5 Programming with class 68 5.1 Getting started with classes ...... 69 5.1.1 Afirstexample ...... 69 5.1.2 Secondexample...... 72 5.1.3 A word about self ...... 74 5.1.4 Built-intypesareclasses,too ...... 74 5.1.5 Specialmethodsforoperators ...... 76 5.2 Modulararithmetic ...... 78 5.3 Rationalnumbers...... 81 5.4 Polynomialswithintegercoefficients ...... 85 Preface

This book is a short introduction to the Python intended for mathematicians and scientists, teachers and students. The main focus is on using Python to do interesting mathematical calculations, with an emphasis on learning by example. A certain amount of basic mathematical literacy on the part of the reader is assumed throughout. No prior experience with any computer programming language is assumed, although some such previous exposure would be very helpful. Python is a concise, elegant, yet powerful programming language espe- cially adaptable to the purpose of mathematical calculations. Its syntax is especially attractive since it often mimics mathematical notation. Moreover, as an interpreted language, it is especially easy to test Python code using the interpreter. Arbitrary-precision arithmetic is supported “out of the box” and it isn’t necessary to declare the type of data. The “batter- ies included” philosophy of Python means that many standard techniques and algorithms are already available in its standard library. Python is used worldwide as a convenient scripting language for web pages. Furthermore, there is now a powerful open-source alternative to Mathematica called Sage which is built on Python, so familiarity with Python enables people to use Sage for many complicated mathematical calculations. I believe that for people who already know something about mathematics and standard mathematical notation, a language such as Python can be mastered faster and better through the study of non-trivial mathematically oriented examples. All the myriad ways of creating strings that say “Hello world” seem to be fairly ubiquitous fare in the current introductory computer programming courses these days, but you won’t find such things a focus of this book. Indeed, there is little discussion of strings herein. Many important topics are, intentionally, left out. I don’t discuss with and try statements, format strings, dictionaries, data files, sets, pickling, or

vi CONTENTS vii exception classes. None of the ways in which Python can be used to interact with operating systems and web pages is discussed. There is little discussion of what’s in the standard library. These things can be found in the extensive Python documentation, or one of the many other books on Python that are now available. The main focus here is on the core language and how to use its capabil- ities, especially for mathematical calculations. This document is intended for mathematically literate people just getting started with Python, and I believe that an introduction should not try to be comprehensive. Object- oriented programming is discussed, in Chapter 5, and several examples are given there, but I don’t give any examples of inheritance. In fact, Chapter 5 is rather terse and also quite a bit more abstract than the other chapters. Thanks are due to Andy Harrington who found a number of errors in a previous version, and suggested numerous improvements.

—S.R. Doty, Chicago 2013 viii CONTENTS Chapter 1

Preliminaries

1.1 What is Python?

Python is an easy to learn, powerful programming language, initially devel- oped by Guido van Rossum. It has efficient high-level data structures and a simple but effective approach to object-oriented programming. Python’s elegant syntax and dynamic typing, together with its interpreted nature, make it an ideal language for scripting and rapid application development in many areas on most platforms. Python is named after the famed British humor group Monty Python’s Flying Circus. This document focuses on learning Python for the purpose of doing math- ematical calculations. We assume the reader has a decent knowledge of basic mathematics, but we try not to assume previous exposure to computer pro- gramming, although some such exposure would certainly be helpful. Python is a good choice for mathematical calculations, since in Python we can write code quickly and test it easily, and the syntax of Python code is similar to the way mathematical ideas tend to be expressed by mathematicians and scientists. Furthermore, Python supports arbitrary precision integer arith- metic out of the box, and as an interpreted language it can be used as a powerful calculator to perform mathematical calculations. Python is also a popular tool used by web developers; especially by a certain company called Google. If you poke around on YouTube you can find videos of Python courses held at Google’s headquarters in Mountain View, California. For readers with prior programming experience, Python allows you to

1 2 CHAPTER 1. PRELIMINARIES use variables without declaring them (i.e., it determines types implicitly), and it relies on indentation as a control structure. You are not forced to define classes of objects in Python (unlike Java) but you are free to do so when convenient.

1.1.1 Free and open-source software

Python is free software in the sense of Richard M. Stallman1, meaning that using it guarantees certain freedoms such as the freedom to give away copies and to study or change the source code. Python is also free in the sense that you can get it without spending any money.

1.1.2 Libraries

Python itself is a fairly compact language, but it comes with many libraries of standard functions that extend its capabilities in many powerful ways. In to the standard libraries that come with any Python installation by default, there are many other libraries that one can install to make Python even more powerful. For the purposes of mathematical and scientific cal- culation, two such libraries are especially worthy of mention: NumPy and SciPy. Both are available at http://www.scipy.org.

1.1.3 Sage

Furthermore, there is a powerful free software product called Sage that is built on top of Python. Sage (see http://sagemath.org) is still undergoing active development, but it is well on its way to becoming a free open-source alternative to the proprietary programs Mathematica, Maple, Magma, and MATLAB. Sage not only makes the full power of Python available for com- putation, it extends that power by including the capabilities of nearly 100 other open-source software packages, including GAP, LAPACK, PARI/GP, MatPlotLib, Maxima, NumPy, R, SciPy, Singular, and SymPy. Since Sage is built on top of Python, it is useful to know how to code in Python in order

1See http://www.fsf.org. The free software movement that Stallman started has led to some amazing advances in technology. A related concept is the idea of open source software, which is less restrictive than Stallman’s original definition of free software. When the two concepts are combined, they are often referred to under the acronym FOSS, which stands for ‘Free and Open Source Software’. 1.2. INSTALLATION AND DOCUMENTATION 3 to write code to run Sage computations. Sage can even interface to Maple and Mathematica if you have the necessary licenses for those programs, and Sage can produce output suitable for including in LATEX documents.

1.2 Installation and documentation

If you use Mac OS X or Linux, then some version of Python should al- ready be installed on your computer by default. If you use Windows, then Python is not always installed. In any case, you can download the latest version (or earlier versions) of Python by visiting the Python home page, at http://www.python.org where you will also find installation instructions. The same site has loads of documentation and other useful information. Don’t forget this website; it is your first reference point for all things Python. You will find there, for example, reference [2], the excellent Python Tutorial. You may find it useful to read along in the Python Tutorial as a supplement to this document, as it is an excellent introduction to Python. You will also find references [3, 4], the Python Library Reference Manual and the Python Language Reference Manual. The Library Reference Manual is useful to find out more about Python’s extensive built-in libraries.

1.3 Python 2 versus Python 3

Python 3.0 was released on February 13, 2009. This is a new version of Python that is not backwards compatible with Python 2.x, which means that code written for Python 2.x may not run properly in Python 3.x, and vice versa. With Python 3 the language designers fixed a number of quirks in the earlier versions of Python; for example print became a function instead of a command, and integer was changed to produce the answer people expect. For the purpose of mathematical calculations, the differences between Python 2 and Python 3 are minor. In this document the differences (that we need to know about) are explained as we go along; see especially 2.1.1 ahead. If you need to know more about the differences between Python 2 and Python 3 you should consult the Python documentation. Chapter 2

Getting Started

2.1 Running Python in a terminal

Python can be used as an interpreter, which is similar to a calculator. In the interpreter, you type a command, and Python produces the answer. Then you type another command, which again produces an answer, and so on. The simplest way (assuming that Python is installed) to start the Python interpreter is to open a terminal window and type python (or perhaps python3 if you have both versions 2 and 3 installed) at the command prompt. Here’s a screenshot of a Terminal session running Python 2.6 on my Linux box:

In Windows the Terminal is also called a command shell or a DOS shell. Note that if Windows cannot find the Python program then you will need to modify your PATH appropriately. In OS X you can find the Terminal program in the Utilities folder under Applications. Under Linux it is usually found in one of the menus.

4 2.1. RUNNING PYTHON IN A TERMINAL 5

The first line shows the command prompt where I typed python to start the Python interpreter. The second line is output from the interpreter show- ing what version of Python is running; in this case it is version 2.6.5. The third line shows what compiler was used to compile Python. The fourth line is self-explanatory. The three symbols >>> on the fifth line indicate a Python prompt for an input command, where I typed “2**1000” to ask Python to compute 21000, the one-thousand power of 2. You can see the result. Then I asked Python to print the message “Hello, World!”. We are already computing with Python! From this example we learn that Python is immensely more powerful than a pocket calculator. We also see that in Python is represented by the ** operator; i.e., you type a**b to compute ab. Remember this, as it is a common newbie mistake to type ^ for exponentiation (by typing a^b) but in Python ^ has a different meaning. At this point you should experiment on your own computer, using the Python interpreter as a calculator. Be assured that you cannot harm any- thing, so play with Python as much as you like. For some more examples: >>> 2*1024 2048 >>> 3+4+9 16 >>> 2**100 1267650600228229401496703205376 >>> pow(2,100) 1267650600228229401496703205376 >>> 3/4 0.75 >>> 2/3 0.6666666666666666 >>> In the above, we first asked for the product of 2 and 1024, then we asked for the sum of 3, 4, and 9, then we computed 2100, 3 divided by 4, and finally 2 divided by 3. Note that the * operator in Python represents , + represents addition, - represents , and / represents division. All as one would expect. Also, pow(a,b) is equivalent to a**b. >>> 2**0.5 1.4142135623730951 >>> 2**(1/2) 1.4142135623730951 Non-integer exponents are supported. Thus the 1/2 power gives the square 6 CHAPTER 2. GETTING STARTED root.

2.1.1 Integer division

Warning: In Python 2, division of does not always produce the expected result. In Python 2, typing a/b produces the integer when a and b are integers, so for instance 2/3 gives the result 0 and 13/2 yields 6. To get the expected result from integer division using Python 2, you need to supply a decimal point in one of the numbers: e.g. typing 2.0/3 instead of 2/3 produces what we would expect. To get the integer quotient of two integers in Python 3, use the // oper- ator (which also works in Python 2): >>> 2//3 0 >>> 57//2 28 >>>

We should think of the // operator as the integer quotient operator.

2.1.2 Variables and assignment

Sometimes you need to ask Python to remember a result for use later in a calculation. For this purpose Python has an assignment statement, used to assign a result to a . For example, >>> x = 3**30 >>>

One problem with assignment is that it prints nothing (its purpose is to remember, not to print) but that’s not a problem since we can always ask Python explicitly to print the variable: >>> x 205891132094649 >>> print(x) 205891132094649

There is a subtlety here. By typing x we are asking Python to evaluate the expression x, which causes Python to look up the value of x and print the result. The command print(x) has the same effect, in this case. On other data types, the effect of print(x) versus the evaluation of x can be 2.1. RUNNING PYTHON IN A TERMINAL 7 different. (One gives the repr of the object while the other gives its str form.)

2.1.3 The magic underscore variable

Python has a special variable _ (the underscore on your keyboard; i.e., the SHIFT-MINUS key) in which the interpreter always stores the result of the previous computation. This is handy for assigning a result to a variable after the fact: >>> 3 * 2**20 3145728 >>> x = _ >>> x 3145728

2.1.4 Compound commands

Here is a slightly more complicated example of a Python command, where we print a table of the first six perfect squares. This is an example of a compound Python statement called a for loop. >>> for n in [1,2,3,4,5,6]: ... print(n**2) ... 1 4 9 16 25 36 >>>

This illustrates several important points. First, the expression [1,2,3,4,5,6] is a list, and we asked Python to print the values of n2 as n varies over the numbers in the list. Second, as already mentioned, this is an example of a compound command, where the whole command is divided over two (or more) lines. The colon at the end of the first line indicates that it is part of a compound command, which continues on the next line(s). The interpreter prompts with ... on the next line instead of the usual >>>; this is the interpreter’s way of telling us it awaits the rest of the command. On the third line we entered nothing, in order to tell the interpreter that the 8 CHAPTER 2. GETTING STARTED command was completed at the second line. Also notice the indentation in the second line, which shows that it is a continuation of the first line. Such indentation is required in compound Python commands. Python uses indentation levels to control its understanding of your code.

2.2 Running Python in IDLE

There is a useful tool called IDLE, named after Eric Idle, a founding mem- ber of Monty Python’s Flying Circus. IDLE is an integrated development environment (an IDE) for Python. It provides menus, an editor, color syn- tax keyword highlighting, and other useful features. If interested, you may download and install this on your computer; in Windows and OS X it prob- ably came along with the standard Python installation. Many people find IDLE useful for running Python, so check it out; you may find it someplace in the system menu. A nice IDLE Tutorial is available online in [1], but using IDLE is completely straightforward and self-explanatory, so you shouldn’t be afraid to just try it out. Here’s a screenshot of an IDLE session on my Linux box:

As you can see, it is almost identical to a Terminal session, except that you don’t see any ... lines for continuation of compound commands. From now on, we won’t bother very much about the difference between running Python in a terminal or via an integrated development environment such as IDLE. 2.3. QUITTING THE INTERPRETER 9

If you choose to run Python always in IDLE, you should still read the examples in the previous section, which apply equally well to an IDLE in- terpreter session.

2.3 Quitting the interpreter

In IDLE or an OS X or Linux Terminal you can quit a Python session by typing CTRL-D (hold down the CTRL key while pressing the D key). In a Windows Terminal you would type CTRL-Z, followed by Enter. In IDLE you can also quit from the menu or just close the window. If the interpreter gets stuck executing a command, either in a Terminal or IDLE, you can quit the current execution by typing CTRL-C. This is quite important to remember, since all programmers create code that gets stuck, sooner or later.

2.4 Loading commands from the library

Python has a very extensive library of functions, documented in the Python Library Reference Manual [3]. The standard Python library is very exten- sive; in a sense it extends the core Python language to be able to do many tasks that are not directly supported in core Python. Python’s function library is organized into a collection of modules.

2.4.1 Importing functions

One of the available modules is especially useful for us: the math module. Let’s see how it may be used. >>> from math import sqrt, exp >>> exp(-1) 0.36787944117144233 >>> sqrt(2) 1.4142135623730951

In this example, we first import the sqrt and exp functions from the math module, then use them to compute e−1 =1/e and √2. Note that we could have loaded all the functions in the math module by using a wildcard *: >>> from math import * 10 CHAPTER 2. GETTING STARTED

This not only imports the desired sqrt and exp functions, but also every other function from the math module. Once we have loaded a function from a module, it is available for the rest of that session. When we start a new session, we have to reload the function if we need it. What would have happened if we forgot to import a needed function? After starting a new session, if we type >>> sqrt(2) Traceback (most recent call last): File "", line 1, in NameError: name ’sqrt’ is not defined we see an example of an error message, telling us that Python does not recognize sqrt. As you progress in Python, you will become quite familiar with error messages like this.

2.4.2 Importing modules

There is a second approach to importing functions, that works as follows. >>> import math >>> math.exp(-1) 0.36787944117144233 >>> math.sqrt(2) 1.4142135623730951

In this approach, we just import the module name math and not the actual functions themselves. Then we can access the functions by putting a math prefix on them, as shown above. In this approach, if you forget the prefix you will get an error message. One reason you might want to use the second approach is when two modules provide the two functions with the same name. If you wish to use both functions, then you will need a prefix in order to distinguish between them.

2.5 Defining functions

It is possible, and very useful, to define our own functions in Python. Gen- erally speaking, if you need to do a calculation only once, then use the 2.5. DEFINING FUNCTIONS 11 interpreter. But when you or others have a need to perform a certain type of calculation more than once, then define a function and save its definition in a file. When you do this, you are in effect extending Python’s capabilities by adding your own function to its standard library; you can even create your own library of modules and import from it. Python programs are often written by creating function definitions and then putting them together. For a simple example of a Python function definition in a Terminal session, look at the compound command

>>> def f(x): ... return x*x ... which defines the squaring function f(x) = x2, a popular example used in elementary math courses. In the definition, the first line is the function header where the name, f, of the function is specified. Subsequent lines give the body of the function, where the output value is calculated. Note that the final step is to return the answer; without it we would never see any results. Continuing the example, we can use the function to calculate the square of any given input:

>>> f(2) 4 >>> f(2.5) 6.25

Usually function definitions will be stored in a module (a file) for later use. These are indistinguishable from Python’s Library modules from the user’s perspective. Files may be created using just about any editor, as discussed in the next section. The name of a function is arbitrary. We could have defined the same function as above, but with the name square instead of f:

>>> def square(x): ... return x*x ... >>> square(3) 9 >>> square(2.5) 6.25 12 CHAPTER 2. GETTING STARTED

2.5.1 Reserved words

Actually, a function name is not completely arbitrary, since we are not al- lowed to use a reserved word as a function name. The list of reserved words for your version of Python can be found in the keyword module in the stan- dard library, by running the following two commands: >>> import keyword >>> print(keyword.kwlist) [’False’, ’None’, ’True’, ’and’, ’as’, ’assert’, ’break’, ’class’, ’continue’, ’def’, ’del’, ’elif’, ’else’, ’except ’, ’finally’, ’for’, ’from’, ’global’, ’if’, ’import’, ’in’, ’is’, ’lambda’, ’nonlocal’, ’not’, ’or’, ’pass’, ’raise’, ’return’, ’try’, ’while’, ’with’, ’yield’]

2.5.2 Lambda functions

By the way, Python also allows us to define functions using a format similar to the Lambda Calculus from mathematical logic. For instance, the above squaring function could alternatively be defined in the following way: >>> square = lambda x: x*x >>> square(2) 4 >>> square(2.5) 6.25

Here lambda x: x*x is known as a lambda expression. Lambda expressions are useful when you need to define a function in just one line; they are also useful in situations where you need a function but don’t want to name it.

2.6 Files

Python allows us to store our code in files (also called modules). This is very useful for more serious programming, where we do not want to retype a long function definition from the very beginning just to change one mistake. In doing this, we are essentially defining our own modules, just like the modules defined already in the Python library. For example, to store our squaring function example in a file, we can use any text editor1 to type the code into

1Many developers rely on Emacs for editing code. Other popular choices are gedit or TextEdit (for OS X). 2.6. FILES 13 a file. We look at three approaches, the first two of which allow us to use any editor. (A fourth way is discussed in 2.8 ahead.)

2.6.1 Running in a Terminal

Suppose we type the following lines into a file: def square(x): return x*x

Notice that we must omit the interpreter’s prompt symbols >>>, ... when typing the code into a file, but the indentation is still important. You should always use the .py extension for Python files. Let’s save the above file under the name “SquaringFunction.py” and then open a Terminal session in order to run it: doty@Mac-2:~/Documents$ python3 Python 3.2.1 (v3.2.1:ac1f7e5c0510, Jul 9 2011, 01:03:53) [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin Type "help", "copyright", "credits" or "license" for more... >>> from SquaringFunction import square >>> square(3) 9 >>> square(2.8) 7.839999999999999

Notice that I had to import the function from the file before I could use it. Importing a command from a file works exactly the same as for library modules. (In fact, some people refer to Python files as “modules” because of this analogy.) Also notice that the file’s extension (.py) is omitted in the import command. Most Python source files will have the .py extension. Python’s import command will not work properly if the imported file lacks the .py extension, so this is important.

2.6.2 Importing from the command line

Here’s a standard time-saver. When using your favorite editor to create the file and a Terminal to run it, you can both open the Python session and run the commands in your file by specifying the file name on the command line after the python command, as follows: doty@Mac-2:~/Documents$ python3 SquaringFunction.py doty@Mac -2:~/Documents$ 14 CHAPTER 2. GETTING STARTED

If you choose to use this approach, then be sure to specify the file extension on the command line in the Terminal. Notice that in this case we got no output from Python. That is because we asked Python to define a function, and nothing more. That’s all Python did when it ran the file — and defining a function produces no output. Usually we would want to test our function definition by including (in the file) some commands to actually try it out. Suppose we edit the file to add the print lines as shown here: def square(x): return x*x

# Test the function just defined above

print("The square of 3 is", square(3)) print("The square of 2.5 is", square(2.5)) and save the new version of the file. (The line starting with the “#” symbol is a comment, which is ignored by the Python interpreter.) Now when we run the file in a Terminal we will see some output, which verifies that our function definition actually worked: doty@Mac-2:~/Documents$ python3 SquaringFunction.py The square of 3 is 9 The square of 2.5 is 6.25 doty@Mac -2:~/Documents$

2.6.3 Using IDLE

A good simple editor is provided by IDLE, and if you choose to always use IDLE then you won’t need to run Python in a Terminal, since IDLE provides its own Run command. To create a new Python file within IDLE, just select New Window from IDLE’s File menu, type in your code, and then save it (don’t forget the .py file extension). You can then run your code directly from IDLE, by simply selecting Run from the menu. If you do this then it isn’t necessary to import the code, as IDLE does the import for you automatically. You still may want to test it, though.

2.7 The development cycle

As indicated above, code is usually developed in a file using an editor. To test the code, import it into a Python session and try to run it. Add some 2.7. THE DEVELOPMENT CYCLE 15 testing code if necessary, as in the example above. Often there is some sort of an error, in which case you need to go back to the file, make your correction(s), save the changes, and test again. This process is repeated until you are satisfied that the code works as desired. The entire process is known as the development cycle. There are two types of errors that you will encounter: they are often called errors of syntax and semantics.

2.7.1 Syntax errors

Syntax refers to the form of a command. Syntax errors occur when the form is invalid. This happens when you make typing errors such as misspellings, call something by the wrong name, forget required punctuation, and for many other reasons. Python will always give an error message for a syntax error, although its error messages are a little hard to follow until you get used to them.

2.7.2 Logical errors

Semantics refers to the meaning of a command, or a of commands. Errors of semantics are also called logical errors, because they are often caused by incorrect logic or a misunderstanding of the logic of the designers. Logical errors may not be detected by Python at all, in some cases; they are sometimes detected by noticing that the code produces incorrect output for some inputs.

2.7.3 Exceptions

Often a syntax or logical error in code will produce an exception when the code is run. Exceptions are also known as run-time errors. Here’s a simple example: >>> from math import sqrt >>> sqrt(-2) Traceback (most recent call last): File "", line 1, in ValueError: math domain error >>> 16 CHAPTER 2. GETTING STARTED where we try to compute the square root of 2. Although the syntax of the command sqrt(-2) is fine, trying to run it throws− a ValueError exception due to a logical error. For another example, >>> from math improt sqrt File "", line 1 from math improt sqrt ^ SyntaxError: invalid syntax

Here we see a syntax error caused by a misspelling.

2.7.4 Testing

It is the responsibility of the coder to thoroughly test his or her code in order to catch all errors. Testing code is an art. It is not at all easy, because just because a program works fine for one type of scenario does not imply it will work in all scenarios. Designing tests that exercise all the possible outcomes can often require a great deal of thought for complex code. Most coders spend more, often much more, than 50% of their development cycle time on testing. Because of the ease with which you can test code snippets in the Python interpreter, and because of Python’s nice syntactical structure, many people feel that using Python makes testing code somewhat simpler, and indeed that is one of the main reasons people choose Python for development work, but testing code will always be a challenge.

2.8 Scripts

This section can be safely skipped by beginners. It is included here only for the sake of completeness. If you use Mac OS X or some other variant of Unix (such as Linux) then you may sometimes be interested in running a Python command asa script. In effect, this allows you to use Python code to add functionality to your operating system. Here’s an example. Use an editor to create a file named SayHi (usually, scripts are named without a file extension) containing the following lines of code #! /usr/bin/python print("Hello World!") print("- From your friendly Python program") 2.8. SCRIPTS 17

The first line tells Python that this is a script, and where to find the binary to run it (on my system the binary is in /usr/bin). After saving the file, make it executable by typing the command chmod +x SayHi in the terminal. To run the script, type the command ./SayHi in the terminal. doty@Mac-2:~/Documents$ chmod +x SayHi doty@Mac-2:~/Documents$ ./SayHi Hello World! - From your friendly Python program doty@Mac -2:~/Documents$

Note that if you move the script someplace in your search path, then you can run it simply by typing its name SayHi without the preceding dot-slash. (Running a command in the current folder must be preceded by a dot-slash, unless the folder is in the system’s PATH.) Type echo $PATH in a terminal to see what folders are in your search path, and type the command which python to see where your Python binary is located — this should match the first line in your script. It is possible to run Python scripts in recent versions of Windows; you should refer to the Python documentation at python.org for details. Chapter 3

Python Commands

In this chapter we systematically discuss (most of) the basic commands of the core Python language. This should probably be skimmed on a first reading, and referred to later as needed.

3.1 Comments and docstrings

In a Python command, anything after a # symbol is a comment for human readers, and is ignored by the Python interpreter. For example: print "Hello world" #this is a silly comment

Comments are not part of the command. They are used to help document the code, so that anyone reading it later can better understand it. Another type of comment is a string, called a documentation string, or docstring for short, that appears right after the first line of a function, mod- ule, or class definition. Docstrings are often enclosed by three consecutive quote symbols, in order to allow them to span more than one line: """This is an example of a long comment that goes on and on and on."""

Actually, you can define any multi-line string using the triple quote conven- tion. By convention, a docstring in a definition tells the human reader what the purpose of that definition. For example, def cube(x):

18 3.2. NUMBERS AND OTHER DATA TYPES 19

"""Return the cube of x""" return(x*x*x)

It is considered best practice to always supply a docstring for every definition you write. You can access a function’s docstring using the special variable __doc__, in which the function’s docstring is always stored. For instance: >>> print(cube.__doc__) Return the cube of x

Any program can access the docstring at the time the function is run. This can be useful when running IDLE or the Python interpreter.

3.2 Numbers and other data types

Python recognizes several different types of data. For instance, 23 and 75 are integers, while 5.0 and 23.09 are floats or floating point numbers.− The type float is used to (usually− only approximately) represent a in mathematics. The number 12345678901 is a long integer and Python 2 prints it with an “L” appended to the end. Python 3 makes no distinction between long and short integers, so you will never see the trailing “L” if you use Python 3. For example, in Python 2 we get >>> 2**100 1267650600228229401496703205376L while in Python 3 the same calculation yields >>> 2**100 1267650600228229401496703205376

The artificial distinction between long and short integers in Python 2 was one of the quirks that was fixed in Python 3. In Python 3 we just have integers (of any length), and there is no such distinction. Usually the type of a piece of data is determined implicitly by the com- putation being performed. This is a major difference between Python and many other high-level programming languages, in which types of variables have to be explicitly declared before being used.

3.2.1 The type function

To see the type of some data, use Python’s built-in type function: 20 CHAPTER 3. PYTHON COMMANDS

>>> type(-75) >>> type(5.0) >>> type(12345678901)

Another useful data type is complex, used for complex numbers. For example:

>>> 2j 2j >>> 2j-1 (-1+2j) >>> complex(2,3) (2+3j) >>> type(-1+2j)

Notice that Python uses j for the complex unit (such that j2 = 1) just as physicists do, instead of the letter i preferred by mathematicians.− The capital letter J can also be used.

3.2.2 Strings

Another important and useful data type is the string type (“string” is short- hand for “character string”). For example, "Hello World!" is a string. Strings are just sequences of characters enclosed in single or double quotes:

>>> "This is a string" ’This is a string’ >>> ’This is a string, too’ ’This is a string, too’ >>> type("This is a string")

Strings are an example of a sequence type. In a sequence type, there is a notion of a first thing in the sequence, followed by its successor, and so on to the end of the sequence. In the case of strings, we have the first character, the next character, and so on. Strings are immutable: it is not possible to change them without creating a new string. 3.2. NUMBERS AND OTHER DATA TYPES 21

3.2.3 Lists and tuples

Other important sequence types used in Python include lists and tuples.A sequence type is formed by putting objects together in a sequence. Here is how we form lists and tuples: >>> [1,3,4,1,6] [1, 3, 4, 1, 6] >>> type( [1,3,4,1,6] ) >>> (1,3,2) (1, 3, 2) >>> type( (1,3,2) )

Notice that lists are enclosed in square brackets while tuples are enclosed in parentheses. Also note that lists and tuples do not need to be homogeneous; that is, the components can be of different types: >>> [1,2,"Hello",(1,2)] [1, 2, ’Hello’, (1, 2)]

Here we created a list containing four components: two integers, a string, and a tuple. Note that components of lists may be other lists, and so on: >>> [1, 2, [1,2], [1,[1,2]], 5] [1, 2, [1, 2], [1, [1, 2]], 5]

By nesting lists within lists in this way, we can build up very complicated data structures. The main difference between tuples and lists in Python is that tuples are immutable while lists are mutable. That is, we can insert, delete, and change entries in a list but not in a tuple. The only way to change an entry of a tuple is to create an entirely new tuple. Sequence types such as lists, tuples, and strings are always ordered. This is quite different from sets in mathematics, which by definition are always unordered. Also, note that repetition is allowed in a sequence, but not in a set.

3.2.4 The range function

The range function is often used to create lists of integers. It has three forms. In Python 2 its simplest form, range(n), produces a list of all numbers 22 CHAPTER 3. PYTHON COMMANDS

0, 1, 2,...,n 1 starting with 0 and ending with n 1. For instance, in Python 2: − −

>>> range(17) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]

Python 3 changed this a bit:

>>> range(17) range(0, 17)

What is going on? In Python 3 ranges have been implemented as iterators, which don’t actually create the list but stand ready to produce the items in the list, one by one, when needed. For most purposes, it does no harm to think of a range as a list. (This change was made for the sake of efficiency: it can take a long time to create very large ranges, and there are situations where not all of the items would be needed.) You can also optionally specify a starting point and an increment to a range, either of which may be negative. For instance, in Python 2 we have:

>> range(1,10) [1, 2, 3, 4, 5, 6, 7, 8, 9] >>> range(-6,0) [-6, -5, -4, -3, -2, -1] >>> range(1,10,2) [1, 3, 5, 7, 9] >>> range(10,0,-2) [10, 8, 6, 4, 2]

Note the use of a negative increment in the last example. Ranges are most often used in for loops, as discussed later in this document. In Python 3 you can explicitly convert a range to a list, by wrapping the range in list, as follows:

>>> range(10,0,-2) range(10, 0, -2) >>> list(range(10,0,-2)) [10, 8, 6, 4, 2] but doing so destroys the gain in efficiency achieved by implementing ranges as iterators. In Python 3 all the list functions (see 3.8) work on ranges as if they were lists, so an explicit conversion to list is usually not necessary. 3.3. EXPRESSIONS 23

3.2.5 Boolean values

Finally, we should mention the boolean type, named after the logician George Boole.1 A boolean value is a value which is either True or False. >>> type(True); type(False)

Boolean types are used in making decisions. For that purpose, the value 0 is always considered False, and any nonzero value is True. Similarly, an empty list, tuple, or string is considered False, while any nonempty sequence type is True. Be careful here: these rules mean that ’0’, [0], and ’False’ are all examples of things that evaluate to True. >>> bool(0) False >>> bool(900) True >>> bool(0.0) False >>> bool(0+0j) False

The built-in function bool converts any given type into a boolean type.

3.3 Expressions

Python expressions are not commands, but rather form part of a command. An expression is anything which produces a value. Computing the value of an expression is called evaluating it. Parentheses are used for grouping in Python expression in exactly the same way as in basic . Some examples of expressions are: (2+2*x)/(x-3), 2**100 + 67, and f((x-1)*(x+1)). Note that in order for Python to make sense of the last one, the variable x must have a value assigned and f should be a previously defined function. Expressions are formed from variables, constants, function evaluations, and operators, combined (roughly speaking) according to the usual rules

1In 1854 Boole published The Laws of Thought, in which mathematical logic was reduced to algebra. His ideas are implemented deep within any digital computer. 24 CHAPTER 3. PYTHON COMMANDS of algebra. Variables must have been previously assigned to some value. Parentheses are used to indicate order of operations and grouping, as usual.

3.3.1 Implied continuation

Some expressions may be typed across more than one line of input. The usual way of achieving this in Python is to use parentheses, as the expression is considered incomplete until a closing right parenthesis if encountered for every left parenthesis. >>> (2 + ... 2) 4

This is convenient for typing very long expressions that won’t fit on a single line. It also works similarly for tuples and lists: >>> x = [1, 3, 5, 7, ... 9, 11, 13, 15] >>> x [1, 3, 5, 7, 9, 11, 13, 15]

Implied continuation of string literals can be achieved too, but the syntax is a little different. Use a backslash character at the end of each continueation line: >>> s = "Now is a good time for \ ... a dip in the pool." >>> s ’Now is a good time for a dip in the pool.’

A triple quote symbol can also be used to create strings that span more than one line, as already discussed in 3.1, but the effect is slightly different from the above.

3.4 Operators

Operators are special symbols used in expressions. There are a number of different types of operators in Python. Here we discuss only the arithmetic operators and the comparison operators. 3.4. OPERATORS 25

3.4.1 Arithmetic operators

Some common binary operators for arithmetic in Python are summarized in the table below:

+ addition - subtraction * multiplication / division ** exponentiation // integer quotient % integer remainder

When applied to integers, the last two produce the integer quotient and the integer remainder, respectively: >>> 33//5 6 >>> 33 % 5 3

The operator // is sometimes called the floor division operator; it may be used on floating point data, and it always produces the integer part of the quotient, i.e., the largest integer which does not exceed the quotient: >>> 33.4//5.3 6.0

When applied to pairs of integers, the operator // produces the integer quotient. In Python 2, the ordinary division operator / works like // for pairs of integers; this problem was fixed in Python 3. So if you’re doing integer division in Python 2 and you want a floating point result, then you need to coerce one of the operands to have the type float. For example, in Python 2: >>> 25/3 8 >>> float(25)/3 8.333333333333334 but in Python 3 you no longer need to worry about this issue. In any arithmetic evaluation, if one (or more) of the operands is of type float, then the result will be of type float. In general, when Python encoun- ters expressions with mixed types, it tries to figure out what is meant, rather 26 CHAPTER 3. PYTHON COMMANDS than just reporting an error. Usually it is pretty smart about such implicit conversions.

3.4.2 Comparison operators

Besides the arithmetic operators we need comparison operators, which are summarized in the table below:

< is less than > is greater than <= is less than or equal to >= is greater than or equal to == is equal to != is not equal to

Notice that a double equals == is used to test for equality. A single equals symbol = is reserved for assignment of values to variables, and is never used to test for equality in Python. The result of a comparison is always a boolean value True or False. Here are a few simple examples: >>> 2 < 3 True >>> 3<2 False >>> 3 <= 2 False >>> 2 <> 3 True >>> 2 != 3 True >>> 0 != 0 False >>> 0 == 0 True

3.4.3 Membership testing

In a for loop the variable ranges over all values of the sequence. We can also test for membership in a sequence, such as a tuple or a list, using the reserved word in. 3.5. VARIABLES AND ASSIGNMENT 27

>>> x = [1,2,3,4,5] >>> 2 in x True >>> 7 in x False

Membership testing also works the same for testing membership in a range. Membership testing also works for strings: >>> ’a’ in "Capitulate" True >>> ’z’ in "Capitulate" False >>> ’it’ in "Capitulate" True in which the last example is a special feature for strings (the string ’it’ is not a single character of the string ’Capitulate’).

3.5 Variables and assignment

3.5.1 Variable naming conventions

In Python, variable names may be any contiguous sequence of letters, num- bers, and the underscore (_) character. The first character must not be a number, and you may not use a reserved word as a variable name. Spaces are not allowed in variable names. Case is important; for instance Sum is a different variable name than sum in Python. Other examples of legal variable names are: a, v1, v_1, abc, Bucket, monthly_total, __pi__, and TotalAssets. Many of the variables used in Python to access information about the underlying system begin and end with a double underscore, so it is probably a good idea to avoid defining such variable names in your programs.

3.5.2 Assignment statements

An assignment statement in Python has the form variable = expression. This has the following effect. First the expression on the right hand side is eval- uated, then the result is assigned to the variable. After the assignment, the variable becomes a name for the result. The variable retains the same 28 CHAPTER 3. PYTHON COMMANDS value until another value is assigned, in which case the previous value is lost. Executing the assignment produces no output; its purpose it to make the association between the variable and its value.

>>> x = 2+2 >>> print(x) 4 >>> x 4

In the example above, the assignment statement sets x to 4, producing no output. If we want to see the value of x, we must print it, or evaluate it. If we execute another assignment to the variable x, then the previous value is lost:

>>> x = 380.5 >>> print(x) 380.5

Python also supports a multiple assignment statement. The general form is var1, var2, ... = expr1, expr2, ... , where variables appear on the left hand side and expressions on the right hand side. When this is executed the expressions on the right hand side are all evaluated and stored in temporary locations; then those values are copied into the locations assigned to the variables. This allows for a statement such as

>>> x,y = y,x which effectively interchanges the values stored in x, y. In mathematics the equation x = x + 1 is nonsense; it has no solution. In computer science, the statement x=x+1 is quite useful. Its purpose is to add 1 to x, and then reassign the result to x. In short, x is incremented by 1.

>>> x = 10 >>> x = x + 1 >>> print(x) 11 >>> x = x + 1 >>> print(x) 12 3.6. DECISIONS 29 3.6 Decisions

The if–else statement is used to make choices in Python code. This is a compound statement. The simplest form is if condition : action 1 else: − action 2 − The indentation is required. The else and its action are optional. The actions action-1 and action-2 may each consist of one or more statements; they must all be indented the same amount. The condition must be an expression which evaluates to True or False. The execution of the if—else is as expected: if the condition evaluates to True then action-1 is executed, otherwise action-2 is executed. In either case execution continues with the statement after the if-else. So an if– else always chooses one of two possible alternatives whereas an if with no corresponding else takes the corresponding action or not. For example, the code x = 1 if x > 0: print("Friday is wonderful") else: print("Monday sucks") print("Have a good weekend") results in the output Friday is wonderful Have a good weekend

Note that the last print statement is not part of the if-else statement (be- cause it isn’t indented). If we change the first line to say x=0 then the output of running the code would be Monday sucks Have a good weekend

More complex decisions may have several alternatives depending on sev- eral conditions. For these the keyword elif is used. It means “else if” and one can have any number of elif clauses between the if and the else. The usage of elif is best illustrated by an example: 30 CHAPTER 3. PYTHON COMMANDS

if x >= 0 and x < 10: digits = 1 elif x >= 10 and x < 100: digits = 2 elif x >= 100 and x < 1000: digits = 3 elif x >= 1000 and x < 10000: digits = 4 else: digits = 0 # more than 4

In the above, the number of digits in x is computed, so long as the number is 4 or less. If x is negative or 10000, then digits will be set to zero. ≥ Note that one can have elif without a final else.

3.7 Loops

Python provides two looping commands: for and while. These are also compound commands.

3.7.1 for loop

The syntax of a for loop is for item in sequence : action

As usual, the action consists of one or more statements, all at the same indentation level. These statements are also known as the body of the loop. The item is a variable name, and sequence is an iterable type (e.g., a list). Execution of the for loop works by setting the variable successively to each item in the sequence, and then executing the body each time. Here is a simple example: for n in [2, 4, 6, 0]: print(n*n*n)

Running this code produces the output 8 64 216 0 3.7. LOOPS 31

3.7.2 while loop

The syntax of the while loop is while condition : action

As before, the action consists of one or more statements all at the same indentation level. The statements in the action are known as the body of the loop. Execution of the loop works as follows. First the condition is evaluated. If True, the body is executed and the condition evaluated again, and this repeats until the condition evaluates to False. Here is a simple example: n = 0 while n < 10: print(n*n) n=n+3

Running this code produces the following output 0 9 36 81

Note that the body of a while loop is never executed if the condition evaluates to False the first time. Also, if the body does not change the subsequent evaluations of the condition, an infinite loop may occur. For example while True: print("Hello") will print Hellos endlessly. To interrupt the execution of an infinite loop, use CTRL-C, as mentioned before.

3.7.3 break, continue, and pass

The pass statement is the simplest of these: it does nothing. It is used when a statement is required syntactically but no action is to take place. For example, if n % 2 <> 0: pass else: print(n, "is even") 32 CHAPTER 3. PYTHON COMMANDS prints a message when n is even and does nothing if n is odd (assuming that n is an integer). It should be noted that this example is poor coding practice, as the equivalent code

if n % 2 == 0: print(n, "is even") has exactly the same effect and is much simpler. The break statement breaks out of the smallest enclosing for or while loop. For instance, we can use a for loop to find the smallest integer n < 1000 such that 2n + 1 is divisible by 113.

for n in range(1000): if (2**n+1) % 113 == 0: print(2**n+1, "= 2 **", n, "+ 1 is divisible by 113") break

This produces the result

16385 = 2 ** 14 + 1 is divisible by 113 showing that n = 14 is the desired number. The break statement in the body of the loop ensures that we don’t keep looking once we have found the answer. Without the break the loop would report all integers n< 1000 such that 2n + 1 is divisible by 113, and it would execute for 1000 cycles instead of just 15. The continue statement represents the opposite concept: it continues with the next iteration of the loop. Here’s an example, where a list of numbers (from 0 to 29 inclusive) is modified so that all even numbers in the list are unchanged but all odd numbers are replaced by their double: x = list(range(30)) for n in x: if n % 2 == 0: continue # go to next element x[n] = 2*x[n] print(x)

This code produces the output

[0, 2, 2, 6, 4, 10, 6, 14, 8, 18, 10, 22, 12, 26, 14, 30, 16, 34, 18, 38, 20, 42, 22, 46, 24, 50, 26, 54, 28, 58] 3.8. LISTS 33

3.7.4 else in loops

One can also (optionally) have an else clause in a loop. This is usually used in list searches in conjunction with break. The else clause of a loop is executed when the loop terminates through exhaustion of the list (with for) or when the condition becomes false (with while), but not when the loop is terminated by a break statement. In other words, the loop is guaranteed to either produce a break or perform the else. Here is an example showing the utility of this construct: for n in range(2,10): for x in range(2,n): if n % x == 0: print(n, "equals", x, "*", n/x) break else: # loop fell through without finding a factor print(n, "is a prime number")

The above code searches for and prints factorizations of numbers between 2 and 10, printing a message that the number is prime when no such factor- ization is found. The code produces the following output. 2 is a prime number 3 is a prime number 4 equals 2 * 2 5 is a prime number 6 equals 2 * 3 7 is a prime number 8 equals 2 * 4 9 equals 3 * 3

3.8 Lists

As already mentioned, a list is a finite sequence of items, and one could use the range function to create lists of integers. In Python, lists are not required to be homogeneous, i.e., the items could be of different types. For example, a = [2, "Jack", 45, "23 Wentworth Ave"] is a perfectly valid list consisting of two integers and two strings. One can refer to the entire list using the identifier a or to the i-th item in the list 34 CHAPTER 3. PYTHON COMMANDS

using the identifier a[i]. (In mathematics, a[i] is often written as ai using a subscript.) >>> a = [2, "Jack", 45, "23 Wentworth Ave"] >>> a [2, ’Jack’, 45, ’23 Wentworth Ave’] >>> a[0] 2 >>> a[1] ’Jack’ >>> a[2] 45 >>> a[3] ’23 Wentworth Ave’

Note that in Python the numbering of list items always begins at zero. So the four items in the above list are indexed by the numbers 0, 1, 2, 3. List items may be assigned a new value; this of course changes the list. (Data types that allow changes are called mutable, so a list is mutable.) For example, with a as above: >>> a [2, ’Jack’, 45, ’23 Wentworth Ave’] >>> a[0] = 2002 >>> a [2002, ’Jack’, 45, ’23 Wentworth Ave’]

Of course, the entire list may be assigned a new value, which does not have to be a list. When this happens, the previous value is lost: >>> a [2002, ’Jack’, 45, ’23 Wentworth Ave’] >>> a = "gobbletygook" >>> a ’gobbletygook’

Here the variable a was a list, but then became a string.

3.8.1 Length of a list; the empty list

Every list has a length, the number of items in the list. This can be accessed by using the len function: >>> x = [9, 4, 900, -45] >>> len(x) 4 3.8. LISTS 35

Of special importance is the empty list of length 0. This is created as follows: >>> x = [] >>> len(x) 0

The empty list has no elements.

3.8.2 Sublists (slicing)

Sublists are obtained by slicing, which works analogously to the range func- tion discussed before. If x is an existing list, then the slice x[start:end] is the sublist consisting of all items in the original list at index positions i such that start i< end. ≤ To use this properly we must remember that indexing items always starts at 0 in Python. For example, in Python 2 >>> x=range(0,20,2) >>> x [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] >>> x[2:5] [4, 6, 8] >>> x[0:5] [0, 2, 4, 6, 8]

Python 3 treats ranges slightly differently: a range is not an actual list (but can be used as if it were one). When taking a slice, either parameter start or end may be omitted: if start is omitted then the slice consists of all items up to, but not including, the one at index position end, similarly, if end is omitted the slice consists of all items starting with the one at position start. For instance, with the list x as defined above we have >>> x[:5] [0, 2, 4, 6, 8] >>> x[2:] [4, 6, 8, 10, 12, 14, 16, 18]

In this case, x[:5] is equivalent to x[0:5] and x[2:] is equivalent to x[2:len(x)]. There is an optional third parameter in a slice, which if present represents an increment, just as in the range function. For example, >>> my_list = range(20) >>> my_list 36 CHAPTER 3. PYTHON COMMANDS

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] >>> my_list[0:16:2] [0, 2, 4, 6, 8, 10, 12, 14] >>> my_list[0:15:2] [0, 2, 4, 6, 8, 10, 12, 14]

Notice that one may cleverly use a negative increment to effectively reverse a list, as in: >>> my_list[18::-1] [17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

In general, the slice x[len(x)::-1] reverses any existing list x. Of course, one could could do this by calling the builtin reversed function, as in x.reversed().

3.8.3 Joining two lists

Addition is defined for lists. Two existing lists may be concatenated together to make a longer list, using the + operator: >>> [2,3,6,10] + [4,0,0,5,0] [2, 3, 6, 10, 4, 0, 0, 5, 0]

This is an example of operator overloading, where a new meaning is assigned to an operator for some new context.

3.8.4 Multiplying a list by a number

It is also possible to multiply a list by an integer n. If x is a list then n*x or x*n define a new list which the same as the list x repeated n times, if n is positive. If n is negative or zero then it is the empty list. >>> 10*[0] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] >>> 4*[1,2,3,4] [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4] >>> x = [2] >>> 11*x [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2] >>> 0*x [] >>> (-1)*x [] 3.8. LISTS 37

This is convenient for initializing lists; for instance if you need a list with 1000 zeros, just type 1000*[0].

3.8.5 List methods

If x is the name of an existing list, we can append an item item to the end of the list using x.append(item) For example, >>> x = [3, 6, 8, 9] >>> x.append(999) >>> x [3, 6, 8, 9, 999] A similar method is called insert, which allows an element to be inserted in the list at a specified position: >>> x = [’a’, ’c’, ’3’, ’d’, ’7’] >>> x.insert(0,100) >>> x [100, ’a’, ’c’, ’3’, ’d’, ’7’] >>> x.insert(3,’junk’) >>> x [100, ’a’, ’c’, ’junk’, ’3’, ’d’, ’7’] One can also delete the first occurrence of some item in the list (if possible) using remove as follows: >>> x.remove(’a’) >>> x [100, ’c’, ’junk’, ’3’, ’d’, ’7’] To delete the item at index position i use x.pop(i), as in: >>> x.pop(0) 100 >>> x [’c’, ’junk’, ’3’, ’d’, ’7’] Notice that pop not only changes the list, but it also returns the item that was deleted. Also, by default x.pop() pops off the last item: >>> x.pop() ’7’ >>> x [’c’, ’junk’, ’3’, ’d’] 38 CHAPTER 3. PYTHON COMMANDS

Many more methods exist for manipulating lists; consult the Python Tutorial [2] or Python Library Reference [3] for more details.

3.8.6 List comprehensions

A list comprehension is a convenient way of generating a list using successive evaluations of an expression as a variable varies over another list or a range. This is quite similar to the way mathematicians often describe sets. For example, to get the list of all even integers 2n as n varies over the numbers from 1 to 5 we can just write: >>> [2*n for n in range(1,6)] [2, 4, 6, 8, 10]

To get a list of the first 10 perfect squares we just do: >>> [n**2 for n in range(1,11)] [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

The hard way of doing this, without using a list comprehension, would involve a loop and would go as follows: >>> x = [] #empty list >>> for n in range(1,11): ... x.append(n**2) ... >>> x [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

The expression to be evaluated in a list comprehension could involve some function. For example, we can define a function f(x)=2x +1 in one line using a lambda expression, and use it to form a list comprehension: >>> f = lambda x: 2*x + 1 >>> [f(n) for n in range(10)] [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

This produces a list of the first 10 odd numbers. (Of course, we don’t need to define a function for that; this was just to illustrate the use of functions in list comprehensions.) It is also possible to comprehend a list over two or more variables. For example, the following two-variable list comprehension produces a list of points with integer coordinates: >>> [(x,y) for x in range(5) for y in range(5)] 3.8. LISTS 39

[(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (1, 0), (1, 1), (1, 2), (1, 3), (1, 4), (2, 0), (2, 1), (2, 2), (2, 3), (2, 4), (3, 0), (3, 1), (3, 2), (3, 3), (3, 4), (4, 0), (4, 1), (4, 2), (4, 3), (4, 4)] The form of the output of the above command was changed slightly in order to enhance readability. The order in which the variables are specified makes a difference: >>> [(x,y) for y in range(5) for x in range(5)] [(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (0, 1), (1, 1), (2, 1), (3, 1), (4, 1), (0, 2), (1, 2), (2, 2), (3, 2), (4, 2), (0, 3), (1, 3), (2, 3), (3, 3), (4, 3), (0, 4), (1, 4), (2, 4), (3, 4), (4, 4)] Again the output form was changed to enhance readability. List compre- hensions are powerful ways of creating patterned lists quickly. In general, a list comprehension consists of brackets containing an ex- pression followed by a for clause, then zero or more for or if clauses. The result will be a new list resulting from evaluating the expression in the con- text of the for and if clauses which follow it. For example, this listcomp combines the elements of two lists if they are not equal: >>> [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y] [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)] and it is equivalent to the more complicated: >>> combs = [] >>> for x in [1,2,3]: ... for y in [3,1,4]: ... ifx!=y: ... combs.append((x, y)) ... >>> combs [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)] Note how the order of the for and if statements is the same in both these examples. It is also possible to nest one list comprehension within another, as in the following example, which transposes row and columns in a matrix: >>> matrix = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]] >>> [[row[i] for row in matrix] for i in range(4)] [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]] 40 CHAPTER 3. PYTHON COMMANDS

See the Python documentation for futher examples.

3.9 Strings

A string in Python is a sequence of characters. In some sense strings are sim- ilar to lists, however, there are important differences. One major difference is that Python strings are immutable, meaning that we are not allowed to change individual parts of them as we could for a list. So if x is an existing string, then x[i] gets the character at position i, but we are not allowed to reassign that character, as in x[5] = ’s’. >>> x = ’gobbletygook’ >>> x[2] ’b’ >>> x[5] ’e’ >>> x[5] = ’s’ Traceback (most recent call last): File "", line 1, in TypeError: ’str’ object does not support item assignment

Just as for lists, string items are indexed starting at 0. Slicing for strings works exactly the same as for lists. The length function len is the same as for lists, concatenation is the same, and so is string multiplication. >>> ’a’ + ’bd’ ’abd’ >>> 20*’x’ ’xxxxxxxxxxxxxxxxxxxx’

The list methods append, insert, delete, and pop are not available for strings, because strings are immutable. If you need to change an existing string, you must make a new, changed, one. Long strings that span several lines can be created using the triple-quote method; you can use either single or double quotes for this as usual: >>> x = """An example ... of a long string, ... spanning more than one line.""" >>> x ’An example\nof a long string,\nspanning more than one line .’ >>> print(x) An example of a long string, 3.9. STRINGS 41

spanning more than one line.

Notice that such a string evaluates differently than it prints. The embedded character \n is called the newline character; it is an example of using a escape sequence in a string. The meaning of the newline character is clear: it immediately moves the printer to a new line. You can also make your own strings with embedded newline characters by embedding them explicitly, without using the triple-quote method. >>> y = "Hello\nHello" >>> y ’Hello\nHello’ >>> print(y) Hello Hello

There are other useful escape sequences for Python strings; see the reference manual [4] for details. There are many methods for manipulating strings, documented in the Python Library Reference Manual [3]. For example, you can capitalize an existing string x using x.capitalize(); this returns a new copy of the string in which the first character has been capitalized. >>> a = ’gobbletygook is enlightening.’ >>> a.capitalize() ’Gobbletygook is enlightening.’

Note that this did not change the string a itself (strings are immutable). Rather, it computed a new string in which the first letter was capitalized, and then returned the result. Some other useful string methods are find and index, which are used to find the first occurrence of a substring in a given string. See the manuals for details. Chapter 4

Developing Functions

In this chapter we look at how to write Python functions in order to to solve various problems. Since this book is intended for the mathematically literate who wish to learn Python, most of the examples are taken from mathematics. The problems are organized by topic, and within each topic we begin by solving simple classic problems and move gently towards more challenging ones. In some cases, functions may already exist in the standard library that will solve the problem, so in a sense we are sometimes guilty of “reinventing the wheel”, but nevertheless consideration of such problems can be instructive. All the examples given in this chapter focus on developing a Python function definition that will solve all well-defined instances of the problem at hand. It should be pointed out that Python code is often developed from a bottom-up approach, by building simple functions that solve parts of the problem, then putting them together in a higher-level function, and so on. For more complex problems, developing function definitions may not be the best approach. In such cases, it may be better to develop a class of objects and corresponding methods. (Functions in a class are called its methods.) Programming with classes of objects and methods is called object- oriented programming. Examples of this paradigm will be considered in the next chapter; it builds on the ideas of this chapter.

42 4.1. WHY FUNCTIONS? 43 4.1 Why functions?

Usually, when solving a problem we will strive for a general solution that solves all instances of the problem. For example, the following code fragment will determine the number of bits (the bitlength) in the binary representation of the number 268091: b = 0 while 2**b <= 268091: b = b+1 b # evaluate b so we can see the answer

The bitlength of a positive integer n is the smallest b such that 2b n< 2b+1. If we type the above code into the interpreter, ≤ >>> b=0 >>> while 2**b <= 268091: ... b=b+1 ... >>> b 19 we find that the bitlength of 268091 is 19. Notice that the last line of the code evaluates the expression b, showing us its final value after the loop finishes. Without the last line, the correct value 19 would still be computed for b, but we wouldn’t see the result. We could replace the last line in the code with print(b), which would produce output looking exactly the same (it still prints 19) but which is, in fact, subtly different. The difference is that the first approach produces an integer result, which could then be used for further computation, while the second prints the answer in the terminal stream without producing any actual numerical result available for further computation. We can see this as follows: >>> b=0 >>> while 2**b <= 268091: ... b=b+1 ... >>> print(b) 19 >>> _ Traceback (most recent call last): File "", line 1, in NameError: name ’_’ is not defined 44 CHAPTER 4. DEVELOPING FUNCTIONS

Here, we used the underscore to access the previous result. This produced an error, showing that there was no previous result available, since we used print instead of just evaluating b. Since we often will need to do further computation with our results, we prefer the first approach. So, remember as a rule of thumb: evaluate, don’t print. (There are legitimate uses for print statements, but the above situation isn’t one of them.) Instead of writing code to determine the bitlength of a particular integer such as 268091, we prefer to write code that determines the bitlength of any given positive integer n. So let’s improve the above code, as follows: n = 268091 b = 0 while 2**b <= n: b = b+1 b # evaluate b so we can see the answer This is slightly better, because to compute the bitlength of a different n we need only change the first line of the code. If we store this code in some file, then we can change the first line to reset n to the desired number, save the changes, and rerun the code. However, this solution is highly unsatisfactory, in that it requires changing the code every time we want to compute the bitlength of a new value of n. There must be a solution that doesn’t require changing the code, and there is. The answer is to define a new function depending on a parameter n, as follows: def bitlength(n): """Return the bitlength of n""" b = 0 while 2**b <= n: b = b+1 return b Notice that the result is returned and not printed. In general, in a function definition one should use the return statement to return answers as values, rather than printing them, so that they are available for further computation. Suppose that we save the above code in a file named Bits.py. Then we can run the code in a terminal: >>> from Bits import bitlength >>> bitlength(268091) 19 >>> bitlength(2) 2 >>> bitlength(123456789012345678901) 67 4.2. LIST PROBLEMS 45

In effect, by defining the new function bitlength, we have extended Python’s calculating capabilities, and we can just type bitlength(n) to compute the bitlength of any given n. The bitlength problem is just an example cho- sen for illustration purposes, and one or more of Python’s built-in library functions might serve a similar purpose. For instance, compare the results produced by bitlength(n) with log(n,2), the base 2 of n. (Note that the log function is found in the math module.) Many of the easier problems can be solved by developing one or more new functions that can be used to solve all the problems of the given type. Python functions are appropriate for this purpose because they accept in- put(s) and return an answer (or answers) which may be used in subsequent computation. As already mentioned, it must be admitted that for more complex prob- lems, it may be more productive to define not just new functions but new classes of data types as well; such new data types are called objects and the corresponding functions that operate on the objects are called methods. The entire paradigm of programming with classes of objects and their methods is called object-oriented programming, or OOP. This is an important idea, but in this chapter our main focus is on the simpler problems, so we will delay the discussion of object-oriented programming for the next chapter.

4.2 List problems

4.2.1 Maximum of a list

The problem is to find the biggest (or smallest) element in a given list of numbers. Here’s a possible solution: def max(x): """Return the biggest list element""" m = x[0] #initial maximum for n in range(len(x)): if x[n] > m: m = x[n] return m

def min(x): """Return the smallest list element""" m = x[0] #initial minimum for n in range(len(x)): 46 CHAPTER 4. DEVELOPING FUNCTIONS

if x[n] < m: m = x[n] return m in which we define two new functions that solve both problems. The idea of the first function is: starting with m = x[0], keep track of the maximum so far as we examine each number on the list in turn, updating m each time we find a value that exceeds the previous value of m. When we reach the end of the list, m is the maximum value on the list. The minimum is similar. Suppose we type these definitions into a file, naming it List.py. Then we can use the definitions in a terminal after importing the functions: >>> from List import * >>> max( [1,2,4,7,-9,0,2,-8,2,1,5,-5,2,1,0,8,3] ) 8 >>> min( [1,2,4,7,-9,0,2,-8,2,1,5,-5,2,1,0,8,3] ) -9

4.2.2 Searching a list

The problem is to find the location (the index) of a given item in a list. If there is more than one occurrence of the item, we will return the location of the first occurrence. This is called a linear search since we look at the items in order one-by-one until we find a match: def linear_search(x, item): """Return the location of item in x""" for n in range(len(x)): if item == x[n]: return n return -1 #didn’t find it

Notice that we have decided to return the impossible index 1 in case we searched the entire list and didn’t find any match, meaning− that the item is not on the list. In some situations, it may be preferable to throw an exception (cause an error) instead.

4.2.3 Finding the first and last maximum

The problem is a variation of the problem considered in 4.2.1. Here we want not the maximum value itself, but the location of its first occurrence. (The 4.2. LIST PROBLEMS 47 maximum could appear more than once.) Here’s a linear search based on 4.2.1 that produces the location of the first maximum: def firstmax(x): """Return the location of the first maximum""" max_loc = 0 for n in range(len(x)): if x[n] > x[max_loc]: max_loc = n return max_loc

Note the use of the greater than comparison here. One the other hand, by replacing greater than by greater than or equal, we obtain code that finds the last maximum: def lastmax(x): """Return the location of the last maximum""" max_loc = 0 for n in range(len(x)): if x[n] >= x[max_loc]: max_loc = n return max_loc

This differs from the previous example in only a single character. Here’s one more example, in which we build on the previous two examples to find the location of the next-to-last maximum in a list: def nexttolastmax(x): """Return the location of the next to last maximum""" b, e = firstmax(x), lastmax(x) if b == e: return b else: for n in range(e-1,b,-1): if x[n] == x[e]: return n

Notice the way we search backwards from the location of the last maximum. Also notice how the previous functions are invoked (called) on the first line.

4.2.4 Reversing a list

The problem is to reverse a given list. We choose to create a new list to hold the answer. def reverse(x): 48 CHAPTER 4. DEVELOPING FUNCTIONS

"""Return the reverse of a list x""" y = [] #empty list for n in range(len(x)-1,-1,-1): y.append(x[n]) return y

Note carefully the range used here. Why must it start at len(x)-1?

4.2.5 Mean and standard deviation

The problem is to compute the mean and standard deviation of a list of data. Recall that the mean µ and standard deviation σ of a list x are given by 1 n 1 n µ = x σ = (x µ)2 n i vn i − i=1 u i=1 X u X where n is the length of the list. We cant implement a Python 3 function to compute these values as follows: from math import sqrt def stats(x): """Return the mean and std deviation of x""" sum = dev = 0 for i in range(len(x)): sum = sum + x[i] mean = sum/len(x) for i in range(len(x)): dev = dev + (x[i]-mean)**2 sd = sqrt(dev/len(x)) return (mean, sd)

Note that the return statement returns a tuple; this is a common way to handle situations where more than one value is to be reported. Also notice the use of a multiple assignment in the first line. In Python 2 this function will fail badly if the list consists entirely of integers, because of the integer division quirk of Python 2 that has already been discussed. Thus, in Python 2 one should coerce the sum and dev to type float before doing the divisions, for instance as follows: from math import sqrt def stats(x): """Return the mean and std deviation of x""" sum = dev = 0.0 # coerce these variables to floats 4.2. LIST PROBLEMS 49

for i in range(len(x)): sum = sum + x[i] mean = sum/len(x) for i in range(len(x)): dev = dev + (x[i]-mean)**2 sd = sqrt(dev/len(x)) return (mean, sd)

4.2.6 Sorting a list

Sorting is a classic problem: to rearrange the items of a list in (ascending) order. We choose to sort the list “in place”, meaning that we do not create a sorted copy of the original list. This destroys the ordering of the original list. (Recall that lists are mutable.) There are many sorting algorithms. The one given here, known as a selection sort, builds on finding the location of the minimum element (see 4.2.1) of the list. The minimum is then swapped into the initial position, and the process repeats with the remainder of the items, and so on. def sort(x): """Sort a list in place. Return nothing.""" for i in range(len(x)-1): m = i for j in range(i+1,len(x)): if x[j] < x[m]: m = j # now m is the location of min in x[i:] x[i],x[m] = x[m],x[i] #swap them

Note the use of the multiple assignment statement that swaps the two el- ements x[i], x[m]. This code is an example of a lop within a loop. The interior loop searches for the location m of the minimum element in the range from i to the end (because we can assume by induction that the first i 1 elements are sorted). When m is found, we swap x with x in order − i m to put xm in its correct position. Testing this code is slightly tricky because the sort function doesn’t return a result. Instead, it achieves its goal as a side effect of running the function. If the code is stored in a file named SortCode.py then we get, for example: >>> from SortCode import * >>> x=[1,6,60,72,1,0,2,60,92,0,2,-1,92,76,45,34] 50 CHAPTER 4. DEVELOPING FUNCTIONS

>>> sort(x) >>> x [-1, 0, 0, 1, 1, 2, 2, 6, 34, 45, 60, 60, 72, 76, 92, 92]

Notice that the command sort(x) produced no output, so we needed to evaluate x in order to see the changes caused by sort. The number of iterations performed by this algorithm is obtained by evaluating the double sum

− − n 1 n n 1 n(n 1) n(n + 1) 1= (n i)= n2 − = − − 2 2 i=0 j=i+1 i=0 X X X where n is the length of the list, so its time complexity is O(n2). More effi- cient sorting algorithms exist; for instance the quicksort algorithm invented by C. A. R. Hoare has average case time complexity O(n log n), although in the worst case its time complexity is still O(n2). Another good sorting algorithm is called heapsort; it has average and worst case time complexity O(n log n) although on average it performs somewhat slower than quicksort. Yet another good sorting method is mergesort. The discussion of these more advanced sorting algorithms is best delayed for another time, as this docu- ment is intended to be introductory; it is however important to know that the science of sorting is rather extensive.

4.2.7 Binary search

A sorted list can be searched must faster than one that is unsorted. Here’s an implementation of binary search, which performs such fast searching: def binary_search(x, target): """Return the index i such that target = x[i], return -1 if no such i exists""" begin, end = 0, len(x)-1 #starting indices mid = (begin+end)//2 #midpoint while begin < end: if target == x[mid]: return mid elif target >x[mid]: begin = mid+1 mid = (begin+end)//2 else: end = mid-1 mid = (begin+end)//2 4.3. ARITHMETIC 51

#at this point begin = end if target == x[begin]: return begin else: return -1 #not found

The idea is to compare the target with the middle element. This splits the list into two sublists (the elements before the middle one, and those after). If the target is equal to the middle element, then we have found a match. Otherwise, whether the target is bigger or smaller than the middle element determines which sublist it must belong to. That sublist is then similarly split into two sublists, by looking at its middle, and so on.

This algorithm takes no more than log2 n steps to search a sorted list of n items. This should be compared with a linear search (see 4.2.2) which will take n steps in the worst case.

4.3 Arithmetic

4.3.1 Computing factorials

The problem is to compute n! (n factorial) for a given positive integer n. Recall that by definition n! is the product of all the integers from 1 through n. Thus n!= n(n 1)! for all n. So a recursive description of the factorial function is: − 1 if n =0 n!= n(n 1)! if n> 0. ( − This translates directly into a recursive Python definition, as follows: def factorial(n): """Return the factorial of n""" if n == 0: return 1 else: return n * factorial(n-1)

This is more or less self-explanatory. Note that (because of the way return works) we do not need the else in the above. We could just as well have coded it as: def factorial(n): """Return the factorial of n""" 52 CHAPTER 4. DEVELOPING FUNCTIONS

if n == 0: return 1 return n * factorial(n-1) and this is equivalent to the previous version. One can also give a non-recursive implementation of the factorial func- tion, as follows: def factorial(n): """Return the factorial of n""" if n == 0: return 1 else: product = 1 for k in range(1, n+1): product = product * k return product

As above, the else is not actually needed. Sometimes a non-recursive ver- sion of an algorithm runs a bit faster than a recursive one; this issue is highly implementation dependent.

4.3.2 Binary representation

The problem is to convert a given positive integer n to binary. We need to l i find the unique binary digits (i.e., bits) di 0, 1 such that n = i=0 di2 ; then the binary representation is the string∈ formed { } from the digits taken in reverse order: d d d . Clearly, d is the remainder of n moduloP 2. Then l ··· 1 0 0 d1 is the remainder of (n d0)/2 modulo 2, and so on. This leads to the following code: − def binary(n): """Return the binary representation of n""" s="" #empty string to hold answer while n > 0: d=n%2 s = str(d) + s #add digit to front of s n = (n-d)//2 #integer quotient return s

The code is a bit tricky. Notice the use of strings to build up the answer; this is because the digits are produced in reverse order from the way we want to read them in the answer. The + operator joins together (concatenates) two strings to form a longer string. 4.3. ARITHMETIC 53

The same code can be used to convert to different bases than base 2, simply by replacing 2 with another number. For instance, replace 2 by 3 to convert to base 3. (In general, the base r expansion of a given positive integer l i n has the form dl d1d0 where n = i−0 dir and di 0, 1,...,r 1 for all i. The base r is··· sometimes called the radix.) ∈ { − } P Let’s also implement the inverse function, which converts a given binary string back into an integer. For this purpose, it is useful to know that the built-in int function attempts to convert data of any type into an integer, so for instance int("3") returns the integer 3, and so forth. def inverse_binary(s): """Convert binary string to integer""" sum = 0 for k in range(len(s)): digit = int(s[k]) sum = 2*sum + digit return sum

This will not work unless the input really is a string of binary digits. It is easy to modify this code to work for a different radix r.

4.3.3 Decimal expansions of fractions

The problem is to compute (as a string) the exact decimal expansion of a a given integer fraction b where b = 0. Python supports arbitrary precision integers (limited by the amount6 of machine storage available) so a and b could be quite large, and in such cases the built-in operation a/b will return only an approximation to the decimal expansion, because of limitations of floating point representations. In the case of an infinite repeating decimal, we cannot compute the exact decimal expansion, but we can detect the number of repeating digits in the expansion. A given of integers leads to a repeating decimal precisely when a remainder appears for the second time. def decimal(a,b): """Return the decimal expansion of a/b""" rmdr = [] #empty list to start q = a//b; r = a % b #get started answer = str(q) + ’.’ #store integer quotient while r != 0 and r not in rmdr: rmdr.append(r) #remember the remainder a = 10*r #compute new dividend 54 CHAPTER 4. DEVELOPING FUNCTIONS

r=a%b #compute new r answer = answer + str(a//b) #add digit if r == 0: #answer is finite return answer else: #answer is infinite repeating n = len(rmdr) - rmdr.index(r) rep = answer[len(answer)-n:] #repetition block answer = answer + ’ ’ + rep + ’ ...’ return answer

This code implements the usual school algorithm of long division, keeping a list of remainders and checking for repetition at each stage. If a non-zero remainder is repeated, then the decimal expansion is infinite, in which case the code produces a string showing two repetition blocks of digits. Testing the code shows how it works: >>> decimal(1,2) ’0.5’ >>> decimal(1,3) ’0.3 3 ...’ >>> decimal(1,17) ’0.0588235294117647 0588235294117647 ...’ >>> decimal(1,113) ’0.0088495575221238938053097345132743362831858407079646017699 115044247787610619469026548672566371681415929203539823 008849 5575221238938053097345132743362831858407079646017699115044247 787610619469026548672566371681415929203539823 ...’ >>> 1/113 0.008849557522123894

It is instructive to compare the computation of decimal(1,113) with Python’s built-in 1/113, as in the last line above.

4.4 Calculus

4.4.1 Derivatives

The problem is to compute an approximation for the derivative of a given function f(x) at a given point x. This depends on both the function f and the given point x. One standard approach is to use a three point approxi- mation

′ f(x + h) f(x h) 1 f(x + h) f(x) f(x) f(x h) f (x) − − = − + − − ≈ 2h 2 h h   4.4. CALCULUS 55 for some small value of h. Note that the three point approximation is the average of the two secant line approximations taken at the input pairs x+h, x and x, x h. − Choosing a good value of h is quite delicate. If h is chosen too small, the subtraction will yield a large rounding error. If h is chosen too large, the calculation of the slope of the secant line will be more accurate, but the estimate of the slope of the tangent by using the secant could be worse. The usual choice for h is to take h = x√ǫ where ǫ depends on the machine’s floating point implementation (sometimes called the machine epsilon). For Python we can take this to be ǫ =2−52, so we have: from math import sqrt def derivative(f, x): """Return a 3 point approx of derivative of f at x""" epsilon = 2**(-52) # machine epsilon for double float h = x * sqrt(epsilon) return (f(x+h) - f(x-h))/(2*h)

In order to test this code it is convenient to use a lambda expression in order to give a one-line definition of the input function: >>> f = lambda x: x*x #the squaring function >>> derivative(f, 2) 4.0 >>> derivative(f, 0.5) 1.0 >>> derivative(f, 0.001) 0.0020000000020559128

As we can see, at least for this function (which is just the squaring function f(x) = x2) the computation of the derivative is quite accurate, even for small values of x near zero. This example illustrates Python’s ability to effortlessly pass functions as parameters to other functions. Of course, in testing derivative we could just as well define the function f in the usual manner, without using a lambda expression: >>> def f(x): ... return x*x ... >>> derivative(f,2) 4.0

Such typing is somewhat cumbersome, which is why people often prefer to use lambda functions in situations such as these. We could also use other functions. Let’s compute the derivative of f(x)=1/x at the point x =1/10: 56 CHAPTER 4. DEVELOPING FUNCTIONS

>>> derivative(lambda x: 1/x, 0.1) -100.0

4.4.2 Trapezoidal rule

The problem is to approximate the definite integral of a continuous function f over a given closed interval [a,b], using n uniform subintervals in the trapezoidal rule. The formula for the trapezoidal approximation is

b h n f(x) dx (f(xk)+ f(xk−1)) a ≈ 2 Z Xk=1 where h =∆x =(b a)/n and xk = a + k∆x for k =0, 1,...,n. This can be implemented as follows:− def trap(f, a,b,n): """Return the trapezoidal rule approx of the integral of f from a to b""" h = (b-a)/n; sum = 0 for k in range(1,n+1): sum = sum + f(a+(k-1)*h) + f(a+k*h) return (h/2)*sum

Again one might find it convenient to use a lambda function definition to test this: >>> trap(lambda x:x*x, 0,1,100) 0.3333499999999999 >>> trap(lambda x:2*x, 0,1,64) 1.0

4.4.3 Simpson’s rule

The problem is to approximate the definite integral of a continuous function f over a given closed interval [a,b], using 2n uniform subintervals in the Simpson’s rule. (For Simpson’s rule the number of subintervals must be even.) The formula for the Simpson’s rule approximation can be written in the form

b h n f(x) dx (f(x2k−2)+4f(x2k−1)+ f(x2k)) a ≈ 3 Z Xk=1 4.4. CALCULUS 57

where now h = ∆x = (b a)/2n and xk = a + k∆x for k = 0, 1,..., 2n. This can be implemented− as follows: def Simpson(f, a,b,n): """Return the Simpson’s rule approx of the integral of f from a to b""" h = (b-a)/(2*n); sum = 0 for k in range(1,n+1): m = 2*k sum = sum + f(a+(m-2)*h) + 4*f(a+(m-1)*h) + f(a+m*h) return (h/3)*sum

In some cases this gives good approximations even with a small number of subintervals: >>> Simpson(lambda x:x*x, 0,1,5) 0.33333333333333337 >>> Simpson(lambda x:x*x*x, 0,1,5) 0.25

4.4.4 Approximating e

x x n →∞ It is well known that e = limn 1+ n . Although this formula is attrac- tive, it is not a good choice for approximating e since the rate of convergence  is somewhat slow. A better formula is the classical Taylor series for ex:

∞ xn x x2 xn ex = =1+ + + + + R (x). n! 1! 2! ··· n! n n=0 X x x2 xn We use the nth order Taylor polynomial Pn(x)=1+ 1! + 2! + + n! 1 ··· with x = 1 to approximate e = e; then Taylor’s remainder Rn(1) gives the error in our approximation. Assuming a factorial function (see 4.3.1) is available, for instance if we imported it from a file, the coding is as follows: def e(n): """Return the number e calculated using an n-th order Taylor polynomial""" sum = 0 #to start with for k in range(n+1): sum = sum + 1.0/factorial(k) return sum

We need to know how many terms of the series to sum (what should n be?) in order to achieve an approximation guaranteed to be as accurate as 58 CHAPTER 4. DEVELOPING FUNCTIONS we desire. From basic calculus we know that for any x there is some real number c strictly between 0 and x such that

ec xn+1 R (x)= . n (n + 1)!

Here we can take x = 1 and use 3 as an upper bound on ec, since 0

If we desire an accuracy with error no worse than 10−15 then we should use 3 −15 the smallest n such that (n+1)! < 10 , or equivalently the smallest n so that (n + 1)! > 3 1015. We can find such an n easily using Python as a calculator: × >>> for n in range(100): ... if factorial(n+1) > 3*10**15: ... break ...... >>> n 17

So we should use n = 17 in order to get the desired accuracy. >>> e(17) 2.7182818284590455

How to make this more robust? We can certainly incorporate the above search for the smallest n into the code. Also it seems efficient to compute our factorials as we go along. Here is a first attempt at such improvements: def e(acc): """Return the number e calculated with error no worse than 10**(-acc), using an n-th order Taylor polynomial""" sum = 0; n = 0; factorial = 1 #to start with while factorial <= 3*10**acc: sum = sum + 1.0/factorial n = n+1 factorial = n*factorial return sum

Running this code reveals an issue, hower. We cannot, it seems, achieve any desired degree of accuracy: 4.4. CALCULUS 59

>>> e(5) 2.71827876984127 >>> e(10) 2.7182818284467594 >>> e(15) 2.7182818284590455 >>> e(20) 2.7182818284590455

Notice that there is no difference in the last two outputs. This is caused by inherent limitations in floating point arithmetic. (The Python Tutorial [2] contains a nice summary of many of these issues.) Since we are interested in accuracy in the decimal expansion of e we should use Python’s built-in decimal module, which needs to be imported. The reader should look at the Python documentation of this module to understand the following code. from decimal import * # import the module def e(acc): """Return the number e calculated with error no worse than 10**(-acc), using an n-th order Taylor polynomial"""

# first set the number of decimal places # use one extra place to help with round-off error getcontext().prec = acc+1

# now do the calculations using the decimal module sum = 0; n = 0; factorial = 1 #to start with while factorial <= 3*10**(acc+1): sum = sum + Decimal(1)/Decimal(factorial) n = n+1 factorial = n*factorial return sum

Here are a few example computations done using this code. The result is much better than before. Note that the last digit in the output is not always trustworthy. >>> e(12) Decimal(’2.718281828459’) >>> e(20) Decimal(’2.71828182845904523536’) >>> e(40) Decimal(’2.7182818284590452353602874713526624977573 ’) >>> e(50) Decimal(’2.71828182845904523536028747135266249775724709369995’) 60 CHAPTER 4. DEVELOPING FUNCTIONS

We can check the precise value of e using Python’s built-in Decimal imple- mentation of the exponential function: >>> getcontext().prec=52 >>> Decimal(1).exp() Decimal(’2.718281828459045235360287471352662497757247093699960’)

This example only scratches the surface of a world of possibilities.

4.5 Number theory

4.5.1 Pythagorean triples

A Pythagorean triple is an ordered triple (a,b,c) of positive integers such that a2 + b2 = c2, such as 32 +42 = 52 and 52 + 122 = 132. These triples define the side lengths of a right triangle, and such triangles are often used by trigonometry instructors. Euclid gave a formula for generating Pythagorean triples in terms of an arbitrary pair of positive integers m and n with m>n. The formula states that the integers a = m2 n2, b =2mn, c = m2 + n2 − form a Pythagorean triple. It is known that this formula generates all prim- itive Pythagorean triples, where a Pythagorean triple is primitive if the numbers a,b,c are pairwise coprime. If (a,b,c) is a primitive Pythagorean triple then (ka,kb,kc) is a Pythagorean triple for all positive integers k, so it suffices to generate just the primitive triples. The triple generated by Euclid’s formula is primitive if and only if m and n are coprime and m n is odd. Here’s a function that generates the Pythagorean triple corresponding− to a given pair m,n: def Pythagorean_triple(m,n): """Return the Pythagorean triple generated by (m,n)""" return (m**2-n**2, 2*m*n, m**2+n**2)

4.5.2 Day of the week from the date

The following formula for the Gregorian calendar calculates the day of the week from any given date after September 14, 1752 (a Thursday) when the Gregorian calendar was adopted by Great Britain and her colonies. First we define the following variables: 4.5. NUMBER THEORY 61

Y one less than the given year, for January or February, or the given year, for the rest of the year. d the given day (1 to 31) m the shifted month (March = 1, ..., February = 12) y the last two digits of Y c the first two digits of Y w the day of week (0 = Sunday, ..., 6 = Saturday)

Then the formula, which uses the floor function x = largest integer k such that k x, says that w is given by ⌊ ⌋ ≤ w = d + 2.6m 0.2 + y + y/4 + c/4 2c (mod 7). ⌊ − ⌋ ⌊ ⌋ ⌊ ⌋− One possible implementation of this goes as follows, using a list to hold the names of the days of the week: from math import floor def day_of_week(year,month,day): """Return the day of the week of the date""" days=["Sunday","Monday","Tuesday","Wednesday", "Thursday","Friday","Saturday"] if month <= 2: Y = year - 1 else: Y = year d = day m = (month + 10) % 12 y = Y % 100 c = Y // 100 w = (d+floor(2.6*m-0.2)+y+floor(y/4)+floor(c/4)-2*c) % 7 return days[w]

4.5.3 Greatest common divisor

The problem is to compute the greatest common divisor (gcd) of two inte- gers. This is a classic problem; the algorithm given in Euclid’s Elements has never been improved on. The idea is simple. Given two positive integers a,b divide one by the other to get an integer quotient q0 and an integer remainder r0, so that

a = bq + r (0 r

Then it is easy to show that gcd(a,b) = gcd(b,r0). So repeat the process, replacing the pair (a,b) with the pair (b,r0). Dividing b by r0 gives another integer quotient q1 and integer remainder r1 such that b = r q + r (0 r

Then gcd(a,b) = gcd(b,r0) = gcd(r0,r1). Continuing in this way, we con- struct a decreasing sequence b>r0 > r1 > of non-negative integers. Such a sequence must eventually reach 0 in a finite··· number, say k, of steps, so rk = 0. Then gcd(a,b) = gcd(rk−1,rk) = gcd(rk−1, 0) = rk−1, the last non-zero remainder in the sequence of remainders. The implementation of this algorithm in Python is quite elegant; this example well illustrates the power of Python to compactly express mathematical algorithms: def gcd(a,b): """Return the gcd of a,b""" while b > 0: a, b =b, a % b return a

Notice the use of the multiple assignment statement to replace the pair a,b with the pair b,r where r is the remainder of the division of a by b. It is also possible to solve the problem recursively. The idea is that

a if b =0 gcd(a,b)= (gcd(b,r) otherwise. where a = bq + r. This leads immediately to the following recursive Python definition of the gcd function: def gcd(a,b): """returns the gcd of its inputs""" if b == 0: return a else: return gcd(b, a % b)

4.5.4 Extended

Starting from the next to last equation from the Euclidean algorithm and solving for rk−1 we get

r − = r − r − q − . k 1 k 3 − k 2 k 1 4.5. NUMBER THEORY 63

(Let’s agree to put a = r−2, b = r−1 so that this equation always makes sense.) Doing the same (solving for the reminder) in the earlier equations and successively substituting leads eventually to integers x, y such that

rk−1 = gcd(a,b)= ax + by. Finding such integers x,y (which are not unique) is the purpose of the ex- tended Euclidean algorithm. In fact, I claim that it is possible to find se- quences xi and yi of integers such that at each stage we have ri = axi +byi for i = 2, 1,...,k. Clearly, we can take x− = 1, y− = 0 (because r− = a) − − 2 2 2 and x−1 = 0, y−1 = 1 (because r−1 = b). With these starting values an induction on i using the equation r = r − r − q now shows that i i 2 − i 1 i x = x − q x , y = y − q y i+1 i 1 − i i i+1 i 1 − i i for all i. Thus we can calculate the x-sequence and y-sequence as we go along in the calculation of the remainder sequence. Note that at each stage we only need to keep track of two consecutive values of the x-sequence (we use variables x, prevx for this) and the same for the y-sequence (we use variables y, prevy). This analysis leads us to the following code, based on the non-recursive code from 4.5.3: def xgcd(a,b): """returns the gcd and the numbers x,y such that gcd = ax+by""" prevx, x = 1, 0 prevy, y = 0, 1 while b: q = a//b x, prevx = prevx - q*x, x y, prevy = prevy - q*y, y a, b =b, a % b return (a, prevx, prevy) Here we have to also generate the sequence of integer as we go along; these are kept in the variable q. Note the use of multiple assignment statements that greatly simplify the code; also note the use of a 3-tuple to return all three values, namely the gcd as well as the values of x,y such that gcd = ax + by. Because the algorithm terminates only when we reach a zero remainder rk = 0, the desired values of x and y are actually xk−1 and yk−1. This is why the values prevx and prevy are returned upon termination of the while loop. The above solution, while correct, is hard to follow. Here’s a more elegant recursive solution (suggested by A. Harrington). 64 CHAPTER 4. DEVELOPING FUNCTIONS

def xgcd(a, b): """returns the gcd g and the numbers x,y such that g = ax+by""" if b == 0: return a, 1, 0 # a = 1*a + 0*b r=a%b q = a//b # a = q*b + r; r = a - q*b, subst below: g, x, y = xgcd(b, r) # gcd = x*b + y*r = x*b + y*(a-q*b) return g, y, x - q*y # =y*a+(x q*y)*b

This can be improved even further by using the faster divmod built-in func- tion: def xgcd(a, b): """returns the gcd g and the numbers x,y such that g = ax+by""" if b == 0: return a, 1, 0 # a = 1*a + 0*b q, r = divmod(a, b) # a = q*b + r; r = a - q*b, subst below: g, x, y = xgcd(b, r) # gcd = x*b + y*r = x*b + y*(a-q*b) return g, y, x - q*y # =y*a+(x q*y)*b

Notice again the nice use of multiple assignemnt to simplify the code and enhance its readability.

4.5.5 Smallest factor of a number

The problem is to find the smallest factor p > 1 of a given integer n > 1. Of course, the smallest such p must be a prime, and p = n if and only if n is prime. The solution is quite easy to code: from math import sqrt def smallest_factor(n): """Return the smallest non-trivial divisor of n""" for p in range(2,n+1): if n % p == 0: return p if p > sqrt(n): return n

Notice the use of the % operator to obtain the remainder of an integer di- vision; if the remainder is zero then we have detected divisibility. The last if statement in the loop returns n as soon as the possible divisor p becomes larger than √n, since we know that n must be prime if it has no prime divisors not exceeding its square root. 4.5. NUMBER THEORY 65

The problem may alternatively be solved using a while loop instead of a for loop, as follows: from math import sqrt def smallest_factor(n): """returns the smallest non-trivial divisor of n""" p = 2 while n % p != 0: p = p+1 if p > sqrt(n): return(n) return p

This may be improved. If n is not divisible by 2 (n is not even) then the smallest factor will necessarily be an odd number, so we can skip all the even possible divisors in the code, This leads us to: from math import sqrt def smallest_factor(n): """returns the smallest non-trivial divisor of n""" if n % 2 == 0: return 2 p = 3 while n % p != 0: p = p+2 if p > sqrt(n): return(n) return p which should perform on average about 50% faster than the previous version. Notice that in this code the entire first if statement appears on a single line; Python allows this in simple situations where no confusion can result. The worst case is when n is an odd prime; in that case the algorithm 1 must perform about 2 √n divisions before it concludes that n is prime and outputs n. Let d be the number of binary digits in n. Then 2d−1 n< 2d, (d−3)/2 1 (d−2)/2 ≤ so 2 2 √n< 2 . This implies that the time it takes to find the smallest factor≤ depends (in the worst case) exponentially on the number of binary digits of the input. This implies that for large enough inputs, you will need to wait years or even centuries for an answer, no matter how fast your computer may be. (The difficulty of factoring large numbers is the foundation of a number of public-key cryptosystems.) Python has a clock function in its library time module, which may be imported in order to measure the time it takes to execute commands. For example, let’s check the timing of smallest_factor (the third, fastest, ver- sion) when applied to a few known Mersenne primes. Recall that a Mersenne 66 CHAPTER 4. DEVELOPING FUNCTIONS prime is a prime of the form n = 2p 1 where p is itself prime. The first few Mersenne primes and their sizes are− shown in the table below.

p n decimal digits in n 2 5 1 3 7 1 5 31 2 7 127 3 13 8191 4 17 131071 6 19 524287 6 31 2147483647 10 61 2305843009213693951 19 89 618970019642690137449562111 27 107 162259276829213363391578010288127 33

Note that the number of binary digits in n = 2p 1 is p. Now we define a function called timing to check the timing of running− smallest_factor(n) for various Mersenne primes. (Mersenne primes are convenient for testing purposes since they are given by an easily typed formula.) >>> from time import clock >>> def timing(n): ... a = clock() ... p = smallest_factor(n) ... return clock() - a ... >>> timing(2**17 - 1) 0.00027800000000000047 >>> timing(2**19 - 1) 0.00044799999999999007 >>> timing(2**31 - 1) 0.017123 >>> timing(2**61 - 1) 410.578371

The last one, namely n =261 1, is a Mersenne prime with 19 decimal (or 61 binary) digits, and it took more− than 410.5 seconds (about 6.8 minutes) for smallest_factor(n) to terminate on my Macbook Pro. The next Mersenne prime is 289 1, which has 27 decimal (or 89 binary) digits. The number of seconds it should− take the same machine to check it is about

410 2(89−2)/2/2(61−2)/2 = 6717440. ∗ 4.5. NUMBER THEORY 67

This is more than 6.7 million seconds, which is about 1866 hours or 77.7 days. Checking the next case is not something we would like to wait around for.

4.5.6 Prime factorization of a number

The problem is to find the complete prime factorization of a given integer n > 1. We solve the problem using one of the smallest_factor functions developed in the preceding section. The idea is as follows. First let p be the smallest factor of n. Then the prime factors of n are p itself together with the prime factors of n/p. This leads to the following recursive code: def factors(n): """Return a list of the prime factors of n""" if n == 1: return [] #empty list else: p = smallest_factor(n) return [p] + factors(n//p)

Notice the use of the + operator in the last line to join two lists together. Here is a non-recursive solution, again based on smallest_factor: def factors(n): """Return a list of the prime factors of n""" primes = [] #empty list to start while n > 1: p = smallest_factor(n) primes.append(p) n = n//p return primes

The two approaches are not equivalent for negative inputs, as the first re- turns an exception for such inputs while the second just returns an empty list. There are better factoring algorithms, but all known methods (for clas- sical computers) are inherently exponential.1

1In 1994 Peter Shor devised a polynomial time algorithm for factoring integers us- ing a quantum computer. Unfortunately, nobody (yet) knows how to build a quantum computer. Chapter 5

Programming with class

In this chapter we introduce Python’s object-oriented capabilities with the class construct, and explore some natural examples applicable to mathe- matics. Object-oriented programming (OOP) has roots that can be traced to the 1960s. As hardware and software became increasingly complex, man- ageability often became a concern. Researchers studied ways to maintain software quality and developed object-oriented programming in part to ad- dress common problems by strongly emphasizing discrete, reusable units of programming logic. Object-oriented programming became the dominant programming methodology in the early and mid 1990s when programming languages supporting the techniques became widely available. By focusing on data and processes rather than just processes, the object- oriented approach encourages the programmer to place data where it is not directly accessible by the rest of the program, potentially limiting the impact of errors and reducing the overall complexity of code. In Python, a class is, loosely speaking, a collection of objects and methods. The objects comprise the data of the class, and the methods are special types of functions used to manipulate the objects in various ways. Object-oriented programming is more abstract than traditional programming, but the increase in abstraction pays dividends in some situations. In some sense, a reasonable way to think of a class is as a new user- defined data type. By introducing such new data types pertaining to the problems you need to solve, you can extend Python’s capabilities as well as solve your problems, and in so doing you may be in a better position to solve future problems as well.

68 5.1. GETTING STARTED WITH CLASSES 69 5.1 Getting started with classes

In Python all data types are objects! This includes integers, floating point numbers, complex numbers, lists, and strings. Even functions are objects. If you’ve been computing in Python then you already have some experience with objects. The class construct in Python allows you to define your own classes of data and use them to create objects of that new type.

5.1.1 A first example

Here is a simple class definition to get started. In general, a class is defined by the keyword class followed by the class name on the first line, followed by a colon, with one or more indented statements on subsequent lines forming the body of the class. The simplest possibility is the following, which we could even type in the interpreter: >>> class Nada: ... pass ... >>>

The pass statement is there because there must be at least one statement in the body of a class definition. Since pass doesn’t do anything, this class (that we chose to name Nada) is an empty class. It doesn’t do anything, but still we have created a new class of data, and we can create objects of that new type: >>> x = Nada() >>> x <__main__.Nada object at 0x1005c7a10>

Here we have created an object of the class Nada, naming it x. When we evaluate x, the system returns the class name and the memory address of the object x. People often say that the object x is an instance of the class; the act of creating an instance of a class (an object) is often called instantiation. In this case, our instance x is an empty object because the class is empty. Let’s put some data in x, so that it isn’t so embarrassingly empty: >>> x.abscissa = 2 >>> x.ordinate = 3

We have now dynamically altered the object x, by defining two attributes associated with x, namely the abscissa and ordinate. So the object x now 70 CHAPTER 5. PROGRAMMING WITH CLASS has some data. But it still can’t do anything, since we haven’t yet defined any methods in the class. A method is just a function that is associated with a class, and hence with every object of that class; it must have at least one parameter, which is expected to be an object of the class. So let’s define a function and assign it to the class. >>> def f(ob): ... return (ob.abscissa, ob.ordinate) ... >>> Nada.fun = f >>> Nada.fun

So now the class Nada has been dynamically altered to have a method named fun. This method is another name for the function f, which operates on an object ob to form an ordered pair of the object’s abscissa and ordinate, and returns that ordered pair: >>> Nada.fun(x) (2, 3)

Here we evaluated the function on the object x in order to produce an ordered pair. Now we come to a subtle yet important issue. There is another way to do this, and this other way is usually preferred. Here’s how it works: >>> x.fun() (2, 3) >>> x.fun >

So what just happened? When we added the method fun to the class Nada, it was bound to the object x and thus became another attribute of x. This is because people want to think of methods as belonging to objects as well as classes. Furthermore, the operand ob became an implicit reference to the object x, for the method x.fun. To say it another way, the function call x.fun() is defined to be equivalent to the function call Nada.fun(x). In effect, the parameter x in the latter notation becomes the prefix of the call in the former. As already mentioned, the notation x.fun() is usually preferred over the notation Nada.fun(x). For one thing, the first notation is shorter, but more importantly, we can can conceptualize the method fun as “belonging to” the object x, and thus the notation x.fun() means “apply the method fun to its object x.” 5.1. GETTING STARTED WITH CLASSES 71

More generally, if g is a method in the class Nada with more than one parameter, say p1, p2, ..., then the call Nada.g(p1, p2, ...) is exactly equivalent to the call p1.g(p2, ...). Here’s an example:

>>> def g(u,v): ... return (u.fun(), v.fun()) ... >>> Nada.g = g >>> Nada.g(x,x) ((2, 3), (2, 3)) >>> x.g(x) ((2, 3), (2, 3))

Here we created a function g of two arguments that returns an ordered pair of ordered pairs. Then we assigned it to the class, making it a method of the class. Finally, we evaluated the method in the two equivalent ways. At this point we could create another object — let’s call it y — of the class Nada, and it will not be empty since we have now associated two methods to the class.

>>> y = Nada() >>> y <__main__.Nada object at 0x1005c7b90> >>> y.fun > >>> y.g >

Here we see that y.fun and y.g are methods associated to the object y. We cannot yet evaluate either method, however:

>>> y.fun() Traceback (most recent call last): File "", line 1, in File "", line 2, in f AttributeError: ’Nada’ object has no attribute ’abscissa’ because the object y has no data, and the methods expect the object to contain data. We need to supply some data to y in order to apply the method fun to it:

>>> y.abscissa = 1.5 >>> y.ordinate = 2.4 >>> y.fun() (1.5, 2.4) 72 CHAPTER 5. PROGRAMMING WITH CLASS

Notice the polymorphism here. An object doesn’t care what type of data it gets, nor does its methods. Thus the object x consists of two integers and two methods, while the object y consists of two floating point numbers and two methods (the same methods). This illustrates the power and increased flexibility of Python over strongly typed languages (e.g., Java) in which the type of all data must be declared explicitly at definition time, which tends to make polymorphism more difficult.

5.1.2 Second example

Usually class definitions are stored in files and imported at run time. Typi- cally most, if not all, methods of the class are specified in the class definition, avoiding the need to add them dynamically later. Furthermore, most classes contain a special constructor method named __init__ for the purpose of creating, and initializing, objects in the class. The name __init__ is a built-in capability in Python that is implicitly called every time the class is instantiated. Let’s redo the class Nada from above, making it a more robust class that now contains a constructor method, along with the methods defined above. Let’s rename the class to something more informative, while we are at it. class Point: """Points are ordered pairs""" def __init__(self, x, y): self.abscissa = x self.ordinate = y def fun(ob): return (ob.abscissa, ob.ordinate) def g(u,v): return (u.fun(), v.fun())

Notice the docstring after the class line. Just as for function definitions, a docstring may optionally be supplied starting on the second line of a class definition. Again, it is considered good practice to include such docstrings in your class definitions. Let’s see how this class may be used in the interpreter, assuming we have imported it already: >>> x=Point(2,3) >>> y=Point(4,5) >>> x.fun() (2, 3) 5.1. GETTING STARTED WITH CLASSES 73

>>> x.g(y) ((2, 3), (4, 5)) >>> y.g(x) ((4, 5), (2, 3))

We just created two Point objects named x and y, initialized them, and then evaluated the methods using the objects. There are two other special built-in methods, named __repr__ and __str__, that are used to define a printable “representation” and “string form” of our class objects. Both of these must return a string, and it is up to the programmer to decide what format the string should have. Python uses the __repr__ form when the object is evaluated, and the __str__ form when it is printed. Moreover, if the class contains a __repr__ definition but no __str__ definition then Python uses the __repr__ definition for both, so usually it is enough just to define __repr__. Let’s do that, at the same time replacing the silly methods fun, g by methods that are more useful. Also, I want to alter the constructor method slightly. from math import sqrt class Point: """Points are ordered pairs"""

def __init__(self, x=0, y=0): self.abscissa = x self.ordinate = y

def __repr__(self): return str((self.abscissa, self.ordinate))

def distance(self, other): dx = self.abscissa - other.abscissa dy = self.ordinate - other.ordinate return sqrt(dx**2 + dy**2)

def scale(self, s): return Point(s*self.abscissa, s*self.ordinate)

After saving the changes and starting a new Python session we must re- import our changed class definition. The changed first line of the __init__ definition provides default values for the data in a new Point object; this makes supplying those arguments optional. >>> a = Point() >>> a (0, 0) 74 CHAPTER 5. PROGRAMMING WITH CLASS

Now we can use the definitions in the class to do useful computations with Point objects: >>> x=Point(3,3) >>> x (3, 3) >>> print(x) (3, 3) >>> x.scale(2) (6, 6) >>> y = Point(x, x.scale(2)) >>> y ((3, 3), (6, 6))

Notice how y is defined as a Point consisting of two other Points. Also notice how the __repr__ method is used to give nicely formatted results when we evaluate or print objects. Most classes define a __repr__ method as well as an __init__ method.

5.1.3 A word about self

In the preceding example, we used the name self for the first parameter of each method definition. Actually, we could use any name, but the standard Pythonic convention is always to use self for the first parameter in method definitions of a class. The reason is that this parameter is a name for the object which will (later) call the method being defined. So in a sense self stands for “the current object.” In other words, if x is an object and f is a method in the class defined with just one parameter, then the invocation x.f() uses x for self, since the current object calling the method f is in fact x.

5.1.4 Built-in types are classes, too

Consider the class int of integers. To see what methods are defined in the class, you can use the built-in dir command: >>> dir(int) ’__abs__’, ’__add__’, ’__and__’, ’__bool__’, ’__ceil__’, ’__class__’, ’__delattr__’, ’__divmod__’, ’__doc__’, ’__eq__’, ’__float__’, ’__floor__’, ’__floordiv__’, ’__format__’, ’__ge__’, ’__getattribute__’, ’__getnewargs__’, ’__gt__’, ’__hash__’, ’__index__’, ’__init__’, ’__int__ ’, 5.1. GETTING STARTED WITH CLASSES 75

’__invert__’, ’__le__’, ’__lshift__’, ’__lt__’, ’__mod__ ’, ’__mul__’, ’__ne__’, ’__neg__’, ’__new__’, ’__or__’, ’__pos__’, ’__pow__’, ’__radd__’, ’__rand__’, ’__rdivmod__’, ’__reduce__’, ’__reduce_ex__’, ’__repr__’, ’__rfloordiv__’, ’__rlshift__’, ’__rmod__’, ’__rmul__’, ’__ror__’, ’__round__’, ’__rpow__’, ’__rrshift__’, ’__rshift__’, ’__rsub__’, ’__rtruediv__’, ’__rxor__’, ’__setattr__’, ’__sizeof__ ’, ’__str__’, ’__sub__’, ’__subclasshook__’, ’__truediv__ ’, ’__trunc__’, ’__xor__’, ’bit_length’, ’conjugate’, ’denominator’, ’from_bytes’, ’imag’, ’numerator’, ’real ’, ’to_bytes’] which returns a sorted list of method names. As you can see, almost all of the method names begin and end with a double underscore, indicating that they are special built-in methods. Notice that __init__, __repr__, and __str__ are all defined for the class of integers. As you might guess, the built-in method __add__ should have something to do with addition. We can query the method’s docstring to learn more: >>> int.__add__.__doc__ ’x.__add__(y) <==> x+y’

Although rather terse, this tells us that x.__add__(y) is defined to be equiv- alent to x+y. In Python, all the usual operators such as + are defined by special built-in methods. Let’s test this out in the interpreter: >>> int.__add__(5,7) 12 >>> (5).__add__(7) 12

This is doing addition of integers the hard way, but it illustrates how addition is implemented as a method in Python. Note that the parentheses around the first integer in the command (5).__add__(7) are necessary: >>> 5.__add__(7) File "", line 1 5.__add__(7) ^ SyntaxError: invalid syntax because without the parentheses, Python interprets 5. as a floating point number. Actually, addition is implemented the same way, through a call to the built-in name __add__, in all classes for which the operator names sense. 76 CHAPTER 5. PROGRAMMING WITH CLASS

>>> float.__add__(9.1, 6.7) 15.8 >>> (9.1).__add__(6.7) 15.8 >>> 9.1.__add__(6.7) 15.8

In this case, the parentheses around the first operand are unnecessary, since Python’s interpreter is able to figure out what was intended. Not only is there an __add__ method defined in the int and float classes, but also the classes complex, str, and even list as well. In the latter two cases, the __add__ method joins two strings or two lists together.

>>> complex.__add__(2+4j, 3-6j) (5-2j) >>> (2+4j) + (3-6j) (5-2j) >>> str.__add__("Hello","World") ’HelloWorld’ >>> "Hello" + "World" ’HelloWorld’ >>> list.__add__([1,2,3], [7,5,5,1]) [1, 2, 3, 7, 5, 5, 1] >>> [1,2,3] + [7,5,5,1] [1, 2, 3, 7, 5, 5, 1]

All of the built-in types in Python are actually classes in their own right. Furthermore, exceptions are classes, too! It is possible, and sometimes use- ful, to define your own exception classes. See the Python manuals for details.

5.1.5 Special methods for operators

Here we list the various special methods, such as the __add__ method dis- cussed in the previous subsection, that Python uses to define the meaning of operators. This is quite useful because Python allows us to use the same technique to define meanings for these operators in our own class definitions, as we shall see later. Here are the special method names associated with the common arithmetic operators: 5.1. GETTING STARTED WITH CLASSES 77

+ __add__(self, other) - __sub__(self, other) * __mul__(self, other) / __truediv__(self, other) // __floordiv__(self, other) % __mod__(self, other) ** __pow__(self, other)

Thus, for example, x.__sub__(y) is equivalent to x-y, and so on. Because of the lack of symmetry in the OOP paradigm (object x calling a method with parameter y is not equivalent to objecty calling the method with parameter x), in some situations the following special method names associated with operators also become important:

+ __radd__(self, other) - __rsub__(self, other) * __rmul__(self, other) / __rtruediv__(self, other) // __rfloordiv__(self, other) % __rmod__(self, other) ** __rpow__(self, other)

These methods are called only if the left operand does not support the corresponding operation and the operands are of different types. For in- stance, to evaluate the expression x-y, where y is an instance of a class that has an __rsub__() method, y.__rsub__(x) is called if x.__sub__(y) returns NotImplemented. In effect, both expressions x.__sub__(y) and y.__rsub__(x) mean “subtract y from x” but the first regards the opera- tion as belonging to the left operand object x while the second regards it as belonging to the right operand object y. Here are the special method names associated with common comparison operators in Python:

< __lt__(self, other) <= __le__(self, other) == __eq__(self, other) != __ne__(self, other) > __gt__(self, other) >= __ge__(self, other) 78 CHAPTER 5. PROGRAMMING WITH CLASS

For example, x.__ne__(y) is equivalent to x != y, and so on. By con- vention, the boolean values False and True are returned for a successful comparison. There are more special names for operators than these. The reader should consult Python’s extensive documentation for complete details.

5.2

Here we want to use OOP to implement Gauss’s modular arithmetic of num- ber theory or, equivalently, we wish to implement arithmetic in the quotient ring Z/mZ of integers modulo m. We make use of the fact that Python’s class statement is executable, just like any other statement. (Executing a class definition just defines the class; i.e., it makes all its attributes and methods available.) Thus, it is possible to have a class definition appear in an if statement, in a loop, or even inside a function definition. So we define a function that creates a class of residues modulo m and supports modular arithmetic modulo m with those residues. We use a func- tion since we can then make the modulus, m, a parameter for the function. In this way, residues modulo m are distinguished from residues modulo n for m = n. We also need to import a helper function, the extended Euclidean algorithm6 xgcd discussed earlier in see 4.5.4. #### we need the extended Euclidean algorithm #### from xgcd import xgcd

# Meta class definition. Defines a class for arithmetic # mod m, for any given m. The class actually depends on m.

def ZMod(m): """Define class of residues mod m. Usage: To create a class for modular arithmetic modulo 26, just type

>>> mod26 = ZMod(26)

after importing this file."""

class mod:

def __init__(self, n=0): self.value = n % m 5.2. MODULAR ARITHMETIC 79

def __repr__(self): return str(self.value)+" mod "+str(m)

def __add__(self, other): if isinstance(other, int): other = mod(other) return mod(self.value + other.value)

def __sub__(self, other): if isinstance(other, int): other = mod(other) return mod(self.value - other.value)

def __neg__(self): return mod(-self.value)

def __mul__(self, other): if isinstance(other, int): other = mod(other) return mod(self.value*other.value)

def __div__(self,other): return self * other.inverse()

def __pow__(self, n): return mod(self.value**n)

def __radd__(self, other): return mod(other) + self

def __rmul__(self, other): return mod(other)*self

def modulus(self): return m

def inverse(self): (g,x,y) = xgcd(self.value, self.modulus()) if g == 1: return mod(x) else: raise ValueError(’Value not invertible’)

def __eq__(self, other): return self.value == other.value 80 CHAPTER 5. PROGRAMMING WITH CLASS

def __ne__(self, other): return not self == other

def int(self): return self.value

return mod # return the class

Some of these algorithms are not best possible. For instance, the implemen- tation of the power method should be done by successive squaring for best efficiency. To use this code we must first import it. Here’s a sample session with the interpreter that puts this through its paces. First, we define the class of mod 31 objects: >>> mod31 = ZMod(31) >>> a = mod31(2) >>> a 2 mod 31 >>> a**5 1 mod 31

We see immediately that 2 is not a primitive root modulo 31. Let’s check something else, say 9. Let’s use a list comprehension to make a list of the residues of 9n (mod 31) as n varies from 1 to 30: >>> b = mod31(9) >>> b 9 mod 31 >>> [b**n for n in range(1,31)] [9 mod 31, 19 mod 31, 16 mod 31, 20 mod 31, 25 mod 31, 8 mod 31, 10 mod 31, 28 mod 31, 4 mod 31, 5 mod 31, 14 mod 31, 2 mod 31, 18 mod 31, 7 mod 31, 1 mod 31, 9 mod 31, 19 mod 31, 16 mod 31, 20 mod 31, 25 mod 31, 8 mod 31, 10 mod 31, 28 mod 31, 4 mod 31, 5 mod 31, 14 mod 31, 2 mod 31, 18 mod 31, 7 mod 31, 1 mod 31]

From this we can see that 9 is not a primitive root modulo 31, either. You can easily find a primitive root modulo 31, however (try 3). We can also work with a much bigger modulus. Let’s pick a Mersenne prime p =261 1 for the modulus, and compute some inverses modulo p. − >>> p = 2**61 - 1 >>> p 2305843009213693951 >>> modp = ZMod(p) 5.3. RATIONAL NUMBERS 81

>>> a = modp(203869108754) >>> a 203869108754 mod 2305843009213693951 >>> b = a.inverse() >>> b 1774983148771139600 mod 2305843009213693951 >>> a * b 1 mod 2305843009213693951

5.3 Rational numbers

In this section we develop a class of rational numbers (fractions) which we will call Rational. This is just for the purpose of having a simple, yet fairly interesting, example for study. It should be noted that Python supplies a fractions module in the standard library that defines a class Fraction providing much of the same functionality defined below. #### first we define a helper function #### def gcd(a,b): """Return the gcd of the pair (a,b)""" while b != 0: a, b = b, a%b return a

#### now here comes the class itself #### class Rational: """A class for rational numbers. The form is reduced to lowest terms with a positive denominator."""

######## Constructor Method ####### def __init__(self, NUM=0, DEN=1): """Constructs a new Rational: always in lowest terms, with the sign in the numerator, if any.""" if DEN == 0: # Rational is undefined print("DEN 0 is not allowed; changed to 0/1.") self.numerator = 0 self.denominator = 1 else: divisor = gcd(NUM, DEN) if DEN//divisor < 0: divisor = -divisor # force it positive self.numerator = NUM//divisor self.denominator = DEN//divisor 82 CHAPTER 5. PROGRAMMING WITH CLASS

######## Arithmetic Methods ######## def __add__(self, other): """Returns a new Rational that is the sum of the two operands.""" if isinstance(self,int): self = Rational(self,1) n = (self.numerator*other.denominator + self.denominator*other.numerator) d = self.denominator*other.denominator return Rational(n,d)

def __sub__(self, other): """Returns a new Rational that is the difference of the two operands.""" return self + (-other)

def __mul__(self, other): """Returns a new Rational that is the product of the two operands.""" n = self.numerator*other.numerator d = self.denominator*other.denominator return Rational(n,d)

def __truediv__(self, other): """Returns a new Rational that is the quotient of the two operands.""" n = self.numerator*other.denominator d = self.denominator*other.numerator return Rational(n,d)

def __pow__(self, n): """Returns a new Rational that is the n-th power of the operand, n a positive integer.""" return Rational(self.numerator**n, self.denominator**n)

def __abs__(self): """Returns a new Rational that is the absolute value of the operand.""" if self.numerator < 0: return Rational(-self.numerator, self.denominator) else: return self

def __neg__(self): """Returns the negative of the operand.""" return Rational(-self.numerator, self.denominator) 5.3. RATIONAL NUMBERS 83

# when the first argument is an integer we need these def __radd__(self, other): return Rational(other) + self

def __rsub__(self, other): return Rational(other) - self

def __rmul__(self, other): return Rational(other)*self

def __rtruediv__(self, other): return Rational(other)/self

######## Comparison Methods ######## def __eq__(self, other): """Returns a boolean indicating if the current instance is equal to other.""" return (self.numerator == other.numerator and self.denominator == other.denominator)

def __ne__(self, other): """Returns a boolean indicating if the current instance is equal to other.""" return not (self == other)

def __lt__(self, other): """Returns a boolean indicating if the current instance is less than other.""" return (self.numerator*other.denominator < self.denominator*other.numerator)

def __gt__(self, other): """Returns a boolean indicating if the current instance is greater than other.""" return (self.numerator*other.denominator > self.denominator*other.numerator)

def __le__(self, other): """Returns a boolean indicating if the current instance is less than or equal to other.""" return (self < other) or (self == other)

def __ge__(self, other): """Returns a boolean indicating if the current instance is greater than or equal to other.""" return (self > other) or (self == other) 84 CHAPTER 5. PROGRAMMING WITH CLASS

######## Type Conversion Methods ######## def __float__(self): """Returns a float approximating the current rational number.""" return float(self.numerator)/self.denominator

def __int__(self): """Returns an integer by truncating the current Rational.""" return self.numerator//self.denominator

def __repr__(self): """Returns a string representation of the Rational, simplified in case it’s an integer.""" if self.denominator == 1: return str(self.numerator) else: return str(self.numerator) + ’/’ + str(self.denominator)

Many of the methods in the above code are self explanatory, but there are a number of subtleties as well. The methods __add__, __sub__, __mul__, __truediv__ implementing addition, subtraction, multiplication, and divi- sion will work correctly in the mixed type case where the left operand is a Rational and the right operand is an int. This is due to the fact that the built-in int class defines numerator and denominator attributes for integers, and those attributes is all that are needed for the right operand. When the left operand is of type int and the right operand of type rational then there are no methods in the class int to perform the neces- sary operation, so the result will be an error unless we implement methods __radd__, __rsub__, __rmul__, __rtruediv__ to catch this situation. So that’s why those methods appear in the class definition. Here are a few examples showing some of the manipulations made pos- sible by importing the module: >>> x = Rational(1,2) >>> x 1/2 >>> y = Rational(4,3) >>> y 4/3 >>> 1 < x False >>> x < 1 5.4. POLYNOMIALS WITH INTEGER COEFFICIENTS 85

True >>> 2*x 1 >>> x + y 11/6 >>> x - y -5/6 >>> x**100 1/1267650600228229401496703205376 >>> (1+x)**20/(1-y)**11 -617673396283947/1048576

As you can see, many arbitrary precision operations are now supported for fractions. But there is still room for improvement. For instance, negative powers of Rational objects are not implemented above, and one would like a more robust constructor method that could accept float inputs such as 1.4.

5.4 Polynomials with integer coefficients

Our next example is a class implementing standard arithmetic operations on polynomials with integer coefficients. As above, we again want to overload the usual operators of arithmetic so that they may be applied to polynomial objects. We can solve this problem by developing two classes, one of which han- dles monomials and the second of which handles polynomials. Of course, mathematically speaking every monomial is a polynomial, so those readers that know about class inheritance might thing that our two clases should have some inheritance relationship, but it turns out that would be undesir- able. Here’s the class that defines monomials: class Monomial: """A class of Monomials""" def __init__(self, c=0, e=0): """A Monomial has a coefficient and an exponent""" self.coefficient = c self.exponent = e

def __repr__(self): if self.coefficient == 0: return str(0) if self.coefficient == 1: 86 CHAPTER 5. PROGRAMMING WITH CLASS

first_part = "" #empty string elif self.coefficient == -1: first_part = "-" else: first_part = str(self.coefficient) if self.exponent == 0: second_part = "" #empty string first_part = str(self.coefficient) elif self.exponent == 1: second_part = "x" else: second_part = "x^"+str(self.exponent) return first_part + second_part

def __mul__(self, other): if isinstance(other, int): other = Monomial(other) if isinstance(other, Monomial): c = self.coefficient*other.coefficient e = self.exponent + other.exponent return Monomial(c,e) if isinstance(other, Poly): terms = [] #empty list to start for term in other.terms: c = self.coefficient*term.coefficient e = self.exponent + term.exponent terms.append(Monomial(c,e)) return Poly(terms)

def __rmul__(self, other): return Monomial(other)*self

def __truediv__(self, other): if isinstance(other, int): other = Monomial(other) if self.coefficient == 0 and other.coefficient !=0: return Monomial(0) if self.coefficient % other.coefficient != 0: raise ValueError("Operation is undefined") if self.exponent < other.exponent: raise ValueError("Operation is undefined") c = self.coefficient // other.coefficient e = self.exponent - other.exponent return Monomial(c,e)

def __rtruediv__(self, other): return Monomial(other)/self 5.4. POLYNOMIALS WITH INTEGER COEFFICIENTS 87

def __add__(self, other): if isinstance(other, int): other = Monomial(other) if isinstance(other, Monomial): if self.exponent == other.exponent: c = self.coefficient + other.coefficient return Monomial(c, self.exponent) else: return Poly([self, other]) if isinstance(other, Poly): list = [] itwasadded = False for term in other.terms: if self.exponent == term.exponent: c = self.coefficient + term.coefficient list.append(Monomial(c, self.exponent)) itwasadded = True else: list.append(term) if not itwasadded: list.append(self) return Poly(list)

def __radd__(self, other): return Monomial(other) + self

def __sub__(self, other): if isinstance(other, int): other = Monomial(other) if isinstance(other, Monomial): if self.exponent == other.exponent: c = self.coefficient - other.coefficient return Monomial(c, self.exponent) else: return Poly([self, -other]) if isinstance(other, Poly): return self + (-other)

def __rsub__(self, other): return Monomial(other) - self

def __neg__(self): return Monomial(-self.coefficient, self.exponent)

def __pow__(self, e): if e < 0: 88 CHAPTER 5. PROGRAMMING WITH CLASS

raise ValueError("Negative powers undefined") else: return Monomial(self.coefficient**e, e*self.exponent)

Note that this class interfaces with the polynomials class (named Poly) defined below in certain cases, for example the sum of two monomials of different degree is a polynomial which is not a monomial. To construct the monomial cxe with (integer) coefficient c and exponent e, one types the expression Monomial(c,e). In particular, the generator x of the ring of polynomials is Monomial(1,1). The implementation above also treats the case of a monomial added to a polynomial, or a monomial multiplied by a polynomial. Once those special cases are defined in the Monomial class, it is fairly easy to define the class Poly of polynomials: class Poly: """A class of polynomials, implemented as lists of Monomials"""

def __init__(self, parm = []): if isinstance(parm, int): parm = Monomial(parm) if isinstance(parm, Monomial): self.terms = [parm] elif isinstance(parm, list): sort_key = lambda term: term.exponent self.terms = sorted(parm, key = sort_key) # remove zero terms newterms = [] for term in self.terms: if term.coefficient != 0: newterms.append(term) self.terms = newterms

def __repr__(self): if len(self.terms) == 0: return str(0) if len(self.terms) == 1: return str(self.terms[0]) out = str(self.terms[0]) #to start for i in range(1, len(self.terms)): if self.terms[i].coefficient > 0: out = out + "+" out = out + str(self.terms[i]) return out 5.4. POLYNOMIALS WITH INTEGER COEFFICIENTS 89

def __add__(self, other): if isinstance(other, int): other = Monomial(other) answer = other for monom in self.terms: answer = monom + answer return answer

def __radd__(self, other): return Poly(other) + self

def __sub__(self, other): return self + (-other)

def __rsub__(self, other): return Poly(other) - self

def __neg__(self): return self*(-1)

def __mul__(self, other): if isinstance(other, int): other = Monomial(other) answer = Poly(0) #zero Poly for monom in self.terms: answer = monom*other + answer return answer

def __rmul__(self, other): return Poly(other)*self

def __pow__(self, e): if e < 0: raise ValueError("Negative powers not supported") ans = Poly(1) for n in range(e): ans = self*ans return ans

After importing both of these class definitions we can use the interpreter to do computations with polynomials with integer coefficients. For ease of computation, it is best to first define the generating monomial x as on the first line below, after which we can create polynomials just by using the symbol x to create polynomial expressions. >>> x = Monomial(1,1) #define generator 90 CHAPTER 5. PROGRAMMING WITH CLASS

>>> x x >>> (x+1)*(x+2) 2+3x+x^2 >>> 3*(1+x)**2 - 3*(1-x)**2 12x >>> (x+1)**8 1+8x+28x^2+56x^3+70x^4+56x^5+28x^6+8x^7+x^8 >>> (1+x)**40 1+40x+780x^2+9880x^3+91390x^4+658008x^5+3838380x^6+ 18643560x^7+76904685x^8+273438880x^9+847660528x^10+ 2311801440x^11+5586853480x^12+12033222880x^13+ 23206929840x^14+40225345056x^15+62852101650x^16+ 88732378800x^17+113380261800x^18+131282408400x^19+ 137846528820x^20+131282408400x^21+113380261800x^22+ 88732378800x^23+62852101650x^24+40225345056x^25+ 23206929840x^26+12033222880x^27+5586853480x^28+ 2311801440x^29+847660528x^30+273438880x^31+76904685 x^32+ 18643560x^33+3838380x^34+658008x^35+91390x^36+9880x ^37+ 780x^38+40x^39+x^40

In displaying the result of the last computation, extra line breaks were in- serted to enhance readability. One thing missing from the Poly class is an implementation of long division of polynomials. We leave this as an exercise for the reader. Bibliography

[1] https://hkn.eecs.berkeley.edu/~dyoo/python/idle_intro/.

[2] Python Tutorial, http://python.org/doc.

[3] Python Library Reference Manual, http://python.org/doc.

[4] Python Language Reference Manual, http://python.org/doc.

91 Index

dir, 74 __repr__, 73 floor, 61 __rfloordiv__, 77 fractions, 81 __rmod__, 77 return, 44 __rmul__, 77 time, 65 __rpow__, 77 !=, 26 __rsub__, 77 %, 25 __rtruediv__, 77 *, 5, 25 __str__, 73 **, 5, 25 __sub__, 76 +, 5, 25 __truediv__, 76 -, 5, 25 assignment statement /, 5, 25 multiple, 64 //, 6, 25 assignment statement, 6, 26, 27 <, 26 multiple, 28, 48, 62 <=, 26 attribute, 69 ==, 26 >, 26 Boole, George, 23 >=, 26 boolean, 23, 78 __add__, 76 break, 32, 33 __eq__, 77 class, 68, 69 __floordiv__, 76 comments, 18 __ge__, 77 complex numbers, 20 __gt__, 77 complexity, 50 __init__, 72 compound command, 7 __le__, 77 constructor, 72 __lt__, 77 defaults, 73 __mod__, 76 optional arguments, 73 __mul__, 76 continuation, implied, 24 __ne__, 77 continue, 32 __pow__, 76 CTRL-C, 9 __radd__, 77 CTRL-D, 9

92 INDEX 93

CTRL-Z, 9 derivatives, 54 factorial, 51 data factoring, 33 boolean, 23 first maximum of a list, 46 complex, 20 greatest common divisor (gcd), 61 floats, 19 last maximum of a list, 47 integers, 19 linear search, 46 lists, 21 long division, 54 strings, 20 maximum of a list, 45 tuples, 21 mean and standard deviation, 48 Decimal type, 59 modular arithmetic, 78 defining functions, 11 monomials, 85 development cycle, 15 next-to-last maximum of a list, divmod, 64 47 docstrings, 18, 72 polynomials, 85 __doc__, 19 prime factorization, 67 editors, 13 Pythagorean triples, 60 elif, 29 quotient ring, 78 else, 29 rational numbers, 81 in loops, 33 recursion, 51 else if, see elif reversing a list, 47 error messages, 10 Simpson’s rule, 56 errors smallest factor, 64 exceptions, 15 sorting a list, 49 logical, 15 trapezoidal rule, 56 run-time, 15 exceptions, 15, 46 syntax, 15 exp, exponential function, 10 escape sequence, 41 exponential growth rate, 65 Euclidean algorithm, 61 expressions, 23 extended, 63 evaluation, 44 file extension, 13 Examples files, source code, 12 approximating e, 57 floating point, 19 binary representation, 52 floating point arithmetic, 59 binary search, 50 for, 7, 22, 30 bitlength, 44 FOSS, 2 day of the week, 60 free software, 2 decimal expansion, 53 functions, 19 definite integral, 56 return versus print, 44 94 INDEX

comments in, 18 multiplication, 36 defining, 11, 42, 44 pop, 37 docstrings, 18 remove, 37 importing, 9, 13 slicing, 35 range, 21 sublists, 35 testing, 14, 16 loops for, 30, 32 IDLE, 8 while, 31, 32 Idle, Eric, 8 if statement, 29 machine epsilon, 55 immutable, 40 math module, 9 import, 9 mathematical logic, 12 importing functions, 9, 13 membership, 26 incrementing a variable, 28 method, 68, 70 indentation, 8 Monty Python’s Flying Circus, 1 installing Python, 3 mutable, 34 instance, 69 instantiation, 69 newline character (\n), 41 integer division, 6 NumPy, 2 integer quotient, 6 object, 45, 68 integers, 19 object-oriented programming, 42, 45, long and short, 19 68 integrated development environment, OOP, 68 IDE, 8 open source software, 2 interpreter, 4 operators, 23 iterators, 22 +,-,*, /,**, //, %, 25 lambda expression, 12, 55, 56 <,>,<=,>=,==,!=, 26 Lambda Calculus, 12 arithmetic, 24 LaTeX, 3 comparison, 26 len (length) floor division, 25 of a list, 34 pass, 31, 69 of a string, 40 polymorphism, 72 list comprehension, 80 previous result, 7 lists, 7, 21, 33 append, 37 quantum computers, 67 comprehension, 38 quitting the interpreter, 9 empty, 35 quitting the current command, 9 insert, 37 joining (adding), 36 range INDEX 95

conversion, 22 standard Python library, 9 range, 21, 33 strings, 20, 40, 52 recursion, 51 strings, long, 40 reserved words, 12 syntax, 15

Sage, 2 terminal, 4 SciPy, 2 testing scripts, 16 functions, 14, 16 self, 74 timing, 65 semantics, 15 tuples, 21, 48 sequence type, 20 underscore, 7, 27 sets, 21 Shor, Peter, 67 ValueError, 16 special method names van Rossum, Guido, 1 __add__, 75, 76 variable, 6, 23 __eq__, 77 variables, 27 __floordiv__, 76 __ge__, 77 while, 30 __gt__, 77 wildcard, *, 9 __init__, 72, 73 __le__, 77 __lt__, 77 __mod__, 76 __mul__, 76 __ne__, 77 __pow__, 76 __radd__, 77 __repr__, 73 __rfloordiv__, 77 __rmod__, 77 __rmul__, 77 __rpow__, 77 __rsub__, 77 __rtruediv__, 77 __str__, 73 __sub__, 76 __truediv__, 76 sqrt, square root, 10 Stallman, Richard M., 2