Coding Bootcamp: Python tutorial (continued)

  • Adapted from the original online tutorial.

  • The material will give you an idea of the Python programming language.

Strings

  • Besides numbers, Python can also manipulate strings.

  • A string is another type of the Python programming language.

  • Strings can be expressed in several ways.

  • They can be enclosed in single quotes ('...') or double quotes ("...") with the same result.

  • The character \ can be used to escape quotes.

  • This is a string enclosed in single quotes.
In [1]:
'spam eggs'
Out[1]:
'spam eggs'
  • And this is a string enclosed in single quotes where we want to include a single quote.

  • We can use \ to escape the single quote.

In [2]:
'doesn\'t'
Out[2]:
"doesn't"
  • Alternatively, we can use double quotes to enclose the string.
In [3]:
"doesn't"
Out[3]:
"doesn't"
  • Conversely, if we want to include double quotes inside a string, we can use single quotes to enclose it.
In [4]:
'"Yes," he said.'
Out[4]:
'"Yes," he said.'
  • Or we can always escape the embedded double quotes with \.
In [5]:
"\"Yes,\" he said."
Out[5]:
'"Yes," he said.'
  • And here is how we can include both double and single quotes inside a single-quoted string.
In [6]:
'"Isn\'t," she said.'
Out[6]:
'"Isn\'t," she said.'
  • In the interactive interpreter, the output string is always enclosed in quotes and special characters are escaped with backslashes.

  • In particular, the string is enclosed in double quotes if the string contains a single quote and no double quotes, otherwise it is enclosed in single quotes.

  • The print() function produces a more readable output, by omitting the enclosing quotes and by printing escaped and special characters.

In [7]:
print('"Isn\'t," she said.')
"Isn't," she said.
  • The character \n embeds a newline character in a string.

  • When the print() function encounters \n in a string, it will start a new line.

In [8]:
s = 'First line.\nSecond line.'
print(s)
First line.
Second line.
  • Note that the Python interpreter will print the string as it is, without translating the newlines.
In [9]:
s
Out[9]:
'First line.\nSecond line.'

Raw strings

  • Apart from newlines, there are other special characters prefaced by \; for example, \t is a tab character.

  • If you don’t want characters prefaced by \ to be interpreted as special characters, you can use raw strings by adding an r before the first quote.

In [10]:
print('C:\some\name')  # here \n means newline!
C:\some
ame
In [11]:
print(r'C:\some\name')  # note the r before the quote
C:\some\name

Strings spanning multiple lines

  • String literals can span multiple lines.

  • One way is using triple-quotes: """...""" or '''...'''.

  • End of lines are automatically included in the string, but it’s possible to prevent this by adding a \ at the end of the line.

In [12]:
print("""\
Usage: thingy [OPTIONS]
     -h                        Display this usage message
     -H hostname               Hostname to connect to
""")
Usage: thingy [OPTIONS]
     -h                        Display this usage message
     -H hostname               Hostname to connect to

String concatenation

Strings can be concatenated (glued together) with the + operator, and repeated with *.

In [13]:
# 3 times 'un', followed by 'ium'
3 * 'un' + 'ium'
Out[13]:
'unununium'
  • Two or more string literals (i.e., the ones enclosed between quotes) next to each other are automatically concatenated.
In [14]:
'Py' 'thon'
Out[14]:
'Python'
  • This only works with two literals though, not with variables or expressions.
prefix = 'Py'
prefix 'thon'  # can't concatenate a variable and a string literal
    prefix 'thon'  # can't concatenate a variable and a string literal
                ^
SyntaxError: invalid syntax
  • If you want to concatenate variables or a variable and a literal, use +:
In [15]:
prefix = 'Py'
prefix + 'thon'
Out[15]:
'Python'
  • This feature is particularly useful when you want to break long strings.
In [16]:
text = ('Put several strings within parentheses '
            'to have them joined together.')
text
Out[16]:
'Put several strings within parentheses to have them joined together.'

String indexing

  • Strings can be indexed (subscripted), with the first character having index 0.

  • There is no separate character type; a character is simply a string of size one.

In [17]:
word = 'Python'
word[0] # character in position 0
Out[17]:
'P'
In [18]:
word[5] # character in position 5
Out[18]:
'n'
  • Indices may also be negative numbers, to start counting from the right.

  • Note that since -0 is the same as 0, negative indices start from -1.

In [19]:
word[-1]  # last character
Out[19]:
'n'
In [20]:
word[-2]  # second-last character
Out[20]:
'o'
In [21]:
word[-6]
Out[21]:
'P'

String slicing

  • In addition to indexing, slicing is also supported.

  • While indexing is used to obtain individual characters, slicing allows you to obtain substrings.

In [22]:
word[0:2]  # characters from position 0 (included) to 2 (excluded)
Out[22]:
'Py'
In [23]:
word[2:5]  # characters from position 2 (included) to 5 (excluded)
Out[23]:
'tho'
  • Note how the start is always included, and the end always excluded.

  • This makes sure that s[:i] + s[i:] is always equal to s.

In [24]:
word[:2] + word[2:]
Out[24]:
'Python'
In [25]:
word[:4] + word[4:]
Out[25]:
'Python'
  • Slice indices have useful defaults.

  • An omitted first index defaults to zero.

  • An omitted second index defaults to the size of the string being sliced.

In [26]:
word[:2]  # character from the beginning to position 2 (excluded)
Out[26]:
'Py'
In [27]:
word[4:]  # characters from position 4 (included) to the end
Out[27]:
'on'
In [165]:
word[-2:] # characters from the second-last (included) to the end
Out[165]:
'on'
  • If we use a third part in the slice, it indicates the step over which we will be traversing the string.
In [167]:
word[::2]
Out[167]:
'Pto'
  • Note that we can go backwards as well as forwards.
In [168]:
word[::-1]
Out[168]:
'nohtyP'
In [170]:
word[:1:-2]
Out[170]:
'nh'
  • One way to remember how slices work is to think of the indices as pointing between characters.

  • The left edge of the first character is numbered 0.

  • Then the right edge of the last character of a string of n characters has index n.

 +---+---+---+---+---+---+
 | P | y | t | h | o | n |
 +---+---+---+---+---+---+
 0   1   2   3   4   5   6
-6  -5  -4  -3  -2  -1
  • The first row of numbers gives the position of the indices 0...6 in the string.

  • The second row gives the corresponding negative indices.

  • The slice from i to j consists of all characters between the edges labeled i and j, respectively.

  • For non-negative indices, the length of a slice is the difference of the indices, if both are within bounds. For example, the length of word[1:3] is 2.

  • Attempting to use an index that is too large will result in an error.
word[42]
IndexError: string index out of range
  • However, out of range slice indexes are handled gracefully when used for slicing.
In [29]:
 word[4:42]
Out[29]:
'on'
In [30]:
word[42:]
Out[30]:
''

String immutability

  • Python strings cannot be changed — they are immutable.

  • Therefore, assigning to an indexed position in the string results in an error.

word[0] = 'J'
TypeError: 'str' object does not support item assignment
word[2:] = 'py'
TypeError: 'str' object does not support item assignment
  • If you need a different string, you should create a new one.
In [31]:
'J' + word[1:]
Out[31]:
'Jython'
In [32]:
word[:2] + 'py'
Out[32]:
'Pypy'

String length

  • The built-in function len() returns the length of a string.
In [33]:
s = 'supercalifragilisticexpialidocious'
len(s)
Out[33]:
34

What is really a string?

  • A string is a sequence of Unicode code points.

  • That means that each character in a string is actually a number that corresponds to a Unicode code point.

  • To see the number that is behind a character we can use the ord() function.

  • String in English
In [34]:
s = 'Hello, World'
print(s)
for x in s:
    print(ord(x), end=":")
print()
Hello, World
72:101:108:108:111:44:32:87:111:114:108:100:
  • Note that uppercase English characters are numbered consecutively.

  • The same goes for lowercase English characters.

  • In what follows, ASCII (American Standard Code for Information Interchange) is an older character encoding, the original encoding for English, which is now a subset of Unicode.

In [35]:
import string

for char in string.ascii_uppercase:
    print(char, "=", ord(char), end=" ")

print()    
for char in string.ascii_lowercase:
    print(char, "=", ord(char), end=" ")
A = 65 B = 66 C = 67 D = 68 E = 69 F = 70 G = 71 H = 72 I = 73 J = 74 K = 75 L = 76 M = 77 N = 78 O = 79 P = 80 Q = 81 R = 82 S = 83 T = 84 U = 85 V = 86 W = 87 X = 88 Y = 89 Z = 90 
a = 97 b = 98 c = 99 d = 100 e = 101 f = 102 g = 103 h = 104 i = 105 j = 106 k = 107 l = 108 m = 109 n = 110 o = 111 p = 112 q = 113 r = 114 s = 115 t = 116 u = 117 v = 118 w = 119 x = 120 y = 121 z = 122 
  • String in Greek
In [36]:
s = "Καλημέρα, Κόσμε"
print(s)
for x in s:
    print(ord(x), end=":")
Καλημέρα, Κόσμε
922:945:955:951:956:941:961:945:44:32:922:972:963:956:949:
  • String in Chinese
In [37]:
s = "你好,世界"
print(s)
for x in s:
    print(ord(x), end=":")
你好,世界
20320:22909:65292:19990:30028:
  • String in Japanese
In [38]:
s = "こんにちは世界"
print(s)
for x in s:
    print(ord(x), end=":")
こんにちは世界
12371:12435:12395:12385:12399:19990:30028:
  • String in Korean
In [39]:
s = "안녕하세요 세계"
print(s)
for x in s:
    print(ord(x), end=":")
안녕하세요 세계
50504:45397:54616:49464:50836:32:49464:44228:
  • A numeric string is not a number!
In [40]:
s = '12345'
print(s)
for x in s:
    print(ord(x), end=":")
12345
49:50:51:52:53:
  • If we want to do the opposite, that is, convert a number to a character, then we can use the chr() function.
In [41]:
for x in [72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100]:
    print(chr(x), end="")
Hello, World

Splitting a string.

  • We can split a string on a specific character using the split() method.

  • split() is a method, because it is a function that belongs to an object (in this case, a string).

  • If we give the maxsplit argument, we will get that number of elements in the result.

  • If we do not specify a character to split, we split on whitespace.

In [42]:
'1,2,3'.split(',')
Out[42]:
['1', '2', '3']
In [43]:
'1,2,3'.split(',', maxsplit=1)
Out[43]:
['1', '2,3']
In [44]:
'1,2,,3,'.split(',')
Out[44]:
['1', '2', '', '3', '']
In [45]:
'1 2  3   4'.split()
Out[45]:
['1', '2', '3', '4']

Lists

  • Python knows a number of compound data types, used to group together other values.

  • The most versatile is the list, which can be written as a list of comma-separated values (items) between square brackets.

  • Lists might contain items of different types, but usually the items all have the same type.

In [46]:
squares = [1, 4, 9, 16, 25]
squares
Out[46]:
[1, 4, 9, 16, 25]

List indexing and slicing

  • Like strings (and all other built-in sequence type), lists can be indexed and sliced.
In [47]:
squares[0] # indexing returns the item
Out[47]:
1
In [48]:
squares[-1]
Out[48]:
25
In [171]:
squares[-3:]  # slicing returns a new list
Out[171]:
[49, 64, 81]
  • As in strings, we can take a part, and use a step.
In [176]:
squares[1:6:2]
Out[176]:
[1, 9, 25]
In [175]:
squares[4:1:-1]
Out[175]:
[16, 9, 4]
  • All slice operations return a new list containing the requested elements.

  • This means that the following slice returns a new (shallow) copy of the list.

  • "Shallow" means that if the elements of the list are themselves compound data types, references to them are created, and not complete copies.

In [50]:
squares[:]
Out[50]:
[1, 4, 9, 16, 25]
  • Lists also support operations like concatenation.
In [51]:
squares + [36, 49, 64, 81, 100]
Out[51]:
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

List mutability

  • Unlike strings, which are immutable, lists are a mutable type, i.e. it is possible to change their content.
In [52]:
cubes = [1, 8, 27, 65, 125]  # something's wrong here
4 ** 3  # the cube of 4 is 64, not 65!
Out[52]:
64
In [53]:
cubes[3] = 64  # replace the wrong value
cubes
Out[53]:
[1, 8, 27, 64, 125]
  • You can also add new items at the end of the list, by using the append() method (we will see more about methods later).
In [54]:
cubes.append(216)  # add the cube of 6
cubes.append(7 ** 3)  # and the cube of 7
cubes
Out[54]:
[1, 8, 27, 64, 125, 216, 343]

Assigning to slices

  • Assignment to slices is also possible, and this can even change the size of the list or clear it entirely.
In [55]:
letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
letters
Out[55]:
['a', 'b', 'c', 'd', 'e', 'f', 'g']
In [56]:
# replace some values
letters[2:5] = ['C', 'D', 'E']
letters
Out[56]:
['a', 'b', 'C', 'D', 'E', 'f', 'g']
In [57]:
# now remove them
letters[2:5] = []
letters
Out[57]:
['a', 'b', 'f', 'g']
In [58]:
# clear the list by replacing all the elements with an empty list
letters[:] = []
letters
[]
Out[58]:
[]

List length

The built-in function len() also applies to lists.

In [59]:
letters = ['a', 'b', 'c', 'd']
len(letters)
Out[59]:
4

Nested lists

  • It is possible to nest lists (create lists containing other lists).
In [60]:
a = ['a', 'b', 'c']
n = [1, 2, 3]
x = [a, n]
x
Out[60]:
[['a', 'b', 'c'], [1, 2, 3]]
In [61]:
x[0]
Out[61]:
['a', 'b', 'c']
In [62]:
x[0][1]
Out[62]:
'b'

More list methods

  • The list data type offers plenty of methods.

  • list.append(x): Add an item to the end of the list. Equivalent to a[len(a):] = [x].

  • list.extend(L): Extend the list by appending all the items in the given list. Equivalent to a[len(a):] = L.

  • list.insert(i, x): Insert an item at a given position. The first argument is the index of the element before which to insert, so a.insert(0, x) inserts at the front of the list, and a.insert(len(a), x) is equivalent to a.append(x).

  • list.remove(x): Remove the first item from the list whose value is x. It is an error if there is no such item.

  • list.pop([i]): Remove the item at the given position in the list, and return it. If no index is specified, a.pop() removes and returns the last item in the list. (The square brackets around the i in the method signature denote that the parameter is optional, not that you should type square brackets at that position. You will see this notation frequently in the Python Library Reference.)

  • list.clear(): Remove all items from the list. Equivalent to del a[:].

  • list.index(x): Return the index in the list of the first item whose value is x. It is an error if there is no such item.

  • list.count(x): Return the number of times x appears in the list.

  • list.sort(key=None, reverse=False): Sort the items of the list in place (the arguments can be used for sort customization, see sorted() for their explanation).

  • list.reverse(): Reverse the elements of the list in place.

  • list.copy(): Return a shallow copy of the list. Equivalent to a[:].

In [63]:
a = [66.25, 333, 333, 1, 1234.5]
print(a.count(333), a.count(66.25), a.count('x'))
2 1 0
In [64]:
a.insert(2, -1)
a.append(333)
a
Out[64]:
[66.25, 333, -1, 333, 1, 1234.5, 333]
In [65]:
a.index(333)
Out[65]:
1
In [66]:
a.remove(333)
a
Out[66]:
[66.25, -1, 333, 1, 1234.5, 333]
In [67]:
a.reverse()
a
Out[67]:
[333, 1234.5, 1, 333, -1, 66.25]
In [68]:
a.sort()
a
[-1, 1, 66.25, 333, 333, 1234.5]
Out[68]:
[-1, 1, 66.25, 333, 333, 1234.5]
In [69]:
a.pop()
1234.5
Out[69]:
1234.5
In [70]:
a
Out[70]:
[-1, 1, 66.25, 333, 333]

Using lists as stacks

  • The list methods make it very easy to use a list as a stack, where the last element added is the first element retrieved (“last-in, first-out”).

  • To add an item to the top of the stack, use append().

  • To retrieve an item from the top of the stack, use pop() without an explicit index.

In [71]:
stack = [3, 4, 5]
stack.append(6)
stack.append(7)
stack
Out[71]:
[3, 4, 5, 6, 7]
In [72]:
stack.pop()
Out[72]:
7
In [73]:
stack
Out[73]:
[3, 4, 5, 6]
In [74]:
stack.pop()
Out[74]:
6
In [75]:
stack.pop()
Out[75]:
5
In [76]:
stack
Out[76]:
[3, 4]

List comprehensions

  • List comprehensions provide a concise way to create lists.

  • Common applications are to make new lists where each element is the result of some operations applied to each member of another sequence or iterable, or to create a subsequence of those elements that satisfy a certain condition.

  • For example, assume we want to create a list of squares.

In [77]:
squares = []
for x in range(10):
    squares.append(x**2)
squares
Out[77]:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
  • Note that this creates (or overwrites) a variable named x that still exists after the loop completes. We can calculate the list of squares without any side effects using a different approach.
In [78]:
squares = [x**2 for x in range(10)]
  • That is more consise and readable.
  • A list comprehension consists of brackets containing an expression followed by a for clause, then zero or more for or if clauses.

  • The result will be a new list resulting from evaluating the expression in the context of the for and if clauses which follow it.

  • This list comprehension combines the elements of two lists if they are not equal.
In [79]:
[(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
Out[79]:
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
  • It is equivalent to the following.
In [80]:
combs = []
for x in [1,2,3]:
    for y in [3,1,4]:
        if x != y:
            combs.append((x, y))
combs
Out[80]:
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
  • Note how the order of the for and if statements is the same in both these snippets.

  • If the expression is a tuple (e.g. the (x, y) in the previous example), it must be parenthesized.

  • Other examples:
In [81]:
vec = [-4, -2, 0, 2, 4]
# create a new list with the values doubled
[x*2 for x in vec]
Out[81]:
[-8, -4, 0, 4, 8]
In [82]:
# filter the list to exclude negative numbers
[x for x in vec if x >= 0]
Out[82]:
[0, 2, 4]
In [83]:
# apply a function to all the elements
[abs(x) for x in vec]
Out[83]:
[4, 2, 0, 2, 4]
In [84]:
# call a method on each element
freshfruit = ['  banana', '  loganberry ', 'passion fruit  ']
[weapon.strip() for weapon in freshfruit]
Out[84]:
['banana', 'loganberry', 'passion fruit']
In [85]:
# create a list of 2-tuples like (number, square)
[(x, x**2) for x in range(6)]
Out[85]:
[(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25)]
  • As we mentioned above, the tuple must be parenthesized, otherwise an error is raised
[x, x**2 for x in range(6)]
 [x, x**2 for x in range(6)]
               ^
SyntaxError: invalid syntax
In [86]:
# flatten a list using a listcomp with two 'for'
vec = [[1,2,3], [4,5,6], [7,8,9]]
[num for elem in vec for num in elem]
Out[86]:
[1, 2, 3, 4, 5, 6, 7, 8, 9]
  • List comprehensions can contain complex expressions and nested functions.
In [87]:
from math import pi
[str(round(pi, i)) for i in range(1, 6)]
Out[87]:
['3.1', '3.14', '3.142', '3.1416', '3.14159']

Nested list comprehensions

  • The initial expression in a list comprehension can be any arbitrary expression, including another list comprehension.

  • Consider the following example of a 3x4 matrix implemented as a list of 3 lists of length 4.

In [88]:
 matrix = [
        [1, 2, 3, 4],
        [5, 6, 7, 8],
        [9, 10, 11, 12],
]
  • The following list comprehension will transpose rows and columns.
In [89]:
[[row[i] for row in matrix] for i in range(4)]
Out[89]:
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
  • As we saw previously, the nested list comprehension is evaluated in the context of the for that follows it, so this example is equivalent to the following.
In [90]:
transposed = []
for i in range(4):
    transposed.append([row[i] for row in matrix])
transposed
Out[90]:
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
  • This, in turn, is the same as the following.
In [91]:
transposed = []
for i in range(4):
    # the following 3 lines implement the nested listcomp
    transposed_row = []
    for row in matrix:
        transposed_row.append(row[i])
    transposed.append(transposed_row)

transposed
Out[91]:
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
  • In the real world, you should prefer built-in functions to complex flow statements.

  • The zip() function would do a great job for this use case.

In [92]:
list(zip(*matrix))
Out[92]:
[(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]

The del statement

  • There is a way to remove an item from a list given its index instead of its value: the del statement.

  • This differs from the pop() method which returns a value.

  • The del statement can also be used to remove slices from a list or clear the entire list (which we did earlier by assignment of an empty list to the slice).

In [93]:
a = [-1, 1, 66.25, 333, 333, 1234.5]
del a[0]
a
Out[93]:
[1, 66.25, 333, 333, 1234.5]
In [94]:
del a[2:4]
a
Out[94]:
[1, 66.25, 1234.5]
In [95]:
del a[:]
a
Out[95]:
[]
  • del can also be used to delete entire variables.
In [96]:
del a
  • Referencing the name a hereafter is an error (at least until another value is assigned to it).

if Statements

  • Perhaps the most well-known statement type is the if statement.
x = int(input("Please enter an integer: "))
Please enter an integer: 10
if x < 0:
    x = 0
    print('Negative changed to zero')
elif x == 0:
    print('Zero')
elif x == 1:
    print('Single')
else:
    print('More')
More
  • There can be zero or more elif parts, and the else part is optional.

  • The keyword elif is short for else if, and is useful to avoid excessive indentation.

  • An if ... elif ... elif ... sequence is a substitute for the switch or case statements found in other languages.

  • There are no switch or case statements in Python.

True and false in Python

  • In Python the following values are considered false:

  • None

  • False

  • zero of any numeric type, for example, 0, 0.0, 0j.

  • any empty sequence, for example, '', (), [].

  • any empty mapping, for example, {}.

  • instances of user-defined classes, if the class defines a __bool__() or __len__() method, when that method returns the integer zero or bool value False.

  • Everything else is considered true.

Boolean operators

  • Python Boolean operators in decreasing order of precedence:
Operation Result Notes
not x if x is false, then True, else False lower priority than non-boolean operators
x or y if x is false, then y, else x short-circuit
x and y if x is false, then x, else y short-circuit
  • not a == b means not (a == b); a == not b is a syntax error.

Identity testing

  • The keyword is is used for identity testing.

  • The negation is not is.

  • Identity testing means checking that two objects are the same, not that they are equal.

  • A frequent use of is is against None: x is None, x is not None, not x is None.

In [160]:
a = "a"
b = "b"
c = a + b
d = "ab"

print("c == d:", c == d)
print ("c is d:", c is d)
c == d: True
c is d: False
In [161]:
ra = range(10)
rb = range(10)
print("ra == rb:", ra == rb)
print("ra is rb:", ra is rb)
ra == rb: True
ra is rb: False

if expressions

  • Apart from if statements, we also have if expressions.

  • These evaluate an expression depending on a condition.

In [98]:
import datetime # use the datetime library

now = datetime.datetime.now().time()

message = "good morning" if now < datetime.time(12) else "good evening"
print(message)
good evening

for Statements

  • The for statement in Python differs a bit from what you may be used to in C or Pascal.

  • It does not always iterate over an arithmetic progression of numbers (like in Pascal).

  • It does not give the user the ability to define both the iteration step and halting condition (as C).

  • Python’s for statement iterates over the items of any sequence (a list or a string), in the order that they appear in the sequence.

In [99]:
# Measure some strings:
words = ['cat', 'window', 'defenestrate']
for w in words:
    print(w, len(w))
cat 3
window 6
defenestrate 12
  • Do not modify a sequence when you are iterating over it.

  • If you do need to do that, do that on a copy of the sequence.

  • The slice notation makes this especially convenient.

In [100]:
for w in words[:]:  # Loop over a slice copy of the entire list.
    if len(w) > 6:
        words.append(w)
print(words)
['cat', 'window', 'defenestrate', 'defenestrate']
  • Note that if we were appending instead of inserting, then if we were not using a copy we would end up with a disaster.

  • We would enter an infinite loop.

  • The following code will never end.

for w in words:  # Loop over the actual list.
    if len(w) > 6:
        words.append(w)
print(words)

The range() Function

  • If you do need to iterate over a sequence of numbers, the built-in function range() comes in handy.

  • It generates arithmetic progressions.

In [101]:
for i in range(5):
    print(i)
0
1
2
3
4
  • The given end point is never part of the generated sequence

  • range(10) generates 10 values, the legal indices for items of a sequence of length 10.

  • It is possible to let the range start at another number, or to specify a different increment (even negative; sometimes this is called the ‘step’).

In [162]:
for i in range(5, 10):
    print(i, end=' ')

print()
for i in range(0, 10, 3):
    print(i, end=' ')

print()
for i in range(-10, -100, -30):
    print(i, end=' ')

print()
for i in range(10, -1, -1):
    print(i, end=' ')
5 6 7 8 9 
0 3 6 9 
-10 -40 -70 
10 9 8 7 6 5 4 3 2 1 0 
  • To iterate over the indices of a sequence, you can combine range() and len().
In [103]:
a = ['Mary', 'had', 'a', 'little', 'lamb']
for i in range(len(a)):
    print(i, a[i])
0 Mary
1 had
2 a
3 little
4 lamb
  • However, it is often more convenient to use the enumerate() function.
In [104]:
for i, v in enumerate(a):
    print(i, v)
0 Mary
1 had
2 a
3 little
4 lamb
  • A strange thing happens if you just print a range.
In [105]:
print(range(10))
range(0, 10)
  • range() returns something that behaves as if it is a list, but in fact it isn’t.

  • It is an object which returns the successive items of the desired sequence when you iterate over it, but it doesn’t really make the list, thus saving space.

  • We say such an object is iterable, that is, suitable as a target for functions and constructs that expect something from which they can obtain successive items until the supply is exhausted.

  • We have seen that the for statement is such an iterator. The function list() is another; it creates lists from iterables.

In [106]:
list(range(5))
Out[106]:
[0, 1, 2, 3, 4]

break statements

  • The break statement, borrowed from C, stops the execution of the nearest enclosing for or while loop.
In [107]:
for name in ["Alice", "Bob", "Carol", "Dan", "Eve"]:
    if name[0] == "C":
        print("Found a name starting with C:", name)
        break
Found a name starting with C: Carol

continue statements

  • The continue statement, also borrowed from C, continues with the next iteration of the loop.
In [108]:
for num in range(2, 10):
    if num % 2 == 0:
        print("Found an even number", num)
        continue
    print("Found a number", num)
Found an even number 2
Found a number 3
Found an even number 4
Found a number 5
Found an even number 6
Found a number 7
Found an even number 8
Found a number 9

pass statements

  • The pass statement does nothing. It can be used when a statement is required syntactically but the program requires no action.
if some_condition:
    pass # Don't know what to do yet, will fill in later

Defining functions

  • This is a function that writes the Fibonacci series to an arbitrary boundary.
In [109]:
def fib(n):    # write Fibonacci series up to n
    """Print a Fibonacci series up to n."""
    a, b = 0, 1
    while a < n:
        print(a, end=' ')
        a, b = b, a+b
    print()


# Now call the function we just defined:
fib(2000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 
  • Coming from other languages, you might object that fib is not a function but a procedure since it doesn’t return a value.

  • In fact, even functions without a return statement do return a value, albeit a rather boring one.

  • This value is called None (it’s a built-in name).

  • Writing the value None is normally suppressed by the interpreter if it would be the only value written.

  • You can see it if you really want to using print().

In [110]:
fib(0)
print(fib(0))

None
  • This is a new version of the Fibonacci function, this time returning the result
In [111]:
def fib2(n): # return Fibonacci series up to n
    """Return a list containing the Fibonacci series up to n."""
    result = []
    a, b = 0, 1
    while a < n:
        result.append(a)    # see below
        a, b = b, a+b
    return result

f100 = fib2(100)    # call it
f100                # write the result
Out[111]:
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
  • The return statement returns with a value from a function.

  • return without an expression argument returns None.

  • Falling off the end of a function also returns None.

  • The statement result.append(a) calls a method of the list object result.

  • A method is a function that ‘belongs’ to an object and is named obj.methodname, where obj is some object (this may be an expression), and methodname is the name of a method that is defined by the object’s type.

  • The method append() shown in the example is defined for list objects; it adds a new element at the end of the list. In this example it is equivalent to result = result + [a], but more efficient.

Default argument values

  • You can specify a default value for one or more arguments. This creates a function that can be called with fewer arguments than it is defined to allow.
In [112]:
def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
    while True:
        ok = input(prompt)
        if ok in ('y', 'ye', 'yes'):
            return True
        if ok in ('n', 'no', 'nop', 'nope'):
            return False
        retries = retries - 1
        if retries < 0:
            raise OSError('uncooperative user')
        print(complaint)
  • This function can be called in several ways.

    • giving only the mandatory argument: ask_ok('Do you really want to quit?')

    • giving one of the optional arguments: ask_ok('OK to overwrite the file?', 2)

    • or even giving all arguments: ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')

  • This example also introduces the in keyword. This tests whether or not a sequence contains a certain value.

Keyword Arguments

  • Functions can also be called using keyword arguments of the form kwarg=value.
In [113]:
def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
    print("-- This parrot wouldn't", action, end=' ')
    print("if you put", voltage, "volts through it.")
    print("-- Lovely plumage, the", type)
    print("-- It's", state, "!")
  • The following calls are valid.
In [114]:
parrot(1000)                                          # 1 positional argument
parrot(voltage=1000)                                  # 1 keyword argument
parrot(voltage=1000000, action='VOOOOOM')             # 2 keyword arguments
parrot(action='VOOOOOM', voltage=1000000)             # 2 keyword arguments
parrot('a million', 'bereft of life', 'jump')         # 3 positional arguments
parrot('a thousand', state='pushing up the daisies')  # 1 positional, 1 keyword
-- This parrot wouldn't voom if you put 1000 volts through it.
-- Lovely plumage, the Norwegian Blue
-- It's a stiff !
-- This parrot wouldn't voom if you put 1000 volts through it.
-- Lovely plumage, the Norwegian Blue
-- It's a stiff !
-- This parrot wouldn't VOOOOOM if you put 1000000 volts through it.
-- Lovely plumage, the Norwegian Blue
-- It's a stiff !
-- This parrot wouldn't VOOOOOM if you put 1000000 volts through it.
-- Lovely plumage, the Norwegian Blue
-- It's a stiff !
-- This parrot wouldn't jump if you put a million volts through it.
-- Lovely plumage, the Norwegian Blue
-- It's bereft of life !
-- This parrot wouldn't voom if you put a thousand volts through it.
-- Lovely plumage, the Norwegian Blue
-- It's pushing up the daisies !
  • The following calls are all invalid:
parrot()                     # required argument missing
parrot(voltage=5.0, 'dead')  # non-keyword argument after a keyword argument
parrot(110, voltage=220)     # duplicate value for the same argument
parrot(actor='John Cleese')  # unknown keyword argument

Recursive functions

  • How do we define the factorial of a number?

  • The factorial $n!$ of a nunber $n$ is defined as follows: $$n!={\begin{cases}1&{\text{if }}n=0,\\(n-1)!\times n&{\text{if }}n>0\end{cases}}$$

  • This is a recursive definition: a function that is defined in terms of itself.

  • Recursive functions are an integral part of computation in general.

  • They are straightforward to define in Python.

  • For instance, here is a definition of the factorial function in Python.

In [115]:
def factorial(n):
    if n == 0:
        return 1
    else:
        return factorial(n-1) * n
    
print(factorial(10))
3628800
  • Another recursive function is the one we can use for calculating the greatest common divisor (GCD) of two numbers.

  • According to Euclid's algorithm, the gcd of two numbers $a$ and $b$ is $a$ if $b = 0$ or the gcd of $b$ and $a \bmod b$ otherwise.

  • This leads to the following function in Python.

In [116]:
def gcd(a, b):
    if b == 0:
        return a
    else:
        return gcd(b, a % b)
    
gcd(135, 25)
Out[116]:
5
  • Note that we can also have the following, more succinct definition:
In [117]:
def gcd(a, b):
    return gcd(b, a % b) if b else abs(a)

gcd(135, 25)
Out[117]:
5

Lambda Expressions

  • Small anonymous functions can be created with the lambda keyword.

  • This function returns the sum of its two arguments: lambda a, b: a+b.

  • Lambda functions can be used wherever function objects are required.

  • They are syntactically restricted to a single expression. Semantically, they are just syntactic sugar for a normal function definition.

  • Like nested function definitions, lambda functions can reference variables from the containing scope.

  • The following lambda expression is a square function.
In [118]:
sq = lambda x: x*x

sq(4)
Out[118]:
16
  • Lambda expressions are particularly useful with functions that take other functions as arguments.
In [119]:
list(filter(lambda x: x%2 == 0, range(10)))
Out[119]:
[0, 2, 4, 6, 8]
In [120]:
pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
pairs.sort(key=lambda pair: pair[1])
pairs
[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]
Out[120]:
[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]

Modules

  • A module is a file containing function definitions and statements.

  • You can think of a module as a library in other programming languages.

  • Python has a rich library, containing modules for all sorts of things:

    • data structures
    • maths
    • interaction with the operating system
    • ...
  • The import statement allows us to use a module in our code
In [121]:
import math
  • Now functions in the math module are available with the math prefix.
In [122]:
for x in range(0, 11):
    y = x * (math.pi / 10)
    print(y, math.cos(y))
0.0 1.0
0.3141592653589793 0.9510565162951535
0.6283185307179586 0.8090169943749475
0.9424777960769379 0.5877852522924732
1.2566370614359172 0.30901699437494745
1.5707963267948966 6.123233995736766e-17
1.8849555921538759 -0.30901699437494734
2.199114857512855 -0.587785252292473
2.5132741228718345 -0.8090169943749473
2.827433388230814 -0.9510565162951535
3.141592653589793 -1.0
  • A variant of the import statement allows us to give shorter names to imported modules.
In [123]:
import itertools as iter

for comb in iter.combinations('ABCD', 2):
    print(comb)
('A', 'B')
('A', 'C')
('A', 'D')
('B', 'C')
('B', 'D')
('C', 'D')
  • There is also a variant of the import statement that allows us to use the definitions in the module without a prefix at all.

  • Of course we must be careful to avoid any conflicts with other names defined in our program.

In [124]:
from collections import Counter

Counter('abracadabra').most_common(3)
Out[124]:
[('a', 5), ('b', 2), ('r', 2)]

Tuples and Sequences

  • We saw that lists and strings have many common properties, such as indexing and slicing operations.

  • They are two examples of sequence data types (such as a list and a range).

  • Since Python is an evolving language, other sequence data types may be added.

  • There is also another standard sequence data type: the tuple.

  • A tuple consists of a number of values separated by commas.

In [125]:
t = 12345, 54321, 'hello!'
t[0]
Out[125]:
12345
In [126]:
t
Out[126]:
(12345, 54321, 'hello!')
In [127]:
# Tuples may be nested:
u = t, (1, 2, 3, 4, 5)
u
Out[127]:
((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))
  • Tuples are immutable.
t[0] = 88888
----> 1 t[0] = 88888

TypeError: 'tuple' object does not support item assignment
In [128]:
# but they can contain mutable objects:
v = ([1, 2, 3], [3, 2, 1])
v
Out[128]:
([1, 2, 3], [3, 2, 1])
In [129]:
v[0].append(4)
v
Out[129]:
([1, 2, 3, 4], [3, 2, 1])
  • On output tuples are always enclosed in parentheses, so that nested tuples are interpreted correctly.

  • They may be input with or without surrounding parentheses, although often parentheses are necessary anyway (if the tuple is part of a larger expression).

  • It is not possible to assign to the individual items of a tuple, however it is possible to create tuples which contain mutable objects, such as lists.

  • Though tuples may seem similar to lists, they are often used in different situations and for different purposes.

  • Tuples are immutable, and usually contain an heterogeneous sequence of elements that are accessed via unpacking (as we'll see shortly) or indexing (or even by attribute in the case of a special kind of tuple called a namedtuple).

  • Lists are mutable, and their elements are usually homogeneous and are accessed by iterating over the list.

  • A special problem is the construction of tuples containing 0 or 1 items.

  • The syntax has some extra quirks to accommodate these.

  • Empty tuples are constructed by an empty pair of parentheses.

  • A tuple with one item is constructed by following a value with a comma (it is not sufficient to enclose a single value in parentheses). Ugly, but effective.

In [130]:
empty = ()
singleton = 'hello',    # <-- note trailing comma
len(empty)
Out[130]:
0
In [131]:
len(singleton)
Out[131]:
1
In [132]:
singleton
Out[132]:
('hello',)
  • The statement t = 12345, 54321, 'hello!' is an example of tuple packing.

  • The values 12345, 54321 and 'hello!' are packed together in a tuple.

In [133]:
t = 12345, 54321, 'hello!'
t
Out[133]:
(12345, 54321, 'hello!')
  • The reverse operation is also possible.
In [134]:
x, y, z = t
x
Out[134]:
12345
In [135]:
y
Out[135]:
54321
In [136]:
z
Out[136]:
'hello!'
  • This is called, appropriately enough, sequence unpacking.

  • It works for any sequence on the right-hand side.

  • Sequence unpacking requires that there are as many variables on the left side of the equals sign as there are elements in the sequence.

  • Note that multiple assignment is really just a combination of tuple packing and sequence unpacking.

Sets

  • Python also includes a data type for sets.

  • A set is an unordered collection with no duplicate elements.

  • Basic uses include membership testing and eliminating duplicate entries.

  • Set objects also support mathematical operations like union, intersection, difference, and symmetric difference.

  • Curly braces or the set() function can be used to create sets.

  • To create an empty set you have to use set(), not {}; the latter creates an empty dictionary, a data structure that we discuss latter.

In [137]:
basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
print(basket)                      # show that duplicates have been removed
{'pear', 'apple', 'orange', 'banana'}
  • Set membership operation
In [138]:
'orange' in basket                 # fast membership testing
Out[138]:
True
In [139]:
'crabgrass' in basket
Out[139]:
False
  • Set provide an easy way to get the unique elements of a sequence.
In [140]:
# Demonstrate set operations on unique letters from two words
a = set('abracadabra')
b = set('alacazam')
print(a) # unique letters in a
print(b) # unique letters in b
{'d', 'c', 'b', 'a', 'r'}
{'l', 'm', 'c', 'a', 'z'}
  • Set difference is with -.
In [141]:
a - b                              # letters in a but not in b
Out[141]:
{'b', 'd', 'r'}
  • Set union is with |.
In [142]:
a | b                              # letters in either a or b
Out[142]:
{'a', 'b', 'c', 'd', 'l', 'm', 'r', 'z'}
  • Set intersection is with &.
In [143]:
a & b                              # letters in both a and b
Out[143]:
{'a', 'c'}
  • Exclusive membership: members that are only in one set, but not in both.
In [144]:
a ^ b                              # letters in a or b but not both
Out[144]:
{'b', 'd', 'l', 'm', 'r', 'z'}
  • Similarly to list comprehensions, set comprehensions are also supported.
In [145]:
a = {x for x in 'abracadabra' if x not in 'abc'}
a
Out[145]:
{'d', 'r'}

Dictionaries

  • Another useful data type built into Python is the dictionary.

  • Dictionaries are sometimes found in other languages as “associative memories” or “associative arrays”.

  • Unlike sequences, which are indexed by a range of numbers, dictionaries are indexed by keys, which can be any immutable type; strings and numbers can always be keys.

  • Tuples can be used as keys if they contain only strings, numbers, or tuples; if a tuple contains any mutable object either directly or indirectly, it cannot be used as a key.

  • You can’t use lists as keys, since lists can be modified in place using index assignments, slice assignments, or methods like append() and extend().

  • It is best to think of a dictionary as an unordered set of key: value pairs, with the requirement that the keys are unique (within one dictionary).

  • A pair of braces creates an empty dictionary: {}.

  • Placing a comma-separated list of key:value pairs within the braces adds initial key:value pairs to the dictionary; this is also the way dictionaries are written on output.

  • The main operations on a dictionary are storing a value with some key and extracting the value given the key.

  • It is also possible to delete a key:value pair with del.

  • If you store using a key that is already in use, the old value associated with that key is forgotten.

  • It is an error to extract a value using a non-existent key.

  • Performing list(d.keys()) on a dictionary returns a list of all the keys used in the dictionary, in arbitrary order.

  • If you want it sorted, just use sorted(d.keys()).

  • To check whether a single key is in the dictionary, use the in keyword.

  • Dictionary creation is similar to set creation, but we use key: value pairs.

  • We can also add items in an existing dictionary with mydict[key] = value.

  • An empty dictionary is simply {} (that's why an empty set must be defined with set().

In [146]:
tel = {'jack': 4098, 'sape': 4139}
tel['guido'] = 4127
tel
Out[146]:
{'guido': 4127, 'jack': 4098, 'sape': 4139}
In [147]:
tel['jack']
Out[147]:
4098
  • We can delete an item from a dictionary with del.

  • Re-assigning to an existing key overwrites the previous value.

In [148]:
del tel['sape']
tel['irv'] = 4127
tel
Out[148]:
{'guido': 4127, 'irv': 4127, 'jack': 4098}
  • To get all the keys in a dictionary we use the keys() method.

  • If we want to put them in a list, we use the list() function on its results.

In [149]:
list(tel.keys())
Out[149]:
['guido', 'jack', 'irv']
  • Dictionaries are unsorted.

  • If we want access them in an ordered way, we have to sort the keys.

In [150]:
sorted(tel.keys())
Out[150]:
['guido', 'irv', 'jack']
  • Membership check in dictionaries is similar to that of sets.
In [151]:
'guido' in tel
Out[151]:
True
In [152]:
'jack' not in tel
Out[152]:
False

Looping Techniques

  • When looping through dictionaries, the key and corresponding value can be retrieved at the same time using the items() method.
In [153]:
knights = {'gallahad': 'the pure', 'robin': 'the brave'}
for k, v in knights.items():
    print(k, v)
robin the brave
gallahad the pure
  • When looping through a sequence, the position index and corresponding value can be retrieved at the same time using the enumerate() function.
In [154]:
for i, v in enumerate(['tic', 'tac', 'toe']):
    print(i, v)
0 tic
1 tac
2 toe
  • To loop over two or more sequences at the same time, the entries can be paired with the zip() function.
In [155]:
questions = ['name', 'quest', 'favorite color']
answers = ['lancelot', 'the holy grail', 'blue']
for q, a in zip(questions, answers):
    print('What is your {0}?  It is {1}.'.format(q, a))
What is your name?  It is lancelot.
What is your quest?  It is the holy grail.
What is your favorite color?  It is blue.
  • To loop over a sequence in reverse, first specify the sequence in a forward direction and then call the reversed() function.
In [156]:
for i in reversed(range(1, 10, 2)):
    print(i)
9
7
5
3
1
  • To loop over a sequence in sorted order, use the sorted() function which returns a new sorted list while leaving the source unaltered.
In [157]:
basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
for f in sorted(set(basket)):
    print(f)
apple
banana
orange
pear
  • It is sometimes tempting to change a list while you are looping over it.

  • However, it is often simpler and safer to create a new list instead.

In [158]:
import math
raw_data = [56.2, float('NaN'), 51.7, 55.3, 52.5, float('NaN'), 47.8]
filtered_data = []
for value in raw_data:
    if not math.isnan(value):
        filtered_data.append(value)

filtered_data
Out[158]:
[56.2, 51.7, 55.3, 52.5, 47.8]
  • Or alternatively if we want to work by deleting items:
In [159]:
raw_data = [56.2, float('NaN'), 51.7, 55.3, 52.5, float('NaN'), 47.8]
for value in raw_data[:]:
    if math.isnan(value):
        raw_data.remove(value)

raw_data
Out[159]:
[56.2, 51.7, 55.3, 52.5, 47.8]

Exercises

Exercise 1

The Greek Tax Identification Number (TIN) consists of 9 digits. The last digit is a check digit. It is calculated as follows:

  1. We remove the check digit, so that we are left with an 8-digit number.
  2. We take the 8 digits one by one, from the right to the left. We multiply each digit by the power of 2 corresponding to its position: the first from the right will be multiplied by $2^1$, the second will be multiplied by $2^2$, and so on.
  3. We take these powers and we sum them.
  4. We calculate the remainder of this sum by 11.
  5. We take this remainder and we calculate its remainder by 10. The result must equal the check digit.

For example, let us say we have the TIN 090034337. The check digit is 7. The other digits are 09003433. We have: $$3\times 2^1 + 3\times 2^2 + 4\times 2^3 + 3\times 2^4 + 9\times 2^7 =$$ $$3\times 2 + 3\times 4 + 4\times 8 + 3 \times 16 + 9\times 128 =$$ $$6 + 12 + 32 + 48 + 1152 = 1250$$

Then, $1250 \bmod 11 = 7$ και $7 \bmod 10 = 7$.

Write a program that will ask the user for TIN and will respond whether it is correct or not. For example:

Enter Tax Identification Number: 090034337
Tax Identification Number valid.

Enter Tax Identification Number: 090034336
Tax Identification Number not valid.

Exercise 2

A simple way to check binary data is the so-called parity bit. A byte consists of 8 bits, so that we can use the last bit to check whether the previous 7 are OK. We can do that by checking that the sum of the 1 bits is an even number (this is actually called even parity; we might require that the sum of 1 bits is odd, which is called odd parity). For example, see the following table where we see the first 7 bits of some numbers, the number of 1 bits in them, the full 8-bit number (including the parity) and the number of 1 bits in the byte.

First 7 bits Number of 1s 8 bits (with parity) Number of 1s
0000000 0 00000000 0
1010001 3 10100011 4
1101001 4 11010010 4
1111111 7 11111111 8

Write a program that asks the user for an 8-bit binary number and replies whether the parity bit checks OK. For example:

Enter binary number: 01010101
Parity check OK.

Enter binary number: 11010101
Parity check not OK.

Exercise 3

Write a program that asks the user for a 10-digit number and will then print it in two lines. The first line will contain the numbers in the odd positions and the second line the numbers in the even positions. For example:

Enter 10 digit number: 1234567890
1 3 5 7 9
 2 4 6 8 0

Take care so that the numbers line up in columns exactly as in the above example: each number must be in a column by itself.

Exercise 4

Write a program that asks the user for a 9-digit number and then prints it in three lines. Each line must contain three digits. For example:

Enter 9 digit number: 123456789
1  4  7
 2  5  8
  3  6  9

Take care so that the numbers line up in columns exactly as in the above example: each number must be in a column by itself.

Exercise 5

To calculate Orthodox Easter Sunday for any year between 1900 and 2099, we can use the following algorithm. Suppose that $y$ is the year.

  1. $a = y \bmod 4$
  2. $b = y \bmod 7$
  3. $c = y \bmod 19$
  4. $d = (19c + 15) \bmod 30$
  5. $e = (2a + 4b - d + 34) \bmod 7$
  6. $\mathit{month} = \left\lfloor{((d + e + 114) / 31)}\right\rfloor$. The symbol $\left\lfloor{x}\right\rfloor$ means the integer part of $x$, that is, $x$ round down towards 0. This is implemented in Python with the math.floor() function, in the math module. But you may not need it, because // implements integer division in Python.
  7. $\mathit{day} = ((d + e + 114) \bmod 31) + 1$
  8. The result is the day and the month in the Julian calendar. To convert it to the Gregorian calendar, which we actually use, we have to add 13 days. Be careful, this may change the month.

Write a program that asks the user for a year and then displays the month and the day of Orthodox Easter in the Gregorian calendar. For example:

Enter year: 2011
Day: 24 Month: 4

You may check your program for 2012 (15/4), 2013 (5/5), 2014 (20/4), 2015 (12/4), 2016 (1/5), 2017 (16/4).

Exercise 6

Write a program that asks the user for 9 numbers: 1 with 1 digit, 1 with 2 digits, 1 with 3 digits, then again 1 with 1 digit, 1 with 2 digits, 1 with 3 digits, and then again 1 with 1 digit, 1 with 2 digits, 1 with 3 digits. Then the program will print them in columns, where each column will contain 1 single digit number, 1 double digit number, and 1 triple digit number. The columns will be 3 characters wide and will be separated from each other with the character +. The numbers inside the columns will be right justified. For example:

Enter numbers: 1 10 100 2 20 200 3 30 300
  1|  2|  3
 10| 20| 30
100|200|300

Exercise 7

To calculate the number of days between two days we can use the following algorithm. Let's say that the first date is given by $y_1$, $m_1$, $d_1$ and the second date is given by $y_2$, $m_2$, $d_1$ ($d_i$ are days, $m_i$ are months, $y_i$ are years).

  1. $c1 = 365y1 + \left\lfloor{y1/4}\right\rfloor - \left\lfloor{y1/100}\right\rfloor + \left\lfloor{y1/400}\right\rfloor + \left\lfloor{(306m1 + 5)/10}\right\rfloor + (d1 - 1)$. In essence, $c1$ is the numbers of days passed since 1/1/1 until the first date.
  2. $c2 = 365y2 + \left\lfloor{y2/4}\right\rfloor - \left\lfloor{y2/100}\right\rfloor + \left\lfloor{y2/400}\right\rfloor + \left\lfloor{(306m2 + 5)/10}\right\rfloor + (d2 - 1)$. In essence, $c2$ is the number of days that have passed since 1/1/1 until the second date.
  3. The days that have elapsed between the two days is $c2 - c1$.

Write a program that asks the user to enter two dates in the form dd/mm/yyyy and will then display the number of days that have elapsed between the two days. Be careful, the result must not be negative, no matter the order in which the user enters the dates. For example (15/3/-44 was the day Julius Caesar was murdered):

Enter dates: 15/3/-44 4/11/2016
752596 days.

Another example (17/12/1903 was the first flight by the Wright brothers).

Enter dates: 28/09/2016 17/12/1903
41193 days.

Exercise 8

Consider a sequence of numbers that describes another sequence, as follows. The sequence in read by pairs. The first part of a pair indicates how many times we should take the second part. If the length of the sequence is an odd number, then we just take the last number by itself. So, the sequence 1234567 means "1 times 2 and 3 times 4 and 5 times 6 and 7", which is: $$ 1 \times 2 + 3 \times 4 + 5 \times 6 + 7$$

Write a program that asks a user to enter a sequence of digits and then calculates the value of the sequence, as defined above. For example:

Enter number sequence: 123456
44

Enter number sequence: 1234567
51

Enter number sequence: 1230123
7

Enter number sequence: 001234

Exercise 9

The Caesar cipher works by substituting each character in a message with a character that occurs $x$ places later on the alphabet, wrapping around from the beginning, if neeeded. So, if $x = 3$ then "ABIGΖΟΟ" will become "DELJCRR".

Write a program that will ask for the number of positions to shift each character and a phrase; then it will output the phrase encrypted with the Caesar cipher. You can assume that the phrase consists only of uppercase English letters (without punctuation or spaces). For example:

Enter shift: 20
Enter phrase: FAILBETTER
ZUCFVYNNYL

Enter shift: 10
Enter phrase: TOBEORNOTTOBETHATISTHEQUESTION
DYLOYBXYDDYLODRKDSCDROAEOCDSYX

Note: this method may have been adequate in Caesar's time, but it is completely useless today.

To be sure that your encoding is correct, write also the decryption program that takes a shift and an encrypted phrase and then produces the original plaintext.

Exercise 10

Write a program that asks a user to enter a sequence of 0s and 1s. Following that, it will identify the longest run of 0s or 1s of the sequence and will print an appropriate message. Examples:

Enter binary sequence: 1111000
Longest run was ones with length: 4

Enter binary sequence: 1010100000
Longest run was zeros with length: 5

Enter binary sequence: 11110000111000
Equal longest run of ones and zeros with length: 4

Exercise 11

The positive integer numbers that can be written as the sum of two or more consecutive positive integers are called polite numbers. The rest are called impolite numbers. The first polite numbers are: 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, ....

It can be proven that the impolite numbers are the powers of 2. Write a program that asks the user for an upper limit and then prints out the polite numbers up to and including the limit. The program should not use logarithms. The numbers should be written in rows of 10 (expect possibly for the last line). Examples:

Enter limit: 10
3 5 6 7 9 10

Enter limit: 16
3 5 6 7 9 10 11 12 13 14
15

Enter limit: 25
3 5 6 7 9 10 11 12 13 14
15 17 18 19 20 21 22 23 24 25

Enter limit: 30
3 5 6 7 9 10 11 12 13 14
15 17 18 19 20 21 22 23 24 25
26 27 28 29 30