0% found this document useful (0 votes)
11 views14 pages

Sol CH 5

Uploaded by

tranngocphung583
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
11 views14 pages

Sol CH 5

Uploaded by

tranngocphung583
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 14

172 5 Some more Python essentials

for initialization, i.e., what the timer needs to do prior to timing of the
loop. If you look carefully at the string-part of this second argument,
you notice an import statement for add and x. You may wonder why
you have to do that when they are defined in your code above, but stay
relaxed about that, it is simply the way this timer function works. What
is required for the timer function to execute the code given in the first
argument, must be provided in the setup argument, even if it is defined
in the code above.
The following line,
x_time = t.timeit(10000) # Time 10000 runs of the whole loop

will cause the whole loop to actually be executed, not a single time, but
10000 times! There will be one recorded time, the time required to run
the loop 10000 times. Thus, if an average time for a single run-through
of the loop is desired, we must divide the recorded time by (in this case)
10000. Often, however, the total time is fine for comparison between
alternatives. The print command brings the recorded time to the screen,
before the next loop is timed in an equivalent way.
Why is the loop run 10000 times? To get reliable timings, the execution
times must be on the order of seconds, that is why. How many times the
requested code snippet needs to be run, will of course depend on the
code snippet in question. Sometimes, a single execution is enough. Other
times, many more executions than 10000 are required. Some trial and
error is usually required to find an appropriate number.
Executing the program produces the following result,
Time, function call: 2.22121 seconds
Time: 1.4738 seconds

So, using the function add to fill the array, takes 50% longer time!

5.7 Exercises

Exercise 5.1: Nested for loops and lists


The code below has for loops that traverse lists of different kinds. One
is with integers, one with real numbers and one with strings. Read the
code and write down the printout you would have got if the program
had been run (i.e., you are not supposed to actually run the program,
just read it!).
5.7 Exercises 173

for i in [1, 2]:


# First indentation level (4 spaces)
print(’i: {:d}’.format(i))
for j in [3.0, 4.0]:
# Second indentation level (4+4 spaces)
print(’ j: {:.1f}’.format(j))
for w in [’yes’, ’no’]:
# Third indentation level (4+4+4 spaces)
print(’ w: {:s}’.format(w))
# First indentation level
for k in [5.0, 6.0]:
# Second indentation level (4+4 spaces)
print(’ k: {:.1f}’.format(k))

Solution. Running the program gives the following printout:


i: 1
j: 3.0
w: yes
w: no
j: 4.0
w: yes
w: no
k: 5.0
k: 6.0
i: 2
j: 3.0
w: yes
w: no
j: 4.0
w: yes
w: no
k: 5.0
k: 6.0

By comparing the printout and the code, it should be clear how program
execution goes.
Filename: read_nested_for_loops.py.

Exercise 5.2: Exception handling - divisions in a loop


Write a program that N times will ask the user for two real numbers a
and b and print the result of a/b. Any exceptions should be handled
properly (for example, just give an informative printout and let the
program proceed with the next division, if any). The user should also be
allowed to stop execution with Ctrl-c.
Set N = 4 in the code (for simplicity) and demonstrate that it handles
different types of user input (i.e., floats, integers, text, just pressing enter,
etc.) in a sensible way.
174 5 Some more Python essentials

Solution. Exceptions will occur if the user gives text as input, or simply
nothing (i.e., just press enter), or if Ctrl-c is typed, or if division by zero
is requested (i.e., b = 0). In any of these cases, the program should stay
in control and handle the situation appropriately.
The program might be implemented as:
import sys

N = 4
i = 1
while i <= N:
# get the numbers
try:
a = float(input(’a: ’))
b = float(input(’b: ’))
except ValueError:
print(’\nError - you must give an integer or a real number!’)
i = i + 1
continue
except KeyboardInterrupt:
print(’\nOk, you want to stop’)
sys.exit(1)

# carry out division


try:
print(’\na/b = {:g}’.format(a/b))
except ZeroDivisionError:
print(’\nError - division by zero’)

i = i + 1

Filename: compute_division.py.

Exercise 5.3: Taylor series, sympy and documentation


In this exercise, you are supposed to develop a Python function that
approximates sin(x) when x is near zero. To do this, write a program
that utilizes sympy to develop a Taylor series for sin(x) around x = 0,
keeping only 5 terms from the resulting expression. Then, use sympy to
turn the expression into a function. Let the program also plot sin(x) and
the developed function together for x in [−π, π].
Solution. The code reads:
import sympy as sym

x = sym.symbols(’x’)
# Compute the 5 first terms in a Taylor series for sin(x) around x = 0.
T_sin5 = sym.sin(x).series(x, 0, 5).removeO()
5.7 Exercises 175

print(T_sin5)
Taylor_sin = sym.lambdify([x], T_sin5, modules=’numpy’)

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(-np.pi, np.pi, 100)


plt.plot(x, np.sin(x), ’r-’, x, Taylor_sin(x), ’b--’)
plt.grid(’on’)
plt.show()

Filename: symbolic_Taylor.py.
Remarks. Part of your task here, is to find and understand the required
documentation. Most likely, this means that you have to seek more
information than found in our book. You might have to read about the
Taylor series (perhaps use Wikipedia or Google), and you probably have
to look into more details about how Taylor series are handled with sympy.
To your comfort, this is a very typical situation for engineers and
scientists. They need to solve a problem, but do not (yet!) have the
required knowledge for all parts of the problem. Being able to find and
understand the required information is then very important.

Exercise 5.4: Fibonacci numbers


The Fibonacci numbers13 is a sequence of integers in which each number
(except the two first ones) is given as a sum of the two preceding numbers:

Fn = Fn−1 + Fn−2 , F0 = 1, F1 = 1, n = 2, 3, . . .
Thus, the sequence starts out as

1, 1, 2, 3, 5, 8, 13, 21, 34, . . .


a) Write a function make_Fibonacci that generates, and returns, the N
first Fibonacci numbers, when N is an input parameter to the function.
Place the function in a module named fibonacci (i.e., a file named
fibonacci.py). The module should have a test block, so that if run as
a program, e.g., the 20 first Fibonacci numbers are printed to screen.
Check that the program behaves as intended.
Solution. See code for complete module below.
13
Read more about the Fibonacci numbers, e.g., on Wikipedia (https://en.wikipedia.
org/wiki/Fibonacci_number).
176 5 Some more Python essentials

b) The famous Johannes Kepler14 found that the ratio of consecutive


Fibonacci numbers converges to the golden ratio, i.e.

Fn+1 1+ 5
lim = .
n→∞ Fn 2
Extend your module by defining a function converging_ratio, which
takes an array (or a list) F with (e.g., 20) Fibonacci numbers as input
and then checks (you decide how) whether Kepler’s understanding seems
correct. Place a call to the function in the test block and run the program.
Was Kepler right?
Solution. See code for complete module given below.
c) With the iterative procedure of the previous question, the ratios con-
verged to the golden ratio at a certain rate. This brings in the concept of
convergence rate, which we have not yet addressed (see, e.g., Chapter 7.5,
or elsewhere). However, if you are motivated, you may get a head start
right now.
In brief, if we define the difference (in absolute value) between FFn+1 n
and the golden ratio as the error en at iteration n, this error (when
small enough) will develop as en+1 = Ceqn , where C is some constant
and q is the convergence rate (in fact, this error model is typical for
iterative methods). That is, we have a relation that predicts how the
error changes from one iteration to the next. We note that the larger
the q, the quicker the error goes to zero as the number of iterations (n)
grows (when en < 1). With the given error model, we may compute the
convergence rate from

ln(en+1 /en )
q= .
ln(en /en−1 )
This is derived by considering the error model for 3 consecutive iterations,
dividing one equation by the other and solving for q. If then a series
of iterations is run, we can compute a sequence of values for q as the
iteration counter n increases. As n increases, the computed q values
are expected to approach the convergence rate that characterizes the
particular iterative method. For the ratio we are looking at here, the
convergence ratio is 1.
Extend your module with a function compute_rates, which takes
an array (or a list) F with (e.g., 20) Fibonacci numbers as input and
computes (and prints) the corresponding values for q. Call the function
14
https://en.wikipedia.org/wiki/Johannes_Kepler
5.7 Exercises 177

from the test block and run the program. Do the convergence rates
approach the expected value?
Later, in Chapter 6.6.2, you will learn that convergence rates are very
useful when testing (verifying) software.
Solution. The code may be written as
import numpy as np

def make_Fibonacci(N):
F = np.zeros(N+1, int)
F[0] = 1
F[1] = 1
for n in range(2, N+1, 1):
F[n] = F[n-1] + F[n-2]
return F

def converging_ratio(F):
golden_ratio = (1 + np.sqrt(5))/2 # exact answer of limit
print(’Ratios:’)
for n in range(0, len(F)-1, 1):
print(’Difference: {:g}’.format(abs(F[n+1]/F[n] - golden_ratio)))
return

def compute_rates(F):
N = len(F)
ratio = np.zeros(N)
golden_ratio = (1 + np.sqrt(5))/2 # exact answer of limit
for n in range(0, N-1, 1):
ratio[n+1] = F[n+1]/F[n]

e = [abs(x_ - golden_ratio) for x_ in ratio] # compute errors


q = [np.log(e[n+1]/e[n])/np.log(e[n]/e[n-1]) # compute rates
for n in range(1, len(e)-1, 1)]
print(’Convergence rates:\n{}’.format(q))
return

if __name__ == ’__main__’:
N = 20
F = make_Fibonacci(N=N)
print(’F: {}’.format(F))
converging_ratio(F)
compute_rates(F)

Running the code, we get the output


F: [ 1 1 2 3 5 8 13 21 34
55 89 144 233 377 610 987 1597 2584
4181 6765 10946]

Ratios:
Difference: 0.618034
Difference: 0.381966
178 5 Some more Python essentials

Difference: 0.118034
Difference: 0.0486327
Difference: 0.018034
Difference: 0.00696601
Difference: 0.00264937
Difference: 0.00101363
Difference: 0.00038693
Difference: 0.000147829
Difference: 5.64607e-05
Difference: 2.15668e-05
Difference: 8.23768e-06
Difference: 3.14653e-06
Difference: 1.20186e-06
Difference: 4.59072e-07
Difference: 1.7535e-07
Difference: 6.69777e-08
Difference: 2.55832e-08
Difference: 9.77191e-09

Convergence rates:
[0.50000000000000022, 2.4404200904125539, 0.75503055617987125,
1.1188262733771752, 0.95885034928008239, 1.0162993425577451,
0.99386095485285253, 1.0023574544293534, 0.99910136923846515,
1.0003435140407022, 0.99986882838273905, 1.0000501087954012,
0.99998086100207262, 1.0000073105520131, 0.9999972075672463,
1.0000010668003683, 0.99999959442072428, 1.000000145950763,
0.99999997066155721]

From the output, we note that the sequence of Fibonacci numbers appears
to be fine. Also, from the printed “difference”, it seems that Kepler was
right about the ratio of two consecutive numbers. Finally, the convergence
rate approaches 1, just as expected.
Filename: Fibonacci_numbers.py.

Exercise 5.5: Read file - total volume of boxes


A file box_data.dat contains volume data for a collection of rectangular
boxes. These boxes all have the same bottom surface area, but (typically)
differ in height. The file could, for example, read:
Volume data for rectangular boxes
10.0 3.0
4.0
2.0
3.0
5.0

Apart from the header, each line represents one box. However, since they
all have the same bottom surface area, that area (10.0) is only given for
5.7 Exercises 179

the first box. For that first box, also the height (3.0) is given, as it is for
each of the following boxes.
a) Write down a formula for computing the total volume of all boxes
represented in the file. That formula should be written such that a
minimum of multiplications and additions is used.
Solution. Basically we have to sum up bottom surface area times height
for all boxes, and since the bottom surface area is the same, we have
a common factor. Thus, considering n boxes, our formula for the total
volume V becomes
n

V =A hi ,
i=1

where A is the bottom surface area and hi is the height of box i, i =


1, 2, ..., n.
b) Write a program that reads the file box_data.dat, computes the
total volume of all boxes represented in the file, and prints that volume
to the screen. In the calculations, apply the formula just derived.
(Note that, as a first step, you may read the file and just print (to
screen) what is read. Comparing this printout with file content (use some
editor) is then a good idea.)
Solution. The code reads:
"""Compute V = A*H = A*(h1+h2+...+hn)"""

filename = ’box_data.dat’
infile = open(filename, ’r’) # Open file for reading
line = infile.readline() # Read and skip first line

line = infile.readline() # Read second line, i.e. 1st box


words = line.split()

A = float(words[0]) # common base area for all boxes


H = float(words[1]) # height initialized, H = h1 + h2 + ...
for line in infile:
words = line.split()
H = H + float(words[0]) # ...add hi
infile.close()
V = A*H # Compute total volume of all boxes

print(’Total volume (m^3): {:g}’.format(V))

Running the program gives 170.0m3 as the total volume. This compares
favorably to what we get by hand.
180 5 Some more Python essentials

c) In the file box_data.dat, after the last line (containing the height of
the “last” box), insert a couple of empty lines, i.e. just press enter a few
times. Then, save the file and run the program anew. What happens?
Explain briefly.
Solution. Unless you already have handled this in your code, you get
an error message, since each line read is supposed to contain a height.
Filename: total_volume_boxes.py.

Exercise 5.6: Area of a polygon (...for the Assignment)


One of the most important mathematical problems through all times has
been to find the area of a polygon, especially because real estate areas
often had the shape of polygons, and it was necessary to pay tax for the
area. We have a polygon as depicted below.

The vertices (“corners”) of the polygon have coordinates (x1 , y1 ),


(x2 , y2 ), . . ., (xn , yn ), numbered either in a clockwise or counter clockwise
fashion. The area A of the polygon can amazingly be computed by just
knowing the boundary coordinates:

1
A= |(x1 y2 + x2 y3 + · · · + xn−1 yn + xn y1 ) − (y1 x2 + y2 x3 + · · · + yn−1 xn + yn x1 )| .
2
Write a function polyarea(x, y) that takes two coordinate arrays with
the vertices as arguments and returns the area.
5.7 Exercises 181

Test the function on a triangle, a quadrilateral, and a pentagon where


you can calculate the area by alternative methods for comparison.
Hint. Since Python lists and arrays have 0 as their first index, it is
wise to rewrite the mathematical formula in terms of vertex coordinates
numbered as x0 , x1 , . . . , xn−1 and y0 , y1 , . . . , yn−1 .
Solution. Code:
"""
Computes the area of a polygon from vertex
coordinates only.
"""

def polyarea(x, y):


n = len(x)
# next we may initialize area with those terms in the
# sum that does not follow the "increasing index pattern"
area = x[n-1]*y[0] - y[n-1]*x[0]
for i in range(0,n-1,1):
area += x[i]*y[i+1] - y[i]*x[i+1]
return 0.5*abs(area)

The function can be tested, e.g., by the lines


# pentagon
x = [0, 2, 2, 1, 0]
y = [0, 0, 2, 3, 2]
print(’Area pentagon (true value = 5): {:g}’.format(polyarea(x, y))
# quadrilateral
x = [0, 2, 2, 0]
y = [0, 0, 2, 2]
print(’Area quadrilateral (true value = 4): {:g}’.format(polyarea(x, y))
# triangle
x = [0, 2, 0]
y = [0, 0, 2]
print(’Area triangle (true value = 2): {:g}’.format(polyarea(x, y))

which may be added after the function definition.


Filename: polyarea.py.

Exercise 5.7: Count occurrences of a string in a string


In the analysis of genes one encounters many problem settings involving
searching for certain combinations of letters in a long string. For example,
we may have a string like
gene = ’AGTCAATGGAATAGGCCAAGCGAATATTTGGGCTACCA’
182 5 Some more Python essentials

We may traverse this string, letter by letter, by the for loop for letter
in gene. The length of the string is given by len(gene), so an alternative
traversal over an index i is for i in range(len(gene)). Letter number
i is reached through gene[i], and a substring from index i up to, but
not including j, is created by gene[i:j].
a) Write a function freq(letter, text) that returns the frequency of
the letter letter in the string text, i.e., the number of occurrences of
letter divided by the length of text. Call the function to determine the
frequency of C and G in the gene string above. Compute the frequency
by hand too.
b) Write a function pairs(letter, text) that counts how many times
a pair of the letter letter (e.g., GG) occurs within the string text. Use
the function to determine how many times the pair AA appears in the
string gene above. Perform a manual counting too to check the answer.
c) Write a function mystruct(text) that counts the number of a certain
structure in the string text. The structure is defined as G followed by A
or T until a double GG. Perform a manual search for the structure too to
control the computations by mystruct.
Solution. Here is a program:
gene = ’AGTCAATGGAATAGGCCAAGCGAATATTTGGGCTACCA’

def freq(letter, text):


counter = 0
for i in text:
if i == letter:
counter += 1
return counter/len(text)

def pairs(letter, text):


counter = 0
for i in range(len(text)):
if i < len(text)-1 and \
text[i] == letter and text[i+1] == letter:
counter += 1
return counter

def mystruct(text):
counter = 0
for i in range(len(text)):
# Search for the structure from position i
if text[i] == ’G’:
print(’found G at {:d}’.format(i))
# Search among A and T letters
j = i + 1
5.7 Exercises 183

while text[j] == ’A’ or text[j] == ’T’:


print(’next is ok: {:s}’.format(text[j]))
j = j + 1
print(’ending is {:s}’.format(text[j:j+2]))
if text[j:j+2] == ’GG’:
# Correct ending of structure
counter += 1
print(’yes’)
return counter

print(’frequency of C: {:.1f}’.format(freq(’C’, gene)))


print(’frequency of G: {:.1f}’.format(freq(’G’, gene)))
print(’no of pairs AA: {:d}’.format(pairs(’A’, gene)))
print(’no of structures: {:d}’.format(mystruct(gene)))

Filename: count_substrings.py.
Remarks. You are supposed to solve the tasks using simple program-
ming with loops and variables. While a) and b) are quite straightforward,
c) quickly involves demanding logic. However, there are powerful tools
available in Python that can solve the tasks efficiently in very compact
code: a) text.count(letter)/len(text); b) text.count(letter*2);
c) len(re.findall(’G[AT]+?GG’, text)). That is, there is rich func-
tionality for analysis of text in Python and this is particularly useful in
analysis of gene sequences.

Exercise 5.8: Compute combinations of sets


Consider an ID number consisting of two letters and three digits, e.g.,
RE198. How many different numbers can we have, and how can a program
generate all these combinations?
If a collection of n things can have m1 variations of the first thing, m2
of the second and so on, the total number of variations of the collection
equals m1 m2 · · · mn . In particular, the ID number exemplified above
can have 26 · 26 · 10 · 10 · 10 = 676, 000 variations. To generate all the
combinations, we must have five nested for loops. The first two run over
all letters A, B, and so on to Z, while the next three run over all digits
0, 1, . . . , 9.
To convince yourself about this result, start out with an ID number
on the form A3 where the first part can vary among A, B, and C, and
the digit can be among 1, 2, or 3. We must start with A and combine it
with 1, 2, and 3, then continue with B, combined with 1, 2, and 3, and
finally combine C with 1, 2, and 3. A double for loop does the work.
184 5 Some more Python essentials

a) In a deck of cards, each card is a combination of a rank and a suit.


There are 13 ranks: ace (A), 2, 3, 4, 5, 6, 7, 8, 9, 10, jack (J), queen
(Q), king (K), and four suits: clubs (C), diamonds (D), hearts (H), and
spades (S). A typical card may be D3. Write statements that generate a
deck of cards, i.e., all the combinations CA, C2, C3, and so on to SK.
Solution. Program:
ranks = [’A’, ’2’, ’3’, ’4’, ’5’, ’6’, ’7’,
’8’, ’9’, ’10’, ’J’, ’Q’, ’K’]
suits = [’C’, ’D’, ’H’, ’S’]
deck = []
for s in suits:
for r in ranks:
deck.append(s + r)
print(deck)

b) A vehicle registration number is on the form DE562, where the letters


vary from A to Z and the digits from 0 to 9. Write statements that
compute all the possible registration numbers and stores them in a list.
Solution. Program:
import string
letters = string.ascii_uppercase
digits = range(10)
registration_numbers = []
for place1 in letters:
for place2 in letters:
for place3 in digits:
for place4 in digits:
for place5 in digits:
registration_numbers.append(
’%s%s%s%s%s’ %
(place1, place2, place3, place4, place5))
print(registration_numbers)

c) Generate all the combinations of throwing two dice (the number of


eyes can vary from 1 to 6). Count how many combinations where the
sum of the eyes equals 7.
Answer. 6
Solution. Program:
dice = []
for d1 in range(1, 7):
for d2 in range(1, 7):
dice.append((d1, d2))
5.7 Exercises 185

n = 0
for d1, d2 in dice:
if d1 + d2 == 7:
n += 1

print(’{:d} combinations give the sum 7’.format(n))

Filename: combine_sets.py.

You might also like