*Hints may be found after all the answers.*

**1**

The expression `17`

is a number and so is a value already – there is no work to do. The expression `1 + 2 * 3 + 4`

will evaluate to the value `11`

, since multiplication has higher precedence than addition. The expression `400 > 200`

evaluates to the boolean `True`

since this is result of the comparison operator `>`

on the operands `400`

and `200`

. Similarly, `1 != 1`

evaluates to `False`

. The expression `True or False`

evaluates to `True`

since one of the operands is true. Similarly, `True and False`

evaluates to `False`

since one of the operands is false. The expression `’%’`

is a string and is already a value.

**2**

The expression evaluates to `11`

. The programmer seems to be under the impression that spacing affects precedence. It does not, and so this use of space is misleading.

**3**

The `%`

operator is of higher precedence than the `+`

operator. So `1 + 2 % 3`

and `1 + (2 % 3)`

are the same expression, evaluating to `1 + 2`

which is `3`

, but `(1 + 2) % 3`

is the same as `3 % 3`

, which is `0`

.

**4**

The comparison operator `<`

considers the words in dictionary order, so `’bacon’ < ’eggs’`

. The uppercase letters are all “smaller” than the lowercase characters, so for example `’Bacon’ < ’bacon’`

evaluates to `True`

. For booleans, `False`

is considered “less than” `True`

.

**5**

The first one is, of course entirely as expected:

```
Python
>>> 1 + 2
3
```

It turns out that the `+`

operator we have been using on numbers to add them can be used on strings to concatenate them:

```
Python
>>> 'one' + 'two'
'onetwo'
```

However, it will not work to mix the two types:

```
Python
>>> 1 + 'two'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'
```

The `*`

operator can also be used on a string and a number, to concatenate the string multiple times:

```
Python
>>> 3 * '1'
'111'
>>> '1' * 3
'111'
>>> print('1' * 3)
111
```

In the last line, we remember the difference between a string and printing a string. When it is (ab)used as a number, `True`

has the value 1, whereas `False`

has the value 0:

```
Python
>>> True + 1
2
>>> False + 1
1
```

Did you notice the `f`

before the quotation mark in the last example? This is a *format string*, which we will discuss in chapter 6:

```
Python
>>> print(f'One and two is {1 + 2} and that is all.')
One and two is 3 and that is all.
```

The part between the curly braces `{}`

has been evaluated and then printed.

**1**

We include the ** return** keyword to make sure the result is returned to us:

```
Python
>>> def times_ten(x):
... return x * 10
...
>>> times_ten(50)
500
```

**2**

This function will have two arguments. We use the ** and** operator, together with the inequality operator

`!=`

.```
Python
>>> def both_non_zero(a, b):
... return a != 0 and b != 0
...
>>> both_non_zero(1, 2)
True
>>> both_non_zero(1, 0)
False
```

**3**

This is a simple function with three arguments. We remember to use ** return**, of course:

```
Python
>>> def volume(w, h, d):
... return w * h * d
...
>>> volume(10, 20, 30)
6000
```

We can now write our `volume_ten_deep`

function:

```
Python
>>> def volume_ten_deep(w, h):
... return volume(w, h, 10)
>>> volume_ten_deep(5, 6)
300
```

Notice that we need ** return** here too: the

`return`

`volume`

will not suffice.**4**

If a lower case character in the range `’a’`

…`’z’`

is not a vowel, it must be a consonant. So we can reuse the `is_vowel`

function we wrote earlier, and negate its result using ** not**:

```
Python
>>> def is_consonant(s):
... return not is_vowel(s)
...
>>> is_consonant('r')
True
>>> is_consonant('e')
False
```

**5**

We could simply return 0 for a negative argument. The factorial of 0 is 1, so we can change that too, and say our new function finds the factorial of any non-negative number:

```
Python
>>> def factorial(x):
... if x < 0:
... return 0
... elif x == 0:
... return 1
... else:
... return x * factorial(x - 1)
...
>>> factorial(-1)
0
```

**6**

We can use a recursive function:

```
Python
>>> def sum_nums(n):
... if n == 1:
... return 1
... else:
... return n + sum_nums(n - 1)
...
>>> sum_nums(10)
55
```

There is a direct mathematical formula too. We use the integer division operator `//`

, which we have not yet seen:

```
Python
>>> def sum_nums(n):
... return (n * (n + 1)) // 2
>>> sum_nums(10)
55
```

Can you see why?

**7**

A number to the power of 0 is 1. A number to the power of 1 is itself. Otherwise, the answer is the current *n* multiplied by *n*^{x − 1}.

```
Python
>>> def power(x, n):
... if n == 0:
... return 1
... else:
... if n == 1:
... return x
... else:
... return x * power(x, n - 1)
...
>>> power(2, 5)
32
```

Notice that we had to put one ** if** and

`else`

`elif`

```
Python
>>> def power(x, n):
... if n == 0:
... return 1
... elif n == 1:
... return x
... else:
... return x * power(x, n - 1)
...
>>> power(2, 5)
32
```

This is easier to read, partly because all the ** return** keywords line up. In fact, we can remove the case for

`n = 1`

since `power(x, 1)`

will reduce to `x * power(x, 0)`

which is just `x`

.**8**

We test each number less than the given number for divisibility using the `%`

modulus operator we learned about in chapter 1, increasing the test divisor by one each time. If it is divisible, we print it.

```
Python
>>> def factors(n, x):
... if x % n == 0: print(n)
... if n < x: factors(n + 1, x)
>>> factors(1, 12)
1
2
3
4
6
12
```

We can clean the solution up by wrapping it in another function which supplies the starting point of `1`

:

```
Python
>>> def factors_simple(x):
... factors(1, x)
>>> factors_simple(12)
1
2
3
4
6
12
```

**1**

We set the step to − 1. We must be careful with the start and stop points. We set the stop point to 0 so that we stop at 1 (i.e. before we get to 0):

**2**

We change the calculation of `column_width`

to take `n * (n - 1)`

as the item with maximum width rather than `n * n`

:

Here is the new result for a table of size ten:

```
Python
>>> times_table(10)
1 2 3 4 5 6 7 8 9 10
2 4 6 8 10 12 14 16 18 20
3 6 9 12 15 18 21 24 27 30
4 8 12 16 20 24 28 32 36 40
5 10 15 20 25 30 35 40 45 50
6 12 18 24 30 36 42 48 54 60
7 14 21 28 35 42 49 56 63 70
8 16 24 32 40 48 56 64 72 80
9 18 27 36 45 54 63 72 81 90
10 20 30 40 50 60 70 80 90 100
```

It is still possible to have excess space in some columns with this method:

```
Python
>>> times_table(4)
1 2 3 4
2 4 6 8
3 6 9 12
4 8 12 16
```

Can you fix this?

**3**

We use a ** for** loop to check each letter in the string, adding one to a local variable each time we see a space:

We use ** return** to make sure the final count is the result of the function. We can use the

`+=`

operator to shorten the common operation of adding to a variable:This works for other operators too.

**4**

This is a classic problem. It sounds easy, and yet requires several changes. We calculate the length of the string with `len`

, so we know how many letters there are left to go. Then, in the ** for** loop itself, we deduct one from that count each time, and print the space only if the count indicates we are not on the last letter.

**5**

A very simple ** while** loop is required:

**6**

We supply the prompt directly to the `input`

function. We must add the newline `\n`

because, unlike `print`

, `input`

does not move to the next line after printing the prompt.

We can now remove the `entered`

variable, but we must use ** pass**, otherwise the

`while`

**7**

We need three variables: `target`

to hold the secret number between 1 and 100, `guess`

to hold the current guess, and `tries`

to count the number of tries.

Inside the ** while** loop, we add one to the number of tries, and keep going until the correct answer is guessed. Then we print the final message with the number of tries. Notice that we have use an

`if`

`elif`

`else`

**8**

We use one giant ** if** construct (in the next chapter we shall discuss better ways to do this). The function

`print_morse_letter`

prints a single code for the given letter, followed by three spaces:Now the main function, to print a whole string, uses `print_morse_letter`

for each letter which is not a space. For spaces it prints an extra four spaces in the output to add to the three following the previous letter.

This implementation has two problems: (1) it prints an extra three spaces at the end of any message not ending with a space; and (2) a space at the beginning of a message will have only four spaces in the output not seven. Can you fix them?

**1**

The `first`

function is simple: we just return the element at index 0. To return the last element we must use the `len`

function to calculate the index. We remember to subtract one, since list indices start at zero.

In fact, we can also write `l[-1]`

to retrieve the last element of a list in Python. What happens in each case if the list is empty?

**2**

We first create a fresh, empty list. Then we can iterate over the input list in order, inserting each element at index 0 in the new list. The effect is to produce a reversed list, which is then returned.

Alternatively, we can use a `range`

with a negative step value, in conjunction with the `append`

method. Again, we begin with a fresh, empty list.

**3**

We will use a ** for** loop to look at each element, updating two variables to keep track of the smallest and largest numbers seen so far. The important thing to to properly initialise the

`minimum`

and `maximum`

variables. We do so by setting them to be equal to the first element of the list. Can you see why?This function has a minor inefficiency; it looks at the first element of the list twice. Can you fix that?

**4**

We need a step value of two, and start and stop values encompassing the whole list:

Of course, such start and stop values are the default, so we may also write:

**5**

We follow the pattern of our second, shorter `evens`

function above, and write simply:

**6**

We begin by making a fresh, empty list. Then, for each element in the original list, we add it to the new list, unless it is already there:

**7**

We use our `setify`

function to make a new list of the unique items in the original list. Then we iterate over this new list, finding the number of each of its elements in the original list using the `count`

method, and print it.

**8**

This is a simple exercise in the use of ** in** and the boolean operator

`and`

**9**

We create a fresh, empty list and append the elements of the original list one by one:

Alternatively, we can use the slice operator with empty start and stop values:

**10**

We make a fresh list with our new `copy_list`

function, remove the value using `remove`

and then return the new list:

**11**

We set up our alphabet and use list slicing to write a function `rotate`

which can rotate it by any number of places from 1 to 25, returning a new string:

Now our encoding and decoding functions are simple, taking the text to encode or decode, and the rotated cipher. They are, as you would expect, somewhat symmetrical:

We remember to treat spaces specially.

**12**

Now that we know about Python’s lists, we can dispense with the huge ** if** construct of our previous Morse code solution, and work from two lists: the letters and their codes:

Now it is simple to modify our previous solution to look up codes in the list using the `index`

method on lists:

**13**

First, we shall write a function which looks at a single guess and calculates how many are a) the correct number in the correct place and b) the correct number in the incorrect place. This is surprisingly delicate, since we must make sure not to double count anything – for example, if the code is `1441`

and the guess is `4444`

we should identify two `4`

s as being correct numbers in the correct places, but the other two `4`

s are not identified as being correct numbers in the wrong place, because we have already used up all the `4`

s in our code.

Our function will take two lists of four numbers each, the code and the guess. We copy them, since we will be changing values in the lists to mark them as used. Then we set up two counters, to keep track of how many numbers are correct and in the right place, or correct and in the wrong place.

The function returns `True`

if the code is completely correct, and `False`

otherwise. Now we can write the main function which asks the user for a guess repeatedly:

There is currently no handling of errors here - what happens if you type in too few or too many numbers, for instance? Can you fix the program to handle this?

**1**

We `split`

to make a list, then the `sort`

method to sort it in place.

**2**

Using the `sorted`

function removes the need to introduce the intermediate name `l`

as in the previous question:

This makes our function a little easier to read.

**3**

Here is the original from chapter 4:

The modification is very simple: we use the `sorted`

function when creating our list of unique values:

**4**

If we write a function to remove any spaces at the front of a list of strings, we can then use it multiple times to deal with spaces at the beginning and end. Here is such a function:

For example, `strip_leading_spaces([’ ’, ’ ’, ’y’, ’e’, ’s’, ’ ’])`

will return `[’y’, ’e’, ’s’, ’ ’]`

. Notice that the `and`

operator does not try its right hand side if its left hand side is false. And so, if `len(l) > 0`

is `True`

, the first element of `l`

will not be tested for equality, and the function succeeds even when the list is empty (or consists only of spaces).

Now we can write the main function, which uses our stripper twice, to remove the spaces at the beginning and end of the list made from the original string. One final reversal brings it back to the correct order, and we `join`

it back into a string.

**5**

We can (ab)use `split`

and `join`

to make a much simpler definition:

This will, however, also remove any excess multiple spaces in between words. Python provides a built-in method `strip`

to remove just the parts at either end, leaving the rest untouched.

**6**

First, our `clip`

function:

Now, we use `map`

with our `clip`

function, not forgetting to use `list`

to get back an ordinary list before returning.

**7**

We have already seen how a slice with a step of `-1`

may be used to reverse a list. A palindrome is something which equals its own reverse, so it is easy to write out the definition:

We can use this `is_palindromic`

function as a filter to return only such strings in a list as are palindromic:

Now, we can build only those numbers in a range whose strings are palindromic.

We must remember to convert them back to integers before returning. We can achieve this with `map`

, of course. Now we can find all the palindromic numbers up to 500:

```
>>> palindromic_numbers_in(1, 500)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66,
77, 88, 99, 101, 111, 121, 131,141, 151, 161, 171,
181, 191, 202, 212, 222, 232, 242, 252, 262, 272,
282, 292,303, 313, 323, 333, 343, 353, 363, 373, 383,
393, 404, 414, 424, 434, 444, 454, 464, 474, 484, 494]
```

We can remove the instances of `list`

since `range`

, `filter`

, and `map`

are happy to accept iterators:

We have to chosen to return an actual list, not a generator, from the final `palindromic_numbers_in`

function, however.

**8**

This is a simple list comprehension, just using `clip`

on each item in the list:

**9**

In this example, we use both ** for** and

`if`

It is just about readable to put the whole thing in one expression:

**1**

We must keep a counter to prevent the printing of an extra comma and space:

This is a lot more complicated than having `print`

do the work for us, but it does allow us some customisation: for example if we wished to print without commas, or without spaces.

**2**

In this instance, format strings do not really help use remove any complexity. In fact, this solution is slightly longer than the one without format strings.

**3**

Without format strings, we must use `str`

explicitly on each integer, as a prelude to calling the `rjust`

method. We can use the `print`

function with multiple arguments to print a whole line, though:

**4**

This is a simple substitution of `zfill`

for `rjust`

:

In fact, we can use `rjust(5, ’0’)`

to achieve the same effect.

**5**

We use the ** with** …

`as`

`while`

Can you see why we had to initialise the `name`

variable to a non-empty string?

**6**

Using a format string allows us to remove the `sep=’, ’`

argument, but not a lot else:

**7**

For each sentence in the list, we find the position (if any) of the word. Remembering that failure to find the word results in a position of -1, we decide what to print to the screen:

**8**

This is a simple alteration to the previous answer:

**1**

This can be achieved by tuple unpacking:

```
Python
>>> a = 1
>>> b = 2
>>> a, b = (b, a)
>>> a
2
>>> b
1
```

Note we do not need parentheses on the tuple when doing multiple assignment of values to names:

```
Python
>>> a = 1
>>> b = 2
>>> a, b = b, a
>>> a
2
>>> b
1
```

However, we cannot write this:

Why not?

**2**

We can use the `items`

method on the dictionary, which allows iterating with a ** for** loop using two variables, one for the key and one for the value. We return the result as a tuple.

For example:

```
Python
>>> unzip({1: 'one', 2: 'two'})
([1, 2], ['one', 'two'])
```

**3**

We initialise a fresh, empty dictionary. Then, looping over the index positions in the list of keys, we add each key and its value to the dictionary.

What happens if the lists `ks`

and `vs`

are of differing lengths?

**4**

Beginning with an empty dictionary, we loop over the items in each existing dictionary, adding the key and its associated value to the union dictionary.

The preference for values from dictionary `a`

is achieved by processing it second. Duplicate entries from dictionary `b`

are thus overwritten.

**5**

The list is being modified by deletion during the ** for** loop, and so the indices change. Here is a possible working version, which repeatedly uses the

`remove`

method which, we remember, removes the first instance of a given element in a list:**6**

We need to assign values to two names here, so we use the `items`

method. The rest is then simple:

The output is not always the same length as the input, because a value may appear multiple times in the input, and so be used multiple times as a key in the output:

```
Python
>>> reverse_dict({1: 2, 2: 1, 3: 1})
{2: 1, 1: 3}
```

**7**

We remember that an empty set is created by `set()`

. We loop over the input words, using `set`

again to build a set of all the letters in each word, and the `|`

operator to add them to our master set, which we then return:

For example:

```
Python
>>> letter_set(['one', 'two', 'three'])
{'w', 'n', 't', 'h', 'o', 'r', 'e'}
```

To do the inverse, we shall need a set of all the letters. Then we can use the set difference operator.

For example:

```
Python
>>> letters_not_used(['one', 'two', 'three'])
{'m', 'd', 'f', 'q', 'l', 'y', 's', 'k', 'g', 'c',
'v', 'j', 'p', 'a', 'u', 'z', 'x', 'b', 'i'}
```

**8**

We can represent sets using dictionaries with the values ignored, for example all set to zero. Here is a function to build such a ‘set’ from a list:

Now we can implement the operations. First, for the ‘or’ operation, we add entries to the new dictionary from both input lists:

For ‘and’, we must check that the item is in both sets:

Set difference is very similar:

Finally, exclusive or can be achieved by using our existing functions:

**9**

We use two ** for** portions to iterate over both input sets. Only when

`x == y`

do we have a match.This code checks every possible combination of elements of `a`

and `b`

and so is not very efficient.

**10**

If the type of the input value `t`

is an integer, we return it. Otherwise, we loop over all the items in t, adding up their sums by recursive application of the `sum_all`

function itself.

The result works on any tuple containing only number and on numbers themselves:

```
Python
>>> sum_all((1, 2, 3))
6
>>> sum_all((1, (1, 2), 3))
7
>>> sum_all(10)
10
```

**1**

We handle the `ValueError`

resulting from `int`

being used on a string which cannot reasonably be converted to an integer, and ignore the error by using ** pass**:

Since the exception is raised by `int`

, the `total`

variable will not be updated in the case of a bad string. So we need not worry about the `+=`

operation receiving a bad input.

**2**

We write two little functions. First, `safe_int`

, which handles the `ValueError`

exception raised by `int`

and returns `None`

instead. Second, the function `not_none`

which returns `True`

if a value is anything other than `None`

. Then we can apply `map`

and `filter`

to build a list of results from `safe_int`

and filter out the `None`

values.

**3**

We handle the `ZeroDivisionError`

exception, returning 0.

**4**

We begin with a fresh dictionary. Then, we iterate over the keys and values of dictionary `a`

. We try to insert the corresponding value from `b`

into the new dictionary. If it fails, we handle `KeyError`

and simply skip that key.

**5**

It is easy to add all the items from our first dictionary to the new one – the keys are already unique. When we add items from the second, we check to see if the key exists already. If it does, we raise `KeyError`

.

**6**

We check to see if the item is already in the set. If it is, we raise `KeyError`

. If not, we add it as usual.

**1**

The ** with** …

`as`

**2**

We use ** with** …

`as`

`file`

argument of the `print`

function to write each key and value:**3**

This is a good example of the complications of reading from a file, expecting entries in a certain format, and finding data not fitting such a format. We begin with an empty dictionary, and then enter a `while`

` True`

loop. We then try to read keys and values, returning if we have reached the end of the file (or if the line is empty).

The `ValueError`

exception which may be raised is caught and a message is printed.

**4**

We open the two input files, and the output file in ‘append’ mode. Then it is as simple as copying the lines across, being sure not to introduce extra newlines.

**5**

We use `read`

to get the whole contents of the file at once, split it into ‘words’, then convert them to integers with `map`

and sum them:

**6**

This is similar to our `append_files`

function from question 4:

**7**

We introduce a dictionary to store the character histogram, then create or increment an entry in the dictionary for each character encountered.

**8**

For the word histogram, we introduce a function `clean_split`

which splits a line into words, then processes each word to remove punctuation, and convert to lowercase.

**9**

We can reuse `clean_split`

here to get the words in each line. Then, we can use `enumerate`

to iterate over the indices and lists of words for each line. We check for the presence of the search term, and print the line and its number if required.

**10**

We read the lines all at once with `readlines`

. Then, by careful use of slices, we print five at a time, waiting for the user to press Enter.

How might this be rewritten to work well on huge files? In that case, reading all the lines at once would be inefficient.

**1**

We calculate the ceiling and floor, and return the closer one, being careful to make sure that a point equally far from the ceiling and floor is rounded up.

**2**

The function returns another point, and is simple arithmetic.

**3**

The whole part is calculated using the `floor`

function. We return a tuple, the first number being the whole part, the second being the original number minus the whole part. In the case of a negative number, we must be careful – `floor`

always rounds downward, not toward zero!

Notice that we are using the unary operator `-`

to make the number positive.

**4**

We need to determine at which column the asterisk will be printed. It is important to make sure that the range 0…1 is split into fifty equal sized parts, which requires some careful thought. Then, we just print enough spaces to pad the line, add the asterisk.

**5**

Our function takes another function as one of its argument. We use a variable to hold the current value, starting at the beginning of the range, and then loop until we are outside the range.

No allowance has been made here for bad arguments (for example, `b`

smaller than `a`

). Can you extend our program to move the zero-point to the middle of the screen, so that the sine function can be graphed even when its result is less than zero?

**1**

Here is the documentation for the `factorial`

function from the `math`

module.

We can try it out:

```
Python
>>> import math
>>> math.factorial(5)
120
>>> math.factorial(5.0)
120
>>> math.factorial(-4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: factorial() not defined for negative values
```

How does our function differ? Here we have picked the improved factorial function from the questions to chapter 2:

```
Python
>>> def factorial(x):
... if x < 0:
... return 0
... elif x == 0:
... return 1
... else:
... return x * factorial(x - 1)
>>> factorial(5)
120
>>> factorial(5.0)
120.0
>>> factorial(-4)
0
```

We return a floating-point number for a floating-point input, unlike `math.factorial`

, and we return zero for a negative input, where `math.factorial`

raises a `ValueError`

exception.

**2**

We assume the string does represent an integer, then check each potential digit. If it is not in the string `string.digits`

, we unset the `is_integer`

variable. We then return the variable as the result of the function.

There is one small problem: `string_is_integer`

with the empty string will return `True`

. Can you fix this?

**3**

This is a simple modification: we replace the use of `random.randint`

with one of `getpass.getpass`

, passing the prompt as an argument.

**4**

This is simple. We return them as a tuple.

What happens when there is no modal value in a list?

**5**

We use two functions: `time.sleep`

, which does nothing for a given number of seconds, allowing us to give the user a count-down; and `time.time`

which returns a floating-point value representing the number of seconds since an arbitrary point in the past. By measuring the time twice, and subtracting, we get the elapsed time.

What happens if the user presses Enter too soon? Can you fix this?

**1**

The `guessing_game`

function is unaltered. We need simply to check that there are enough arguments in `sys.argv`

. If there are, we pass the string representing the maximum number to `guessing_game`

:

If not, we use the default value of `’100’`

. We could, in fact, pass the number `100`

instead of the string `’100’`

, since the `int`

function does not care if it is passed something which is already an integer. This, however, would make the program as a whole more difficult to read. Better to keep our types consistent.

**2**

We first write the file `draw.py`

with our existing plotter:

Now the main `plot.py`

program can use ** import** to access the

`plot`

function from the `draw`

module, passing the fabricated function `f`

built from the command line argument (one function can sit inside another):And so we may write:

What errors might occur? The wrong number of arguments is handled in our program, but what if `float`

fails?

**3**

We write three little functions to list, add, and remove notes:

The main part of the program, then, must decode the command line to decide which operation to do, and what parameters it needs. If the command line too short or malformed, we print a message.

**1**

We remember `square`

takes the length of the side of the square as an argument:

Now we can write `many_squares`

which takes how many square to use for the star, and the length of the sides, and calls `square`

repeatedly.

**2**

The `poly`

function is unaltered. We define a new function `many_poly`

which takes the number of sides, the number of polygons to draw, and the length of the side of each polygon, and calls `poly`

repeatedly, turning between each one.

Here is the result of `many_poly(7, 16, 100)`

: