Previous Up Next

Chapter 9  Case study: word play

9.1  Reading word lists

For the exercises in this chapter we need a list of English words. There are lots of word lists available on the Web, but the one most suitable for our purpose is one of the word lists collected (and contributed to the public domain) by Grady Ward as part of the Moby lexicon project. It is a list of 113,809 official crosswords; that is, words that are considered valid in crossword puzzles and other word games. In the Moby collection, the filename is 113809of.fic; I include a copy of this file, with the simpler name words.txt, along with Swampy.

This file is in plain text, so you can open it with a text editor, but you can also read it from Python. The built-in function open takes the name of the file as a parameter and returns a file object you can use to read the file.

>>> fin = open('words.txt')
>>> print fin
<open file 'words.txt', mode 'r' at 0xb7f4b380>

fin is a common name for a file object used for input. Mode 'r' indicates that this file is open for reading.

The file object provides several methods for reading, including readline, which reads characters from the file until it gets to a newline, and returns the result as a string:

>>> fin.readline()
'aa\r\n'

The first word in this particular list is “aa,” which is a kind of lava. The sequence \r\n represents two whitespace characters, a carriage return and a newline, that separate this word from the next.

The file object keeps track of where it is in the file, so if you call readline again, you get the next word:

>>> fin.readline()
'aah\r\n'

The next word is “aah,” which is a perfectly legitimate word, so stop looking at me like that. Or, if it's the whitespace that's bothering you, we can get rid of it with the string method strip:

>>> line = fin.readline()
>>> word = line.strip()
>>> print word
aahed

You can also use a file object as part of a for loop. This program reads words.txt and prints each word, one per line:

fin = open('words.txt')
for line in fin:
    word = line.strip()
    print word
Exercise 1   Write a program that reads words.txt and prints only the words with more than 20 characters (not counting whitespace). There are not very many words that long, so as a sample their representativenesses could be questioned.

9.2  Exercises

There are solutions to these exercises in the next section. You should at least attempt each one before you turn the page.

Exercise 2   In 1939 Ernest Vincent Wright published a 50,000 word novel called Gadsby that does not contain the letter 'e'. Since 'e' is the most common letter in English, that's not easy to do.

In fact, it is difficult to construct a solitary thought without using that most common symbol. It is slow going at first, but with caution and hours of training you can gradually gain facility.

All right, I'll stop now.

Write a function called has_no_e that returns True if the given word doesn't have the letter “e” in it.

Modify your program from the previous section to print only the words that have no “e” and compute the percentage of the words in the list have no “e.”

Exercise 3   Write a function named avoids that takes a word and a string of forbidden letters, and that returns True if the word doesn't use any of the forbidden letters.

Modify your program to prompt the user to enter a string of forbidded letters and then print the number of words that don't contain any of them. Can you find a combination of 5 forbidden letters that excludes the smallest number of words?

Exercise 4   Write a function named uses_only that takes a word and a string of letters, and that returns True if the word contains only letters in the list. Can you make a sentence using only the letters acefhlo? Other than “Hoe alfalfa?”
Exercise 5   Write a function named uses_all that takes a word and a string of required letters, and that returns True if the word uses all the required letters at least once. How many words are there that use all the vowels aeiou? How about aeiouy?
Exercise 6   Write a function called is_abecedarian that returns True if the letters in a word appear in alphabetical order (double letters are ok). How many abecedarian words are there?

Exercise 7   A palindrome is a word that reads the same forward and backward, like “rotator” and “noon.” Write a boolean function named is_palindrome that takes a string as a parameter and returns True if it is a palindrome.

Modify your program from the previous section to print all of the palindromes in the dictionary and then print the total number of palindromes.

9.3  Search

All of the exercises in the previous section have something in common; they can be solved with the search pattern we saw in Section 8.6. The simplest example is:

def has_no_e(word):
    for letter in word:
        if letter == 'e':
            return False
    return True

The for loop traverses the characters in word. If we find the letter “e”, we can immediately return False; otherwise we have to go to the next letter. If we exit the loop normally, that means we didn't find an “e”, so we return True.

You can write this function more concisely using the in operator, but I wanted to start with this version because it demonstrates the logic of the search pattern.

avoids is a more general version of has_no_e but it has the same structure:

def avoids(word, forbidden):
    for letter in word:
        if letter in forbidden:
            return False
    return True

We can return False as soon as we find a forbidden letter; if we get to then end of the loop, we can return True.

uses_only is similar except that the sense of the condition is reversed:

def uses_only(word, available):
    for letter in word: 
        if letter not in available:
            return False
    return True

Instead of a list of forbidden words, we have a list of available words. If we find a letter in word that is not in available, we can return False.

uses_all is also similar, except that we reverse the role of the word and the string of letters:

def uses_all(word, required):
    for letter in required: 
        if letter not in word:
            return False
    return True

Instead of traversing the letters in word, the loop traverses the required letters. If any of the required letters do not appear in the word, we can return False.

If you were really thinking like a computer scientist, you would have recognized that uses_all was an instance of a previously-solved problem, and you would have written:

def uses_all(word, required):
    return uses_only(required, word)

This is an example of a program development method called problem recognition, which means that you recognize the problem you are working on as an instance of a previously-solved problem, and apply a previously-developed solution.

9.4  Looping with indices

We could write the functions in the previous section with for loops, because we only needed characters in the strings; we didn't have to do anything with the indices.

For some of the other exercises, like is_abecedarian, we need the indices, so it is easier to use a while loop:

def is_abecedarian(word):
    i = 0
    while i < len(word)-1:
        if word[i+1] < word[i]:
            return False
        i = i+1
    return True

The loop starts at i=0 and ends when i=len(word)-1. Each time through the loop, it compares the ith character (which you can think of as the current character) to the i+1th character (which you can think of as the next).

If the next character is less than (alphabetically before) the current one, then we have discovered a break in the abecedarian trend, as we return False.

If we get to the end of the loop without finding a fault, then the word passes the test. To convince yourself that the loop ends correctly, consider an example like 'flossy'. The length of the word is 6, so the loop stops when i is 5, so the last time the loop runs is when i is 4, which is the index of the second-to-last character. So on the last iteration, it compares the second-to-last character to the last, which is what we want.

The structure for is_palindrome is similar except that we need two indices; one starts at the begining and goes up; the other starts at the end and goes down.

def is_palindrome(word):
    i = 0
    j = len(word)-1

    while i<j:
        if word[i] != word[j]:
            return False
        i = i+1
        j = j-1

    return True

Or, if you noticed that this is an instance of a previously-solved problem, you might have written:

def is_palindrome(word):
    return is_reverse(word, word)

Assuming you did Exercise 8.7.

9.5  Glossary

file object:
A value that represents an open file.
problem recognition:
A way of solving a problem by expressing it as an instance of a previously-solved problem.

Previous Up Next