In this case study, we will be solving word puzzles by searching for words that have certain properties. For example, we’ll find the longest palindromes in English and search for words whose letters appear in alphabetical order.

Read 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 (see https://wikipedia.org/wiki/Moby_Project).

The word list we will use is in resources/code on GitHub. Download words.txt and put it under folder oim3640/data.

This file is in plain text, so obviously you can open it with a text editor. You can also read it using 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('data/words.txt') # or fin = open('data/words.txt') 
line = repr(fin.readline()) 
#https://docs.python.org/3/library/functions.html#repr

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

We 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('data/words.txt')
for line in fin:
    word = line.strip()
    print(word)

Exercise 01

Download word.py in resources/code on GitHub and finish the functions in word.py to solve the following problems.

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

1. Write a program that reads words.txt and prints only the words with more than 20 characters (not counting whitespace).

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.

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 that have no “e”.

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 forbidden 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?

4. According to Dan Brown's The Da Vinci Code, there are 62 other English words of varying lengths that could be formed using the letters in word "planets". Can you find them out?

Da Vinci Code

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 string.

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?

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?

Stop here and finish word.py.

All the functions can be solved with the search pattern:

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

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

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

uses_all is 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

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 plan called reduction to a previously solved problem, which means that you recognize the problem you are working on as an instance of a solved problem and apply an existing solution.

Looping with indices

For is_abecedarian we have to compare adjacent letters, which is a little tricky with a for loop:

def is_abecedarian(word):
    previous = word[0]
    for c in word:
        if c < previous:
            return False
        previous = c
    return True

Exercise 02

  1. Rewrite is_abecedarian using recursion

  2. Rewrite is_abecedarian using while loop.

Exercise 03

1. This question is based on a Puzzler that was broadcast on the radio program Car Talk (https://www.cartalk.com/radio/puzzler):

Give me a word with three consecutive double letters. I’ll give you a couple of words that almost qualify, but don’t. For example, the word committee, c-o-m-m-i-t-t-e-e. It would be great except for the ‘i’ that sneaks in there. Or Mississippi:M-i-s-s-i-s-s-i-p-p-i. If you could take out those i’s it would work. But there is a word that has three consecutive pairs of letters and to the best of my knowledge this may be the only word. Of course there are probably 500 more but I can only think of one. What is the word?
Finish cartalk.py to find it.

2. (optional) Here’s another Car Talk Puzzler(https://www.cartalk.com/radio/puzzler) you can solve with a search (not a search for the solution) :

“Recently I had a visit with my mom and we realized that the two digits that make up my age when reversed resulted in her age. For example, if she’s 73, I’m 37. We wondered how often this has happened over the years but we got sidetracked with other topics and we never came up with an answer.

“When I got home I figured out that the digits of our ages have been reversible six times so far. I also figured out that if we’re lucky it would happen again in a few years, and if we’re really lucky it would happen one more time after that. In other words, it would have happened 8 times over all. So the question is, how old am I now?”

Write a Python program that searches for solutions to this Puzzler. Hint:you might find the string method zfill useful.