Untitled article
SourceNow 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 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 4s as being correct numbers in the correct places, but the other two 4s are not identified as being correct numbers in the wrong place, because we have already used up all the 4s 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?
5. More with Lists and Strings
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 in a list comprehension to process the list of strings.
It is just about readable to put the whole thing in one expression:
6. Prettier Printing
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 construct to safely open and close the file. Then it is a matter of carefully constructing the while condition and the variable it sets to get the right result. We then rearrange the parts of the name the user types in, and print it to file:
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:
7. Arranging Things
1
This can be achieved by tuple unpacking:
Python
>>> a = 1
>>> b = 2
>>> a, b = (b, a)
>>> a
2
>>> b
1Note 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
1However, 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)
108. When Things Go Wrong
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.
9. More with Files
1
The with … as construct allows us to combine the two statements in the original into a single block:
2
We use with … as again, using the optional 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.
10. The Other Numbers
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?
11. The Standard Library
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 valuesHow 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)
0We 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?
12. Building Bigger Programs
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.
Project 1: Pretty Pictures
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):
3
The number of segments used to approximate a circle ought to be related to its circumference not its radius.
The smoothness may be fine-tuned by writing int(circumference * 1.5), int(circumference * 0.5) etc.
4
We write a function grid which takes four arguments: the first two to represent the starting point (sx, sy) and the latter two to give the number of circles in the x and y directions:
Here is the result of grid(-100, -100, 5, 4):
5
There are three dimensions to this data: red, green, and blue. And so we cannot display it directly on a 2D screen – we must flatten it in some way. We have chosen to slice the cube of data into slices based on the red value, and display the slices side by side.
First, we will need a function to draw a filled square of a given size at a given position.
Read Next page