# MAS212 Class Test 1 (2017)¶

## Aim:¶

• To help you learn some Python essentials and programming skills to enjoy the remainder of this course.

How it works:

• Complete the notebook below, as follows. Click on a question, then from the menu select Insert -> Insert Cell Below. From the dropdown box (next to the "stop" symbol) choose whether you wish to insert 'Code' or 'Markdown'. Save regularly!
• Press Shift-Enter to execute the code in a cell.
• Submit the .ipynb file via http://somas-uploads.shef.ac.uk/mas212 by 23.59pm on Sun 8th Oct 2017. If you are off-campus, you will need to use a VPN to submit.
• Your lecturer will mark each question as either 2 (good), 1 (needs revision), 0 (not attempted/wrong).
• This is an open-book test, which means you may consult books, notes and internet resources. Do not discuss or share your test with anyone. Copy-and-pasting is not permitted. Please give answers in your own words.

Key Resources:

• Lecture materials and links on course web page: http://sam-dolan.staff.shef.ac.uk/mas212/

## A. The Python language

1. Describe the three numeric data types in Python 3.

The three numeric types are int, float and complex. int is an integer, a whole number, that is, any finite member of the set $\mathbb{Z}$. float is a floating-point number: a representation of a member of the reals $\mathbb{R}$ with a finite precision for its mantissa and exponent. complex is a representation of a complex number in $\mathbb{C}$: it can be thought of as a pair of floats, representing its real and imaginary parts.

2. Describe two key differences between lists and arrays. (More specifically, between the list and ndarray types).

Some key differences between lists and arrays include the following:

• Arrays are homogeneous, whereas lists are heterogeneous. In other words, all elements of an array must have the same data type, whereas elements of lists may have different data types.
• Arrays are typically fixed in size, whereas lists can be lengthened or shortened.
• Arrays are typically contiguous chunks of memory, whereas lists are not.
• Arrays may be operated on with universal functions (ufuncs, see below), which operate on a whole array in an efficient manner. This enables vectorizaton. Universal functions are not available for lists.
• Arrays can be multi-dimensional, whereas lists are one-dimensional (though lists-of-lists can be used to mimic multi-dimensional structures).

3. What does 'mutable' mean? Give three examples of data types that are mutable. (N.B. Common data types include int, list, tuple, dict, set, string, float).</b>

Mutable means "can be changed". The container types list, dict and set are mutable, whereas int, tuple, string and float are not.

4. What is a Universal Function? (in the context of the numpy module)

A universal function, or ufunc, is a function which operates on a whole array, or arrays, in an element-by-element ("elementwise") fashion. numpy includes many ufuncs, which, under the hood, are typically implemented in C (a low-level compiled language) to improve their speed and efficiency. A example ufunc (np.log()) is shown below:

In [1]:
import numpy as np
A = np.linspace(1.0, 10.0, 20)
np.log(A)

Out[1]:
array([ 0.        ,  0.38776553,  0.66647893,  0.88420242,  1.06289421,
1.2144441 ,  1.34602046,  1.46228027,  1.56642053,  1.66073121,
1.7469089 ,  1.82624565,  1.89974811,  1.96821591,  2.03229476,
2.09251362,  2.14931122,  2.2030555 ,  2.25405805,  2.30258509])

5(a) What is "broadcasting"? (in the context of the numpy module).
(b) What is the shape of A*B, if the arrays A and B have shapes (1,3,4) and (2,5,3,1)?

Broadcasting is a set of rules for combining a pair of arrays of different, or the same, shapes and sizes using arithmetic operations. Where possible, the smaller array is “broadcast” across the larger array to make the shapes compatible. It is efficient because the loops over the array elements are carried out in C, which is a low-level compiled language which is much faster than Python.

## B. Python code

6. Python as a calculator: Calculate $6 - 2$, $(3^2 + 5^2)^{1/3}$, $\tan(\pi/8)$ and $(3 + 5i)^{3}$.

In [2]:
import math
[6-2, (3**2+5**2)**(1/3.0), math.tan(math.pi/8.0), complex(3,5)**3]

Out[2]:
[4, 3.239611801277483, 0.4142135623730951, (-198+10j)]

7. Find the following sum: $$\sum_{k=1}^{10} \frac{1}{k^2} = 1 + \frac{1}{4} + \frac{1}{9} + \ldots + \frac{1}{100}$$

In [3]:
sum([1.0/k**2 for k in range(1,11)])

Out[3]:
1.5497677311665408

8. Print the value of $\ln(2)$ to 9 significant figures.

In [4]:
print("%.9f" % math.log(2.0))

0.693147181


9. Using the formula below, calculate Euler's number $e$ to an accuracy of 10 significant figures. $$e = \sum_{n=0}^{\infty} \frac{1}{n!}$$

In [5]:
tot = 1  # the running total for the sum
fac = 1  # the running total for the factorial
for k in range(1,100):
fac = fac*k
if fac > 10**10:  # if the next term in the series is smaller than 1e-10 we may stop the loop
break
tot += 1.0/float(fac)
print("e = %.9f" % tot)

e = 2.718281828


10. Use the decimal package to display $e$ to 50 decimal places.

In [6]:
from decimal import Decimal, getcontext
getcontext().prec = 51
Decimal(1).exp()

Out[6]:
Decimal('2.71828182845904523536028747135266249775724709369996')

11. Write a function sumdigits(N) to compute the sum of the digits of a number. For example, sumdigits(1234) would return 10 (as 1+2+3+4=10).
Use the function to compute the sum of the digits in your registration number.

In [7]:
def sumdigits(N):
l = [int(s) for s in str(N)]
return sum(l)
sumdigits(123456789)  # supposing my reg number were 123456789

Out[7]:
45

12. Apply the Newton-Raphson method $$x_{n+1} = x_n - \frac{f(x_n)}{f'(x_n)}$$ to compute the largest root of $f(x) = x^3 - 5x - 1$ to 10 decimal places.

In [8]:
def NR_iterate(x):
"""Carry out one step of NR method for this polynomial"""
return x - (x**3 - 5*x - 1) / (3.0*x**2 - 5.0)
xs = [3.0]  # initial guess
for k in range(100):
x = NR_iterate(xs[-1])
if abs(x - xs[-1]) < 1.0e-11:   # Check whether accuracy criterion is satisfied; if so, break out of loop
print("after %d iterations:" % k)
break
xs.append(x)
print("%.10f" % xs[-1])

after 5 iterations:
2.3300587396


## C. String processing

In [9]:
s = "It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it was the season of light, it was the season of darkness, it was the spring of hope, it was the winter of despair, we had everything before us, we had nothing before us, we were all going direct to Heaven, we were all going direct the other way. In short, the period was so far like the present period, that some of its noisiest authorities insisted on its being received, for good or for evil, in the superlative degree of comparison only."
print(s)

It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it was the season of light, it was the season of darkness, it was the spring of hope, it was the winter of despair, we had everything before us, we had nothing before us, we were all going direct to Heaven, we were all going direct the other way. In short, the period was so far like the present period, that some of its noisiest authorities insisted on its being received, for good or for evil, in the superlative degree of comparison only.


13. How many times does the word 'it' appear in this paragraph? (N.B. disregard case, so count 'It' as well as 'it')

In [17]:
s1 = s.lower().replace(",", "").replace(".", "")  # remove punctuation
words = s1.split(" ")
words.count("it")

Out[17]:
10

14. How many words are in this paragraph? How many different words?

In [18]:
s1 = s.lower().replace(",", "").replace(".","")  #  Remove punctuation
words = s1.split(" ")   # list of words
print(len(words))       # number of words
print(len(set(words)))  # number of distinct words

119
58


15. The letter 'e'/'E' appears 69 times in this paragraph. Which letter is the next most common, and how many times does it appear?

In [22]:
alphabet = "abcdefghijklmnopqrstuvwxyz"
counts = [s.lower().count(c) for c in alphabet]
for i in range(len(alphabet)):
print("%s appears %d times" % (alphabet[i], counts[i]))

a appears 28 times
b appears 5 times
c appears 7 times
d appears 14 times
e appears 69 times
f appears 19 times
g appears 13 times
h appears 28 times
i appears 45 times
j appears 0 times
k appears 2 times
l appears 12 times
m appears 5 times
n appears 22 times
o appears 44 times
p appears 10 times
q appears 0 times
r appears 27 times
s appears 42 times
t appears 48 times
u appears 5 times
v appears 5 times
w appears 21 times
x appears 0 times
y appears 4 times
z appears 0 times


So 't' is the next most frequently-occurring letter. It appears 48 times.

## D. numpy module

In [23]:
import numpy as np


16. Create a new array A containing the following data: $$A = \begin{bmatrix} 1 & 2 & 3 & \ldots & 10 \\ 2 & 3 & 4 & \ldots & 11 \\ \ldots \\ 5 & 6 & 7 & \ldots & 14 \end{bmatrix}$$

In [24]:
# Here I make this from a list-of-lists. Other ways are possible
A = np.array([[j+k for j in range(1,11)] for k in range(0,5)])
print(A)

[[ 1  2  3  4  5  6  7  8  9 10]
[ 2  3  4  5  6  7  8  9 10 11]
[ 3  4  5  6  7  8  9 10 11 12]
[ 4  5  6  7  8  9 10 11 12 13]
[ 5  6  7  8  9 10 11 12 13 14]]


17. Find the matrix product A B where $$A = \begin{bmatrix} 5 & 6 & 2 \\ 1 & -1 & \frac{3}{4} \end{bmatrix} , \quad \quad B = \begin{bmatrix} 1 & 2 \\ 3 & -4 \\ 5 & 0 \end{bmatrix}$$

In [25]:
A = np.matrix("5,6,2 ; 1,-1,0.75")   # an easy way to define a matrix
B = np.matrix("1,2 ; 3,-4 ; 5,0")
C=A*B
print(C)

[[ 33.   -14.  ]
[  1.75   6.  ]]


18. Find the eigenvalues of the real symmetric matrix $$A = \begin{bmatrix} -1 & 2 & 3 \\ 2 & 4 & 1 \\ 3 & 1 & -3 \end{bmatrix} .$$ and verify that the sum of the eigenvalues is zero.

In [26]:
A = np.matrix("-1,2,3 ; 2,4,1 ; 3,1,-3")
eigs = np.linalg.eig(A)[0]
print("The eigenvalues are %.6f, %.6f and %.6f" % tuple(eigs))
print("The sum of eigenvalues is %.4e, which is zero to within machine precision." % sum(eigs))

The eigenvalues are -5.177534, -0.037039 and 5.214573
The sum of eigenvalues is -3.5527e-15, which is zero to within machine precision.


## E. Cryptography

In [27]:
def scramble(s, keyword="aardvark"):
"""Scrambles a string."""
n = len(keyword)
s2 = ""
for k in range(len(s)):
c = s[k]
cnew = c
if c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
k = (ord(c) + ord(keyword.upper()[k % n]) - 2*65) % 26
cnew = chr(65 + k)
elif c in 'abcdefghijklmnopqrstuvwxyz':
k = (ord(c) + ord(keyword.lower()[k % n]) - 2*97) % 26
cnew = chr(97 + k)
s2 = s2 + cnew
return s2

scramble("Hello everyone, blah blah blah.")

Out[27]:
'Hecoj vferprie, blrk bckh sovh.'

19. Describe in your own words what the scramble function does.

The scramble function encrypts a message using a keyword. It is a version of a Vigenère cipher, which is a method of encrypting alphabetic text by using a series of different Caesar ciphers based on the letters of a keyword (https://en.wikipedia.org/wiki/Vigenère_cipher).

To see how this works, we can line up the characters in the plaintext with the characters in the keyword and the encrypted message, as below :

 Hello everyone, blah blah blah. aardvarkaardvarkaardvarkaardvar Hecoj vferprie, blrk bckh sovh. 

The ciphertext is generated from the plaintext by using the characters in the keyword to move letters "around the letter wheel". Note that 'a' in the keyword leads to no change, whereas 'r' moves on by 17 (as 'r' is the 18th letter of the alphabet). Punctuation is not affected.

20. Write a function unscramble to reverse its effect.

Use your function to decipher the following quote by a famous literary detective. From which novel is it taken?</b>

In [30]:
txt = "Hon jfkon ydqe S srly ky yfx tykt nkzn iou kvvv elzpdnrded wce smpfvnisve, zcakovei medkinj, hfgevvu idzrosdwlv, mlvo so tyh tieth?"

In [31]:
# One way would be to copy-and-paste the scramble function and change some signs in the right places.
# An alternative way is to deduce a new keyword that generates the inverse transformation.
def unscramble(s, keyword="aardvark"):
reversekey = "".join([chr(((-(ord(c) - 97)) % 26) + 97) for c in keyword])
return scramble(s, reversekey)
unscramble(txt)

Out[31]:
'How often have I said to you that when you have eliminated the impossible, whatever remains, however improbable, must be the truth?'

This is a line spoken by Sherlock Holmes in "The Sign of the Four" (1890) by Arthur Conan Doyle.

In [ ]: