Course in Python
Course in Python
Course in Python
Editor-in-Chief
Endre Süli, Oxford, UK
Series Editors
Mark A. J. Chaplain, St. Andrews, UK
Angus Macintyre, Edinburgh, UK
Shahn Majid, London, UK
Nicole Snashall, Leicester, UK
Michael R. Tehranchi, Cambridge, UK
The Springer Undergraduate Mathematics Series (SUMS) is a series designed for
undergraduates in mathematics and the sciences worldwide. From core foundational
material to final year topics, SUMS books take a fresh and modern approach.
Textual explanations are supported by a wealth of examples, problems and fully-
worked solutions, with particular attention paid to universal areas of difficulty.
These practical and concise texts are designed for a one- or two-semester course
but the self-study approach makes them ideal for independent use.
Roozbeh Hazrat
A Course in Python
The Core of the Language
Roozbeh Hazrat
Centre for Research in Mathematics and Data Science
Western Sydney University
Penrith, NSW, Australia
© The Editor(s) (if applicable) and The Author(s), under exclusive license to Springer Nature Switzerland
AG 2023
This work is subject to copyright. All rights are solely and exclusively licensed by the Publisher,
whether the whole or part of the material is concerned, specifically the rights of translation, reprinting,
reuse of illustrations, recitation, broadcasting, reproduction on microfilms or in any other physical way,
and transmission or information storage and retrieval, electronic adaptation, computer software, or by
similar or dissimilar methodology now known or hereafter developed.
The use of general descriptive names, registered names, trademarks, service marks, etc. in this publication
does not imply, even in the absence of a specific statement, that such names are exempt from the relevant
protective laws and regulations and therefore free for general use.
The publisher, the authors, and the editors are safe to assume that the advice and information in this
book are believed to be true and accurate at the date of publication. Neither the publisher nor the
authors or the editors give a warranty, expressed or implied, with respect to the material contained herein
or for any errors or omissions that may have been made. The publisher remains neutral with regard to
jurisdictional claims in published maps and institutional affiliations.
This Springer imprint is published by the registered company Springer Nature Switzerland AG
The registered company address is: Gewerbestrasse 11, 6330 Cham, Switzerland
Python has become, for good reason, a very popular programming language, with a
substantial number of followers around the globe. In addition to its intuitive program-
ming language, Python now offers numerous libraries which provide powerful tools
and methods to do, among many other things, mathematics, physics, text processing,
and musical composition. Once one grasps the core of the language, then help, hints,
and a wealth of sample codes are just a google away.
Besides many books on Python, there is an uncountable number of documents on
the internet. However, I wanted to have one source that I can follow to systematically
teach (or learn) the core of the language before diving into more advanced fronts. I
also wanted to have a source that is short, to the point and which provides interesting
programming examples; examples that I enjoy coding, modifying and experimenting
with.
This book grew out of a course I gave at Western Sydney University. It allows the
reader to learn Python by going through interesting exercises step by step, with short
and concise explanations. I have tried to let the reader learn from the codes and
avoid long and exhausting explanations, as the codes will speak for themselves. Also
I have tried to inspire the reader’s imagination by showing that in Python (as in the
real world) there are many ways to approach a problem and solve it.
Thus, this book could be considered for a course in Python, or for self-study. It mainly
concentrates on the core of Python programming. I have mostly chosen problems
having something to do with natural numbers as they do not need any particular
background. The codes have been written in Jupyter using Python version 3.
Acknowledgement. My thanks go to Thomas Fischbacher from Google Research
for going through the book and providing insightful comments; to the students at
Western Sydney University for their contributions to the classes, which made them
lively and enjoyable, and to the anonymous referees for their support and feedback.
My thanks also go to Dr. Remi Lodh at Springer for his generous support throughout
the preparation of the book.
v
vi Preface
How to use the book. This book can be used as a one semester course in programming
in Python (13 weeks, 3 hours each week) at the undergraduate level or a compact crash
course (4 days, 6 hours each day) for more advanced students. Each chapter starts
with a description of new tools and topics and provides examples. The examples
are chosen so that the reader can learn from the codes by typing and running
them and then modifying them and experimenting with them. Long and exhausting
explanations are avoided. The lecturer can code the examples in the class along with
students. The students are then encouraged to take the lead role and compose codes
themselves for some of the exercises. The problems at the end of each chapter can
be given to students as homework so that they present the codes in the class the
next week. The book concludes with projects which demonstrate how to use Python
to explore mathematics. Some of these projects can be given as assignments to the
students.
Please start with the codes in the book, change them, tear them apart and turn them
upside down, and create your own better programs.
1 Basics of Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1 Basic Arithmetic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Integers, Reals and Complex Numbers . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3 Objects and their Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.4 Importing Libraries in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.5 Variables in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.6 Equalities and Boolean Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.7 Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
1.8 Strings as Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.9 Input and Output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
vii
viii Contents
4 Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
4.1 Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
4.1.1 The scope of functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
4.1.2 Functions, default values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
4.1.3 Functions, specific types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
4.2 Functional Programming: Anonymous (lambda) Functions . . . . . . . . 103
4.2.1 Selecting from a collection . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
4.2.2 Functional programming, reduce . . . . . . . . . . . . . . . . . . . . . . . 113
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249
Chapter 1
Basics of Python
This first chapter gives an introduction to using Python “out of the box”, demonstrat-
ing how to use ready-made commands, performing basic arithmetic and building up
computations. One of the reasons why Python has become the language of choice
for many is its external libraries, which provide powerful tools that one can use to
investigate and analyse problems in a multitude of areas. We will showcase some of
them in this first chapter, such as working with symbols and the first instances of
symbolic computations.
Python handles all sort of numerical calculations, both exact computations and
4
approximations. If we would like to calculate 3 × 4 × 5 × 6 + 1 or 23 we only need
to enter them correctly into Python.
[1]: 3 * 4 * 5 * 6 + 1
[1]: 361
[2]: 2**3**4
[2]: 2417851639229258349412352
Python is a powerful calculator with the basic arithmetic operations; +, − for addition
and subtraction and ∗, ∗∗ for multiplication and for raising to powers. One uses round
brackets () to group the expressions together.
[3]: (2**3)**4
[3]: 4096
[4]: 2**(3**4)
[4]: 2417851639229258349412352
[5]: 180630077292169281088848499041
[6]: 20615673**4
[6]: 180630077292169281088848499041
[7]: 2**9941 - 1
[7]: 346088282490851215242960395767413316722628668900238547790489283
445006220809834114464364375544153707533664486747635050186414707
093323739706083766904042292657896479937097603584695523190454849
100503041498098185402835071596835622329419680597622813345447397
208492609048551927706260549117935903890607959811638387214329942
787636330953774381948448664711249676857988881722120330008214696
844649561469971941269212843362064633138595375772004624420290646
813260875582574884704893842439892702368849786430630930044229396
033700105465953863020090730439444822025590974067005973305707995
078329631309387398850801984162586351945229130425629366798595874
957210311737477964188950607019417175060019371524300323636319342
657985162360474512090898647074307803622983070381934454864937566
479918042587755749738339033157350828910293923593527586171850199
425548346718610745487724398807296062449119400666801128238240958
164582617618617466040348020564668231437182554927847793809917495
802552633233265364577438941508489539699028185300578708762293298
033382857354192282590221696026655322108347896020516865460114667
379813060562474800550717182503337375022673073441785129507385943
306843408026982289639865627325971753720872956490728302897497713
583308679515087108592167432185229188116706374484964985490944305
1.1 Basic Arithmetic 3
412774440794079895398574694527721321665808857543604774088429133
272929486968974961416149197398454328358943244736013876096437505
146992150326837445270717186840918321709483693962800611845937461
435890688111902531018735953191561073191960711505984880700270887
058427496052030631941911669221061761576093672419481606259890321
279847480810753243826320939137964446657006013912783603230022674
342951943256072806612601193787194051514975551875492521342643946
459638539649133096977765333294018221580031828892780723686021289
827103066181151189641318936578454002968600124203913769646701839
835949541124845655973124607377987770920717067108245037074572201
550158995917662449577680068024829766739203929954101642247764456
712221498036579277084129255555428170455724308463899881299605192
273139872912009020608820607337620758922994736664058974270358117
868798756943150786544200556034696253093996539559323104664300391
464658054529650140400194238975526755347682486246319514314931881
709059725887801118502811905590736777711874328140886786742863021
082751492584771012964518336519797173751709005056736459646963553
313698192960002673895832892991267383457269803259989559975011766
642010428885460856994464428341952329487874884105957501974387863
531192042108558046924605825338329677719469114599019213249849688
100211899682849413315731640563047254808689218234425381995903838
524127868408334796114199701017929783556536507553291382986542462
253468272075036067407459569581273837487178259185274731649705820
951813129055192427102805730231455547936284990105092960558497123
779789849218399970374158976741548307086291454847245367245726224
501314799926816843104644494390222505048592508347618947888895525
278984009881962000148685756402331365091456281271913548582750839
07891469979019426224883789463551
[8]: 13 = 4 x 3 + 1
We will explore the function print more at the end of this chapter.
Exercise 1.1 Show that the number 142857 is cyclic, meaning if we multiply it by
any of the numbers 1 to 6, the result will be a permutation of the digits of the original
number 142857.
Solution
Later in Chapter 3 we will write a program to find cyclic numbers. Checking a
number is cyclic, however, is quite easy:
[10]: (-1+0j)
As an example, we calculate ( 11 + 1
2 + 13 ) × 3 and (3 − 6𝑖) 2 .
[11]: 5.5
[12]: (3 - 6j)**2
1.2 Integers, Reals and Complex Numbers 5
[12]: (-27-36j)
[13]: (13+0j)
One of the reasons why Python has become so popular is that it has a large number
of libraries which contain ready to use tools and functions. Various mathematical
√
functions such as sin, cos or and log are available in different libraries of Python.
The first library we will use is math, which makes many of these functions available to
us. Note how we import this library into Python and how the functions (or methods)
are used from this library. Once we have imported the library, we can access its
built-in functions via dot “.”.
math.pi
[14]: 3.141592653589793
[15]: math.sin(math.pi/2)
[15]: 1.0
Exercise 1.2 Show that for any chosen angle 𝑥, Python gives sin2 (𝑥) + cos2 (𝑥) = 1.
Solution
As an instance, choosing 𝜋/5 for the angle and translating the expression correctly
into Python, we have
[16]: 1.0
Of course, one could choose any other angle, run the code, and obtain 1 again.
Solution
The only challenge here is to translate the mathematical expression correctly into
Python.
[17]: 2.998863793038475
Solution
One can immediately see a repeating pattern within this expression, and we will
later write elegant codes to capture and compute such expressions. For the moment
we can write the following. Note that the notation refers to the previous output in
Jupyter. The symbol has other uses, as we will see throughout the book.
[18]: 3 + 1/2
[18]: 3.5
[19]: 4 + 1/_
[19]: 4.285714285714286
[20]: 5 + 1/_
[20]: 5.233333333333333
[21]: 6 + 1/_
[21]: 6.191082802547771
[22]: 6.191082802547771
Besides the math library, which provides many valuable mathematical functions,
there are two other libraries in Python that are heavily used: numpy and sympy. The
1.2 Integers, Reals and Complex Numbers 7
library numpy is designed for numerical computations whereas sympy is for symbolic
computations and calculus. We look at these libraries in detail in Chapters 6 and
7. Here we just give an indication of how these libraries behave. All these libraries
provide the basic mathematical functions, such as trigonometric functions sin, cos,
tan, etc., however their methods of computation differ.
[24]: math.sin(math.pi/5)
[24]: 0.5877852522924731
[25]: numpy.sin(numpy.pi/5)
[25]: 0.5877852522924731
[26]: sympy.sin(sympy.pi/5)
√
√︄
[26]: 5 5
−
8 8
√︃ √
The sympy library gives 58 − 85 as the value of sin(𝜋/5), which is the exact value.
This shows that sympy is not approaching the expressions numerically.
Next we will check the identity
We were expecting the output 1. In order to further simplify the output, we can use
the function simplify in the sympy library.
[28]: sympy.simplify(_)
[28]: 1
We will check the same identity within the numpy and math libraries.
8 1 Basics of Python
[29]: 1.0
[30]: 1.0
We will dive into symbolic computations and sympy later on in this book. Here we
just give a snippet of how to work with symbols. Using the library sympy we can
introduce 𝑥 as a symbol called x. Python can then carry out arithmetic symbolically
with 𝑥, without enquiring what the value of 𝑥 is.
[31]: x = sympy.symbols('x')
[32]: x + 1
[32]: 𝑥 + 1
[33]: (2 * x + 3)**2
[33]: (2𝑥 + 3) 2
[34]: x = sympy.symbols('SometimesUPandsometimesDOWN')
[35]: x
[35]: 𝑆𝑜𝑚𝑒𝑡𝑖𝑚𝑒𝑠𝑈𝑃𝑎𝑛𝑑𝑠𝑜𝑚𝑒𝑡𝑖𝑚𝑒𝑠𝐷𝑂𝑊 𝑁
[36]: x /(1 + x)
[36]: 𝑆𝑜𝑚𝑒𝑡𝑖𝑚𝑒𝑠𝑈𝑃𝑎𝑛𝑑𝑠𝑜𝑚𝑒𝑡𝑖𝑚𝑒𝑠𝐷𝑂𝑊 𝑁
𝑆𝑜𝑚𝑒𝑡𝑖𝑚𝑒𝑠𝑈𝑃𝑎𝑛𝑑𝑠𝑜𝑚𝑒𝑡𝑖𝑚𝑒𝑠𝐷𝑂𝑊 𝑁 + 1
We calculate the expression sin2 (𝑥) + cos2 (𝑥) within sympy. Python then returns the
correct identity of 1 for this expression.
[37]: 1
Solution
We first use the library math to calculate the first expression.
[38]: 55.000000000000014
Note that we have got a floating point number which is almost 55. This is to be
expected as the functions in the math library approach calculations “numerically”
and these methods will not give exact results. However the results are generally
extremely good with high precision.
Approaching the computations with numpy built-in functions would give the same
approximation.
[39]: 55.000000000000014
Employing sympy capabilities, we can actually show that equality holds for this
identity.
[40]: √5 − 1 − √5 10 + 1 + √5 10
5120
[41]: sympy.simplify(_)
[41]: 55
We emphasise that floats (real numbers) are all about approximations.
[42]: 0.30000000000000004
[43]: -5.551115123125783e-17
[44]: 0
Because Python is so widely used, help, hints and good samples are always a google
away. But one can also get a summary of the functions by using the command help.
[45]: help(round)
round(number, ndigits=None)
Round a number to a given precision in decimal digits.
[46]: type(3)
[46]: int
[47]: type(math.sqrt(2))
[47]: float
[48]: complex
Once we have an object, we can access their methods and functions via dot “.”. As
an example, the float object has a method called is integer, which determines
if a real number is indeed an integer.
1.3 Objects and their Types 11
[49]: x = math.sqrt(16)
[50]: x.is_integer()
[50]: True
Find all the integers 𝑚 less than 10 such that 𝐴 is an integer. Show that 𝐴 is always
odd.
Solution
Although we don’t currently have many tools at our disposal, we can translate the
formula for 𝐴 in Python, replace 𝑚 by 1, 2, . . . and each time check if the computation
returns an integer with the method is integer(). Later on, when we know how to
create loops, we will revisit this exercise in Chapter 5 (Exercise 5.7) and find all the
numbers up to 500.
[51]: m = 2
x = ((m + 3)**3 + 1)/(3 * m)
x.is_integer()
[51]: True
[52]: x
[52]: 21.0
When working with a complex number 𝑎 + 𝑏𝑖, we would always like to have access to
the real part, 𝑎, and the imaginary part, 𝑏. The complex object comes with methods
to obtain this information.
With x.imag we can access the imaginary part of the complex number 𝑥. This
imaginary part is a float object which comes with its own methods, which we now
use.
[55]: x.imag.is_integer()
[55]: True
[56]: x.real.is_integer()
[56]: False
Although the library math provides basic functions, such as sin and cos, these
functions are designed to handle real (float) numbers. If we are working with complex
numbers, we need to import the library cmath and its functions, which allow us to
handle complex arithmetic.
Exercise 1.7 If 𝑎 and 𝑏 are real numbers, show that the real part of
2
cos(𝑎 + 𝑏𝑖) + sin(𝑎 + 𝑏𝑖)
is equal to
1 + sin(2𝑎) cosh(𝑏) 2 + sinh(𝑏) 2
Solution
Although this identity should be valid for any 𝑎 and 𝑏, we are going to choose some
values for 𝑎 and 𝑏 and test the claim. Later when we define functions in Python
we shall be able to check this for any value of 𝑎 and 𝑏. Since we are working with
complex numbers, we import the library cmath.
[57]: (4.420954861117013-1.5093064853236153j)
[58]: 4.420954861117013
[59]: -1.5093064853236153
1.4 Importing Libraries in Python 13
[60]: (4.420954861117013+0j)
Comparing the results we see the statement of the exercise is valid for this particular
𝑎 and 𝑏.
We have seen that import math makes the library math available for use. If the
name of a library is long, one can introduce an alias.
[62]: np.sin(np.pi / 2)
[62]: 1.0
Although one could use import numpy as MyDarling or any other alias, the
name np has become quite widespread in the community.
If one uses certain methods of a library quite often, one can import them directly.
[64]: x = symbols('x')
y = symbols('what?')
[66]: 1
We should warn the reader that confusion might arise as to where these methods
belong. If one imports methods from different libraries then the later methods will
overwrite the earlier ones.
14 1 Basics of Python
In order to feed data into a computer program one needs to define variables to be
able to assign data to them. Python’s methodology in assigning data to variables is
slightly different than in other languages. We start with the simplest assignment of a
number to a variable.
[67]: x = 2
[68]: x
[68]: 2
As long as you use common sense, any names you choose for variables are valid
(provided they are not used as Python keywords). Names like x, y, x3, myfunc,
xQuaternion as well as x 3, my func, x Quaternion are all fine. Also note that
Python is case-sensitive, thus xy and xY are considered as two different variables.
We assign values to these variable using ; to write the code in one line.
[70]: xy
[70]: 12
[71]: xY
[71]: -1.4
We could also assign the values in line as a tuples. We will see these concepts in
Chapter 2.
[73]: xY
[73]: -1.4
[74]: (137.04-12j)
One crucial difference in Python that we need to understand at this early stage is
that the variables are pointers. In other languages the command x = 2 creates a cell
or (an object) labelled x and 2 is stored in that cell, whereas in Python there is an
integer object 2 and x is a pointer, pointing to that object. There is no immediate
1.6 Equalities and Boolean Statements 15
harm if we just think of x as a variable that 2 is assigned to, but in the background
we have to be aware of the Python philosophy.
[75]: x = y = z = 10
The way we should understand this line is that we have created three pointers all
pointing to the integer object 10. We could change one of the pointers, pointing to a
different object without changing the direction of the other two pointers.
[76]: z = 12
We print the values of these variables. Note that the command \n in print breaks
the line as the result shows.
[77]: x is pointing to 10
y is pointing to 10
z is pointing to 12
This becomes more clear when we work with objects that can be changed (mutable),
for example lists.
On some occasions the existence of a variable is needed more so than the variable
itself. Sometimes we require that a dummy variable runs through a loop, or a list. In
this case we can simply use instead of coming up with a name.
[78]: _ = 5
[79]: _
[79]: 5
[81]: print(a,'and', b)
Primarily there are two equalities in Python = and ==. The first one creates a pointer,
whereas == is used for comparison. The result of the comparison is a boolean value
of True or False
16 1 Basics of Python
[82]: 1 + 2 == 3
[82]: True
[83]: True
[84]: False
Recall the Euclidean division 𝑛 = 𝑚𝑞 + 𝑟. We can check its validity here with an
example.
[85]: 23 == (23 // 4) * 4 + 23 % 4
[85]: True
However we should be careful when working with floats, as the computations have
been done up to a certain precision.
[86]: False
[87]: 0.30000000000000004
[88]: 2 > 3
[88]: False
[89]: True
1.6 Equalities and Boolean Statements 17
[90]: True
[91]: True
Solution
In fact the way this statement is written is misleading! Indeed 1 < 2 < 3 means
exactly 1 < 2 and 2 < 3. However in the statement above, Python evaluates the
left-hand side (which is True) and then evaluates the right-hand side (again True)
and then compare the two. Therefore the statement below also gives True, which is
not mathematically equivalent.
[92]: True
using the math library check that these equalities hold for any 𝑥.
Solution
The hyperbolic function sinh is available in the math library. We will check if this
−𝑥
function returns the same value as e −e
𝑥
2 , here for 𝜋. Later in Chapter 4, we define
−𝑥
functions within Python. Once this is done, we can define the function 𝑓 (𝑥) = e −e
𝑥
2
and systematically compare these two functions.
p = math.pi
sh = (math.exp(p) - math.exp(-p))/2
math.sinh(p) == sh
18 1 Basics of Python
[93]: False
Again, we can see that, although the results are not precisely the same, they are very
very close!
1.7 Strings
We have seen several types of objects: integers, floats, complex as well as booleans.
We finish this chapter with the type string.
[96]: str
One can perform certain ‘arithmetic’ operations with strings, as the following exam-
ples show.
[98]: message * 3
[99]: message
[101]: message
We have already mentioned that everything in Python is an object. Once the object
is defined, the functions and methods are all at our disposal to use. Here we will
use several methods available for str objects. The examples below show how these
methods are used.
[102]: message
[103]: message.capitalize()
[104]: message.lower()
[105]: message.upper()
[106]: message
All the methods used above, upper, lower, capitalize are provided within the
object of string. As is clear from the example, they operate on the object message
but do not change the object itself.
One very useful method when working with strings is to use format strings, which
allows us to pass data inside a string where the location is determined by {}.
There is a simpler way to use format strings, as the following shows. They can be
used very nicely with the command print, as we will see later on.
[111]: fn = 'Lustig'
ln = 'Sabzian'
[112]: x = 2
y = math.sqrt(x)
We close this chapter with two useful commands. The command input asks for data
to be given to the program by the user. The data entered is captured as a string.
Here is a standard way to change the data to numbers, in case one requires the input
to be integers, or reals etc.
Name Dad
age 92
[115]: 'Dad is 92 years old.'
The command print will create an output. The following examples show the various
ways one can use print.
[117]: 25.4
As mentioned, the format strings fit very nicely with the print command.
[119]: x = 2
y = math.sqrt(x)
print(f'the square root of {x} is {y}')
Finally there are certain special characters that can be used with strings. Among
them are \n and \t, which give a new line and a new tab, respectively.
[120]: print('Hello\tworld\nfinal\tgreetings')
We finish the chapter with an amusing example, showcasing several of the methods
available for working with strings.
Problems
1) Compute
√︂ √︂ √︂
1 1 1 1 1 1
1+ 2 + 2 + 1+ 2 + 2 + 1+ 2
+ 2
1 2 2 3 3 4
2) Compute
1
1 1 1 5 5 5 5 11 11 11 11
+ ++ + + + + + + + +
2 3 5 7 2 3 5 7 2 3 5 7
3) Show that for any two positive numbers 𝑎 and 𝑏, if 𝑎 + 𝑏 = 1, then
1 2 1 2 25
𝑎+ + 𝑏+ ≥ .
𝑎 𝑏 2
4) Use Python to show that
3𝜋 2𝜋 √
tan + 4 sin = 11.
11 11
Note, one needs to use the math library or numpy to have access to the square
root, sine and tangent functions.
5) Show that √︃√
3
64 22 + (1/2) 2 − 1 = 4.
6) Show that
√︂ √︂
2𝜋 4𝜋 10𝜋 1 √ 1 √
sin( ) + sin( ) + · · · + sin( )= 5− 5 + 5+ 5
10 10 10 2 2
7) Show that
1
1 𝜋 3𝜋 1 9𝜋 1 27 𝜋 1
2 + cos 20 2 + cos 20 2 + cos 20 2 + cos 20 = 16 .
1 + sin(𝑥) − cos(𝑥)
= tan(𝑥/2).
1 + sin(𝑥) + cos(𝑥)
Chapter 2
Lists and Tuples
In Python, lists provide the first basic building blocks for working with and handling
data.
One can think of a computer program as a function which accepts some (crude) data
or information and gives back the data we would like to obtain. The classic Python
provides certain tools to collect and handle data, such as list, tuple, set and
dictionary. Libraries such as numpy further provide capable tools to work with a
large collection of data. In this chapter we will study list and tuple; how to collect
data and how to access and process the elements in these collections. We will study
set and dictionary in Chapter 5.
Data Science starts with handling data, such as data cleansing, aggregation, trans-
formation, and data visualisation, and lists are the first stop in this process. Once we
grasp the concept of the lists and how to work with them, we can comfortably work
with other objects which are designed for handling data.
We start with an example of a list:
[2]: type(L)
[2]: list
[3]: L
list is one of the ways to collect data in Python. As this example shows, the data
(of any type and format) are arranged between square brackets, [ and ]. Lists respect
order and repetition of the data:
[4]: False
[5]: False
[6]: 2 in [1, 2, 3]
[6]: True
[7]: False
Similar to strings, we can do certain arithmetic operations with lists. Recall the list
L above.
[10]: [1, 2, 3, 4]
As the above examples show, we can add another list to the end of a given list by
adding them together or repeat the list by multiplication.
[11]: L
[12]: L[0]
[12]: 3
[13]: L[4]
[13]: 'xˆ2+x+1'
[14]: L[-1]
[14]: 21.333333333333332
[15]: L[-2]
[15]: 'xˆ2+x+1'
In Python indexing of elements starts from 0. Thus we start counting from 0 (which
refers to the first element of the list!) Examining the above examples reveals that
L[i] gives the 𝑖 + 1-th member of the list. Thus L[0] points to the first element
in the list. To obtain consecutive elements of a list one can use the command
L[n : m]. Here L[n : m] retrieves the elements in the list L, from the 𝑛 + 1-th up
to (at most) the 𝑚-th item. The command L[n : m : s] introduces “step” 𝑠, and
one retrieves elements 𝑛 + 1, 𝑛 + 1 + 𝑠, 𝑛 + 1 + 2𝑠, . . . up to the 𝑚-th element. The
examples below make this clear.
[16]: L
[17]: L[1 : 5]
[19]: L[1 : 5 : 2]
Notice that in the above examples, we start with the second element in the list and
go all the way up to the sixth element and each time pick every second element.
In the command L[n : m], if we leave out n, the list starts from the beginning, and
if we leave out m, it goes all the way to the end. The examples below make this clear.
[20]: L
[21]: L[ : 5]
[22]: L[ : -1]
[23]: L[4 : ]
[24]: L[ : 100]
[26]: L[ : : -1]
[27]: L == L[ : 4] + L[4 : ]
[27]: True
Solution
We first define our given lists.
[28]: campus = ['Parramatta ', ' Campbelltown ', ' Kingswood ']
[31]: total
[33]: CA
[33]: ['Parramatta ', ' Campbelltown ', ' Kingswood ', 30, 10, 7]
Yet another way to put these together (in the absence of loops):
[35]: CA = []
CA
Of course, for a larger collection of lists, and with the loop facilities at our disposal
(which we will meet in Chapter 3), the above code could be modified to pair the lists
together via a loop.
Returning to our list L, we can replace an element inside the list by singling out that
element and assigning new data to it.
[37]: L
[39]: L
[41]: L
One of the secrets of writing code comfortably is that one should be able to manipulate
lists easily. Often in applications, situations like the following arise:
• Given {𝑥1 , 𝑥2 , · · · , 𝑥 𝑛 } and {𝑦 1 , 𝑦 2 , · · · , 𝑦 𝑛 }, produce
{𝑥1 , 𝑦 1 , 𝑥2 , 𝑦 2 , · · · , 𝑥 𝑛 , 𝑦 𝑛 },
and
{𝑥1 , 𝑦 1 }, {𝑥2 , 𝑦 2 }, · · · , {𝑥 𝑛 , 𝑦 𝑛 } .
{𝑥1 + 𝑦 1 , 𝑥2 + 𝑦 2 , · · · , 𝑥 𝑛 + 𝑦 𝑛 }.
{𝑥1 , 𝑥1 + 𝑥2 , · · · , 𝑥1 + 𝑥2 + · · · + 𝑥 𝑛 }.
2.2 Lists as Objects 29
n o
{𝑥1 }, {𝑥2 , . . . , 𝑥 𝑛 } , {𝑥1 , 𝑥2 }, {𝑥 3 , . . . , 𝑥 𝑛 } . . . {𝑥1 , . . . 𝑥 𝑛−1 }, {𝑥 𝑛 } .
As we progress, we will see Python and especially the library numpy provide tools
to produce such combinations from lists and other collections of data.
Recall that everything in Python is an object, which comes with its own tools (i.e.,
methods). With this view, lists are objects which we can change, the so-called mutable
objects. The objects that cannot be changed are called immutable. We have already
seen one object that, once created, cannot be modified, namely the object integers.
We will demonstrate how to use some of the list methods to modify a given list.
[44]: L
append is one of the methods available in the object list. It adds an item to the end
of the list, as the example above demonstrates. Already the method append allows
us to solve the above exercise differently.
[46]: total
[48]: total
[50]: total
[50]: ['Parramatta ', 30, ' Campbelltown ', 10, ' Kingswood ', 7]
Besides append, list comes with several other methods, such as count, reverse,
. . . . As an example:
[51]: 3
[52]: L.reverse()
L
Lists can have other lists as elements. We can envisage the notion of matrices in
mathematics via lists of list. Recall that an 𝑛 × 𝑚 matrix is mathematical objects with
𝑛-rows and 𝑚-columns. An 𝑛 × 𝑚 matrix 𝐴 then consists of 𝑛 × 𝑚 objects (usually
numbers) and an entry on the 𝑖-th row and 𝑗-column can be denoted by 𝑎 𝑖 𝑗 . We
translate the 3 × 3 matrix 𝐴 below into Python
123
𝐴 = 4 5 6®.
© ª
«7 8 9¬
The example below can be thought of as a matrix with three rows and three columns
[54]: L
[55]: L[0]
[55]: [1, 2, 3]
[56]: L[0][0]
[56]: 1
2.2 Lists as Objects 31
[58]: L
Solution
Here is a clever way to do this, benefiting from the arithmetic on lists.
[59]: S = [[0] * 3] * 4
S
One needs to be a bit careful here: when we use list * 3, Python creates three
copies of the same object. This becomes clear when we change one of the objects,
as the example below shows:
[60]: [[0, 'upset', 0], [0, 'upset', 0], [0, 'upset', 0],
[0, 'upset', 0]]
[62]: [[0, 'upset', 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]
[63]: S = [[0] * 3] * 3
S
Of course one needs to be mindful of what one adds to a list and how to handle it.
We give a curious example and leave it to the reader to further explore this direction.
[66]: L.append(L)
[67]: print(L)
[68]: L[-1]
[69]: L[-1][0]
[69]: 3
[70]: L[-1][-1]
[71]: L[-1][-1][-1]
We can now explore the notion of pointers (variables) and mutable objects a bit more
using lists. Suppose we define two pointers both pointing to a list.
[72]: x = y = [1, 2, 3, 4]
[73]: x
[73]: [1, 2, 3, 4]
[74]: y
[74]: [1, 2, 3, 4]
Now if we modify the object [1, 2, 3, 4] then both pointers x and y which are
pointing to this object will show this change
2.2 Lists as Objects 33
[76]: y
[78]: x
In order to show how versatile lists are, we use the Python library PIL, which handles
images. Recall we can import libraries into Python as follows:
[80]: x = Image.open('dog.jpg')
y = Image.open('Napoleon.jpg')
The files dog.jpg and Napoleon.jpg are in the local directory. We have now
assigned x and y to these pictures, respectively.
[81]: x
[81]:
34 2 Lists and Tuples
[82]: y
[82]:
Now we can define a list which contains different types of objects, including images
(144, 178)
The object image comes with its own methods and tools. We just sample one or two
of them here.
[85]: pic[4].rotate(180)
2.2 Lists as Objects 35
[85]:
[86]: pic[1].effect_spread(10)
[86]:
Exercise 2.3 Given a list, swap the first and the last element of the list.
Solution
First, we define a list to work with.
[89]: L
[91]: L
[93]: L
and finally there is yet another elegant way using the sequences:
[94]: [f, *r , l] = L
[95]: [l, *r , f]
Here f is assigned to the first element of the list L, l is assigned to the last element
and *r assigned to the sequence between the first and last element. Thus in the next
line [l, *r, f] we simply swap the first and last element and keep the sequence
*r. Python can even recognise the assignment if we drop the brackets:
[96]: f, *r , l = L
«𝑔 ℎ 𝑘 ¬
Solution
We will later see that the library numpy has been designed to work seamlessly with
matrices. Here using classical Python tools, we can write.
[98]: m = [['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'k']]
[103]: n
[103]: [['a', 'd', 'g'], ['b', 'e', 'h'], ['c', 'f', 'k']]
Before we explore other data structures in Python, we re-visit strings. One can access
the elements within a string in a similar manner as lists
[105]: s[0]
[105]: 'T'
[106]: s[1]
[106]: 'o'
[107]: s[ : 5]
[108]: s[5 : ]
[109]: s[ : : -1]
[110]: s = 'kayak'
38 2 Lists and Tuples
[111]: s[ : : -1] == s
[111]: True
One of the most used libraries in Python is matplotlib. This library is used for
plotting data and creating professional and magnificent two-dimensional graphics.
We will devote an entire Chapter 7 to this library. Here we give the first instances of
how list along with matplotlib allow us to plot data. First, we import the library
into Python.
[113]:
Solution
Later we will see how to efficiently generate lists containing the data we need. Here
we have to do this by “hand”. We define two lists, one for the 𝑥-coordinate and the
values of sin for the 𝑦-coordinate.
x = [0, 0.3, 0.5, 0.8, 1, 1.3, 1.5, 1.8, 2, 2.3, 2.5, 2.8, 3,
↩→3.3, 3.5]
[115]:
Now that we have access to the lists x and y, we can do some experiments.
[116]:
Despite the efforts of many throughout history, Isaac Newton included, it is not
possible to convert cheap metals into gold. But everyone now knows that it is
possible to turn data into gold, and this particular type of alchemy is called Data
Science.
Python provides several libraries to import data into the computer, in order to model
them, visualise them, study them and predict future behaviour. One way to import
data into Python is in the form of a list. We demonstrate it here by using the library
csv. More professional libraries such as panda are also available to handle data of
various formats.
40 2 Lists and Tuples
The following csv file was downloaded from the Reserve Bank of Australia home-
page. It provides the interest rates since October 2002. The code shows how to use
the csv library to upload the data into Python.
RBA_data[ : 10]
Once we have imported the data, we can visualise them, for example, using
matplotlib. As always the data we receive is raw and one needs to work on it
to make it ready for the process. We do this here for this example, although the
method of list comprehension used will be studied in Chapter 5.
[120]: plt.plot(d);
[120]:
2.5 Tuples 41
2.5 Tuples
tuple, similar to list, is designed to handle data. tuple behaves very much like
list. The main difference is that one can modify an exiting list, i.e., they are mutable
objects, whereas once a tuple is defined, one cannot modify it, i.e., they are unmutable
objects.
The examples below show how one can define and handle tuples.
Note that for a list the objects are gathered together with brackets [ ], whereas
for a tuple, one uses curly brackets (). But accessing the objects within tuples is
exactly like lists.
[122]: t[2]
[122]: 'new'
[123]: t[ : 3]
[124]: t[ : : -2]
[124]: (1.5, 2)
The following example demonstrates what we mean when we say list is mutable
whereas tuple is immutable.
[125]: s = [1, 2, 3]
[126]: t = (1, 2, 3)
[127]: type(s)
[127]: list
[128]: type(t)
[128]: tuple
[130]: s
42 2 Lists and Tuples
However executing t[1] = 'new data' generates an error. The error indicates
that once a tuple has been defined, one cannot change it.
Tuples are defined as a collection of data between round brackets (, ). In fact, one
does not need to add the brackets when defining tuples.
[132]: seasons
[134]: good_time
In Chapter 5 we will introduce dictionaries and sets, which are also designed
to handle data, and gives us yet more power to collect, process and model data.
2.6.1 Languages
The library nltk (Natural Language Toolkit) is a wonderful library that we can
use to work with languages. NLTK has been used in many areas, including natural
language processing, computational linguistics, artificial intelligence, information
retrieval, and machine learning.
One needs to import and download the library and its data before one can use it.
[135]: True
The library nltk contains a variety of texts, such as the list of all English language
words, or a selection of books from Project Gutenberg.
Now we can import all the English words (available in this package).
2.6 More Examples of Working with Data 43
[137]: type(word_list)
[137]: list
word list is a list of over 200 thousand English words, now available to us.
[138]: len(word_list)
[138]: 236736
[139]: True
[140]: ['A',
'beefhead',
'commerceless',
'Einsteinian',
'grievingly',
'jheel',
'mountaintop',
'pasilaly',
'pun',
'sheikly',
'tenorite',
'unomnipotent']
[141]: ['austen-emma.txt',
'austen-persuasion.txt',
'austen-sense.txt',
'bible-kjv.txt',
'blake-poems.txt',
'bryant-stories.txt',
44 2 Lists and Tuples
'burgess-busterbrown.txt',
'carroll-alice.txt',
'chesterton-ball.txt',
'chesterton-brown.txt',
'chesterton-thursday.txt',
'edgeworth-parents.txt',
'melville-moby dick.txt',
'milton-paradise.txt',
'shakespeare-caesar.txt',
'shakespeare-hamlet.txt',
'shakespeare-macbeth.txt',
'whitman-leaves.txt']
We now import Hamlet into the program using the function sents, which divides
the text up into its sentences, where each sentence is a list of words.
[143]: len(Hamlet_sentences)
[143]: 3106
[144]: Hamlet_sentences[1226]
[146]: len(Hamlet_words)
[146]: 37360
[147]: True
[148]: Hamlet_words.count('Lust')
[148]: 2
We note that the contents we import via nltk are not lists, but they behave like lists
as we have seen.
[149]: type(Hamlet_words)
2.6 More Examples of Working with Data 45
[149]: nltk.corpus.reader.util.StreamBackedCorpusView
There are also some books available in nltk which we can upload.
[151]: ['In',
'the',
'beginning',
'God',
'created',
'the',
'heaven',
'and',
'the',
'earth',
'.']
[152]: len(text3)
[152]: 44764
[153]: text3.count('God')
[153]: 231
We can also determine the location of a word in the text, i.e. its distance from
the beginning of the text, measured in words. This positional information can be
46 2 Lists and Tuples
displayed using a dispersion plot. Each stripe represents an instance of a word, and
each row represents the entire text
[154]:
In later chapters we will be using the nltk library to write more interesting codes.
Problems
𝑎𝑏 𝑐
𝑀 = 𝑑 𝑒 𝑓 ®
© ª
«𝑔 ℎ 𝑘 ¬
Using list methods produce the following matrices from the list 𝑀:
𝑎𝑑𝑔
• The transpose of 𝑀, i.e., 𝑏 𝑒 ℎ ® .
© ª
«𝑐 𝑓 𝑘¬
𝑎00
• The main diagonal of 𝑀, namely 0 𝑒 0 ® .
© ª
«0 0 𝑘 ¬
2.6 More Examples of Working with Data 47
00𝑐
• The anti-diagonal of 𝑀, namely 0 𝑒 0 ® .
© ª
«𝑔 0 0¬
𝑐 𝑏𝑎
• The matrix 𝑓 𝑒 𝑑 ® .
© ª
«𝑔 ℎ 𝑘¬
12𝑎
• The matrix 3 4 𝑑 ® .
© ª
«𝑔 ℎ 𝑘 ¬
★★★
• The matrix ★ 𝑋 ★ ® .
© ª
«★ ★ ★¬
𝑎𝑏 𝑐 𝑎1 𝑏2 𝑐3
© 1 2 3ª
𝑑 𝑒 𝑓 ® −→ 𝑑 𝑒 𝑓 ® .
© ª
1 2 3
«𝑔 ℎ 𝑘 ¬ «𝑔 ℎ 𝑘 ¬
Write a code to apply this process to a 3 × 3-matrix. Try the code for
123
2 3®.
1
© ª
«1 2 3¬
4) From the Reserve Bank of Australia, import data in the csv format of the interest
rates since October 2002 and try to obtain various data out of the imported file
(see §2.4).
Chapter 3
Decisions and Repetitions
The statement
if cond:
this
else:
that
where cond is a Boolean expression, i.e., has the value of True or False, will
execute the code block this if the cond value is True and that otherwise. That
means, in either case, one of the statements this or that will be performed (but not
both). So this gives us the ability to make a decision about which part of the code
one wants to perform. Here is an example:
[1]: if True:
print('100%')
print('checked')
else:
print('0%')
[1]: 100%
checked
We need to go through the above code carefully. The decoration and spacing above
is not for nicety. Python uses spacing to determine the structures. The code block of
this belonging to the if statement is determined by adding spacing, and likewise
for the code block that which belongs to the else part of the command. In other
languages one uses \begin, \end, ( and ) or { and } to group things together and
give structure to codes, but the spacing in Python both plays this role and makes the
code much easier to read. In particular, this approach cuts the number of brackets
one uses in Python to group things together and it makes the code visually much
more appealing. Later on in this chapter we will look at loops and repeating a block
of code. That will also done by determining the code that we would like to repeat
via indenting. Putting these together, a code in Python will look like the following,
where the grey blocks represent structured codes.
if cond:
else:
loop
if cond:
else:
When working with commands that require structures, Python expects us to follow
the proper indenting, as the following shows. If we run the code below, we encounter
an error message, which tells us that the indenting has not been done correctly.
[2]: if True:
print("100%")
print('checked')
else:
print("0%")
[2]:
Cell In[2], line 2
print("100%")
ˆ
IndentationError: expected an indented block after 'if'
statement on line 1
For the next example we look at the mysterious Collatz function, which is defined as
follows:
𝑥/2 if 𝑥 is even,
𝑓 (𝑥) =
3𝑥 + 1 if 𝑥 is odd.
if n % 2 == 0:
f = n//2
else:
f = 3 * n + 1
f
enter a number 71
[3]: 214
In fact, using the method divmod we can write a rather smarter code.
[4]: help(divmod)
divmod(x, y, /)
Return the tuple (x//y, x%y). Invariant: div*y + mod == x.
da = divmod(n, 2)
if da[1] == 0:
f = da[0]
else:
f = 3 * n +1
f
enter a number 71
[5]: 214
Exercise 3.1 Write a code to accept two numbers x and y and, if the difference is less
than 0.00001, then print x=y.
Solution
This is a good exercise when working with floats. As we saw in Chapter 1, when
working with floats we often don’t get exact values but some approximations of the
computations.
print(x, "=", y)
else:
print(x, "not =", y)
So far, via if-else, we have been able to control the flow of the code if there are two
possibilities (i.e., a fork in the road). The if statement is designed to control the flow
of the code if there are several possibilities at hand, via the format
if cond:
fork1
elif:
fork2
elif:
fork3
...
else:
otherwise
We will define the function below, which shows how if-elif-else works
−𝑥, if |𝑥| < 1
𝑓 (𝑥) = sin(𝑥), if 1 ≤ |𝑥| < 2
cos(𝑥),
otherwise.
if abs(x)<1:
f = -x
elif 1<= abs(x) <2:
f = math.sin(x)
else:
f = math.cos(x)
f
and observe that for arbitrary real numbers 𝑎, 𝑏 such that 𝑎 < 𝑏 < 0 we have
𝑓 (𝑥, 𝑏) 1 + e𝑏
>
𝑓 (𝑥, 𝑎) 1 + e𝑎
for any 𝑎 ≤ 𝑥 ≤ 𝑏.
Solution
We need to define the function 𝑓 (𝑥, 𝑦) and then evaluate 𝑓 (𝑥, 𝑎) and 𝑓 (𝑥, 𝑏). It makes
sense to define a stand alone function 𝑓 which accepts 𝑥 and 𝑦 and compute 𝑓 (𝑥, 𝑦).
This can be done when we learn how to define functions in Python in Chapter 4. At
this stage we define this function within our code.
y = a
if x == y:
fa = exp(x)
else:
fa = (exp(x) - exp(y))/(x - y)
y = b
if x == y:
fb = exp(x)
else:
fb = (exp(x) - exp(y))/(x - y)
z = (1 + exp(b))/(1 + exp(a))
print(fb / fa > z)
[8]: True
Exercise 3.3 Write a code to check if a number is an integer or a float, and whether
it is positive or negative.
Solution
Recall that the function type will give back the type of the object. Thus using type
we can determine if the input is an integer or a float.
[9]: x=-3.14
if type(x) != int and type(x) != float:
print('input is not a number')
elif x == 0:
print(x, 'is zero')
elif x > 0:
if type(x) == int:
print(x, 'is a positive integer')
else:
print(x, 'is a positive real number')
else:
if type(x) == int:
print(x, 'is a negative integer')
else:
print(x, 'is a negative real number')
Note in the code above how an if-else is used within another conditional structure.
Solution
Recall we can reverse a string by reading its characters from right to left as follows:
[10]: x = 'university'
x[: : -1]
[10]: 'ytisrevinu'
if x == x[ : : -1]:
print(x, "is palindromic")
3.2 Decision Making: The Forks on the Road 55
else:
print(x, "is not palindromic")
Exercise 3.5 Write a code to accept a list and then remove the first and last element
of the list if they are the same. Enhance the code so that if the list is empty it says the
list is palindromic. Further enhance the code so that it works for strings as well.
Solution
Let us start with a list, capture the first and last elements and then print all other
elements. We have all the tools to do this.
[12]: a a and the list without the first and last element
['b', 'c', 'd', 'e']
[14]: x
[15]: if x == []:
print('palindromic')
elif x[0] == x[-1]:
x = x[1 : -1]
print(x)
else:
print(x)
You will see if we run the code 4 times, we get the word ‘palindromic’. This shows
a way to write a code for checking if a list is palindromic, and we can make it more
efficient if we have tools to repeat a block of codes. This is done next when we
introduce loops in Python.
It is very easy to modify the code to handle strings as well.
[17]: x = 'kayak'
if x == [] or x == '':
print("Palindromic")
elif x[0] == x[-1]:
x = x[1 : -1]
print(x)
else:
print(x)
[17]: aya
3.3.1 For-loop
A primary ability that a computer language provides is the ability to repeat a certain
code ‘fast’. Python provides three kind of loops that enable us to repeat part of our
codes. These are quite similar to the loops that exist in any procedural language like
Fortran or C. We will introduce for and while loops and study nested loops, that
is, loops defined inside each other. We finish the chapter by looking at other nested
commands.
The first way to create a repetition is to define a list and ask Python to go through
the list. This way we can repeat a block as the number of elements of the list.
for i in list:
block
The block itself could consists of a large number of lines. Similar to an if statement,
we specify the block belonging to the for-loop with an appropriate spacing. The
program goes through the list and each time runs the block.
[18]: 2
3
4
5
Here the parameter n goes through the list (from left to right) [2, 3, 4, 5] each
time picking up one of the items in the list and executing the block, here the line
print(n). The program terminates when n reaches the end of the list.
As one can see, the arrangement is very similar to an if statement.
[19]: n = 4
if n in [2,3,4,5]:
print(n)
[19]: 4
Going through a list to create a loop gives us an amazing power, as we can create
loops going through practically all kind of lists which contain all kinds of objects!
[22]: WESTERN
SYDNEY
UNIVERSITY
Python provides a range object which comes in very handy: one can create a loop
using range. range(m) creates a range from 0 to 𝑚 − 1.
[23]: phishing
phishingphishing
phishingphishingphishing
phishingphishingphishingphishing
58 3 Decisions and Repetitions
phishingphishingphishingphishingphishing
phishingphishingphishingphishingphishingphishing
[24]: phishing
phishingphishingphishingphishingphishingphishing
phishingphishingphishingphishingphishingphishingphishingphishi
ngphishingphishingphishing
Note that range is neither a list nor a tuple, it is just an iterator object. As promised,
we will create a list containing images and then create a loop going through the
images and modify them accordingly.
[26]:
3.3 Loops and Repetitions 59
[27]: scale = 1
for pic in [x, y] * 2:
s = (pic.size[0] // scale, pic.size[1] // scale)
display(pic.resize(s))
scale *= 2
[27]:
60 3 Decisions and Repetitions
Exercise 3.6 A word is called palindromic if it reads the same backwards as forwards,
e.g., madam or kayak. Using the library nltk import all the words in the English
language into Python and then find all the palindromic words.
Solution
Recall that the library nltk provides tools and data related to language.
Now finding the palindromic words is an easy test going through the whole list.
[29]: acca adda affa ajaja alala alula amma anana anna arara atta
boob civic deed deedeed degged elle hallah immi kakkak kayak
keek kelek lemel level maam madam mesem minim murdrum noon
otto peep poop radar redder refer repaper retter rever reviver
rotator rotor siris sooloos tebbet teet tenet terret toot
ululu yaray
Solution
This exercise is quite similar to the previous one.
[31]: noon
noon
deed
[32]: texts()
Exercise 3.8 The sum of two positive integers is 5432 and their least common multiple
is 223020. Find the numbers.
Solution
The math library does not yet include the function for the least common multiple of
two numbers. However, the greatest common divisor is available via math.gcd. On
the other hand, we know the identity
𝑚𝑛
lcm(𝑛, 𝑚) = .
gcd(𝑛, 𝑚)
[33]: 5
[34]: 150
Exercise 3.9 Determine all the positive integers 𝑛 between 3 and 50 for which 22008
is divisible by
𝑛 𝑛 𝑛
1+ + + .
1 2 3
𝑛 𝑛 𝑛!
Here is the binomial coefficient: is defined as
𝑚 𝑚 𝑚!(𝑛 − 𝑚)!
Solution
Both factorial and binomial functions are available in the math library, as the fol-
lowing examples show:
3.3 Loops and Repetitions 63
[37]: True
[38]: True
We are ready to write the code. The difficulty is in translating the large formula into
Python correctly.
if (2**2008) % f== 0:
print(n)
[39]: 3
7
23
[40]: 2**2008
[40]: 293921457990209158203605299501486587909713331734705971322276540
627396162916446800347304828497025605099122166947580790470002462
453980942164845038427178663215460172772211999436801763274619494
514870858053094562524786640935586934754211705131586663593866165
516791188895740950898251790395677822812580408244051664241072407
000213774342091481108259990786393027841098246954768962126136340
818524880106908845781292048893428214830405175756437514347929224
149123944676950789355316620691925989560420249809810474574291853
773889494338599752572893233746059542823106006739520449114953730
10647749329399156163119321894151520256
Exercise 3.10 Notice that 122 = 144 and 212 = 441, i.e., the numbers and their
squares are reverses of each other. Find all the numbers up to 10000 with this
property.
Solution
The first thing to take care of is the reversal of the digits of a number. We can use the
trick that we used for strings, reading the characters of a string from right to left via
64 3 Decisions and Repetitions
s[: : -1]. So we convert a number into a string, read the characters from right to
left, and then convert it back to a number.
[41]: x=str(12345)
[42]: x[ : : -1]
[42]: '54321'
[43]: 54321
[44]: 54321
Now we need to translate the property we are after into code, that is, we need to raise
a number to the power of two, reverse it, and then compare the result to its reverse
to the power of two; in code it reads:
int(str(n**2)[ : : -1]) == int(str(n)[: : -1])**2.
Putting these together:
[46]: 112**2
[46]: 12544
[47]: 211**2
3.3 Loops and Repetitions 65
[47]: 44521
Solution
Let’s spill the beans: The number 142857 is the only 6-digit cyclic number! We first
check this via a simple loop.
How to actually find this number? One approach is to sort all the digits of the number
from smaller to larger. Then we check that any multiple of the number, when similarly
sorted, has the same sequence of digits.
[49]: str(142857)
[49]: '142857'
[50]: sorted(str(142857))
[51]: str(142857 * 2)
[51]: '285714'
[52]: True
↩→sorted(str(i * 6)):
print(i)
66 3 Decisions and Repetitions
[53]: 142857
Here is a bit of a re-working of the above code. We set x to be the digits of a number,
sorted, and a boolean value y = True. Then we examine multiples of the number,
each time comparing the result with x. If the arrangements do not match, we change
the value of the Boolean statement to false. This will keep track of whether the
arrangement of the digits changes.
[54]: x = sorted(str(142857))
y = True
for i in range(1, 7):
y = y and x == sorted(str(142857 * i))
print(y)
[54]: True
Recall that, for some operation #, we can write y = y # s in the shorter form y #=
s.
[55]: x = sorted(str(142857))
y = True
for i in range(1, 7):
y &= x == sorted(str(142857 * i))
print(y)
[55]: True
We can improve the above code even more. If in some instance the value of y
becomes false, namely, if the digits of the new number differs from the digits of x,
we do not need to go further through the rest of the loop. In this case, we can stop
the loop, using the command break. The break statement breaks out of the loop
entirely.
[56]: x = sorted(str(142857))
y == True
for i in range(1, 7):
if x != sorted(str(142857 * i)):
y = False
print(y)
break
[57]: 142857
√
Exercise 3.12 Define 𝑓 (𝑥) = 1 + 𝑥. We have
v
u
t √︄ √︂
√
√︃
𝑓 ( 𝑓 ( 𝑓 ( 𝑓 ( 𝑓 (𝑥))))) = 1+ 1+ 1+ 1 + 1 + 𝑥.
Solution
Later we will define the function 𝑓 in Python and directly calculate the composition
of functions. At the moment the only tool we have is loops.
[58]: 1.6118477541252516
This is the first time we have used as a variable within a loop. If one does not
explicitly need the variable which runs through the loop, one can use instead.
Solution
We create a loop, let n go through the list from 1 to 100, and on each occasion check
if 2𝑛 − 1 is divisible by 7. This is done by checking if the remainder of 2𝑛 − 1 by 7
is zero: in Python: if (2**n - 1) % 7 == 0.
[59]: 3 6 9 12 15 18 21 24 27 30 33 36 39 42 45 48 51 54 57 60 63
66 69 72 75 78 81 84 87 90 93 96 99
Solution
Recall that the library matplotlib is used for plotting data. We need to create two
lists x and y then plt.plot(x,y) will produce a graph determined by pairs from
the lists. This time we can use the for-loop to generate the lists.
x = []
y = []
for i in range(0, 200):
step = i * 2 * math.pi / 200
x.append(step)
y.append(math.sin(step))
plt.plot(x,y)
[60]:
Exercise 3.15 Consider the complex function (𝑖 is the imaginary number here)
1 1
𝑔(𝑡) = e−2𝑖𝑡 + e5𝑖𝑡 + e19𝑖𝑡
2 5
and plot the graph where 𝑥 and 𝑦 are the real and imaginary part of 𝑔(𝑡) for
0 ≤ 𝑡 ≤ 2𝜋.
Solution
We need to use the exponential function with complex numbers. The library math
provides the function exp, however here we can only use real numbers with this
function. The library cmath provides functions allowing us to work with complex
numbers. For this reason we import the function exp from the library cmath.
3.3 Loops and Repetitions 69
Once we calculate the function 𝑔, we can retrieve the real part with g.real and the
imaginary part with g.imag. We collect these values into two lists of x and y and
using matplotlib we plot the graph.
x = []
y = []
for t in range(0, 2000):
s = t * 2 * math.pi / 200
g = exp(-2j * s) + (1/2)*exp(5j * s) + (1/5)*exp(19j * s)
x.append(g.real)
y.append(g.imag)
plt.axis('off');
plt.axis('equal');
plt.plot(x,y);
[61]:
Finally, we revisit the interest rate data we have imported from the Reserve Bank of
Australia. Now we have a tool to extract parts of the list.
RBA_data[ : 10]
['Mar-2003', '4.75'],
['Apr-2003', '4.75'],
['May-2003', '4.75'],
['Jun-2003', '4.75']]
[64]: dates = []
data = []
for d in RBA_data:
dates.append(d[0])
data.append(float(d[1][0]))
plt.plot(data);
[65]:
3.3 Loops and Repetitions 71
In many applications there are several factors (variables) which change simultane-
ously, and this calls for what we call a nested loop. Instead of trying to describe the
situation abstractly, let us look at some examples.
Let us find all the pairs (𝑛, 𝑚) for 𝑛, 𝑚 ≤ 10 such that 𝑛2 + 𝑚 2 is a square number
(e.g., (3, 4) as 32 + 42 = 52 ).
Note that here we are working with two parameters 𝑛 and 𝑚, and each of them has
a range between 1 and 10. We can design two for-loops, each taking care of one of
the variables.
[66]: 3 4
4 3
6 8
8 6
Here the outer loop starts with the counter n getting the value 1. Then it is the turn
of the block inside this loop, which is again another loop run. In the inner loop m
in (1, 11) makes the counter m run from 1 to 10. This done, in the outer loop
n takes the
√ value 2 and then m runs from 1 to 10 and so on, each time checking
whether 𝑛2 + 𝑚 2 is an integer. This is done with a method available in floats,
namely .is integer.
As one can see, we get the pairs (3, 4) and (4, 3), which for us are the same answer.
In order to remove this repetition, we can modify the code.
The reader should see that in the inner loop, m needs to start from n and goes all the
way to 10. This is enough to find all the pairs up to 10 with the desired property.
Can you say how many times the if line is going to be performed?
Exercise 3.16 Pick an odd number 𝑝. Then find a pair (𝑞, 𝑟) of positive integers such
that 𝑝 2 + 𝑞 2 = 𝑟 2 .
72 3 Decisions and Repetitions
Solution
Clearly we have to set a bound for 𝑞, let’s say we will check up to 100 for 𝑞 and 𝑟.
The following code should find all the pairs up to 100.
Exercise 3.17 We asked ChatGPT to write a Python code to find the smallest number
that can be written as a sum of two cubes. Here is the code. What is the issue with
it!?
# Define the upper limit for the search (You can adjust
this if needed)
↩→
upper_limit = 1000
[68]: The smallest number that can be written as a sum of two cubes
is: 2
3.3 Loops and Repetitions 73
Solution
The smallest positive integer that can be written as the sum of two positive cubes is
obviously 2 = 13 + 13 . This does not require a code! However the code provided by
ChatGPT finds the answer correctly (although the nested loop runs 1 million times!)
and the overall arrangement of the code also is quite impressive.
Exercise 3.18 Define a 3 × 2 matrix (𝑎 𝑖 𝑗 ) with entries 𝑎 𝑖 𝑗 = 𝑖 − 𝑗. Then find the sum
of all the entries.
Solution
We have seen how to handle matrices using lists. Now that we have the ability
to create loops, we can generate the entries of the matrix systematically. We first
represent a generic 3 × 2-matrix by A=[[0,0], [0,0], [0,0]]. Next translating
𝑎 𝑖 𝑗 = 𝑖 − 𝑗 into code we get A[i][j] = i - j. Our task now is to run i and j from
0 to 2.
for i in range(3):
for j in range(2):
A[i][j] = i - j
print(A)
X = 0
for i in range(3):
for j in range(2):
X += A[i][j]
print(f'The sum of all the entries is {X}')
1 2 ··· 𝑛
+ 1 + 2 ··· 2𝑛 ®
© ª
𝑛 𝑛
. . .. .. ®®
. ..
. . . ®
« ··· ··· · · · 𝑛2 ¬
Solution
Here is a step-by-step process describing how to create such a matrix, when 𝑛 = 5.
74 3 Decisions and Repetitions
[71]: L = []
for i in range(5):
L += [i+1]
print(L)
[71]: [1, 2, 3, 4, 5]
[72]: L=[]
for j in range(5):
for i in range(1, 6):
L += [i + 5*j]
print(L)
[72]: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20, 21, 22, 23, 24, 25]
[73]: S = []
for j in range(5):
L = []
for i in range(1, 6):
L += [i + 5*j]
S += [L]
print(S)
[73]: [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15],
[16, 17, 18, 19, 20], [21, 22, 23, 24, 25]]
We can approach this exercise differently, by splitting a list. We consider the list
of numbers 1 to 25 and each time collect 5 consecutive elements from the list and
append it to a new list.
[74]: size = 5
m = [*range(1, size**2+1)]
mat = []
for i in range(0, len(m), size):
mat.append(m[i : i + size])
print(mat)
[74]: [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15],
[16, 17, 18, 19, 20], [21, 22, 23, 24, 25]]
Solution
Recall that if 𝐴 is a 𝑛 × 𝑚 matrix and 𝐵 is a 𝑚 × 𝑝 matrix, then the product matrix
𝐶 := 𝐴 · 𝐵 is an 𝑛 × 𝑝 matrix, where the entries of 𝐶 are
𝑚
∑︁
𝐶𝑖, 𝑗 = 𝑎 𝑖𝑘 𝑏 𝑘 𝑗 ,
𝑘=1
where 1 ≤ 𝑖 ≤ 𝑛 and 1 ≤ 𝑗 ≤ 𝑝. We can translate this into Python via three nested-
loops. We will later see that several Python libraries such as sympy and numpy
provide matrix multiplication, allowing us to perform linear algebra.
We define two sample matrices 𝐴 and 𝐵, and 𝐶 is designed to collect the entries of
the product of 𝐴 and 𝐵.
[76]: [[-1, -1, 6], [-1, -2, 0], [-8, -1, 0]]
Exercise 3.21 Find the number of palindromic words in all the standard books
available in nltk.
Solution
We have already seen how to find the palindromic words in any of the given books.
All we need to do is to create another loop which runs through all the books available.
[78]: for book in [text1, text2, text3, text4, text5, text6, text7,
↩→text8, text9]:
sum=0
for word in book:
if len(word)> 3 and word == word [ : : -1]:
76 3 Decisions and Repetitions
sum += 1
print(f'The number of palindromic words in {book} is
↩→{sum}')
While loops provide another way to repeat a block of code. This time the block is
going to be repeated until a certain condition is satisfied, i.e., a boolean expression
becomes True. The while-loop has the form
while cond:
block
We start with the following example. Consider 𝑛 = 123; while n is not divisible by
7, append the digit 1 to the right of 𝑛. Note that we don’t know from the outset how
many 1s we need to add to the right-hand side of the given number until it becomes
divisible by 7.
[79]: n = 123
while n % 7 !=0:
n = n*10 + 1
print(n)
[79]: 1231111
3.3 Loops and Repetitions 77
n%7 !=0 is our Boolean statement (condition). The while-loop repeats the block
belongs to it (specified via spacing) while n%7 !=0 returns True. Here n=10n+1 is
the block of code we want to repeat. The code n=10n+1 simply takes the number
n and places 1 at the far right of the number (right?). So the aim is to put as many
1s to the right of the original n, which is 123 here, to get a number divisible by 7.
The While loop does exactly this. It is going to repeat the above code until n%7 !=0
becomes False. That is, until 𝑛 becomes divisible by 7. And this is what we were
looking for.
Exercise 3.22 Find the smallest positive integer 𝑚 such that 5293 +1323 𝑚 is divisible
by 262417.
Solution
[80]: m = 1
while (529**3 + (132**3)*m) % 262417 !=0:
m += 1
m
[80]: 1984
Exercise 3.23 Find the smallest multiple of 99999 that contains no 9s amongst its
digits.
Solution
Recall that within a string we could check if an element belongs to it (similar
to lists). So we convert the number into a string and check whether the digit 9
belongs to it.
[81]: n = 99999
i = 1
while '9' in str(n):
i += 1
n = 99999 * i
print(f'the {i}th multiple of 99999 is {n} which has no digit
↩→9')
Note that it is important to get the flow of the code right, if we swap the two lines in
the block of the while-loop the answer is not correct.
[82]: n = 99999
i = 1
while "9" in str(n):
n = 99999 * i
i += 1
print(i, " ", n)
Although the library math provides the function gcd to calculate the greatest common
divisor, here we try to write an efficient way to compute the gcd of two given numbers.
Let us start with a naive approach:
[83]: 4
The code starts with 𝑖 = 1 and if 𝑖 divides both 𝑛 and 𝑚, it would be collected into
the variable gcd. We then increase 𝑖 and test again. The loop runs 𝑖 from 1 up to the
smaller number 𝑛 or 𝑚, and eventually it gives us gcd. We could improve the code
by starting 𝑖 from the smaller number and decreasing its value, checking each time
if 𝑖 divides both 𝑛 and 𝑚. As soon as this happens, we stop the loop via break and
print this value, which would be the greatest common divisor of 𝑛 and 𝑚.
[84]: 4
We can use the elegant Euclidean algorithm, which says that if for integers 𝑛 and 𝑚
we have 𝑛 = 𝑚𝑞 + 𝑟, where 𝑞, 𝑟 ∈ Z, then gcd(𝑛, 𝑚) = gcd(𝑚, 𝑟). Using this fact we
can write:
[85]: n = 36; m = 16
while m != 0:
n , m = m, n % m
print(n)
[85]: 4
[86]: True
Exercise 3.24 Write a code to get a number and create a list of its digits. Modify the
code to give the list of digits in any given base.
Solution
Recall that for any 𝑛 ∈ N, and any 1 ≤ 𝑏 ≤ 10, one can write
∑︁
𝑛= 𝑟𝑖 𝑏𝑖
1234 = 2 × 40 + 0 × 41 + 1 × 42 + 3 × 42 + 0 × 43 + 1 × 44 .
Then we say the number 1234 can be written as 103102 in base 4. If we choose 𝑏 to
be 10, then we retrieve all the digits of the number.
The code is easy to compile, if we recall that we can write n = n // b + n % b.
Now that the code is working with base 10, we can modify it to work for any base.
Exercise 3.25 Write a code to check if a number 𝑘 is of the form 2𝑚 3𝑛 . Enhance the
code to find the 𝑚 and 𝑛 and write 𝑘 = 2𝑚 3𝑛 .
Solution
As we don’t know how many 2s and 3s appear in the decomposition of the number 𝑘,
we can use a while loop to keep dividing 𝑘 by 2 until the result is no longer divisible
by 2. Next we get the result and start dividing it by 3, until the result is no longer
divisible by 3. If the result has been reduced to 1, it means the original number must
be of the form 𝑘 = 2𝑚 3𝑛 .
[89]: k = l = 16 * 3 * 2 * 5
while l % 2 == 0:
l = l // 2
while l % 3 == 0:
l = l // 3
if l == 1:
print(f'{k} is of the form 2ˆm 3ˆn')
else:
print(f'{k} is not of the form 2ˆm 3ˆn')
Next we modify the code slightly and keep track of the number of times we divide
the number by 2 and 3, respectively.
3.3 Loops and Repetitions 81
Problems
1) Find the number of positive integers 0 < 𝑛 < 20000 such that 1997 divides
𝑛2 + (𝑛 + 1) 2 . Try the same code for 2009 and 2022.
1000
2) Show that the number of 𝑘 between 0 and 1000 for which is odd is a
𝑘
power of 2.
𝑛
Note that is the binomial coefficient defined by
𝑚
𝑛 𝑛!
=
𝑚 𝑚!(𝑛 − 𝑚)!
(𝑚 + 3) 3 + 1
𝐴= .
3𝑚
Find all the integers 𝑚 less than 500 such that 𝐴 is an integer. Show that 𝐴 is
always odd.
6) For a given 𝑛, calculate the series
82 3 Decisions and Repetitions
13 + 23 + · · · + 𝑛3 ,
1 1 1
1+ + +···+
1 2! 𝑛!
3𝑛 + 4𝑛 + · · · + (𝑛 + 2) 𝑛 = (𝑛 + 3) 𝑛
𝑎 1 + 𝑎 2 + · · · + 𝑎 𝑛 = 𝑎 𝑛+2 − 1,
𝑎 4𝑛 − 𝑎 𝑛−2 𝑎 𝑛−1 𝑎 𝑛+1 𝑎 𝑛+2 = 1,
𝑎 2𝑛 = 𝑎 𝑛 (𝑎 𝑛−1 + 𝑎 𝑛+1 ).
3.3 Loops and Repetitions 83
15) Compute
√︂ √︂ √︂
1 1 1 1 1 1
1+ 2 + 2 + 1+ 2 + 2 +···+ 1+ 2
+ .
1 2 2 3 2022 20232
converges.
17) Write
10
Ö (2𝑛 − 1)(2𝑛 + 1) 1 × 3 3 × 5 5 × 7
= ··· .
𝑛=1
2𝑛 × 2𝑛 2×24×46×6
18) Investigate
e 2 21 2 4 14 4 6 6 8 18 8 10 10 12 12 14 14 16 161
= ···
2 1 33 5577 9 9 11 11 13 13 15 15
19) Investigate
∞ 2𝑛 √
∑︁ (−1) 𝑛 ∑︁ 1 3𝜋 5+1 𝜋
= log − log 5.
𝑛=0
2𝑛 + 1 𝑘=0
2𝑛 + 4𝑘 + 3 8 2 16
20) Find the smallest number expressible as the sum of two cubes in two different
ways (hint: the number is less than 3000).
Chapter 4
Functions
4.1 Functions
Functions in mathematics define rules about how to handle data. A function is a rule
which assigns to each element in its domain a unique element in a specific range. For
example, the function 𝑓 defined as 𝑓 (𝑛) = 𝑛2 + 1 will receive as an input (a number)
𝑛 and its output will be 𝑛2 + 1. Another way to think of this is that one can assign an
object to the parameter 𝑛 in the function and 𝑓 will process the object according to
the rules defined within the function.
Functions (in programming) provide a way to split the code into mini-programs with
their own (local) variables and codes and one can pass inputs into them and receive
outputs. In this way one can break a long program into logically smaller programs,
each piece being a stand-alone function.
We start by defining simple functions in Python.
Here f is the name of the function. What comes after : is the body of the function.
One can think of f as a mini-program and one can call this function anytime in the
main program. This function has a poor soul: no parameter to pass objects into it.
Next we improve this function by passing data into it.
Here the function f comes with a parameter x. We can pass data to f via the parameter
x. It is important to note that we have not specified any type for the objects passed
into f via x. This opens up our hand.
Next we define a function name f accepting data (a variable) 𝑥 and returning 𝑥 2 + 1,
namely 𝑓 (𝑥) = 𝑥 2 + 1.
[5]: f(3)
[5]: 10
[6]: 10.869604401089358
[7]: f(f(2))
[7]: 26
[8]: True
Note that we can pass to the function f data of type integers, floats or complex
numbers, as the body of the function f is defined to do arithmetic on numbers. Later
we see we can specify what type of data a function can accept.
1
Next we define the function 𝑓 (𝑥) = 1+𝑥 and then calculate
1
1
,
1+ 1+𝑥
4.1 Functions 87
[10]: f(1)
[10]: 0.5
1
One can see that 𝑓 ( 𝑓 (𝑥)) = 1
1+ 1+𝑥
, thus we could evaluate the function with the
composition
[11]: f(f(1))
[11]: 0.6666666666666666
As one can see, the function is designed to accept (integer, float, complex) numbers.
Python allows us to do symbolic computation, namely to work with a symbol 𝑥 and
carry out the arithmetic symbolically. For this we need to use the library sympy and
specify that x is a symbol. Once this is done, Python can comfortably carry out the
computations using this symbol.
def f(x):
return 1 / (1 + x)
x=sympy.symbols('x')
[13]: f(x)
[13]: 1
𝑥+1
[14]: f(f(f(x)))
[14]: 1
1
1+ 1
1+ 𝑥+1
[15]: x=sympy.symbols('elephant')
def g(x):
return x /(1 + x)
[16]: g(g(g(x)))
88 4 Functions
This makes it clear that in the definition of the function f(x), the parameter x has
no type assigned to it. So one can pass any object with any type into the function.
It is the body of the function which determines what type one should pass into the
function. As long as the functions are correctly designed for certain types, Python
handles passing those types into the functions. The following example demonstrates
this.
[19]: f_image(y)
[19]:
v
u
t √︄ √︂
√
√︃
𝑓 ( 𝑓 ( 𝑓 ( 𝑓 ( 𝑓 (𝑥))))) = 1+ 1+ 1 + 1 + 1 + 𝑥.
4.1 Functions 89
Solution
This exercise shows how comfortably Python can compose functions, something that
becomes complicated and involved when done repeatedly.
[21]: f(f(f(f(f(1)))))
[21]: 1.616121206508117
def sf(x):
return sympy.sqrt(1 + x)
[23]: x=sympy.symbols('x')
sf(sf(sf(sf(sf(x)))))
v
u
[23]: t√︄√︂√︃
√
𝑥+1+1+1+1+1
Functions allow us to break a program into smaller, more manageable pieces. Each
function governs its own local variables: the variables we define inside the functions.
These local variables cannot be accessed from outside the function, so the scope of
these variables is within the function they are defined. In contrast, the variables
defined in the main body of the program are global variables, and these can be used
throughout the code, also within the functions.
Exercise 4.2 Define the functions 𝑝(𝑛) = 𝑛(𝑛 + 1)(𝑛 + 2)(𝑛 + 3) + 1 and 𝑞(𝑛) =
(𝑛2 + 3𝑛 + 1) 2 and observe that they are equal.
Solution
Defining these functions is easy:
90 4 Functions
def p(n):
#square_num is a local variable belonging to the function
↩→p
square_num = n * (n + 1) * (n + 2) * (n + 3) + 1
return square_num
def q(n):
#square_num is a local variable belonging to the function
↩→q
square_num = (n**2 + 3 * n + 1) ** 2
return square_num
print(p(3), q(3))
In this code, we have defined two functions p and q. Note that in both functions,
there is a local variable square num. These variables belong exclusively to their
functions. One cannot call them from outside the function. Although they have the
same name, they don’t interfere with each other; one is defined and belongs to the
function p and the other to q.
Solution
This will be the first time that we define a variable within the body of a function.
The function is called 𝑒 𝑝(𝑛) and inside it we define sum = 0. Note that this sum is
a local variable, that is, it is only defined inside the function. We cannot call it from
outside, which is a good thing.
def ep(n):
s=0
for i in range(n+1):
s += 1/ math.factorial(i)
return s
4.1 Functions 91
ep(10)
[25]: 2.7182818011463845
[26]: 0.71828, 0.0, -0.0, -0.0, -0.0, -0.0, -0.0, -0.0, -0.0, -0.0,
Solution
We first write a function, called sumadd, which accepts 𝑛 and returns 1 + 2 + · · · + 𝑛.
sumadd(10)
[27]: 55
Now using sumadd we can translate the function 𝑓 (𝑛) into Python.
Exercise 4.5 Plot the graphs of the functions 𝑓 (𝑥) = 2 exp(−𝑥 2 ) and 𝑔(𝑥) =
cos(sin(𝑥) + cos(𝑥)) between [−𝜋, 𝜋].
Solution
We first define the functions 𝑓 and 𝑔 and then compute them in the range [−𝜋, 𝜋].
We will systematically work with the library matplotlib in Chapter 7. Here we
will only use it to plot the graph. The commands used in the code related to the plot
are rather self-explanatory and used to ‘decorate’ the output graph.
def f(x):
return 2 * exp(-x**2)
def g(x):
return cos(sin(x) + cos(x))
x = []
fl = []
gl = []
for i in range(0, 100):
s = -pi + i * 2 * pi/100
x.append(s)
fl.append(f(s))
gl.append(g(s))
plt.figure(figsize=(3, 3));
plt.plot(x,fl)
plt.plot(x,gl);
[29]:
def p(x,y):
return math.sqrt(x**2 + y**2).is_integer()
[31]: p(3, 4)
[31]: True
[32]: p(5, 3)
[32]: False
Using the function p we can easily find the Pythagorean pairs, here up to 20.
[33]: 3 4
5 12
6 8
8 15
9 12
12 16
15 20
We have seen p(x) accepts one object, whereas p(x, y) can receive two objects.
If we need to pass a sequence of data into a function, one brilliant method is to do
just that, passing a sequence into a function. The following example shows how this
is done: We write the function
𝑓 (𝑥1 , 𝑥2 , . . . , 𝑥 𝑛 ) = 𝑥 12 + 𝑥22 + · · · + 𝑥 𝑛2 .
[35]: f(2, 3)
[35]: 13
[37]: f(1, 2)
[37]: (1, 2)
[38]: f(1, 3, 5, 6)
[38]: (1, 3, 5, 6)
f(1, 2, 3)
[40]: 14
[41]: f(*range(4))
[41]: 14
√︂
√
√︃ √︁
Exercise 4.6 Write the function 𝑓 (𝑥1 , 𝑥2 , . . . , 𝑥 𝑛 ) = 𝑥1 + 𝑥2 + 𝑥3 + · · · + 𝑥 𝑛
and calculate 𝑓 (1, 2, . . . , 10).
Solution
We define a function which accepts the sequence (𝑥1 , 𝑥2 , · · · , 𝑥 𝑛 ) and then calculates
the expression. A priori we don’t know how long the sequence passing into the
function is.
return math.sqrt(s)
f(1,2,3)
[42]: 2.1753277471610746
Going through the code, f(1,2,3) will assign the tuple (1, 2, 3) to the parameter
x. Next we run i through the tuple (1, 2, 3). In the first run, 𝑖 = 1 and we obtain
√
0 + 1, which we assign√︁to sum. In the next round of the loop, 𝑖 = 2 and with the
√ √
previous
√︁√ sum, we obtain 0 + 1 + 2. Simplifying, this is 1 + 2. Next, for 𝑖 = 3, we
get 1 + 2 + 3. The loop is complete and the next √︃
line return math.sqrt(sum)
√︁√
will take another root for the result, i.e. we obtain 1 + 2 + 3. Rearranging, we
obtain √︂
√
√︃
3 + 2 + 1.
Let us investigate this symbolically. We use sympy methods to define the function.
[45]: sf(x, y, z)
√︂
[45]: √︃
√
𝑥3 + 𝑥1 + 𝑥2
But if we pass the reverse of this sequence into the function, i.e.:
[47]: sf(x, y, z)
96 4 Functions
√︂
[47]: √︃
√
𝑥1 + 𝑥2 + 𝑥3
then we get the right output. This clearly shows we have to start the loops from
the right-hand side of sequence rather than the left-hand side. We have seen how to
reverse lists or tuples.
[49]: sf(x,y,z)
√︂
[49]: √︃
√
𝑥3 + 𝑥1 + 𝑥2
Solution
Here, similar to the previous exercise, we need to start with the last fraction, 𝑐1𝑚 , and
work our way up to 𝑐 0 . The next step in the process (a loop) would be 𝑐 𝑚−1 + 𝑐1𝑚
and then 𝑐 𝑚−2 + 𝑐 1+ 1 . This process can be captured by the code
𝑚−1 𝑐𝑚
s = 0
for i in c[: : -1]:
s = i + 1/s
As usual, in the first round of the loop, the sum s would start with 0. However, in
that case we get an error message as we are using 1/s. A smart way to avoid this is
to define an if statement inside the code to check if the value of s is zero, and in
that case we ignore 1/s.
[51]: f(1, 2, 3, 4)
[51]: 1.4333333333333333
[52]: f(*range(1,100))
[52]: 1.4331274267223117
Again, we can run the code symbolically to check if the process is working the way
we had in mind.
[54]: f(x, y, z)
[54]: 𝑥 + 1
1 1
𝑥2 + 𝑥3
[55]: s = sympy.symbols('x:11')
[56]: f(*s)
[56]: 𝑥 + 1
0 1
𝑥1 + 𝑥2 + 1
𝑥3 + 1
𝑥4 + 1
𝑥5 + 1
𝑥6 + 1
𝑥7 + 1
𝑥8 + 1
𝑥9 + 𝑥1
10
[58]: 1.4333333333333333
98 4 Functions
We will revisit this exercise and write a code using a functional programming
approach.
Solution
The function 𝑎 𝑛 or rather, 𝑎(𝑛), is a recursive function, that is, it calls itself. We
write two different codes for this. The first one is a direct translation of 𝑎(𝑛) into
Python.
for n in range(11):
print(a(n), end=' ')
Next we give an alternative code. Here the code does not immediately look like a
recursive function.
for n in range(11):
print(a(n), end=' ')
Solution
The function isprime from the sympy library is designed to check if a number is
prime.
4.1 Functions 99
Here we write our own function to determine the primeness of a positive integer.
[63]: primeQ(2)
[63]: True
[64]: primeQ(13*17)
[64]: False
Next we check whether the Mersenne number 231 − 1 is prime. We do this both using
our function and the built in function isprime from the sympy library. We can also
time the whole operation to see how long it actually takes to complete the job. This
can be done by importing the function time from a library with the same name.
start_time = time.time()
print(primeQ(2**31 - 1))
end_time = time.time()
print("%s seconds" %(end_time - start_time))
[65]: True
59.52177691459656 seconds
[66]: True
0.0055501461029052734 seconds
This little experiment shows there is a lot of room to improve our code for the
function primeQ!
Exercise 4.10 1. Write a function to accept a sequence of numbers and then print
the sequence which only contains the primes of the original sequence.
2. Write a function to accept a sequence of numbers and then print the sequence
up to the occurrence of the first prime.
Solution
Python offers a technique called list comprehension. Using list comprehension, one
can very easily and elegantly write the above codes. Here, however, we write a code
with the techniques we have studied so far.
[71]: []
Here is another approach, using the sequence to pass the list inside the function and
then using the break once the first prime number has been spotted..
4.1 Functions 101
[74]: ()
We have seen that one can pass objects into functions. We now design functions for
which the objects are pre-defined. This means, if one does not pass any object for a
given parameter, the pre-defined values will be used.
Next we modify the function address, giving default values to the parameters,
which will be used if those parameters are not assigned any object by the user.
Exercise 4.11 Write a function to ask for the values of 𝑎, 𝑏 and 𝑛, and produce the
𝑛-th Fibonacci number with the initial values 𝑎 and 𝑏, i.e., 𝑓0 = 𝑎, 𝑓1 = 𝑏 and
𝑓𝑛 = 𝑓𝑛−1 + 𝑓𝑛−2 . Further, modify the function so that if the user does not enter the
initial values 𝑎 or 𝑏, then they take the default values 𝑎 = 1 and 𝑏 = 1.
Solution
First we write the code of the function without specifying any initial values. The
approach in generating the Fibonacci sequence in this code is similar to Exercise 4.8.
[82]: fibonacci(10, 1, 1)
[82]: 55
[84]: fibonacci(10)
[84]: 55
[85]: 16
As we discussed, the function def f(x): can accept any type of object. If we would
like to restrict the type of object that f can handle from the outset, we can specify
the type of x.
4.2 Functional Programming: Anonymous (lambda) Functions 103
Here the function f expects the parameter to be passed into it to be of the type list
and integer. This way of writing code also helps us to understand what a code does
when first reading it.
[88]: 'hell'
Sometimes we need to ‘define a function as we go’ and use it on the spot. Python
enables us to define a function without giving it a name, use it, and then move
on! These functions are called anonymous functions. Obviously if we need to use a
specific function frequently, then the best approach is to give it a name and define it,
as we did earlier. Here is an anonymous function equivalent to 𝑓 (𝑥) = 𝑥 2 + 4:
[90]: 104
With the keyword lambda we define an anonymous function. Here the function has
one variable, 𝑥, and its output is 𝑥 2 + 4. One can think of this as 𝑥 ↦→ 𝑥 2 + 4, where
the lambda is that arrow.
The following might defeat the purpose: giving a name to an anonymous function.
But let’s make sure we are comfortable with the concept.
[91]: f = lambda x: x + 3
[92]: f(1)
104 4 Functions
[92]: 4
[93]: 5
[94]: 6
As the above two examples show, as long as the body of the function can handle the
types, one can pass any type of object into the parameters of the function.
Similar to the ‘classical’ functions, we can define default values for parameters.
[96]: 6
[100]: (3, 2, 1)
[103]: sum([1,2,3])
[103]: 6
[104]: 6
In Python there are many ways to construct a collection of objects, for example via
list, tuple, dictionary or iterators. There are times when we would like to apply
a function to all the objects of a list or a collection. Suppose f is a function and
[a,b,c] is a list. We want to be able to push the function f inside the list and get
[f(a),f(b),f(c)]. This can be done using the command map.
As a first example, we map the function sin from the math library to the list of
integers 1 to 9.
[106]:
Oftentimes, one defines a function via lambda and then applies it to the list, as the
example below demonstrates.
[108]: list(l)
[108]: [0.0,
1.0,
1.2246467991473532e-16,
-1.0,
-2.4492935982947064e-16,
1.0,
3.6739403974420594e-16,
-1.0,
-4.898587196589413e-16,
1.0]
Of course we could create this list via a for loop, but there are times when functional
programming is much shorter, more elegant and easier to read.
[109]: L=[]
for x in range(10):
L.append(sin(x * pi/2))
L
[109]: [0.0,
1.0,
1.2246467991473532e-16,
-1.0,
-2.4492935982947064e-16,
1.0,
3.6739403974420594e-16,
-1.0,
-4.898587196589413e-16,
1.0]
4.2 Functional Programming: Anonymous (lambda) Functions 107
Here are some more examples showing the versatility of this approach.
x=symbols('x')
y=symbols('y')
[112]: list(lx)
[112]: [x + y,
x**2 + 2*x*y + y**2,
x**3 + 3*x**2*y + 3*x*y**2 + y**3,
x**4 + 4*x**3*y + 6*x**2*y**2 + 4*x*y**3 + y**4]
Next we use the map function to rotate a picture certain number of times.
[115]: list(x_pic);
[115]:
108 4 Functions
Exercise 4.12 Recall that the formula 𝑛(𝑛 + 1)(𝑛 + 2)(𝑛 + 3) + 1 produces a square
number. Using functional programming, calculate this number for 1 ≤ 𝑛 ≤ 10 and
its square root.
Solution
Let us define a function f(n) which gives back (n, n(n + 1)(n + 2)(n + 3)
+ 1). We then map this function inside the list of integers from 1 to 10.
def f(n):
x=n * (n + 1) * (n + 2) * (n + 3) + 1
return x , math.sqrt(x)
list(map(f, range(1,11)))
Here is another example, showing practically anything can be seen as a function and
thus sent into a list via map. We will be using the plotting facility of the sympy library
here. The function pl.plot(f) will plot the function f. We define an anonymous
function lambda f : pl.plot(f) and then we replace f with different functions
in a list, via mapping the lambda function inside the list. This allows us to plot all
the graphs within one line of code!
x = symbols('x')
l = map(lambda f : pl.plot(f), (sin(x), x + sin(x)**3, cos(x)
↩→+ sin(x)**3))
list(l);
[117]:
the elements of the set. Each time we prompt the map statement, it will apply the
function 𝑓 to the set in order. Of course by invoking list we can obtain the entire
action, as we have done so far (see Problem 14).
So far we have been able to create a collection of objects, and apply a function to
each of the objects. The next step is to be able to choose, from a collection of objects,
certain objects which fit a specific description. This can be achieved by using the
command filter, as the following example demonstrates.
How many numbers of the form 3𝑛5 + 11, when 𝑛 varies from 1 to 500, are prime?
We define the anonymous function lambda n : 3 * n**5 + 11 and map it to the
list of the first 500 positive integers.
Next we use the function isprime from the sympy library to check which of these
numbers are prime. To select, among these lists, those which are prime, we use
filter.
p = filter(isprime, s)
[120]: x = list(p)
[122]: len(x)
[122]: 32
isprime(x) is true.
In mathematical syntax, this is
𝑥 ∈ 𝑠 | 𝑖𝑠𝑝𝑟𝑖𝑚𝑒(𝑥) .
Exercise 4.13 For which 1 ≤ 𝑛 ≤ 1000 does the Mersenne formula 2𝑛 − 1 produce
a prime number.
Solution
Once we know how to combine lambda function with filter this code is very easy
to put together.
[124]: list(s)
[124]: [2, 3, 5, 7, 13, 17, 19, 31, 61, 89, 107, 127, 521, 607]
Let us take a deep breath and go through this one-liner slowly. The function lambda
n : isprime(2**n - 1) is an anonymous function which returns True if the
number 2𝑛 − 1 is prime and False otherwise. That is, the output of this anonymous
function is a boolean value. We thus can use filter with this function.
[125]: True
[126]: L=[]
for i in range(1, 1001):
if isprime(2**i - 1):
L.append(i)
L
[126]: [2, 3, 5, 7, 13, 17, 19, 31, 61, 89, 107, 127, 521, 607]
112 4 Functions
Exercise 4.14 Find the number of positive integers 0 < 𝑛 < 20000 such that 1997
divides 𝑛2 + (𝑛 + 1) 2 . Try the same code for 2023.
Solution
We do this in one go.
[127]: 20
First things first, we define a lambda function which checks, for a number 𝑛, whether
the expression 𝑛2 + (𝑛 + 1) 2 is divisible by 1997. This can be done as follows:
The above function is a Boolean function and will return True or False.
[129]: False
This means 132 + (13 + 1) 2 is not divisible by 1997. Next we use this function, with
filter, and pass it into the range(1,20000). We remind the reader that
filter(lambda n: (n**2+ (n+1)**2) % 1997 == 0, range(1,20000))
is an iterator and in itself will not perform the calculations. It needs us to prompt it
to start the calculations. The command list will do just that, and the iterator will
go through the whole range and create a list. The command len then gives us the
length of this list.
Solution
To add the numbers together we can use the function sum.
[130]: sum([1,2,3])
[130]: 6
[132]: list(l)
[132]: [1, 8, 27, 64, 125, 216, 343, 512, 729, 1000, 1331, 1728,
30664297]
[133]: 30670381
[134]: s = 0
for i in range(1,14):
s += i**3
print(s)
[134]: 30670381
Finally we introduce the reduce function. Recognizing how to use this function
allows us to create very elegant and short programs. The function reduce works as
follows:
reduce(f,[x,y,z,t]) gives f(f(f(x, y), z), t)
[136]: 15
If we follow the code, we see the program first assigns 1 and 2 to 𝑥 and 𝑦 respectively,
and returns 𝑥 + 𝑦, which is 1 + 2. Next this will be assigned to 𝑥 and 𝑦 takes the next
item in the list, namely 3 and again 𝑥 + 𝑦 returns 1 + 2 + 3. Repeating this, we get the
sum of all the digits in the list.
114 4 Functions
[141]: 𝑥0 + 𝑥1 + 𝑥2 + 𝑥3 + 𝑥 4 + 𝑥5 + 𝑥 6 + 𝑥7 + 𝑥8 + 𝑥9
[142]: 𝑥0 𝑥 1 𝑥 2 𝑥3 𝑥4 𝑥5 𝑥 6 𝑥7 𝑥8 𝑥9
We revisit Exercise 4.7 and employ reduce to write the code.
Solution
The code is short, but it requires a bit of concentration to see how it works. The best
approach is to work out the first couple of steps by hand to observe that the result
takes the form of the continued fraction described in the exercise.
L=[1, 2, 3, 4]
L.reverse()
reduce(lambda x, y: (1/x) + y, L)
[143]: 1.4333333333333333
4.2 Functional Programming: Anonymous (lambda) Functions 115
Here we produce this continued fraction using sympy. We need a list of symbols of
the form 𝑐 0 , 𝑐 1 , . . . , 𝑐 𝑚 for a given 𝑚. The command symbols('c:m'), where 𝑚 is
a positive integer, does that.
[144]: symbols('c:10')
[144]: (c0, c1, c2, c3, c4, c5, c6, c7, c8, c9)
def L(m):
sym = 'c:' + str(m)
return symbols(sym)
[145]: 𝑐 + 1
9 1
𝑐8 + 𝑐7 + 1
𝑐6 + 1
𝑐5 + 1
𝑐4 + 1
𝑐3 + 1
𝑐2 + 1
𝑐1 + 𝑐1
0
Exercise 4.17 Find all the numbers up to one million which have the following
property: if 𝑛 = 𝑑1 𝑑2 · · · 𝑑 𝑘 then 𝑛 = 𝑑1 ! + 𝑑2 ! + · · · + 𝑑 𝑘 ! (e.g. 145 = 1! + 4! + 5!).
Solution
We first need to get the digits of a given number. One way to do this is to convert
the number into a string, get the list of all the letters, and then convert all the letters
back to numbers.
[146]: str(1234)
[146]: '1234'
[147]: list(str(1234))
Now that we have all the digits, using map we can push the factorial function inside
the list and calculate 𝑛! for each digit.
116 4 Functions
Finally, we add all the results together and, as we have seen, we can use the elegant
reduce to do so.
[150]: 33
[152]: f(1234)
[152]: 33
We are ready to use the function f to found out those numbers 𝑛 = 𝑑1 𝑑2 · · · 𝑑 𝑘 such
that 𝑛 = 𝑑1 ! + 𝑑2 ! + · · · + 𝑑 𝑘 !
[153]: 1
2
145
40585
Exercise 4.18 Notice that 122 = 144 and 212 = 441, i.e., the numbers and their
squares are reverses of each other. Find all the numbers up to 10000 with this
property.
Solution
We first define a function which reverse the digits of a given number. We use strings
and benefit from the functions available for this object.
[155]: re(12345)
4.2 Functional Programming: Anonymous (lambda) Functions 117
[155]: 54321
Having this function under our belt, the solution to the problem is just one line.
Notice that the problem is asking for the numbers 𝑛 such that re[n**2]=re[n]**2.
[157]: 1, 2, 3, 10, 11, 12, 13, 20, 21, 22, 30, 31, 100, 101, 102,
103, 110, 111, 112, 113, 120, 121, 122, 130, 200, 201, 202,
210, 211, 212, 220, 221, 300, 301, 310, 311, 1000, 1001,
1002, 1003, 1010, 1011, 1012, 1013, 1020, 1021, 1022, 1030,
1031, 1100, 1101, 1102, 1103, 1110, 1111, 1112, 1113, 1120,
1121, 1122, 1130, 1200, 1201, 1202, 1210, 1211, 1212, 1220,
1300, 1301, 2000, 2001, 2002, 2010, 2011, 2012, 2020, 2021,
2022, 2100, 2101, 2102, 2110, 2111, 2120, 2121, 2200, 2201,
2202, 2210, 2211, 3000, 3001, 3010, 3011, 3100, 3101, 3110,
3111
In the last exercise we will experiment with the amusing topic of finding secret
messages in ancient texts and scriptures. Consider a text in the form of a string of
letters 𝑙 1 𝑙 2 · · · 𝑙 𝑘 . Then an equidistant letter sequence of length 𝑠 is a subsequence
𝑙 𝑛 𝑙 𝑛+𝑑 · · · 𝑙 𝑛+(𝑠−1) 𝑑 . Here 𝑑 is called the skip. Note that this means taking letters
from the text with uniform spacing. The subsequence can be thought of as a vertical
section of text which appears when writing the text around a cylinder with a fixed
circumference. The topic of finding hidden messages in scriptures by choosing the
correct spacing has been a theme of many articles. Here we will look at
Exercise 4.19 Search Jane Austen’s Sense and Sensibility, for the word “google”,
which might appear as a equidistant letter sequence.
Solution
Recall from 2.6.1 that the library nltk contains several classical texts as well as
tools to process texts.
[159]: len(text2)
[159]: 141576
When we look inside the books, they are lists of strings (words). For our purpose we
need to put add the characters together.
[161]: s1 = 'Hello'
s2 = 'World'
[162]: 'HelloWorld'
Next we use reduce to get the strings, two at a time, and put them together.
[163]: 'InthebeginningGodcreatedtheheavenandtheearth'
Now we are ready to look at equidistant letter sequences. We can find the word
‘google’ in Jane Austen’s book, published in 1811.
for j in range(len(x_text)):
if x_text[0 + j : l_w * interval + j : interval].
↩→lower() == 'google':
[165]: google appears in the book starting from the 312852th letter
with interval 90
[166]: 'googlE'
Problems
1. For integers 2 ≤ 𝑛 ≤ 200, find all 𝑛 such that 𝑛 divides (𝑛 − 1)! + 1. Show that
there are 46 such 𝑛.
2. Write a function which returns an answer of True if the 𝑛’th prime number is
of the form 4𝑘 + 1 for some integer 𝑘. Hence produce a list of those positive
integers 𝑛 lying between 1 and 100 such that the 𝑛’th prime is of such a form
4𝑘 + 1. Verify that there are 47 such 𝑛.
3. Suppose 𝑃𝑖 is the 𝑖-th prime number. Let 𝐸 𝑖 = 𝑃1 × 𝑃2 × · · · × 𝑃𝑖 + 1 be the 𝑖-th
Euclid number. Define a function Euclidnumber(n) to produce the 𝑛-th Euclid
number. Use this function and produce the list of the first 20 Euclid numbers.
Find out which of these numbers are in fact prime. Find the indices of the prime
Euclid numbers (i.e, 𝑖, where 𝐸 𝑖 is prime), where 1 ≤ 𝑖 ≤ 100.
4. Write a function to calculate the following
1 1 1 1 1
𝑝(𝑛) = 1 + √ + √ √ +√ √ +√ √ +···+ √ √
1 1+ 2 2+ 3 3+ 4 𝑛−1+ 𝑛
values are 𝑓 (1) = 1, 𝑓 (2) = 2, 𝑓 (3) = 9 and 𝑓 (4) = 62. Define this function in
Python. What are 𝑓 (5) and 𝑓 (10)?
7. Let 𝐴(𝑥, 𝑦) = (𝑎 𝑖 𝑗 (𝑥, 𝑦)) be the 𝑛 × 𝑛 matrix with
0 if 𝑖 = 𝑗
𝑎 𝑖 𝑗 (𝑥, 𝑦) = 𝑖+ 𝑗
sin(𝑥) 𝑖 cos(𝑦) 𝑗 sin(𝑦) + cos(𝑥) if 𝑖 ≠ 𝑗 .
v
t 𝑛
∑︁
∥𝑥∥ = 𝑥 2𝑘 .
𝑘=1
Write a function vnorm which computes the norm of a real vector of any length.
(Write the function in two different ways: using procedural loops and also using
functional programming).
9. For a number 𝑛, a proper divisor 𝑘 is a number which is not 𝑛 and which divides
𝑛. For example, {1, 3, 5} are all the proper divisors of 15. Consider the sum
of all the proper divisors of a number. Now consider the sum of all the proper
divisors of this new number and repeat the process. If one eventually obtains
the number which one started with, then this number is called a social number.
Write a program to show that 1264460 is a social number. Check whether 14316
is also a social number.
10. Using functional programming, demonstrate that for any sequence of positive
numbers 𝑎 1 , 𝑎 2 , . . . , 𝑎 𝑛 we have
1 1 1
𝑎1 + 𝑎2 + · · · + 𝑎 𝑛 + +···+ ≥ 𝑛2
𝑎1 𝑎2 𝑎𝑛
11. We call a pair of prime numbers 𝑝 and 𝑞 friends if 𝑝𝑞 and 𝑞 𝑝 are both prime (by
𝑝𝑞 we mean the juxtaposition of the numbers together). For example, the prime
numbers 563 and 587 are friends as 563587 and 587563 are both primes. Write
a program to produce all the prime friends for some suitable range of primes.
12. The number 𝜋 starts with
3.14159265358979323846264338327950288419716939937510582097494
45923078164062862089986280348253421170679 · · ·
and continues with no pattern. Search the first 100, 000 digits of 𝜋 and
check if your birthday appears as a sequence of digits in 𝜋. For exam-
4.2 Functional Programming: Anonymous (lambda) Functions 121
ple the date Thursday 8th of April of 1971 appears in the digits of 𝜋:
3.1415926535897932384626433832795028841971693 · · ·
13. Recall that if 𝑓 : 𝐴 → 𝐵 and 𝑔 : 𝐵 → 𝐶 are two functions, then 𝑔 𝑓 : 𝐴 → 𝐶
is defined by 𝑔 𝑓 (𝑎) = 𝑔( 𝑓 (𝑎)), where 𝑎 ∈ 𝐴. Explain what the following code
does and rewrite it by using only one map.
[2]: list(z)
14. Recall that map creates an iterator that will be executed once it is prompted. Run
the following code and explain how the map works.
[2]: 1
4
9
16
25
36
[3]: 64
81
Chapter 5
List Comprehension and Generators
[1]: list(range(10))
[1]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[2]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
{𝑖 | 0 ≤ 𝑖 ≤ 9}.
[3]: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
© The Author(s), under exclusive license to Springer Nature Switzerland AG 2023 123
R. Hazrat, A Course in Python, Springer Undergraduate Mathematics Series,
https://doi.org/10.1007/978-3-031-49780-3_5
124 5 List Comprehension and Generators
As the first example, we generate the numbers 2𝑛 − 1, for 𝑛 between 1 and 10.
Mathematically, this is the set
{2𝑛 − 1 | 1 ≤ 𝑛 ≤ 10}.
Before we go further, let us give other alternatives for the same program via proce-
dural programming and functional programming, respectively.
[5]: L = []
for n in range(1, 11):
L.append(2**n - 1)
L
𝜋
Exercise 5.1 Generate the list of (𝑥, cos(𝑥)), for 𝑥 = 3 𝑖, where, {𝑖 | 0 ≤ 𝑖 ≤ 5}.
Solution
Mathematically, this is the set
n 𝜋 o
𝑥, cos( 𝑖) | 0 ≤ 𝑖 ≤ 5 .
3
Translating this directly via list comprehension (and rounding the results of cosine)
we have:
[7]: [(0, 1.0), (1, 0.5), (2, -0.5), (3, -1.0), (4, -0.5),
(5, 0.5)]
5.1 List Comprehension 125
In building up codes via list comprehension, we can introduce conditions inside the
list in a natural way. As a first example, we write a program to generate all numbers
between 1 and 50 which are divisible by 7.
Mathematically speaking, this is the set
𝑛 | 0 ≤ 𝑛 ≤ 50, and 7 | 𝑛 .
In Python, using list comprehension, we can create this list, in a similar manner
Building on the previous code, here is a list of numbers between 1 and 199 which
are divisible by 3 and by 5 but not by 7.
[9]: [15, 30, 45, 60, 75, 90, 120, 135, 150, 165, 180, 195]
[10]: [15, 30, 45, 60, 75, 90, 120, 135, 150, 165, 180, 195]
Notice how naturally one can incorporate the conditions inside the list comprehen-
sion.
Exercise 5.2 Suppose a list contains numbers and text. Write a code to extract all
the text from the list.
126 5 List Comprehension and Generators
Solution
We create the list and then use list comprehension to collect the data.
[15]: x_text
[17]: x_rest
Recall the notion of Pythagorean pairs (𝑚, 𝑛), i.e., pairs (𝑚, 𝑛) such that 𝑚 2 + 𝑛2 is
a square number. We use list comprehension to find Pythagorean pairs.
As (6, 8) and (8, 6) are the same pair for us, in order to avoid repetition, we can start
the second loop from the starting point of the first loop.
Once we have the basic code, it is easy to modify it and obtain interesting Pythagorean
pairs. For example, one can change the above code so that 𝑚 = 𝑛 + 10, so we are
looking for Pythagorean pairs (𝑚, 𝑚 + 10).
5.1 List Comprehension 127
One should be aware of the order of the loops created in the list comprehension.
The outer-loop is the one which should be completed first before the inner-loop runs
again. The following codes will clarify the difference:
[23]: L = []
for i in range(1, 4):
for j in ['a', 'b', 'c']:
L.append((i, j))
L
Exercise 5.3 Show that the only 𝑛 less than 1000 such that
3𝑛 + 4𝑛 + · · · + (𝑛 + 2) 𝑛 = (𝑛 + 3) 𝑛
Solution
Recall the function sum gives the sum of the entries of a list.
[24]: 2
3
We can incorporate the above code into one long (nested) list comprehension. We
leave it to the reader to judge which code is easier to read and understand.
[25]: [2, 3]
Solution
Here is the code:
[26]: [4, 1, 10, 2, 16, 3, 22, 4, 28, 5, 34, 6, 40, 7, 46, 8, 52,
↩→9, 58]
Exercise 5.5 Find the first 5 positive integers 𝑛 such that 𝑛6 + 1091 is prime. Show
that all these 𝑛 are between 3500 and 8500.
Solution
We will be using the isprime function available in sympy to test the numbers.
5.1 List Comprehension 129
Since the question gives a clue how far one needs to go, we create a list
range(1,10000) and check among 𝑛 in this list when 𝑛6 + 1091 is prime.
If one didn’t know how far one needs to go to get all five positive 𝑛 such that 𝑛6 +1091
is prime, one option would have been to create a while loop.
[29]: count = 0
n = 0
while count < 5:
n += 1
if isprime(n**6 + 1091):
print(n)
count += 1
[29]: 3906
4620
5166
5376
5460
Exercise 5.6 Determine all the positive integers 𝑛 between 3 and 50 for which 22008
is divisible by
𝑛 𝑛 𝑛
1+ + + .
1 2 3
Solution
Recall that the binomial coefficient (the number of ways of choosing 𝑚-items among
𝑛-items) is
𝑛 𝑛!
:= .
𝑚 𝑚!(𝑛 − 𝑚)!
Both the factorial and the binomial coefficient comb are available in the math library.
[31]: True
Find all the integers 𝑚 less than 500 such that 𝐴 is an integer. Show that 𝐴 is always
odd.
Solution
We define the function 𝐴(𝑚) and then compose a list comprehension to find 𝑚’s
such that 𝐴(𝑚) is an integer
Here is yet another way to write the code using functional programming.
[35]: [351, 357, 369, 378, 381, 383, 392, 395, 398]
Solution
The all() function returns True if all items in an iterable are true, otherwise
it returns False. The tuple comprehension (x % k for k in range(2, √ 1 +
math.isqrt(x)) checks if the number 𝑥 is divisible by numbers up to 𝑥. If there
is an instance when 𝑥 is indeed divisible, then x % k returns 0. Within Boolean
statements, all positive numbers represent True and zero represent False. Thus
if there is one number dividing 𝑥, using all the combinations of Boolean outputs
inside the tuple comprehension is False, otherwise the result is True meaning no
number divides 𝑥, i.e., 𝑥 is a prime number. The next list comprehension in the code
uses this prime test to find prime numbers of the form of 𝑛2 +𝑛+1 for 350 ≤ 𝑛 ≤ 400.
5.2.1 Sets
To handle a collection of data, we have worked with list and tuple. With list
we can collect data, access the elements, and modify the list by adding or deleting
items, i.e. they are mutable objects. tuples are immutable but we can still access
their elements.
Next we introduce two more objects for handling data, sets and dictionaries.
The concept of sets in Python is the closest to the notion of sets in mathematics: a
collection of data where order and duplications of members does not change the set.
[36]: X = {1, 2, 3, 2, 1}
[37]: X
[37]: {1, 2, 3}
[38]: True
[39]: True
Compare the above with the examples of lists in Chapter 2, where we get False in
all cases.
Mathematical operations on sets such as union, intersection, and complements are
available within set objects. We are going to demonstrate these methods with two
sets; the set of prime numbers smaller than 20 and the set of odd numbers up to 20.
𝑋 ∩ 𝑌 = 𝑥 | 𝑥 ∈ 𝐴 and 𝑥 ∈ 𝐵 .
[42]: X.union(Y)
[43]: X.union(['a','b'])
Note that union is a method belonging to the set object. However this method does
not change the object.
[44]: X
[45]: X | Y == X.union(Y)
[45]: True
[46]: True
5.2 Sets and Dictionaries 133
The operation
𝑋 \𝑌 = 𝑥 ∈ 𝑋 |𝑥 ∉ 𝑌
[47]: X - Y
[47]: {2}
[48]: X.difference(Y)
[48]: {2}
[49]: X
[50]: X.symmetric_difference(Y)
[51]: XˆY
[52]: Y - X
[53]: X - Y
[53]: {2}
[54]: True
For the empty set, use set() as { } is reserved for the empty dictionary, which we
will study later.
134 5 List Comprehension and Generators
[55]: X = set(); X
[55]: set()
[56]: X.union({2})
[56]: {2}
Note that one cannot access items in a set by referring to an index (as we do for lists
or tuples), but we can loop through the set items using a for loop, or ask if a specified
value is present in a set, by using the in keyword.
Solution
We will use a set to collect the remainders as we are only interested in the actual
remainders and not how many of them we obtain when dividing 𝑛2 + 41 by 7.
[57]: A = set()
for n in range(100):
A = A | {(n**2 + n + 41) % 7}
print(A)
[57]: {1, 4, 5, 6}
[58]: A = set()
for n in range(100):
A |= {(n**2 + n + 41) % 7}
print(A)
[58]: {1, 4, 5, 6}
[59]: A = set()
for n in range(100):
A = A.union({(n**2 + n + 41) % 7})
print(A)
[59]: {1, 4, 5, 6}
Recall the list comprehension approach to programming. We can use “set compre-
hension” to do the job.
5.2 Sets and Dictionaries 135
[61]: print(A)
[61]: [6, 1, 5, 4, 5, 1, 6, 6, 1, 5, 4, 5, 1, 6, 6, 1, 5, 4, 5, 1,
6, 6, 1, 5, 4, 5, 1, 6, 6, 1, 5, 4, 5, 1, 6, 6, 1, 5, 4, 5,
1, 6, 6, 1, 5, 4, 5, 1, 6, 6, 1, 5, 4, 5, 1, 6, 6, 1, 5, 4,
5, 1, 6, 6, 1, 5, 4, 5, 1, 6, 6, 1, 5, 4, 5, 1, 6, 6, 1, 5,
4, 5, 1, 6, 6, 1, 5, 4, 5, 1, 6, 6, 1, 5, 4, 5, 1, 6, 6, 1,
5]
[63]: print(B)
[63]: {1, 4, 5, 6}
Exercise 5.10 Consider the following sets 𝑈, 𝐴 and 𝐵 and find the shaded areas in
the pictures.
U = {-34, 23, 50, 'cat', 'dog', 'mouse', 'food',
'one','sydney'}
A = {'cat', 'dog', 50, 'sydney', 'food'}
B = {'dog', 23, -34, 50, 'food'}
[64]:
136 5 List Comprehension and Generators
Solution
We first define these sets.
To obtain (a):
[68]: U - B
To obtain (b):
[69]: A - B
[70]: (A - B) | (B - A)
[71]: A.symmetric_difference(B)
[72]: B.symmetric_difference(A)
To obtain (d):
[73]: AˆB
[74]: BˆA
[75]: U - (AˆB)
[76]: U - A.symmetric_difference(B)
( 𝐴 ∪ 𝐵) 𝑐 = 𝐴𝑐 ∩ 𝐵 𝑐
Solution
We only need to translate this to Python:
[77]: U - (A | B) == (U - A) & (U - B)
[77]: True
Exercise 5.12 Demonstrate the following identities with the example above
𝐴 ∩ ( 𝐴 ∪ 𝐵) = 𝐴
( 𝐴 ∩ 𝐵) ∪ ( 𝐴 ∪ 𝐵 𝑐 ) 𝑐 = 𝐵
Solution
Again, we need to translate these identities into Python:
[78]: A & (A | B) == A
[78]: True
[79]: True
138 5 List Comprehension and Generators
Exercise 5.13 How many different words are used in the The Book of Genesis! Find
the longest words in the book.
Solution
Recall the library nltk. We upload the library and get the entire text of Genesis.
Then using set we get rid of the repeated words.
[81]: text3
[82]: len(text3)
[82]: 44764
There are about 44 thousand words. But as this is a list, many of the words have been
repeated. Using set we can drop the repeated words.
[84]: len(g_words)
[84]: 2789
5.2.2 Dictionaries
Dictionaries are another way to collect and organise data. They come in pairs, a key
and values associated to the key. Both keys and values are objects. However keys are
unmutable objects whereas values can be mutable. We start by defining a dictionary
which consists of four keys, A, B, C, D and the values associated to them. The
example shows how to access the values and how to add more data to the dictionary.
[88]: score['A']
[88]: 100
[89]: score['C']
[89]: 50
[91]: score
[91]: {'A': 100, 'B': 70, 'C': 50, 'F': 0, 'G': 'back'}
[93]: score
[93]: {'A': 100, 'B': 70, 'C': 50, 'F': 0, 'G': 'back', 'm range':
↩→[49, 48, 47, 46]}
[94]: score.keys()
[95]: score.values()
140 5 List Comprehension and Generators
[95]: dict values([100, 70, 50, 0, 'back', [49, 48, 47, 46]])
In fact, the .keys() is not needed above, since iterating over a mapping is the same as
iterating over its keys.
[98]: True
[99]: 70 in score
[99]: False
[101]: score
[101]: {'A': 100, 'B': 70, 'C': 50, 'G': 'back', 'm range': [49, 48,
↩→47, 46]}
[102]: score.pop('m_range')
[103]: score
[105]: score
[105]: {'A': 100, 'B': 70, 'C': 50, 'G': 'back', 'check': 34,
↩→'uncheck': -30}
5.2 Sets and Dictionaries 141
One way to build a dictionary from two lists is to use the method zip. If 𝑥 =
(𝑥1 , 𝑥2 , . . . , 𝑥 𝑛 ) and 𝑦 = (𝑦 1 , 𝑦 2 , . . . , 𝑦 𝑛 ), then zip(x,y) will pair elements of 𝑥 and
𝑦.
Now we can build a dictionary. Here are three different ways to do this:
[108]: L={}
for key, value in zip(x,y):
L[key] = value
Oftentimes we need to return a certain value if the key we are looking for is not in
the list. In this case we can use get with a default value.
[111]: L
[112]: L['b']
[112]: 2
[113]: L.get('b')
142 5 List Comprehension and Generators
[113]: 2
[115]: L
Finally, once we get access to a value in the dictionary, we can use all the methods
available to that particular object.
[117]: L
[118]: L['a'].append('Mori')
[119]: L
Exercise 5.14 Write a program to create a dictionary with two keys even and odd
and collect even and odd numbers from 1 to 20 into the corresponding keys.
Solution
First, we define a dictionary, consisting of two keys, even and odd, each having the
value of an empty list. We then go through the numbers adding even numbers to the
even key and odd numbers to the odd key.
Godfrey Hardy, a very prominent English mathematician who was also a bit ec-
centric, invited the Indian mathematician Srinivasa Ramanujan to go to Cambridge.
Ramanujan became an exceptional collaborator of Hardy. Hardy recalled:
I remember once going to see him when he was ill at Putney. I had ridden in taxi
cab number 1729 and remarked that the number seemed to me rather a dull one,
and that I hoped it was not an unfavourable omen. “No,” Ramanujan replied, “it is
a very interesting number; it is the smallest number expressible as the sum of two
cubes in two different ways.
Exercise 5.15 Write a code to find the smallest number expressible as the sum of two
cubes in two different ways.
Solution
We will be using dictionary to find the smallest number. The code consists of
two dictionaries: cube and rama. In cube the keys are positive integers and the
values are their cubes. We add to the cube more keys and their values as long as
needed, until we find the desired number. For rama, the keys are the sums of cubes
𝑏 3 +𝑎 3 and the associated value is (𝑏, 𝑎). We generate the dictionary rama as follows:
Now each time we create a new sum of cubes 𝑦 3 + 𝑥 3 , we first check if this has
already appeared in rama by asking cube(y)+cube(x) in rama. If this is not the
case then we add it to rama, namely rama[cube(y)+cube(x)]=(y,x). However,
if it is the case that cube(y)+cube(x) is already in rama, then there is a key
cube(b)+cube(a) which matches this. Thus 𝑦 3 + 𝑥 3 = 𝑏 3 + 𝑎 3 . In this case we can
print both the key and also the pairs (𝑦, 𝑥) and (𝑏, 𝑎) and we have completed the
code.
Here is the actual code:
144 5 List Comprehension and Generators
As an further exercise, modify the above code to find more numbers that can be
expressible as the sum of two cubes in two different ways.
5.3 Generators
A generator is one of those tools in Python that allows us to write codes that can
handle a very large number of items, even when we don’t know a priori how many
items we are dealing with. In fact we can handle infinity itself!
When we define a list, the sequence of items in the list are all already cooked and
ready and can be used right away, whereas with generators, the items in the sequence
will be, well, generated upon request. The following examples make this clear:
[124]: l
[124]: [0, 1, 32, 243, 1024, 3125, 7776, 16807, 32768, 59049]
All the elements of the list l have now been generated. l contains 10 elements. l
can be used as many times as one would like. Now as for a generator:
Notice the difference between the definition of l and g. In g, where the range goes
all the way up to 10 million, Python does not create the list of 10 million numbers!
(it might not even make sense to do so!) The definition of g is there and when it is
needed we can prompt Python to generate as many elements of g as needed.
[126]: [0, 1, 32, 243, 1024, 3125, 7776, 16807, 32768, 59049]
The function next prompts Python to generate the next item of the generator. It
also keeps track of where the item in the sequence is located. Therefore if we call
next(g) ten times, it will create the first 10 numbers in the sequence and stop there.
The beauty of this is, Python still knows where the position of the iterator is, so next
time when we prompt g, it starts from there.
Exercise 5.16 Find the first 5 positive integers 𝑛 such that 𝑛6 + 1091 is prime.
Solution
We have seen this exercise before. The exercise previously also told us that the first
five 𝑛’s such that 𝑛6 + 1091 is prime are between 3500 and 8500. Therefore we knew
from the outset how far we needed to increase 𝑛. If we didn’t know the bound for
𝑛, then the notion of generators allows us to write a one-line code for this exercise.
Since the generators run the code upon request, we can define a tuple comprehension,
allowing for 𝑛 to go all the way to a very large number, say 1 million, checking if
𝑛6 + 1091 is prime. Once this generator is defined, we prompt it five times, so it will
give us the first five 𝑛, as wanted.
Note that at the moment x is a generator and we need to prompt it so that we get the
first 𝑛 such that 𝑛6 + 1091 is prime.
In order to obtain the first five items in one go, we could use a method from the
itertools library.
list(itertools.repeat(next(x), 5))
146 5 List Comprehension and Generators
Or we could use another method, also available in the wonderful itertools library.
list(itertools.islice(x, 5))
Note that since the pointer was at the fifth prime, when we prompt the generator x
again, it would give us the next 5 primes generated by the formula 𝑛6 + 1091.
Exercise 5.17 Find the smallest multiple of 99999 that contains no 9’s amongst its
digits.
Solution
We have seen this exercise before, where we used a while-loop to get the answer.
Here we use the concept of generators to approach the problem.
[133]: 1111188888
The library itertools provides very interesting methods to compose codes with.
We will look at one of them, takewhile, and encourage the reader to explore the
others.
5.3 Generators 147
[137]: 'In the beginning God created the heaven and the earth . And
the earth was without form , and void ; and darkness was upon
the face of the deep . And the Spirit of God moved upon the
face of the waters . And God said , Let there be light : and
there was light . And God saw the light , that it was good :
and God divided the light from the darkness . And God called
the light Day , and the darkness he called Night . And the
evening and the'
The method takewhile(cond, iter) goes through the iter list until the condi-
tion cond fails.
We can define a generator via functions, the so-called generator functions. We de-
scribe the idea with an example. Recall the function which returns the 𝑛-th Fibonacci
number.
[139]: fibs(10)
[139]: 55
Notice that in the generator function, we have replaced return by yield and the
yield is located within the loop. There is a substantial difference between fibs and
gfibs. When we run fibs(1000) Python calculates the 1000-th Fibonacci number
and returns it, whereas gfibs(1000) just defines such a generator. Each time we call
this function, the variable runs one time inside the loop, the next Fibonacci number
in the sequence is generated, and yield keeps this value and the generator stops
there. If we call the function again, we will get the next number in the list. So the code
fibs(1000000) might mess up the computer memory whereas gfibs(1000000)
is in standby and we can access the Fibonacci numbers up to 1000000 when needed.
[141]: d = gfibs(10000000)
[143]: [89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]
Here is an amusing example showing how the generator functions keeps track of
each call.
for i in gene(5):
print(i)
Solution
Here is the code, defining a function with ‘yield’, which gives a generator.
Exercise 5.19 About 110 years ago, in 1914, Ramanujan came up with the following
formula for 1/𝜋. He always said the ideas for these types of formulas were divinely
inspired, and a glance at the formula makes one believe it!
√ ∑︁ ∞
1 8 (4𝑛)! 1103 + 26390𝑛
= 2
𝜋 99 𝑛=0 (4𝑛 𝑛!) 4 994𝑛
Write a program to see how many terms of the sum in the right-hand side are needed
to approximate 1/𝜋 up to 17-significant digits.
Solution
Notice that Ramanujan’s formula consists of an infinite sum. Of course we can’t let
a program loop infinitely many times, but what we can do is go as far as needed to
get the desired approximation. The generator count does just that.
def Ram1914(n):
a = (1103 + 26390 * n) / (99**(4 * n))
b = factorial(4 * n)/(4**n * factorial(n))**4
150 5 List Comprehension and Generators
return a * b
s = 0
c = sqrt(8) / 99**2
for i in count():
s += Ram1914(i)
if abs(c * s - 1 / pi) < 0.00000000000000001:
print(f'Number of iterations: {i+1}, Ramanujan
↩→formula: {c * s} and 1/pi: {1/pi}')
break
It is remarkable that with just three iterations we can get so close to 1/𝜋.
Problems
(1 + 2 + 3 + · · · + 𝑛) 2 = (13 + 23 + 33 + · · · + 𝑛3 ).
Note: Since we haven’t yet worked with symbolic computations in Python, one
approach is to check that the above identity holds for various values for 𝑛.
2. Consider the following series:
1×33×55×7
··· .
2×24×46×6
(2𝑛 − 1)(2𝑛 + 1)
.
2𝑛 × 2𝑛
1
𝑓 (𝑐 1 , 𝑐 2 , . . . , 𝑐 𝑚 ) = 𝑐 1 −
2
𝑐2 −
𝑚−1
···− .
𝑐𝑚
5.3 Generators 151
Note that if a denominator happens to become zero, the program should give a
warning and stop the execution.
4. Investigate if
√
√︃
√ √︁ √ √︁
2 2 2+ 2 2+ 2+ 2
= ···
𝜋 2 2 2
5. An integer 𝑑 𝑛 𝑑 𝑛−1 𝑑 𝑛−2 . . . 𝑑1 is palindromic if
(for example 15651). Write a code to ask for a number 𝑑 𝑛 𝑑 𝑛−1 𝑑 𝑛−2 . . . 𝑑1 and
find out if it is palindromic. Enhance the code further so that if the number is not
palindromic then the code tests whether 𝑑 𝑛 𝑑 𝑛−1 𝑑 𝑛−2 . . . 𝑑1 + 𝑑1 𝑑2 . . . 𝑑 𝑛−1 𝑑 𝑛
is (for example, 108+801=909). Furthermore, write a code to give the number
of times needed to repeat this procedure until one gets a palindromic number,
starting with 𝑑 𝑛 𝑑 𝑛−1 𝑑 𝑛−2 . . . 𝑑1 (if it takes more than 150 times, let the function
return infinity).
6. Write a code to check for every integer 𝑛 that the following inequality holds:
√ √4 √8 √
2𝑛
2 4 8 · · · 2𝑛 ≤ 𝑛 + 1.
7. A happy number is a number such that if one squares its digits and adds them
together, and then takes the result and squares its digits and adds them together
again, and so on, repeating this process, then one eventually reaches the number
1. Find all the happy ages, i.e., happy numbers up to 100.
8. Consider a positive integer. Sort the decimal digits of this number in ascend-
ing and descending order. Calculate the difference of these two numbers (for
example, starting from 5742, we get 7542 − 2457 = 5085). This is called the
Kaprekar routine. First check that starting with any 4-digit number and repeating
the Kaprekar routine, you always reach either 0 or 6174. Then find out, among
all the 4-digit numbers, what is the maximum number of iterations needed in
order to get to 6174.
9. Explain what the following code does:
[1]: m = [('a', 'b', 'c'), ('d', 'e', 'f'), ('g', 'h', 'k')]
152 5 List Comprehension and Generators
11. We asked ChatGPT to write a code to find Ramanujan’s cab number, namely
the smallest positive integer that can be written as a sum of two cubes in two
different way. The code given is as follows. It does not generate the result, i.e.,
1729. Where does the code go wrong?
if current_sum in sums_of_cubes:
# Check if the numbers are different to
↩→find two different ways
if sums_of_cubes[current_sum] != num:
# Update the smallest sum if a
↩→smaller one is found
return smallest_sum
result = find_smallest_sum_of_two_cubed()
print(f"The smallest number that can be written as a sum
↩→of two cubes in two different ways is: {result}")
One of the abilities of Python, via its library sympy, is to handle symbolic computa-
tions, i.e., Python can comfortably work with symbols (we have seen some examples
of this already in Chapter 1). Working symbolically allows one to treat the situation
“abstractly” without assigning any value to the parameters. When it is needed, one
can then assign specific objects to the parameters. The sympy library also allows us
to do calculus symbolically, solve equations and calculate derivatives and integrals.
In this chapter we will look at some of these features.
As a first example, consider the expression (𝑥 + 𝑦) 2 . One can use Python to expand
this expression symbolically. All we need to do is to import the library sympy,
introduce the symbols x and y to the program, and then use the method expand.
[2]: x = symbols('x')
y = symbols('y')
[3]: x + y
[3]: 𝑥 + 𝑦
One can also introduce a sequence of symbols
[5]: 𝑥 2 + 𝑦 2
As one can see, Python can now handle the symbols x and y.
© The Author(s), under exclusive license to Springer Nature Switzerland AG 2023 153
R. Hazrat, A Course in Python, Springer Undergraduate Mathematics Series,
https://doi.org/10.1007/978-3-031-49780-3_6
154 6 The sympy Library
[6]: (x + y)**2
[6]: (𝑥 + 𝑦) 2
[7]: 𝑥 2 + 2𝑥𝑦 + 𝑦 2
2 2
+ 2
4𝑥 − 4𝑥𝑦 + 𝑦 4𝑥 − 4𝑥𝑦 + 𝑦 2
The method factor can do the inverse of expand, namely factorise an expression.
= 𝑥 8 (𝑥 2 + 𝑥 + 1) − 𝑥 7 (𝑥 2 + 𝑥 + 1) + 𝑥 5 (𝑥 2 + 𝑥 + 1) − 𝑥 4 (𝑥 2 + 𝑥 + 1)
+ 𝑥 3 (𝑥 2 + 𝑥 + 1) − 𝑥(𝑥 2 + 𝑥 + 1) + 𝑥 2 + 𝑥 + 1
= (𝑥 2 + 𝑥 + 1) (𝑥 8 − 𝑥 7 + 𝑥 5 − 𝑥 4 + 𝑥 3 − 𝑥 + 1)
Solution
The only challenge is to translate the expression correctly into Python.
[12]:
2 𝑥 2 + 1 𝑥 4 + 14𝑥 2 + 1 𝑥 8 + 44𝑥 6 + 166𝑥 4 + 44𝑥 2 + 1
𝑥 16 + 376𝑥 14 + 4380𝑥 12 + 15944𝑥 10 + 24134𝑥 8 + 15944𝑥 6 + 4380𝑥 4 + 376𝑥 2 + 1
Exercise 6.2 Prove that the product of four consecutive numbers plus one is always
a square number.
Solution
Suppose the number is 𝑛. Then we are looking at 𝑛(𝑛 + 1)(𝑛 + 2)(𝑛 + 3) + 1. We ask
Python to factorise this expression
[13]: n = symbols('n')
[14]: factor(n * (n + 1) * (n + 2) * (n + 3) + 1)
2
[14]: 𝑛2 + 3𝑛 + 1
The result clearly shows this expression is a square number. The proof is complete!
156 6 The sympy Library
Solution
1 1 1
If we define 𝑓 (𝑥) = 1+𝑥 then 𝑓 ( 𝑓 (𝑥)) = 1
1+ 1+𝑥
and 𝑓 ( 𝑓 ( 𝑓 (𝑥))) = 1+ 1 . This
1+ 1
1+𝑥
shows a way to capture the left-hand side of the above equality without going through
the pain of typing it.
x = symbols('x')
left_side = f(f(f(f(x))))
[16]: left_side
[16]: 1
1
1+ 1+ 1
1+ 1
𝑥+1
Next, in order to work with this expression, we import the method simplify from
the sympy library. This function will try to, well, simplify the expressions as much
as possible.
simplify(left_side)
[17]: 2𝑥 + 3
3𝑥 + 5
[18]: True
Exercise 6.4 Recall that if one wants to prove, by mathematical induction, that a
statement 𝑃(𝑛) is valid for all natural numbers 𝑛, one needs first to check 𝑃(1) is
valid, and then assuming 𝑃(𝑘) is correct, prove that 𝑃(𝑘 + 1) is also valid.
Using symbolic Python and mathematical induction, prove the following identities:
6.1 sympy, Symbolic Python 157
𝑛(𝑛 + 1)
1+2+···+𝑛 =
2
𝑛(𝑛 + 1)(2𝑛 + 1)
12 + 22 + · · · + 𝑛2 = .
6
Solution
We prove the first identity. Define a function as follows:
[20]: g(1)
[20]: 1.0
Now we suppose it is correct for 𝑘 and check that it is also valid for 𝑘 + 1. Thus we
assume
𝑘 (𝑘 + 1)
1+2+···+ 𝑘 = = 𝑔(𝑘)
2
and we need to show that
We ask symbolic Python for help, checking for equality of these two expression
k = sympy.Symbol('k')
(g(k) + k + 1).equals(g(k + 1))
[21]: True
Thus we have proved, by mathematical induction, that the first identity is valid. Here
is the code for the second identity.
f(1)
[22]: 1.0
158 6 The sympy Library
n = sympy.Symbol('n')
(f(n) + (n + 1)**2).equals(f(n + 1))
[23]: True
Here we will showcase the graphical abilities of sympy with various interesting
examples. We start by defining the function 𝑓 (𝑥) = sin(𝑥)/𝑥 and plot its graph
between −10𝜋 and 10𝜋.
def f(x):
return sin(x) / x
x = symbols('x')
6.2 Graphics in sympy 159
[25]: f(x)
[26]: f(x**2)
[26]: sin 𝑥 2
𝑥2
[27]: plot(f(x));
[27]:
If we don’t determine the range, as in the above example, Python uses its own default
range (between −10, and 10.
[28]:
[29]:
[31]: s
Recall that *s will unpack the list into a sequence and thus we can get the plots of
all the functions in the list s in one go.
[32]: plot(*s);
[32]:
Exercise 6.5 Define the function 𝑓 (𝑥) = ||𝑥| − 1| and plot 𝑓 (𝑥), 𝑓 ( 𝑓 (𝑥)) and
𝑓 ( 𝑓 ( 𝑓 (𝑥))). The absolute value function | | is defined as Abs in Python’s sympy
module.
Solution
Let us understand this function. The absolute value of 𝑥 is defined to be −𝑥 if 𝑥 is
negative and 𝑥 otherwise. We get:
𝑥−1 if 𝑥≥1
−𝑥 + 1
if 0≤𝑥<1
𝑓 (𝑥) =
𝑥+1 if −1 ≤ 𝑥 < 0
−𝑥 − 1
if 𝑥 ≤ −1.
6.2 Graphics in sympy 161
The reader might imagine that for 𝑓 ( 𝑓 (𝑥)) one should consider several other cases.
Once we have defined 𝑓 (𝑥), we can ask Python to plot them for us.
def f(x):
return Abs(Abs(x) - 1)
x = symbols('x')
plot(f(x))
plot(f(f(x)))
plot(f(f(f(x))));
[33]:
t = symbols('t')
[34]:
Exercise 6.6 Draw the butterfly curve, discovered by Temple H. Fay, given by
𝑥(𝑡) = sin(𝑡) ecos(𝑡 ) − 2 cos(4𝑡) − sin5 (𝑡/12) ,
𝑦(𝑡) = cos(𝑡) ecos(𝑡 ) − 2 cos(4𝑡) − sin5 (𝑡/12) .
Solution
Clearly, the coordinates (𝑥, 𝑦) in the equation depend on a variable 𝑡. Thus we need
to use plot parametric to handle this equation. We define functions 𝑥(𝑡) and 𝑦(𝑡),
and once we have these ready we pass them into plot parametric; sympy can
handle the rest.
t = symbols('t')
def x(t):
return sin(t) * (exp(cos(t)) - 2 * cos(4 * t) - sin(t/
↩→12)**5)
def y(t):
return cos(t) * (exp(cos(t)) - 2 * cos(4*t) - sin(t/
↩→12)**5)
[35]:
Next we find the graph of all the points (𝑥, 𝑦) which satisfy the equation
𝑥 4 − (𝑥 2 − 𝑦 2 ) = 0. For such equations, one uses plot implicit as follows:
x, y = symbols('x y')
[36]:
2𝑦 3 + 𝑦 2 − 𝑦 5 = 𝑥 4 − 2𝑥 3 + 𝑥 2 .
Solution
Can you see the bouncing wagon in the picture?
x, y = symbols('x y')
164 6 The sympy Library
[37]:
We can plot several graphs on the same 𝑥-𝑦-plane. For example, we would like to
know how many pairs of real numbers (𝑥, 𝑦) satisfy the system of equations
2 − 𝑥3 = 𝑦
2 − 𝑦 3 = 𝑥 + sin(𝑦).
By plotting the graphs of these two equations, we should be able to deduce the
number of solutions.
[38]: p1 = plot_implicit(Eq(2 - x**3, y), (x, -5, 5), (y, -5, 5),
↩→line_color='blue', show=False);
p1.append(p2[0])
p1.show()
[38]:
Note that here 𝑝1 and 𝑝2 are two objects. With show=False, we initially don’t plot
the graph. We create the object and then add the second one to the first one and then
plot this new object.
To obtain the region of all points (𝑥, 𝑦) which satisfy the inequality 𝑥 4 + (𝑥 −2𝑦 2 ) > 0,
we can use plot implicit.
6.3 Three-Dimensional Graphs 165
[39]:
In a similar manner, one can define functions of several variables. The chart below
shows what command one needs to choose to plot functions defined in different
formats.
Three-dimensional
mathematical Concrete example
function of 3-dimensional function sympy method
𝑦 = 𝑓 (𝑥, 𝑦) 𝑦 = sin(𝑧 2 + 𝑦 2 )e −𝑥 2 plot3d
𝑥 = 𝑓 (𝑡),
𝑥 = sin(3𝑡),
𝑦 = 𝑔(𝑡), 𝑦 = cos(4𝑡), plot3d parametric line
𝑧 = ℎ(𝑡)
𝑧 = sin(5𝑡)
𝑥 = 𝑓 (𝑡, 𝑢),
𝑥 = sin(3𝑡),
𝑦 = 𝑔(𝑡, 𝑢), 𝑦 = cos(4𝑡), plot3d parametric surface
𝑧 = ℎ(𝑡, 𝑢)
𝑧 = sin(5𝑡)
√︁
Here is a simple example defining 𝑓 (𝑥, 𝑦) = 𝑥2 + 𝑦2
f(x, y)
√︃
[40]: 𝑥2 − 𝑦2
166 6 The sympy Library
Once the function is defined, it is very easy to plot its graph via the sympy library.
plot3d(f(x, y));
[41]:
[42]:
Note that, for two-dimensional equations, such as √︁𝑓 (𝑥) = 𝑥 + sin(𝑥), one uses plot
whereas for three-dimensional equations, such as 𝑥 2 + 𝑦 2 , one uses plot3d.
Solution
We first translate the formula into Python and use plot3d within sympy to create
the graph.
6.3 Three-Dimensional Graphs 167
x, y = symbols('x y')
[43]:
This is not the picture we were expecting. Restricting the range as explicitly men-
tioned in the exercise we get:
[44]:
Exercise 6.9 Define the function 𝑓 (𝑥, 𝑦) = ||𝑥| − |𝑦|| and plot its graph for −10 ≤
𝑥, 𝑦 ≤ 10.
Solution
For plotting a function with two variables we can use the function plot3d of the
sympy.plotting module.
168 6 The sympy Library
x, y = symbols('x y')
[45]:
Exercise 6.10 Plot the graph of 𝑓 (𝑥, 𝑦) = 𝑥𝑦 sin(𝑥 2 ) cos(𝑦 2 ) when −2𝜋 ≤ 𝑥 ≤ 0
and −2𝜋 ≤ 𝑦 ≤ 0.
Solution
In the previous exercise, we first defined a function 𝑓 (𝑥, 𝑦), and then used the function
within plot3d. Here we directly use the equation inside plot3d.
x, y = symbols('x y')
[46]:
𝑥 = sin(3𝑡),
𝑦 = cos(4𝑡),
𝑧 = sin(5𝑡).
t = symbols('t')
plot3d_parametric_line(sin(3*t), cos(4*t), sin(5*t), (t, -pi,
↩→pi));
[47]:
[48]: t = symbols('t')
plot3d_parametric_line(sin(t), cos(t), t, (t, -10*pi,
↩→10*pi));
170 6 The sympy Library
[48]:
Solving equations and finding roots for different types of equations and relations are
one of the main endeavours of mathematics. For polynomials with one variable, i.e.,
of the form 𝑎 𝑛 𝑥 𝑛 + 𝑎 𝑛−1 𝑥 𝑛−1 + · · · + 𝑎 1 𝑥 + 𝑎 0 , it has been proved that there is no
formula for finding the roots when 𝑛 ≥ 5 (in fact, when 𝑛 = 3 or 4, the formulas
are not that pretty!). This forces us to find numerical ways to estimate the roots of
equations. However, if it is possible, we might be able to find the exact solutions to
an equation using sympy.
As a first example, we use sympy to find the solutions of the equation 𝑥 2 −3𝑥 −10 = 0.
[49]: [-2, 5]
[50]: {−2, 5}
Here we use the function solve, which is available in the sympy library. The second
argument in the function tells Python x is the variable of the equation. As the result
shows, the command solveset returns a set containing the solutions.
Plotting the graph of the polynomial 𝑥 2 − 3𝑥 − 10 confirms that −2 and 5 are the
roots of the equation 𝑥 2 − 3𝑥 − 10 = 0.
6.4 Calculus with sympy 171
[51]:
[52]:
[53]:
v
u
u √︃ √
3 113079𝑖
𝑖 t − 92 + 23 11
+ 2 47
u
√︃
3 47
√ − v
u √︃ √ 8 + 72
113079𝑖
t
3 47
3 8 +
113079𝑖
72 4 94 + √︂ 23
√ +2 8 + 72
3
3 47 + 113079𝑖
3 8 72
− +
4 2
√︄ √︃ √
9 23 3 113079𝑖
4 + √︃
3 47
√ + 2 478 + 72
113079𝑖
3 8 + 72
,
2
172 6 The sympy Library
v
u
u √︃ √
3 113079𝑖
𝑖 t − 92 + 23 11
+ 2 47
u
√︃
3 47
√ + v
u √︃ √ 8 + 72
113079𝑖
t
3 47
3 8 +
9 23 113079𝑖
72 4 4+ 3 √ +2 8 + 72
√︂
3 47 + 113079𝑖
3 8 72
− −
4 2
√︄ √︃ √
9 23 3 113079𝑖
4 + √︃
3 47
√ + 2 478 + 72
113079𝑖
3 8 + 72
,
2
√︄ √︃ √
9 23 3 113079𝑖
4 + √︃
3 47
√ + 2 478 + 72
113079𝑖
3 3 8 + 72
− +
4 2
v
u
u √︃ √
3 113079𝑖
𝑖 t − 92 + 23 11
+ 2 47
u
√︃
3 47
√ + v
u √︃ √ 8 + 72
113079𝑖
t
3 47
3 8 +
9 23 113079𝑖
72 4 4+ 3 √ +2 8 + 72
√︂
3 47 + 113079𝑖
8 72
,
2
√︄ √︃ √
9 23 3 113079𝑖
4 + √︃
3 47
√ + 2 478 + 72
113079𝑖
3 3 8 + 72
+ +
4 2
v
u
u √︃ √
3 113079𝑖
𝑖 t − 92 + 23 11
+ 2 47
u
√︃
3 47
√ − v
u √︃ √ 8 + 72
113079𝑖
t
3 47
3 8 +
9 23 113079𝑖
72 4 4+ 3 √ +2 8 + 72
√︂
3 47 + 113079𝑖
8 72
The reader can try for themselves to see solve cannot give any solutions to the
equation sin(𝑥) = 𝑥 − 1, although there is one real solution to this equation, as the
following graph shows.
plot(sin(x) - x + 1);
[54]:
The graph shows there is solution around 2. We could write a simple program to
estimate the root.
6.4 Calculus with sympy 173
x = 1.5
while sin(x) - x + 1 > 0.00001:
x += 0.001
Two important machineries in calculus are differentiation and integration, and both
use the concept of limit. We assume the reader is familiar with calculus. In the
problems below we showcase some of sympy’s abilities in this area.
We start with the limit of the function
cos(𝑥) − 1
lim .
𝑥→0 sin(𝑥)
sympy provides the command limit for exploring the limit of a function.
x = symbols('x')
limit((cos(x)-1)/sin(x), x, 0)
[56]: 0
The graph of the function confirms that, when 𝑥 tends to 0, the limit of this function
is indeed 0.
[57]:
We can also explicitly ask sympy to calculate the limit when the variable tends from
the right or left to a given value.
[58]: −∞
[59]: ∞
Solution
This limit tells us that, if the value of 𝑥 is very small, one can essentially replace
sin(𝑥) with 𝑥. We first plot the graph of the equation sin(𝑥)/𝑥 as well as the constant
line 1.
[60]:
This clearly shows that the constant line 1 touches (and is tangent to) the graph of
sin(𝑥)/𝑥 at 𝑥 = 0. We can confirm this with the sympy command limit.
6.4 Calculus with sympy 175
[61]: limit(sin(x)/x, x, 0)
[61]: 1
Next we look at derivatives and integrals. The following table gives an overview of
how sympy handles these operations.
One can use oo and -oo for ∞ and −∞ in the calculations above.
As a first example, we calculate
∫ ∞
e− 𝑥 d𝑥.
0
x= symbols('x')
[62]: 1
This means the area under the curve e−𝑥 from 0 to ∞ is exactly 1.
[63]:
176 6 The sympy Library
∫ 1∫ 1
cos(𝑥 2 + 𝑦 2 + 𝑥𝑦)d𝑥d𝑦.
−1 −1
Solution
We start from the top and work our way down. We need the methods integrate
and diff from the sympy library in order to handle the integration and derivation.
[65]: diff(sin(x)/x)
[66]: diff(sin(x)/x, x, 2)
2 cos ( 𝑥 ) 2 sin ( 𝑥 )
[66]: − sin (𝑥) − 𝑥 + 𝑥2
𝑥
[67]: diff(sin(x)/x, x, x)
2 cos ( 𝑥 ) 2 sin ( 𝑥 )
[67]: − sin (𝑥) − 𝑥 + 𝑥2
𝑥
To obtain an expression in the form 𝑝/𝑞, one can use the command cancel within
the library sympy. Recall that refers to the last output.
cancel(_)
6.4 Calculus with sympy 177
[69]: 𝑦 (𝑥𝑦 + 2) e 𝑥 𝑦
[71]: integrate(cos(x**2 + y**2 + x*y), (x, -1, 1), (y, -1, 1))
[71]:
√ √ ∫1 ! √ √ !
2 𝜋 3𝑦 2 2𝑦 2
× sin 𝑆 √ − √ d𝑦+
2 4 2 𝜋 𝜋
−1
∫1 ! √ √ !!
3𝑦 2 2𝑦 2
− sin 𝑆 √ + √ d𝑦+
4 2 𝜋 𝜋
−1
∫1 ! √ √ !
3𝑦 2 2𝑦 2
cos 𝐶 √ + √ d𝑦+
4 2 𝜋 𝜋
−1
∫1 ! √ √ !!
3𝑦 2 2𝑦 2
− cos 𝐶 √ − √ d𝑦
4 2 𝜋 𝜋
−1
[72]: round(_)
[72]: 3
Solution
We have seen examples of this type. The only thing to notice is we can use plot3d
to plot a sequence of functions, as the code shows.
178 6 The sympy Library
x, y = symbols('x y')
[73]:
𝑎𝑖 𝑗 = 𝑥 𝑖 + 𝑥 𝑗 + 𝑥 𝑖 𝑗 .
√
Symbolically compute the determinant of 𝐴? If 𝑥 = 2, what is the inverse of the
matrix 𝐴?
Solution
Note that the entries of the matrix contain the symbol 𝑥. Thus sympy is the library to
use here to define the function. We can pass the symbol 𝑥 inside the function m. This
function puts together the matrix. It consists of a nested loop, the first loop taking
care of the rows and the other loop putting together the entries of each row.
def m(x):
A = []
for i in range(1, 5):
row = []
for j in range(1, 5):
row += [x**i + x**j + x**(i*j)]
A += [row]
return Matrix(A)
6.4 Calculus with sympy 179
x = symbols('x')
A = m(x)
[74]: 3𝑥 2𝑥 2 + 𝑥 2𝑥 3 + 𝑥 2𝑥 4 + 𝑥
2𝑥 2 + 𝑥 𝑥 4 + 2𝑥 2 𝑥 6 + 𝑥 3 + 𝑥 2 𝑥 8 + 𝑥 4 + 𝑥 2
3
2𝑥 + 𝑥 𝑥 6 + 𝑥 3 + 𝑥 2 𝑥 9 + 2𝑥 3 𝑥 12 + 𝑥 4 + 𝑥 3
2𝑥 4 + 𝑥 𝑥 8 + 𝑥 4 + 𝑥 2 𝑥 12 + 𝑥 4 + 𝑥 3 𝑥 16 + 2𝑥 4
[75]: A.det()
[76]: factor(A.det())
[76]: 𝑥 11 (𝑥 − 1) 6 (𝑥 + 1) 2 𝑥 2 + 𝑥 + 1 3𝑥 9 − 𝑥 8 + 2𝑥 5 − 𝑥 2 − 𝑥 + 1
√
For the final part
√ of the exercise, to compute the inverse of the matrix when 𝑥 = 2,
we first pass 2 into the function m(x), and then use the method inv within this
object to compute its inverse. The computation is impressive and should not be lost
on the reader.
[78]: mat_inv
√ √ √ √
[78]: 1766118−1248857 √2 −480344+339683 √2 −730010+516175 2√ 172154−121729 2√
−5587390+3950878
√ 2 −5587390+3950878√2 −11174780+7901756
√ 2 −11174780+7901756 √ 2
−339683+240172 √2 −2165050+1531031 √2 181333−128264 2 √ 169553−119887 2 √
−3950878+2793695 −15803512+11174780 2 −15803512+11174780 2 −15803512+11174780
−25415+17965√2 2 √
8941−6304 2 √
√
55676−39385 2 √
√
−12698+8981 2 √
2
√
−388988+275054 −777976+550108 −1555952+1100216 −1555952+1100216
2251−1604√2 2 √ 2
3175−2206 2 √
√
−4816+3367 2√
2 √
−1786+1269 2√
2
√
−146656+103738 2 −293312+207476 2 −586624+414952 2 −586624+414952 2
[80]: mat
√ √ √ √
[80]: √3 2 2 + 4 √5 2 2 + 8
2+4 8 2 2 + 10 22
√
5 2 2√2 + 10 20√2 2√2 + 68
√ √
2+8 22 2 2 + 68 264
Next we multiply these two matrices; we are expecting the 4 × 4-identity matrix,
however we obtain a massive expression. We will not try to display the output here
180 6 The sympy Library
as it will extend beyond the margin (this is not Fermat’s comment!). But we will ask
Python to simplify the output.
[82]: simplify(_)
Problems
for 0 ≤ 𝑡 ≤ 14𝜋.
2. Plot the graph of
for 0 ≤ 𝑡 ≤ 14𝜋.
3. Consider the following function.
100
!
sin 2𝜋𝑘 2 𝑥
∑︁ 𝑥2
𝑓 (𝑥):= +
𝑘=1
4𝜋 2 𝑘 5 2𝑘
This function was suggested by Sungkon Chang as an example of a function
that looks quite “innocent” but whose derivatives behave quite wildly. Plot 𝑓 (𝑥),
𝑓 ′ (𝑥) and 𝑓 ′′ (𝑥) and observe this behaviour.
4. Consider the following functions of two variables
𝑥 𝑎1 𝑎2 · · · 𝑎 𝑛
𝑎2 · · · 𝑎 𝑛 ®
© ª
𝑎1 𝑥
𝑥 · · · 𝑎 𝑛 ® = (𝑥 + 𝑎 1 + · · · + 𝑎 𝑛 )(𝑥 − 𝑎 1 ) · · · (𝑥 − 𝑎 𝑛 )
®
det 𝑎 1 𝑎2
.. .. .. .. ®®
. . . . ®
« 𝑎1 𝑎2 𝑎3 · · · 𝑥 ¬
7. Write a function to check that, for any 𝑛, the following identity holds:
1 1 1 ··· 1 1
𝑎1 · · · 𝑎1
© ª
𝑏1 𝑎1 𝑎1 ®
𝑎2 · · · 𝑎2
®
det 𝑏 1 𝑏2 𝑎 2 ® = (𝑎 1 − 𝑏 1 )(𝑎 2 − 𝑏 2 ) · · · (𝑎 𝑛 − 𝑏 𝑛 )
.. .. .. .. .. ®®
. . . . . ®
« 𝑏1 𝑏2 𝑏3 · · · 𝑏 𝑛 𝑎𝑛 ¬
8. The Hilbert matrix is a square matrix whose element in row i and column j is
1
𝑖+ 𝑗 −1 . Construct the Hilbert matrix of order 6 (i.e. a 6 × 6-matrix) and compute
its determinant and its inverse. Try this for 7 × 7 and 8 × 8 matrices. Construct a
table which shows the determinant of Hilbert matrices of orders 1 through 10.
9. Construct a 10 × 10 upper triangular matrix 𝐴 (see Wikipedia for the definition
of such matrices) whose nonzero entries are random integers. Show that its
determinant is equal to the product of the entries on its main diagonal. Now
consider the transpose matrix 𝐴𝑡 and show that its determinant (det( 𝐴𝑡 )) is the
same as 𝐴. What is det( 𝐴 + 𝐴𝑡 )?
10. Investigate how many solutions the equation sin 𝑥 2 − cos 𝑥 3 = 0 has for
0 ≤ 𝑥 ≤ 𝜋.
11. Plot the graphs of the functions 2 exp(−𝑥 2 ) and cos(sin(𝑥) + cos(𝑥)) between
[−𝜋, 𝜋]. Investigate where they intersect.
12. Define the functions
182 6 The sympy Library
1
𝑓 (𝑡, 𝑎) = 2 + sin(𝑎𝑡),
2
sin(𝑏𝑡)
𝑔(𝑡, 𝑏, 𝑐) = cos 𝑡 + ,
𝑐
sin(𝑏𝑡)
ℎ(𝑡, 𝑏, 𝑐) = sin 𝑡 + .
𝑐
when 0 ≤ 𝑡 ≤ 2𝜋.
13. Let 𝑔(𝑥) = sin(𝑥) + cos(𝑥). Plot graphs of 𝑔(𝑔(𝑔(𝑔(𝑥)))) and 𝑔(𝑔(𝑔(𝑔(𝑔(𝑥)))))
for 𝑥 lying between 0 and 𝜋. There are four points where the graphs cross. Find,
numerically, their (𝑥, 𝑦) coordinates.
14. Let 𝐴 denote the 3 × 3 matrix
𝑥 + 𝑦 𝑥2 + 𝑦 𝑥3 + 𝑦
𝑥 + 𝑦2 𝑥2 + 𝑦2 𝑥3 + 𝑦2 ®
© ª
2 3 2 3 3 3
«𝑥 + 𝑦 𝑥 + 𝑦 𝑥 + 𝑦 ¬
(yes, the fact that the bottom left-hand entry does not fit the pattern of the rest
of the entries is intended!) and let 𝐵 denote its inverse. Show that the sum of the
entries in the first row of 𝐵 is 0. What is the sum of the entries in each of the
second and third rows? Also find the sum of the entries in each column of 𝐵.
15. Investigate and determine the values of 𝑥 between 0 ≤ 𝑥 ≤ 2𝜋 such that we have
the inequality
√︁ √︁ √
2 cos(𝑥) ≤ 1 + sin(2𝑥) − 1 − sin(2𝑥) ≤ 2.
Chapter 7
The numpy Library
In this chapter we will look at one of the most powerful and most used libraries of
Python, that is, numpy, which is used working with data. The library numpy allows
us to handle large lists of values; here a list is called an array. It provides a powerful
way to do arithmetic on arrays. We can use arrays to handle large collections of inputs,
and to handle matrices and thus linear algebra, one of the branches of mathematics
that has become vital in all aspects of computer science.
As is customary, we import numpy as the alias np.
[3]: x
[4]: list(x)
[6]: y
© The Author(s), under exclusive license to Springer Nature Switzerland AG 2023 183
R. Hazrat, A Course in Python, Springer Undergraduate Mathematics Series,
https://doi.org/10.1007/978-3-031-49780-3_7
184 7 The numpy Library
[8]: z
[10]: t
What we have done so far is to convert lists or tuples into numpy arrays. There are
functions within numpy which allow us to directly create arrays.
[12]: z
[13]: z.shape
[13]: (3, 6)
[15]: ar + ar
[16]: ra = ar * 2
[17]: ra
Notice in the line above the comparison is done for each entry and thus we get three
boolean True values.
Besides creating arrays in numpy from lists or tuples as we have done so far, there
are several functions within numpy which we can use to comfortably generate arrays.
The two main ones are arange and linspace. The following examples show their
scopes.
[20]: array([5., 5.3, 5.6, 5.9, 6.2, 6.5, 6.8, 7.1, 7.4, 7.7, 8.,
8.3, 8.6, 8.9, 9.2, 9.5, 9.8])
[21]: np.linspace(1, 4, 6)
This command is quite handy when plotting graphs; we divide the area of interest
using linspace, feed the corresponding values into the function and plot the result.
y = np.sin(x)
plt.plot(x, y);
[22]:
Here the sin function in the line y = np.sin(x) applies to each entry of the array
x. This is known as a vector calculation. We will see this method in more detail later
in this chapter and how it fits brilliantly with matplotlib.
Here are some more functions within numpy to generate arrays.
[23]: np.zeros(3)
[25]: z.shape
[25]: (3, 6)
[29]: x
[30]: x[0]
[30]: 1
[31]: x[-1]
[31]: 10
188 7 The numpy Library
[32]: x[2 : 6]
[33]: x[4 : ]
[34]: x[ : 7]
[36]: x
[38]: mat[ : ] = 0
[39]: mat
[41]: mat
[43]: mat
7.1 numpy, Numerical Python 189
[45]: mat
[47]: mat
[49]: mat
[51]: mat
[52]: xf = mat.astype(np.int64)
[53]: xf
123
𝐴 = 4 5 6®
© ª
«7 8 9¬
«7 8 9 ¬
Solution
First things first, we need to define the matrix using numpy’s array. This is a small
matrix and we could just enter the entries by hand.
[55]: x
Here is yet another way to define this matrix via list comprehension.
[57]: x = np.array(t)
[58]: x
7.1 numpy, Numerical Python 191
1 4 7
[60]: x[ : ,0]
[61]: x[ : ,1]
Here we ask Python to grab all the entries up to the second item from the array by :2,
so, [1, 2, 3], and [4, 5, 6]. Next by 1: we grab all the entries starting from 1,
so [2, 3], and [5, 6].
[64]: x
There is one difference between assigning values to np.array and lists. The
following example will show this:
[66]: x
[67]: y = x[3 : 8]
[68]: y
192 7 The numpy Library
[69]: y[ : ] = 999
[70]: y
[71]: x
[71]: array([ 5, 6, 7, 999, 999, 999, 999, 999, 13, 14, 15,
16])
This shows that when we specify y = x[3 : 8], then y is still pointing to the
portion of the same object as x. Thus changing y would change x as well.
To create a new array, one can use the method .copy().
[73]: z[ : ] = -1
[74]: z
[75]: x
[75]: array([ 5, 6, 7, 999, 999, 999, 999, 999, 13, 14, 15,
16])
This is the difference with lists. The example below shows the behaviour of lists.
[77]: t
[78]: z = t[3 : 8]
[79]: z
[81]: z
[82]: t
numpy has “all” the tools available for doing linear algebra, such as determinant,
eigenvalues, eigenvectors and many more.
One of the questions asked earlier when we were working with lists was the fol-
lowing: Given x = {𝑥1 , 𝑥2 , · · · , 𝑥 𝑛 } and y = {𝑦 1 , 𝑦 2 , · · · , 𝑦 𝑛 }, how can one produce
{𝑥 1 + 𝑦 1 , 𝑥2 + 𝑦 2 , · · · , 𝑥 𝑛 + 𝑦 𝑛 }?
[84]: [2 3 4 5 6]
[7 6 5 4 3]
[85]: x + y
[86]: x * y
It is known that matrix calculation is a tedious job. It will take well over 10 minutes
to multiply
2 −3 13 −4 8 11 34 −21 0 −43
12 1 −18 −4 2 ® 12 −33 9 −12 7 ®
© ª © ª
18 21 10 0 9 ® × 16 −7 −43 84 3 ®
® ®
8 −12 −4 0 −3 ® 4 9 12 −1 −54 ®
® ®
« 15 −7 2 4 2 ¬ « 7 22 −5 23 0 ¬
A = np.array(t)
[89]: A
B = np.array(s)
[91]: B
[92]: np.dot(A,B)
[93]: A.dot(B)
[94]: B.dot(A)
Solution
Representing this system via the language of matrices points to a natural way to use
matrix calculus to solve the equations. Writing
3 2 𝑥 3
=
10 −1 𝑦 5
then clearly
−1
𝑥 3 2 3
= .
𝑦 10 −1 5
Now we use numpy to compute the inverse of the matrix and get the result.
The library numpy provides a method to calculate the inverse of non-singular matri-
ces.
196 7 The numpy Library
print(inv_A)
Or we could use the matrix object’s dot method and do all these in one line:
[98]: np.linalg.inv(A).dot(B)
In fact numpy also provides a method to solve the system of equations directly.
[99]: np.linalg.solve(A,B)
Getting help from sympy graphics from Chapter 6, we plot the lines 3𝑥 + 𝑦 = 4 and
10𝑥 − 𝑦 = 5 and observe that they indeed intersect at one point.
x, y = symbols('x y')
p1.append(p2[0])
p1.show()
7.1 numpy, Numerical Python 197
[100]:
3𝑥 + 2𝑦 − 4𝑧 = 2
3𝑥 + 2𝑦 − (4 + 𝑅)𝑧 = 12
3𝑥 + (2 + 𝑆)𝑦 − 4𝑧 = 3.
First find the unique solution for this system when 𝑅 = 0.05 and 𝑆 = −0.05. Next
explore if 𝑅 and 𝑆 change, how the solution behaves, and how far the new solution
would be from initial solution.
Solution
This interesting problem was investigated by Shiskowski and Frinkle (Example 3.3.5)
in their book Principles of Linear Algebra with Mathematica, Wiley & Sons, 2011.
We first define the coefficient matrix and the value matrix in order to use numpy’s
linear system solve method.
def sys(r,s):
A = np.array([[3, 2, -4], [3, 2, -4 - r], [3, 2 + s,
↩→-4]])
return A
sys(0.05, -0.05)
init_sol
The following graph explains why this system of equations is so sensitive to changes
of 𝑅 and 𝑆. The planes are almost parallel, so a slight change to 𝑅 or 𝑆 will move
the intersection of the planes by a large margin.
x, y = symbols('x y')
[104]:
The distance between two points (𝑥1 , 𝑦 1 , 𝑧1 ) and (𝑥2 , 𝑦 2 , 𝑧2 ) in space is measured by
7.1 numpy, Numerical Python 199
√︃
(𝑥1 − 𝑦 1 ) 2 + (𝑥 2 − 𝑦 2 ) 2 + (𝑥3 − 𝑦 3 ) 2 .
np.linalg.norm(np.array([2,0,0]))
[105]: 2.0
dis_fuc(1,2)
[106]: 62.46554605896313
Show that for 1 ≤ 𝑛 ≤ 10 the determinant of 𝐴𝑛 is negative for odd values and
positive for even values of 𝑛.
Solution
We first define a function that, for each 𝑛, will generate the matrix 𝐴𝑛 . Then we
use the determinant function within numpy to evaluate the determinant of 𝐴𝑛 . The
function numpy.linalg.det evaluates the determinant of a matrix.
Note that the function A(n) contains a local variable mat. The initial value of this
variable is the 𝑛 × 𝑛 array defined by mat = np.empty([n, n]). We then fill the
entries of this array as described by the matrix 𝐴𝑛 .
In the next exercise, we define another matrix. This time we first use a list com-
prehension to define the matrix and then convert it into array within the numpy
library.
Exercise 7.5 Let 𝐵𝑛 denote the 𝑛 × 𝑛 matrix with (𝑖, 𝑗)-th entry equal to
1
2 𝑗 −𝑖 2
if 𝑖 > 𝑗
𝑏𝑖 𝑗 = 1 1
𝑖− 𝑗 + 𝑛2 − 𝑗 −𝑖
if 𝑗 > 𝑖
0
if 𝑖 = 𝑗 .
Define a function 𝐵(𝑛) to generate this matrix for any 𝑛. Look at the numerical
values of the determinant of 𝐵𝑛 for 3 ≤ 𝑛 ≤ 15.
Solution
We define the function B(n) which generates the above matrix for any n. We first use
list comprehension to define the matrix. We give an alternative code using a nested
for-loop as well.
[109]: np.array(B(5))
[111]: [-0.11928571428571424,
-0.03525757631818237,
-0.018062205431347532,
-0.013023479578175417,
-0.009958843179948978,
-0.007821845590148313,
-0.0062884030416988125,
-0.005158398365984703]
Next we generate the matrix B(n) using a nested for-loop. Here the first loop for
i will generate the rows and for j generates the columns. We then generate each
entry aij of each row and we complete each row via row = row + aij. Once each
row is ready then, via b = b + [row], we collect the rows into the matrix b.
b += [row]
return b
[113]: np.array(B(5))
plt.plot(det_list, 'b--');
[115]:
1 2 ··· 𝑛
𝑛 + 1 𝑛 + 2 ··· 2𝑛 ®
© ª
𝑑 (𝑛) = . .. .. .. ®® .
.. . . . ®
« ··· ··· · · · 𝑛2 ¬
Solution
We try several different ways to generate this matrix. We first start with list compre-
hension, then nested for-loops and then we give an approach using numpy’s method
reshape.
7.1 numpy, Numerical Python 203
return s
[117]: A(3)
[118]: A(7)
[120]: A1(7)
And finally, the method reshape provides the easiest way to generate this matrix.
[122]: A
204 7 The numpy Library
[124]: A2(3)
[125]: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Exercise 7.7 Write a function to accept a matrix 𝐴𝑛𝑛 and produce the 𝑛2 × 𝑛2 matrix
𝐵 as follows,
𝑎 11 0 0 𝑎 12 0 0 𝑎 1𝑛 0 0
©© .. ª © .. ª © .. ªª
0
. 0 ®® 0
. 0 ®® · · · 0
. 0 ®® ®®
« 0 0 𝑎 11 ¬ « 0 0 𝑎 12 ¬ « 0 0 𝑎 1𝑛 ¬ ®
®
··· ··· ··· ···
®
®
.. .. .. .. ®.
®
. . . . ®
0 0 𝑎 𝑛𝑛 0 0 ®
®
𝑎 𝑛1
© ª © ª ®
.. ··· · · · 0 . . . 0 ®® ®®
0 . 0 ®®
«« 0 0 𝑎 𝑛1 ¬ « 0 0 𝑎 𝑛𝑛 ¬ ¬
Solution
We write the program for a 3×3 matrix. It is very easy to adapt this to an 𝑛×𝑛-matrix.
The challenge is to get the indices of the entries right. We design three nested-loops,
7.1 numpy, Numerical Python 205
𝑘 for moving down the columns, 𝑖 for moving right in each row and 𝑗 to fill the
diagonals with the given entries of the matrix 𝐴.
B = np.zeros((9, 9))
for k in range(3):
for i in range(3):
for j in range(3):
B[j + k * 3, i * 3 + j] = A[k, i]
[127]: B
[127]: array([[1., 0., 0., 2., 0., 0., 3., 0., 0.],
[0., 1., 0., 0., 2., 0., 0., 3., 0.],
[0., 0., 1., 0., 0., 2., 0., 0., 3.],
[4., 0., 0., 5., 0., 0., 6., 0., 0.],
[0., 4., 0., 0., 5., 0., 0., 6., 0.],
[0., 0., 4., 0., 0., 5., 0., 0., 6.],
[7., 0., 0., 8., 0., 0., 9., 0., 0.],
[0., 7., 0., 0., 8., 0., 0., 9., 0.],
[0., 0., 7., 0., 0., 8., 0., 0., 9.]])
round(det(A)**3, 7) == round(det(B),7)
[128]: True
We finish this section by looking at a very useful function available in numpy, namely
meshgrid. One of the applications of this function is to create a contour plot of a
graph. This we will do in Chapter 8. Here we explain how meshgrid works.
x = [1, 2, 3]
y = [10, 11]
np.meshgrid(x, y)
𝑧4
𝑧3
𝑧2
𝑧1
𝑥1 𝑥2 𝑥3 𝑥4
𝑥
𝑦1
𝑦2
𝑦3
𝑦4
𝑦
7.2 Universal Functions 207
When working with arrays, the built-in numpy functions behave similarly to the
way functions are mapped to lists, that is, the functions are applied to each entry of
the array. This allows for powerful and yet short codes.
As an example, consider the following two np.array
`x = array([x1, x2, ..., xn)]`
`y = array([y1, y2, ..., yn)]`
Making the functions sin and cos available by importing them from the numpy
library, the expression sin(x) + cos(y**2) produces
array([sin(x1)+cos(y1**2), sin(x2)+cos(y2**2), ...,
sin(xn)+cos(yn**2)]).
Writing mathematically, given 𝑥 = (𝑥 1 , 𝑥2 , . . . , 𝑥 𝑛 ) and 𝑦 = (𝑦 1 , 𝑦 2 , . . . , 𝑦 𝑛 ), the
expression sin(𝑥) + cos(𝑦 2 ) generates
[131]: [ 1 2 3 4 5 6 7 8 9 10 ]
[ 1 3 7 15 31 63 127 255 511 1023 ]
1 + sin(𝑥) − cos(𝑥)
= tan(𝑥/2).
1 + sin(𝑥) + cos(𝑥)
Solution
We define the right- and left-hand side of this identity as two functions, evaluate
them for variety of data, and then compare the results.
def npleft_side(x):
return np.tan(x/2)
[133]: array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0.])
Problems
• {𝑥 1 + 𝑦 1 , 𝑥2 + 𝑦 2 , · · · , 𝑥 𝑛 + 𝑦 𝑛 },
• {𝑥 1 , 𝑥1 + 𝑥 2 , · · · , 𝑥1 + 𝑥2 + · · · + 𝑥 𝑛 },
n o
•
{𝑥1 }, {𝑥2 , . . . , 𝑥 𝑛 } , {𝑥1 , 𝑥2 }, {𝑥3 , . . . , 𝑥 𝑛 } . . . {𝑥1 , . . . 𝑥 𝑛−1 }, {𝑥 𝑛 } ,
• and,
{𝑥 1 , 𝑦 1 }, {𝑥1 , 𝑦 2 }, · · · , {𝑥 1 , 𝑦 𝑛 }, {𝑥 2 , 𝑦 1 }, {𝑥2 , 𝑦 2 }, · · · , {𝑥2 , 𝑦 𝑛 }, · · · ,
{𝑥 𝑛 , 𝑦 1 }, {𝑥 𝑛 , 𝑦 2 }, · · · , {𝑥 𝑛 , 𝑦 𝑛 } .
2. The matrix 𝑒 𝑖,𝑛 𝑗 (𝑎) is defined as an 𝑛 × 𝑛 matrix with ones on the diagonal, 𝑎 in
the 𝑖-th row and 𝑗-th column and 0 everywhere else, for example:
1𝑎0
𝑒 31,2 (𝑎) = 0 1 0 ®
© ª
«0 0 1¬
Find out how long it takes Python to evaluate the inverse of 𝐴(𝑛) for 𝑛 =
15, 16, . . . 30 (this might take some seconds).
5. Consider a 10 × 10 matrix with positive integers as its entries. Write a function
𝜃 which is the sum of the third largest number of each row.
Now create a 10 × 10 matrix 𝑀 whose entries are the integers 1 to 100:
1 2 ··· 10
12
11 ··· 20
© ª
®
𝑀 = . . ®.
.. .. ... ..
.
®
®
« 91 92 · · · 100 ¬
Show that 𝜃 (𝑀) is greater than the sum of the numbers in some row.
Chapter 8
The matplotlib Library and Projects
In this chapter we will look more closely at the Python graphics library matplotlib,
which is widely used for plotting data and creating professional and magnificent
two-dimensional graphics. This library works perfectly with numpy. With numpy we
handle the data and with matplotlib we visualise them.
Let us remind ourselves of the most basic way we can use the library.
© The Author(s), under exclusive license to Springer Nature Switzerland AG 2023 211
R. Hazrat, A Course in Python, Springer Undergraduate Mathematics Series,
https://doi.org/10.1007/978-3-031-49780-3_8
212 8 The matplotlib Library and Projects
[3]:
Staying with lists, we calculate the sin function for certain values and plot the result.
x = range(-10, 10)
y = [math.sin(i) for i in x]
[5]:
We won’t spend too much time on how to style the outputs. The examples below
show some samples and the codes for any combinations and decorations one has in
mind are just a google away.
As mentioned, matplotlib works perfectly with numpy. This allows us to do all
kind of calculations with ease, since numpy allows the use of universal functions,
i.e. we can apply functions to each component of the array.
The example below produces the plot of the function sin(𝑥)/𝑥, where −10 ≤ 𝑥 ≤ 10.
[6]:
sin( 𝑥 )
Exercise 8.1 Plot the graph of 𝑓 (𝑥) = cos( 𝑥 ) , for 0 ≤ 𝑥 ≤ 10𝜋.
Solution
The universal function facility of numpy allows us to treat an array as single data.
We first define an array 𝑥 = (𝑥 1 , 𝑥2 , · · · , 𝑥 𝑛 ), then when using numpy’s sin and cos,
the expression 𝑦 = sin(𝑥)/cos(𝑥) gives
sin(𝑥 ) sin(𝑥 ) sin(𝑥 𝑛 )
1 2
𝑦= , ,··· , .
cos(𝑥1 ) cos(𝑥 2 ) cos(𝑥 𝑛 )
We can then simply pass 𝑦 into the plot command.
plt.plot(x, y);
[7]:
matplotlib allows us to plot several equations at the same time. For this one
introduces the equations into plot by using a list containing all the equations.
As an example we plot the graphs of the functions sin( 𝑥 21− 𝑥 ) and cos( 𝑥 21− 𝑥 ) in the
range [0, 𝜋].
y = np.sin(1/(x**2 - x))
z = np.cos(1/(x**2 - x))
plt.plot(x, y, x, z);
214 8 The matplotlib Library and Projects
[8]:
[9]:
The way we can think of this is that plt.plot creates an object and each time we
feed it the coordinates, we create new AA figure object can have several Axes, and
Python then shows all of them together in one figure.
sin(𝑥) − cos(𝑛𝑥)
𝑓𝑛 (𝑥) =
1+𝑥
between 0 and 7𝜋 as 𝑛 ranges from 1 to 10.
Solution
We define the function 𝑓𝑛 in Python, which depends on 𝑛. We then create a loop,
running 𝑛 from 0 to 9.
def f(n):
return (np.sin(x) - np.cos(n * x))/(1+x)
for n in range(10):
plt.plot(x, f(n));
8.1 matplotlib, Plotting Data 215
[10]:
As one can see, although the plt.plot is repeated 10 times, all the graphs appear
in one figure, as the plot defines a single object consisting of all these coordinates.
One can see that there is no difference between plotting a parametric plot and
functions of the form 𝑓 (𝑥) = sin(𝑥). Here, we plot the parametric function 𝑥 = sin(𝑡),
𝑦 = cos(𝑡).
[11]:
We expect to get a perfect circle. A closer look shows Python has used different
scales for 𝑥 and 𝑦-axis. We adjust this:
[12]: plt.axis('equal')
plt.plot(x,y);
[12]:
[13]: plt.axis('equal')
for i in range(10):
plt.plot(x*i + i ,y*i);
216 8 The matplotlib Library and Projects
[13]:
using sympy graphics. Here we will plot this parametric equation using matplotlib.
[17]:
8.1 matplotlib, Plotting Data 217
for 0 ≤ 𝑡 ≤ 14𝜋.
Solution
This is very similar to the previous exercise. We generate the 𝑥 and 𝑦-coordinates
and then use plot to create the graph.
[21]:
We remark that it is not necessary to define the functions x(t) and y(t). It is enough
to define t = np.linspace(0, 14*np.pi, 1000)) and then set
x axis = np.cos(t) + 1/2*np.cos(7*t) + 1/3*np.sin(17*t)
y axis = np.sin(t) + 1/2*np.sin(7*t) + 1/3*np.cos(17*t)
218 8 The matplotlib Library and Projects
Solution
Given the type of conditions introduced in the body of this function, we revert to
defining the function element-wise.
x = np.arange(-4, 4, 0.1)
y = list(map(f, x))
The function 𝑓 (𝑥) is not a continuous function, as becomes more clear from its
graph.
[24]:
So far we have been using plot as a function, giving two list of x and y for the
coordinates to create the graph. However, since plots are objects, we can create new
8.2 Plots as Objects 219
plots simply by changing the attributes of an existing plot. The following examples
show how much more we can get when we work in this manner. We will be using
the same data generated for the butterfly graph from the above example.
[25]:
[26]: fig
[26]:
Here we have defined the plot object fig which comes with is own “Axes” object
ax. The object ax is where we assign the data and decorate the graph, such as adding
titles and scales. We can then plot the whole graph using the object fig.
A figure object can have several Axes, which then allows us to handle several plots
in one go. The line fig, ax = plt.subplots(m,n) creates an object fig, with
𝑚 × 𝑛 Axes. Each of these Axes can have their own 𝑥-−𝑦 data and specifications, thus
the object fig contains 𝑚 × 𝑛 graphs. Think of fig as an 𝑚 × 𝑛 matrix, with each
entry a graph with its own specification. We can access each of these subplots with
ax[i,l], again similar to accessing entries of a matrix. The following examples
shows how wonderful this approach is.
def y(t):
return np.sin(t) + 1/2*np.sin(7*t) + 1/3*np.cos(17*t)
def rrange(i):
220 8 The matplotlib Library and Projects
fig, ax = plt.subplots(3)
ax[0].axis('equal')
ax[1].axis('equal')
ax[2].axis('equal')
ax[0].plot(x(rrange(0.1)), y(rrange(0.1)));
ax[1].plot(x(rrange(1.1)), y(rrange(1.1)));
ax[2].plot(x(rrange(2.1)), y(rrange(2.1)));
[27]:
[28]:
Exercise 8.5 Define the Conway recursive sequence 𝑎(1) = 1, 𝑎(2) = 1 and
Solution
Note that the Conway function 𝑎(𝑛) = 𝑎(𝑎(𝑛 − 1)) + 𝑎(𝑛 − 𝑎(𝑛 − 1)) is quite a
complicated recursive function; it calls itself twice within its definition. However
if we just translate it straight into Python, the program takes care of this recursive
behaviour.
[31]: plt.plot(y);
[31]:
for l in range(1,3):
for k in range(1,3):
222 8 The matplotlib Library and Projects
[33]:
The last loop will hide the x labels and tick labels for the top plots and the y ticks
for the right plots.
It is interesting to see that a slight change in the definition of the Conway recursive
function makes the behaviour of the sequence quite chaotic. This has been studied by
K. Pinn in the article “A chaotic cousin of Conway’s recursive sequence”, available
on arXiv.org.
8.3 Animation
In order to create an animation, we need to enable the interactive plot. This can be
done by the following command in Jupyter.
Next we will use FuncAnimation within the command animation. The idea
is to generate several frames of plots and then show them one after the other.
The command FuncAnimation just does that. In the line FuncAnimation(fig,
animate, frames=100, interval=20, ...) we create a fig, which is our
graph object. This, as before, we do with fig, ax = plt.subplots(). We know
ax.plot(x,y) will create the graph. The idea is to modify the 𝑥 and 𝑦 coordinates
accordingly and then ask Python to show them frame by frame with some interval.
This is done by defining a function animate which takes care of these changes
and frames to send the parameters into animate. The interval sets the delay (in
milliseconds) between frames. As usual, we start with examples.
8.3 Animation 223
fig, ax = plt.subplots()
ax.set_xlim(0, 10*np.pi)
ax.set_ylim(-1.1, 1.1)
def animate(i):
x = np.arange(0, (i/10)*np.pi, 0.1)
y = np.sin(x)
line.set_data((x, y))
return line,
ani = animation.FuncAnimation(
fig, animate, frames=100, interval=20, blit=True)
plt.show()
[35]:
Now that we have the blueprint for creating graphs, we modify the above example to
see how the graph of the parametric equations
is created.
def x(t):
return np.cos(t) + 1/2*np.cos(7*t) + 1/3*np.sin(17*t)
224 8 The matplotlib Library and Projects
def y(t):
return np.sin(t) + 1/2*np.sin(7*t) + 1/3*np.cos(17*t)
fig, ax = plt.subplots()
ax.set_xlim(-np.pi, np.pi)
ax.set_ylim(-2, 2)
line, = ax.plot([])
def animate(i):
t = np.linspace(0, (i/10)*np.pi, 40000)
xc = x(t)
yc = y(t)
line.set_data((xc, yc))
return line,
ani = animation.FuncAnimation(
fig, animate, frames=100, interval=200, blit=True)
plt.show()
[36]:
Solution
For plotting a polar graph, one is given the angle 𝜃 and the length 𝑟 = 3 cos(6𝜃).
Using trigonometry, we can obtain the (𝑥, 𝑦) coordinates as follows:
𝑥 = 𝑟 sin(𝜃)
𝑦 = 𝑟 cos(𝜃)
8.3 Animation 225
[38]: plt.axis('off');
plt.axis('equal');
plt.plot(x,y);
[38]:
This is another example of multiple graphs. We are going to plot the polar graph
above and then introduce some “noise” to the result using the command random.
[39]: 0.43010950529070247
[40]:
The library matplotlib allows one to create the contour plot of functions as well.
The following two examples demonstrate this ability. For this we need to use numpy
and the function meshgrid.
Solution
Recall the function meshgrid discussed in Chapter 7. Suppose 𝑥 = (𝑥1 , 𝑥2 , · · · , 𝑥 𝑚 )
and 𝑦 = (𝑦 1 , 𝑦 2 , · · · , 𝑦 𝑛 ). Then np.meshgrid(x, y) gives two matrices of the
form
[[array([[x_1, x_2, ..., x_m],
[x_1, x_2, ..., x_m],
[x_1, x_2, ..., x_m],
.................. ,
[x_1, x_2, ..., x_m]),
array([[y_1, y_1, ..., ., y_1],
[y_2, y_2, ..., ., y_2],
[. , . , ..., ., . ],
[. , . , ..., ., . ],
[y_n, y_n, ., ., y_n]])]
plt.axis('scaled')
plt.colorbar()
8.3 Animation 227
plt.show()
[42]:
sin(𝑥 2 + 𝑦 2 )
𝑓 (𝑥, 𝑦) = .
𝑥+𝑦
Plot the contour of
𝑓 𝑓 𝑓 (𝑥, 𝑦), 𝑓 (𝑥, 𝑦) , 𝑓 𝑓 (𝑥, 𝑦), 𝑓 (𝑥, 𝑦) .
Solution
Here again we encounter a complex composition of functions. However, once the
function 𝑓 (𝑥, 𝑦) is defined, Python can handle the composition itself. This is yet
another example of how to use the function meshgrid to produce contours.
plt.axis('scaled')
plt.colorbar()
plt.title('countourplot')
plt.show()
[45]:
The following amusing algorithm was suggested in a short paper by Anne Burns
to create “Persian rug-like patterns” using a recursive procedure. (Anne M. Burns,
Persian recursion, Mathematics Magazine, Vol. 70, No. 3, 1997, 196–199.) Following
Burns’ paper, we describe the procedure here and then we will write a Python code
to generate some of these patterns.
Consider a 2𝑛 + 1 × 2𝑛 + 1 celled square. The aim is to colour each cell based on
the colour of the corners already coloured. This is achieved by allocating a number
between 0 and 𝑚 − 1 to each cell, 𝑚 being the number of available colours. We begin
to colour the outer cells first, colouring them all the same colour in order to form a
border. We then apply the following procedure recursively:
• Use the four cells in the corners and a four-variable function to determine a different
colour. For example the function could be
𝑓 (𝑐 1 , 𝑐 2 , 𝑐 3 , 𝑐 4 ) = (𝑐 1 + 𝑐 2 + 𝑐 3 + 𝑐 4 ) mod 𝑚.
• Allocate the newly determined colour to the interior row cells and column cells in
the centre.
• Apply this scheme to all of the four new bordered squares generated at each
execution.
Once all cells have been coloured the function will terminate.
8.4 Case Study, Persian Carpets 229
Solution
We first translate the procedure into Python, writing a code to generate a matrix with
the colours assigned to the entries. This requires a recursive function, finding the
middle row and column and then calling the function again four times for each of
the four new matrices after the partition.
In order to put the code together, we start with an example. Consider the 23 +1×23 +1
cells with 𝑚 = 7 and the boundary cells all 4.
444444444
4 0 0 0 0 0 0 0 4®
© ª
4 0 0 0 0 0 0 0 4®
®
4 0 0 0 0 0 0 0 4®
®
4 0 0 0 0 0 0 0 4®.
®
4 0 0 0 0 0 0 0 4®
®
4 0 0 0 0 0 0 0 4®
®
4 0 0 0 0 0 0 0 4®
®
«4 4 4 4 4 4 4 4 4¬
In Python, we consider the matrix x and eventually fill all the entries with the specific
numbers.
[47]: s = 2**3 + 1
m = 7
[49]: x
[49]: array([[0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0.]])
230 8 The matplotlib Library and Projects
[51]: x
[51]: array([[4., 4., 4., 4., 4., 4., 4., 4., 4.],
[4., 0., 0., 0., 0., 0., 0., 0., 4.],
[4., 0., 0., 0., 0., 0., 0., 0., 4.],
[4., 0., 0., 0., 0., 0., 0., 0., 4.],
[4., 0., 0., 0., 0., 0., 0., 0., 4.],
[4., 0., 0., 0., 0., 0., 0., 0., 4.],
[4., 0., 0., 0., 0., 0., 0., 0., 4.],
[4., 0., 0., 0., 0., 0., 0., 0., 4.],
[4., 4., 4., 4., 4., 4., 4., 4., 4.]])
Now by following the Persian procedure, we get to the numbers in the corners (here
all 4) and compute
4 + 4 + 4 + 4 mod 7 = 2
which we place in centre of matrix as shown:
4 4 4 4 4 4 4 4 4
4 □ □ □ 2□□□ 4®
© ª
4 □ □ □ 2□□□ 4®
®
4 □ □ □ 2□□□ 4®
®
4 2 2 2 2 2 2 2 4®
®
4 □ □ □ 2□□□ 4®
®
4 □ □ □ 2□□□ 4®
®
4 □ □ □ 2□□□ 4®
®
«4 4 4 4 4 4 4 4 4¬
In Python we can achieve this by defining the function cx(l, r, t, b), where
𝑙, 𝑟, 𝑡 and 𝑏 are the left, right, top and bottom coordinates of the matrix.
[52]: x.shape
[52]: (9, 9)
l = 0; r = x.shape[0] - 1; t = 0; b = x.shape[1] - 1
cx(l, r, t, b).astype(int)
8.4 Case Study, Persian Carpets 231
[53]: 2
Notice how x[(t+1) : b, mc] and x[mr,(l+1) : r] now assign the new num-
bers to the middle columns and rows.
[55]: x
[55]: array([[4., 4., 4., 4., 4., 4., 4., 4., 4.],
[4., 0., 0., 0., 2., 0., 0., 0., 4.],
[4., 0., 0., 0., 2., 0., 0., 0., 4.],
[4., 0., 0., 0., 2., 0., 0., 0., 4.],
[4., 2., 2., 2., 2., 2., 2., 2., 4.],
[4., 0., 0., 0., 2., 0., 0., 0., 4.],
[4., 0., 0., 0., 2., 0., 0., 0., 4.],
[4., 0., 0., 0., 2., 0., 0., 0., 4.],
[4., 4., 4., 4., 4., 4., 4., 4., 4.]])
Next the function colorgrid[l, r, t, b] will assign the new numbers to the
entries of the matrix.
x[(t+1):b,mc] = cx(l, r, t, b)
x[mr,(l+1):r] = cx(l, r, t, b)
[56]: array([[4., 4., 4., 4., 4., 4., 4., 4., 4.],
[4., 5., 0., 3., 2., 3., 0., 5., 4.],
[4., 0., 0., 0., 2., 0., 0., 0., 4.],
[4., 3., 0., 6., 2., 6., 0., 3., 4.],
[4., 2., 2., 2., 2., 2., 2., 2., 4.],
[4., 3., 0., 6., 2., 6., 0., 3., 4.],
[4., 0., 0., 0., 2., 0., 0., 0., 4.],
[4., 5., 0., 3., 2., 3., 0., 5., 4.],
[4., 4., 4., 4., 4., 4., 4., 4., 4.]])
Note that, within colorgrid, we have called the function four times to take care of
the four squares created after partitioning the matrix.
Next we assign colours to the numbers and create our Persian carpet. This can be
done by the method matshow within matplotlib.
[57]:
[58]:
Putting all these together, we define the function perCarpet to generate interesting
patterns.
x = np.empty([2**n+1, 2**n+1])
x[ : ,0] = x[ : ,-1] = x[0, : ] = x[-1, : ] = z
[60]:
[61]:
[62]:
In many applications as the size increases, the time it takes to complete the task
also increases. The following case study is a good example showcasing how we can
8.5 Case Study, Predictive Estimation 235
analyse the time dependence of an algorithm. Let us donate the size of an application
by n and the time it takes to run the program and obtain the result by t. It rarely
happens that the relationship between the size and time is linear, i.e., t = 𝑘 n + 𝑙,
for some constants 𝑘 and 𝑙. If this is the case, then by sampling some small n and
measuring t, and plotting (n, t), we should obtain a straight line. If this is not the
case (and again oftentimes it is not), and if the relation is of the form
t = 𝑘 n𝑙
or
t = 𝑘 𝑙n,
then we might also be able to find the relation by running the program for small
values of n.
Suppose the relation is of the form t = 𝑘 n𝑙 . Then taking logs we have
Now if we run the application for some (small) values of n, measure the time it takes
to complete the task, and then plot log(t) versus log(n), we should end up with a
straight line. This will then be an indication that the relation between t and n is
indeed of the form t = 𝑘 n𝑙 . If the graph we obtain does not look like a straight line,
then we can consider the next case of t = 𝑘 𝑙 n . Taking logs we get
In this case, the graph of log(t) versus n should create a straight line.
Once this is done, we can use regression methods available in Python to get a least-
squares best fit linear approximation to the line. Thus we get an equation of line
𝑦 = 𝑎𝑥 + 𝑏 which approximates the sample. Once we have the equation of the line,
we should be able to calculate the constants 𝑘 and 𝑙. This is done by using the identity
elog( 𝑥 ) = 𝑥.
Case study
Let 𝐵𝑛 denote the 𝑛 × 𝑛 matrix with (𝑖, 𝑗)-th entry equal to
1
𝑖2 − 𝑗
if 𝑖 > 𝑗
𝑏𝑖 𝑗 = 1
𝑗 −𝑖 if 𝑗 > 𝑖
0
if 𝑖 = 𝑗.
that is approximately linear and hence obtain and test a formula that predicts the
value of the sequence. Define a function that predicts the value of the determinant of
𝐵𝑛 . What is the largest percentage error that your formula has for 30 ≤ 𝑛 ≤ 50?
Solution
We start by importing the libraries needed and then define the matrix.
B(3)
[65]: B(5)
Note that, it looks like det(𝐵(𝑛)) is positive for even values of 𝑛 and negative for
odd values. It is now easy to plot the values and observe the pattern.
[69]:
As one can see there are two sub-sequences here. We treat them separately.
[70]: det_B[ : : 2]
[70]: [-0.3333333333333333,
-0.05890949328449329,
-0.020606972438086804,
-0.011037045008369608,
-0.00691202302161902,
-0.004740845068644299,
-0.0034551244433575485,
-0.0026303788255851936,
-0.002069586219401722,
-0.0016709092704200235,
-0.0013773309712474416,
-0.0011548848970943254,
-0.0009823045823221005,
-0.0008457200609293778,
-0.0007357709130155691]
[71]: det_B[1 : : 2]
238 8 The matplotlib Library and Projects
[71]: [0.1488095238095238,
0.03162805947646787,
0.01468496294836567,
0.008611643884091177,
0.005672896223774175,
0.0040217496236219985,
0.0030006019535249876,
0.0023247863447106655,
0.0018542623250821055,
0.0015134903892876795,
0.001258765078394007,
0.001063359924241922,
0.0009101799734072838,
0.0007878755713934514]
[72]:
[73]:
[75]: X
[75]: array([ 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27,
↩→29])
8.5 Case Study, Predictive Estimation 239
[76]: Y
Now we have the sample values for 𝑛 (in the list X) and 𝑡 (in the list Y). Next we
need to determine whether the relationship between 𝑛 and 𝑡 is of the form 𝑛 − log or
log-log. First we plot (n, log(t)) for the samples we have.
[77]:
This clearly shows the relationship between n and t is not of the form t = 𝑘 𝑙 n . We
now plot (log(n), log(t)).
[78]:
This clearly shows the relation is of the form t = 𝑘 n𝑙 . We now use the regression facil-
ities in Python to obtain the approximation. We need to import LinearRegression.
The rest of the code is self-explanatory.
[82]: X
240 8 The matplotlib Library and Projects
[82]: array([[1.09861229],
[1.60943791],
[1.94591015],
[2.19722458],
[2.39789527],
[2.56494936],
[2.7080502 ],
[2.83321334],
[2.94443898],
[3.04452244],
[3.13549422],
[3.21887582],
[3.29583687],
[3.36729583]])
[83]: LinearRegression()
We can now ask the model that we devised to predict the values of the (log of)
determinants and compare them with the actual values.
[86]: y_pred
[87]:
[88]: a = model.coef_[0]
b = model.intercept_
Since the equation 𝑦 = −2.22𝑥 +0.23 models the equation log(t) = 𝑙 log(n) +log(𝑘),
in order to obtain t, i.e., the actual determinants, we use the identity 𝑒 log(t) = t.
[92]: -17.390995636384897
We have analysed the list of n for odd values. A similar treatment can be done for
the even values. We leave it as an exercise to the reader.
To get the Thue–Morse sequence, start with 0 and then repeatedly replace 0 with 01
and 1 with 10. So the first four numbers in the sequence are
0
01
0110
01101001
one unit if you encounter 1 in the Thue–Morse sequence and rotate counterclockwise
by an angle of −𝜋/3 if you encounter 0. (The resulting curve converges to the Koch
snowflake, a fractal curve of infinite length containing a finite area – see, for example,
the Thue–Morse sequence in Wikipedia.)
Solution
We start by creating the Thue–Morse sequence, using the following clever code!
return x
[95]: len(ThueMorse(15))
[95]: 16384
directions = ThueMorse(15)
L = [[0, 0]]
facing = 0
if step == "1":
L.append([L[-1][0] + cos(facing * pi / 3), L[-1][1] +
↩→sin(facing * pi / 3)])
else:
facing = (facing + 1)
x = np.array(L)[:, 0]
y = np.array(L)[:, 1]
plt.figure(figsize=(5, 5))
plt.xlim([np.amin(L) - 1, np.amax(L) + 1])
plt.ylim([np.amin(L) - 1, np.amax(L) + 1])
plt.plot(x, y, color='green')
plt.show()
[96]:
Problems
for 𝑥 ranging over the interval [0, 2𝜋]. Then find (numerically) the root of the
equation 𝑓 (𝑥) = 0 lying between 4 and 5.
2. Plot the graph of the expression
50
∑︁ sin(𝑛𝑥)
𝑥(2𝜋 − 𝑥)
𝑛=1
𝑛
for 0 ≤ 𝑥 ≤ 2𝜋.
3. Consider the function sin(𝑥 2 ) cos(𝑦 2 ). Plot the function and its contour. Create
an animation, observing how the areas change as the plot develops.
8.6 Case Study, the Thue–Morse Sequence 245
4. Let 𝐵𝑛 denote the 𝑛 × 𝑛 matrix with (𝑖, 𝑗)-th entry equal to 1/(|𝑖 − 𝑗 |) if 𝑖 ≠ 𝑗
and 0 if 𝑖 = 𝑗. Find a simple formula to predict the value of the determinant of
𝐵𝑛 for large values of 𝑛. What is the largest percentage error that your prediction
has for 20 ≤ 𝑛 ≤ 40? (Hint: Look at the determinants of 𝐵𝑛 for 2 ≤ 𝑛 ≤ 10
first. You will see that predictions are only possible for certain subsequences of
the integers. Obtain sufficient data for each of these subsequences so that you
can find a transformation that is approximately linear and hence obtain and test
a formula for each subsequence. Put the resulting formulae together for the final
predictive function.)
√
5. Consider the first 10000 digits of 2 and present them as a “random walk” by
converting them √ into base 4, representing 4 directions (up, down, left and right).
We know that 2 is an irrational number and irrational numbers have decimal
expansions that neither terminate nor √ become
√ periodic.
√ Write a code to produce
this random walk. Try this code with 3, 6 and 13. Is there any comparison
one can make among these numbers?
6. (For this problem, see Case study 8.5.) A double sequence (𝑎 (𝑚,𝑛) ) is defined
by the rules
The value of 𝑎 (10,10) is 67. Produce a table comparing the values of 𝑎 (𝑛,𝑛) with
𝑛 for 1 ≤ 𝑛 ≤ 20. Find a simple function that predicts the size of 𝑎 (𝑛,𝑛) and
compare your predictions for 𝑎 (30,30) and 𝑎 (100,100) with the correct values.
If the definition of 𝑎 (𝑚,𝑛) is altered so that
then the terms 𝑎 (𝑛,𝑛) alternate in sign. By considering separately the sequences
𝑎 (2,2) , 𝑎 (4,4) , . . . and 𝑎 (1,1) , 𝑎 (3,3) , 𝑎 (5,5) , . . . produce (and verify) a simple for-
mula for estimating the values of 𝑎 (𝑛,𝑛) in this case.
7. (For this problem, see Case study 8.5.) Define an 𝑛 × 𝑛 matrix 𝐴𝑛 = (𝑎 𝑖 𝑗 ) whose
𝑖, 𝑗-th entries are
𝑖 + 𝑗 if 𝑖 = 𝑗
𝑎 𝑖 𝑗 = 𝑖+ 𝑗
𝑖 if 𝑖 ≠ 𝑗 .
Find out how long it takes Python to evaluate the inverse of 𝐴(𝑛) for 𝑛 =
15, 16, . . . 30 (this might take some seconds). Hence obtain a formula which
estimates how long it takes to calculate the inverse of 𝐴(𝑛) in general. Test this
formula experimentally by timing the calculation of the inverse of 𝐴(31). Use
your formula to estimate how long it would take to find the inverse of 𝐴(100).
246 8 The matplotlib Library and Projects
−𝑥,
if |𝑥| < 1
Í10
𝑓 (𝑥) = 𝑥
𝑛=1 sin( 𝑛 ), if 1 ≤ |𝑥| < 2
10
Í 𝑥
𝑛=1 cos( 𝑛 ), otherwise.
How many roots does 𝑓 (𝑥) have in the interval [−4, 4]?
Further Reading
There are many excellent books about Python. Once one knows the core of the
language, one can easily use these books. Here we collect a few books that are worth
consulting.
• Eric Matthes, Python Crash Course, 3nd Edition, No Starch Press, 2022.
This comprehensive beginner-level book on Python contains many interesting
worked out projects.
• Eli Stevens, Luca Antiga, and Thomas Viehmann, Deep Learning with PyTorch,
Manning, 2020.
This book provides more advanced uses of Python in data science.
© The Author(s), under exclusive license to Springer Nature Switzerland AG 2023 247
R. Hazrat, A Course in Python, Springer Undergraduate Mathematics Series,
https://doi.org/10.1007/978-3-031-49780-3
248 Further Reading
*, 36 exp, 6
//, 3 factor, 153
==, 15 filter, 111
Eq, 196 float, 53
False, 16 format, 19
Image, 33 functools, 113
Integrate, 175 help, 10
LinearRegression, 239 input, 20
Matrix, 178 intersection, 132
PIL, 33, 58, 88 int, 20
True, 16 is integer(), 11
%, 3 itertools, 145
, 6, 15 join, 118
\n, 15 keys, 139
\t, 21 lambda, 103
all(), 131 limit, 173
and, 16 linalg, 195
animation, 222 linspace, 185
append, 29 lower, 19
arange, 185 map, 105
array, 183 math, 5
cancel, 176 matplotlib, 38, 105, 211
capitalize, 19 next, 144
cmath, 12 nltk, 42, 60, 117, 138, 146
comp, 62 not, 16
cos, 5 numpy, 7, 183
csv, 39, 69 or, 16
def, 85 pi, 5
del, 140 plot3d parametric line, 165
det, 178 plot3d parametric surface, 165
dict, 141 plot3d, 165
difference, 133 plot implicit, 163, 196
diff, 175 plot parametric, 162
divmod, 51 plot, 158, 211
dot, 194 pop, 140
effect spread, 35 print, 3
expand, 153 range, 57
© The Author(s), under exclusive license to Springer Nature Switzerland AG 2023 249
R. Hazrat, A Course in Python, Springer Undergraduate Mathematics Series,
https://doi.org/10.1007/978-3-031-49780-3
250 Index