Jump to content

Archive:Think Python/Case study: word play: Difference between revisions

From IdeaWazaWiki
wikademia>Whiteknight
m Think Python: Automatically uploading HTML source of this book from http://www.greenteapress.com/thinkpython/html/. Will convert to wikitext in a separate step
 
wikademia>Wikademia
 
(2 intermediate revisions by 2 users not shown)
Line 1: Line 1:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
{{Think Python/Page}}
            "http://www.w3.org/TR/REC-html40/loose.dtd">
<HTML>
<HEAD>


<META http-equiv="Content-Type" content="text/html; charset=US-ASCII">
== Chapter&#XA0;9&#XA0;&#XA0;Case study: word play ==
<META name="GENERATOR" content="hevea 1.10">
 
<LINK rel="stylesheet" type="text/css" href="book.css">
=== 9.1&#XA0;&#XA0;Reading word lists ===
<TITLE>Case study: word play</TITLE>
 
</HEAD>
 
<BODY >
 
<A HREF="book009.html"><IMG SRC="previous_motif.gif" ALT="Previous"></A>
 
<A HREF="index.html"><IMG SRC="contents_motif.gif" ALT="Up"></A>
For the exercises in this chapter we need a list of English words.
<A HREF="book011.html"><IMG SRC="next_motif.gif" ALT="Next"></A>
<HR>
<H1 CLASS="chapter"><A NAME="htoc108"><FONT COLOR=black><FONT SIZE=3>Chapter&#XA0;9</FONT></FONT></A><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;Case study: word play</FONT></FONT></H1><H2 CLASS="section"><A NAME="toc98"></A><A NAME="htoc109"><FONT COLOR=black><FONT SIZE=3>9.1</FONT></FONT></A><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;Reading word lists</FONT></FONT></H2><P><FONT COLOR=black><FONT SIZE=3>
</FONT></FONT><A NAME="wordlist"></A></P><P><FONT COLOR=black><FONT SIZE=3>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
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
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
contributed to the public domain by Grady Ward as part of the Moby
lexicon project</FONT></FONT><SUP><A NAME="text16" HREF="#note16"><FONT COLOR=black><FONT SIZE=3>1</FONT></FONT></A></SUP><FONT COLOR=black><FONT SIZE=3>. It
lexicon project<SUP>1</SUP>. It
is a list of 113,809 official crosswords; that is, words that are
is a list of 113,809 official crosswords; that is, words that are
considered valid in crossword puzzles and other word games. In the
considered valid in crossword puzzles and other word games. In the
Moby collection, the filename is </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>113809of.fic</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>; I include a copy
Moby collection, the filename is <TT>113809of.fic</TT>; I include a copy
of this file, with the simpler name </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>words.txt</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>, along with
of this file, with the simpler name <TT>words.txt</TT>, along with
Swampy.</FONT></FONT></P><P><A NAME="@default668"></A><FONT COLOR=black><FONT SIZE=3>
Swampy.
</FONT></FONT><A NAME="@default669"></A></P><P><FONT COLOR=black><FONT SIZE=3>This file is in plain text, so you can open it with a text
 
 
 
 
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
editor, but you can also read it from Python. The built-in
function </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>open</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> takes the name of the file as a parameter
function <TT>open</TT> takes the name of the file as a parameter
and returns a </FONT></FONT><FONT COLOR=black><FONT SIZE=3><B>file object</B></FONT></FONT><FONT COLOR=black><FONT SIZE=3> you can use to read the file.</FONT></FONT></P><P><A NAME="@default670"></A><FONT COLOR=black><FONT SIZE=3>
and returns a '''file object''' you can use to read the file.
</FONT></FONT><A NAME="@default671"></A><FONT COLOR=black><FONT SIZE=3>
 
</FONT></FONT><A NAME="@default672"></A><FONT COLOR=black><FONT SIZE=3>
 
</FONT></FONT><A NAME="@default673"></A><FONT COLOR=black><FONT SIZE=3>
 
</FONT></FONT><A NAME="@default674"></A><FONT COLOR=black><FONT SIZE=3>
 
</FONT></FONT><A NAME="@default675"></A></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>&gt;&gt;&gt; fin = open('words.txt')
 
 
 
<PRE CLASS="verbatim">&gt;&gt;&gt; fin = open('words.txt')
&gt;&gt;&gt; print fin
&gt;&gt;&gt; print fin
&lt;open file 'words.txt', mode 'r' at 0xb7f4b380&gt;
&lt;open file 'words.txt', mode 'r' at 0xb7f4b380&gt;
</FONT></FONT></PRE><P><FONT COLOR=black><FONT SIZE=3><TT>fin</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> is a common name for a file object used for
</PRE>
input. Mode </FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3>'r'</FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3> indicates that this file is open for
<TT>fin</TT> is a common name for a file object used for
reading (as opposed to </FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3>'w'</FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3> for writing).</FONT></FONT></P><P><A NAME="@default676"></A><FONT COLOR=black><FONT SIZE=3>
input. Mode <CODE>'r'</CODE> indicates that this file is open for
</FONT></FONT><A NAME="@default677"></A></P><P><FONT COLOR=black><FONT SIZE=3>The file object provides several methods for reading, including
reading (as opposed to <CODE>'w'</CODE> for writing).
</FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>readline</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>, which reads characters from the file
 
 
 
 
The file object provides several methods for reading, including
<TT>readline</TT>, which reads characters from the file
until it gets to a newline and returns the result as a
until it gets to a newline and returns the result as a
string:</FONT></FONT></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>&gt;&gt;&gt; fin.readline()
string:
<PRE CLASS="verbatim">&gt;&gt;&gt; fin.readline()
'aa\r\n'
'aa\r\n'
</FONT></FONT></PRE><P><FONT COLOR=black><FONT SIZE=3>The first word in this particular list is &#X201C;aa,&#X201D; which is a kind of
</PRE>
lava. The sequence </FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3>\r\n</FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3> represents two whitespace characters,
The first word in this particular list is &#X201C;aa,&#X201D; which is a kind of
lava. The sequence <CODE>\r\n</CODE> represents two whitespace characters,
a carriage return and a newline, that separate this word from the
a carriage return and a newline, that separate this word from the
next.</FONT></FONT></P><P><FONT COLOR=black><FONT SIZE=3>The file object keeps track of where it is in the file, so
next.
if you call </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>readline</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> again, you get the next word:</FONT></FONT></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>&gt;&gt;&gt; fin.readline()
 
The file object keeps track of where it is in the file, so
if you call <TT>readline</TT> again, you get the next word:
<PRE CLASS="verbatim">&gt;&gt;&gt; fin.readline()
'aah\r\n'
'aah\r\n'
</FONT></FONT></PRE><P><FONT COLOR=black><FONT SIZE=3>The next word is &#X201C;aah,&#X201D; which is a perfectly legitimate
</PRE>
The next word is &#X201C;aah,&#X201D; which is a perfectly legitimate
word, so stop looking at me like that.
word, so stop looking at me like that.
Or, if it&#X2019;s the whitespace that&#X2019;s bothering you,
Or, if it&#X2019;s the whitespace that&#X2019;s bothering you,
we can get rid of it with the string method </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>strip</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>:</FONT></FONT></P><P><A NAME="@default678"></A><FONT COLOR=black><FONT SIZE=3>
we can get rid of it with the string method <TT>strip</TT>:
</FONT></FONT><A NAME="@default679"></A></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>&gt;&gt;&gt; line = fin.readline()
 
 
 
<PRE CLASS="verbatim">&gt;&gt;&gt; line = fin.readline()
&gt;&gt;&gt; word = line.strip()
&gt;&gt;&gt; word = line.strip()
&gt;&gt;&gt; print word
&gt;&gt;&gt; print word
aahed
aahed
</FONT></FONT></PRE><P><FONT COLOR=black><FONT SIZE=3>You can also use a file object as part of a </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>for</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> loop.
</PRE>
This program reads </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>words.txt</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> and prints each word, one
You can also use a file object as part of a <TT>for</TT> loop.
per line:</FONT></FONT></P><P><A NAME="@default680"></A><FONT COLOR=black><FONT SIZE=3>
This program reads <TT>words.txt</TT> and prints each word, one
</FONT></FONT><A NAME="@default681"></A></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>fin = open('words.txt')
per line:
 
 
 
<PRE CLASS="verbatim">fin = open('words.txt')
for line in fin:
for line in fin:
     word = line.strip()
     word = line.strip()
     print word
     print word
</FONT></FONT></PRE><DIV CLASS="theorem"><FONT COLOR=black><FONT SIZE=3><B>Exercise&#XA0;1</B></FONT></FONT><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;<EM>
</PRE><DIV CLASS="theorem">'''Exercise&#XA0;1'''&#XA0;&#XA0;''
Write a program that reads </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>words.txt</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM> and prints only the
Write a program that reads ''''<TT>words.txt</TT>'''' and prints only the
words with more than 20 characters (not counting whitespace).</EM></FONT></FONT><P><A NAME="@default682"></A></P></DIV><H2 CLASS="section"><A NAME="toc99"></A><A NAME="htoc110"><FONT COLOR=black><FONT SIZE=3>9.2</FONT></FONT></A><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;Exercises</FONT></FONT></H2><P><FONT COLOR=black><FONT SIZE=3>There are solutions to these exercises in the next section.
words with more than 20 characters (not counting whitespace).''
You should at least attempt each one before you read the solutions.</FONT></FONT></P><DIV CLASS="theorem"><FONT COLOR=black><FONT SIZE=3><B>Exercise&#XA0;2</B></FONT></FONT><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;<EM>
 
</DIV>=== 9.2&#XA0;&#XA0;Exercises ===
 
There are solutions to these exercises in the next section.
You should at least attempt each one before you read the solutions.
<DIV CLASS="theorem">'''Exercise&#XA0;2'''&#XA0;&#XA0;''
In 1939 Ernest Vincent Wright published a 50,000 word novel called
In 1939 Ernest Vincent Wright published a 50,000 word novel called
</EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3>Gadsby</FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM> that does not contain the letter &#X201C;e.&#X201D; Since &#X201C;e&#X201D; is
''Gadsby'' that does not contain the letter &#X201C;e.&#X201D; Since &#X201C;e&#X201D; is
the most common letter in English, that&#X2019;s not easy to do.</EM></FONT></FONT><P><FONT COLOR=black><FONT SIZE=3><EM>In fact, it is difficult to construct a solitary thought without using
the most common letter in English, that&#X2019;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
that most common symbol. It is slow going at first, but with caution
and hours of training you can gradually gain facility.</EM></FONT></FONT></P><P><FONT COLOR=black><FONT SIZE=3><EM>All right, I&#X2019;ll stop now.</EM></FONT></FONT></P><P><FONT COLOR=black><FONT SIZE=3><EM>Write a function called </EM></FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3><EM>has_no_e</EM></FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3><EM> that returns </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>True</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM> if
and hours of training you can gradually gain facility.''
the given word doesn&#X2019;t have the letter &#X201C;e&#X201D; in it.</EM></FONT></FONT></P><P><FONT COLOR=black><FONT SIZE=3><EM>Modify your program from the previous section to print only the words
 
''All right, I&#X2019;ll stop now.''
 
''Write a function called ''<CODE>''has_no_e''</CODE>'' that returns ''''<TT>True</TT>'''' if
the given word doesn&#X2019;t have the letter &#X201C;e&#X201D; in it.''
 
''Modify your program from the previous section to print only the words
that have no &#X201C;e&#X201D; and compute the percentage of the words in the list
that have no &#X201C;e&#X201D; and compute the percentage of the words in the list
have no &#X201C;e.&#X201D;</EM></FONT></FONT></P><P><A NAME="@default683"></A></P></DIV><DIV CLASS="theorem"><FONT COLOR=black><FONT SIZE=3><B>Exercise&#XA0;3</B></FONT></FONT><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;<EM>
have no &#X201C;e.&#X201D;''
Write a function named </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>avoids</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM>
 
</DIV><DIV CLASS="theorem">'''Exercise&#XA0;3'''&#XA0;&#XA0;''
Write a function named ''''<TT>avoids</TT>''''
that takes a word and a string of forbidden letters, and
that takes a word and a string of forbidden letters, and
that returns </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>True</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM> if the word doesn&#X2019;t use any of the forbidden
that returns ''''<TT>True</TT>'''' if the word doesn&#X2019;t use any of the forbidden
letters.</EM></FONT></FONT><P><FONT COLOR=black><FONT SIZE=3><EM>Modify your program to prompt the user to enter a string
letters.''
''Modify your program to prompt the user to enter a string
of forbidden letters and then print the number of words that
of forbidden letters and then print the number of words that
don&#X2019;t contain any of them.
don&#X2019;t contain any of them.
Can you find a combination of 5 forbidden letters that
Can you find a combination of 5 forbidden letters that
excludes the smallest number of words?
excludes the smallest number of words?
</EM></FONT></FONT></P></DIV><DIV CLASS="theorem"><FONT COLOR=black><FONT SIZE=3><B>Exercise&#XA0;4</B></FONT></FONT><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;<EM>
''
Write a function named </EM></FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3><EM>uses_only</EM></FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3><EM> that takes a word and a
</DIV><DIV CLASS="theorem">'''Exercise&#XA0;4'''&#XA0;&#XA0;''
string of letters, and that returns </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>True</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM> if the word contains
Write a function named ''<CODE>''uses_only''</CODE>'' that takes a word and a
string of letters, and that returns ''''<TT>True</TT>'''' if the word contains
only letters in the list. Can you make a sentence using only the
only letters in the list. Can you make a sentence using only the
letters </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>acefhlo</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM>? Other than &#X201C;Hoe alfalfa?&#X201D;
letters ''''<TT>acefhlo</TT>''''? Other than &#X201C;Hoe alfalfa?&#X201D;
</EM></FONT></FONT></DIV><DIV CLASS="theorem"><FONT COLOR=black><FONT SIZE=3><B>Exercise&#XA0;5</B></FONT></FONT><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;<EM>
''</DIV><DIV CLASS="theorem">'''Exercise&#XA0;5'''&#XA0;&#XA0;''
Write a function named </EM></FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3><EM>uses_all</EM></FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3><EM> that takes a word and a
Write a function named ''<CODE>''uses_all''</CODE>'' that takes a word and a
string of required letters, and that returns </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>True</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM> if the word
string of required letters, and that returns ''''<TT>True</TT>'''' if the word
uses all the required letters at least once. How many words are there
uses all the required letters at least once. How many words are there
that use all the vowels </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>aeiou</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM>? How about </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>aeiouy</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM>?
that use all the vowels ''''<TT>aeiou</TT>''''? How about ''''<TT>aeiouy</TT>''''?
</EM></FONT></FONT></DIV><DIV CLASS="theorem"><FONT COLOR=black><FONT SIZE=3><B>Exercise&#XA0;6</B></FONT></FONT><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;<EM>
''</DIV><DIV CLASS="theorem">'''Exercise&#XA0;6'''&#XA0;&#XA0;''
Write a function called </EM></FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3><EM>is_abecedarian</EM></FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3><EM> that returns
Write a function called ''<CODE>''is_abecedarian''</CODE>'' that returns
</EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>True</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM> if the letters in a word appear in alphabetical order
''''<TT>True</TT>'''' if the letters in a word appear in alphabetical order
(double letters are ok).  
(double letters are ok).  
How many abecedarian words are there?
How many abecedarian words are there?
</EM></FONT></FONT></DIV><P><A NAME="@default684"></A></P><H2 CLASS="section"><A NAME="toc100"></A><A NAME="htoc111"><FONT COLOR=black><FONT SIZE=3>9.3</FONT></FONT></A><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;Search</FONT></FONT></H2><P><A NAME="@default685"></A><FONT COLOR=black><FONT SIZE=3>
''</DIV>
</FONT></FONT><A NAME="@default686"></A></P><P><FONT COLOR=black><FONT SIZE=3>All of the exercises in the previous section have something
 
=== 9.3&#XA0;&#XA0;Search ===
 
 
 
 
All of the exercises in the previous section have something
in common; they can be solved with the search pattern we saw
in common; they can be solved with the search pattern we saw
in Section&#XA0;</FONT></FONT><A HREF="book009.html#find"><FONT COLOR=black><FONT SIZE=3>8.6</FONT></FONT></A><FONT COLOR=black><FONT SIZE=3>. The simplest example is:</FONT></FONT></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>def has_no_e(word):
in Section&#XA0;8.6. The simplest example is:
<PRE CLASS="verbatim">def has_no_e(word):
     for letter in word:
     for letter in word:
         if letter == 'e':
         if letter == 'e':
             return False
             return False
     return True
     return True
</FONT></FONT></PRE><P><FONT COLOR=black><FONT SIZE=3>The </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>for</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> loop traverses the characters in </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>word</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>. If we find
</PRE>
the letter &#X201C;e&#X201D;, we can immediately return </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>False</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>; otherwise we
The <TT>for</TT> loop traverses the characters in <TT>word</TT>. If we find
the letter &#X201C;e&#X201D;, we can immediately return <TT>False</TT>; otherwise we
have to go to the next letter. If we exit the loop normally, that
have to go to the next letter. If we exit the loop normally, that
means we didn&#X2019;t find an &#X201C;e&#X201D;, so we return </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>True</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>.</FONT></FONT></P><P><A NAME="@default687"></A><FONT COLOR=black><FONT SIZE=3>
means we didn&#X2019;t find an &#X201C;e&#X201D;, so we return <TT>True</TT>.
</FONT></FONT><A NAME="@default688"></A><FONT COLOR=black><FONT SIZE=3>
 
</FONT></FONT><A NAME="@default689"></A></P><P><FONT COLOR=black><FONT SIZE=3>You can write this function more concisely using the </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>in</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>
 
 
 
 
You can write this function more concisely using the <TT>in</TT>
operator, but I started with this version because it  
operator, but I started with this version because it  
demonstrates the logic of the search pattern.</FONT></FONT></P><P><A NAME="@default690"></A></P><P><FONT COLOR=black><FONT SIZE=3><TT>avoids</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> is a more general version of </FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3>has_no_e</FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3> but it
demonstrates the logic of the search pattern.
has the same structure:</FONT></FONT></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>def avoids(word, forbidden):
 
<TT>avoids</TT> is a more general version of <CODE>has_no_e</CODE> but it
has the same structure:
<PRE CLASS="verbatim">def avoids(word, forbidden):
     for letter in word:
     for letter in word:
         if letter in forbidden:
         if letter in forbidden:
             return False
             return False
     return True
     return True
</FONT></FONT></PRE><P><FONT COLOR=black><FONT SIZE=3>We can return </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>False</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> as soon as we find a forbidden letter;
</PRE>
if we get to the end of the loop, we return </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>True</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>.</FONT></FONT></P><P><CODE><FONT COLOR=black><FONT SIZE=3>uses_only</FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3> is similar except that the sense of the condition
We can return <TT>False</TT> as soon as we find a forbidden letter;
is reversed:</FONT></FONT></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>def uses_only(word, available):
if we get to the end of the loop, we return <TT>True</TT>.
 
<CODE>uses_only</CODE> is similar except that the sense of the condition
is reversed:
<PRE CLASS="verbatim">def uses_only(word, available):
     for letter in word:  
     for letter in word:  
         if letter not in available:
         if letter not in available:
             return False
             return False
     return True
     return True
</FONT></FONT></PRE><P><FONT COLOR=black><FONT SIZE=3>Instead of a list of forbidden words, we have a list of available
</PRE>
words. If we find a letter in </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>word</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> that is not in
Instead of a list of forbidden words, we have a list of available
</FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>available</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>, we can return </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>False</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>.</FONT></FONT></P><P><CODE><FONT COLOR=black><FONT SIZE=3>uses_all</FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3> is similar except that we reverse the role
words. If we find a letter in <TT>word</TT> that is not in
of the word and the string of letters:</FONT></FONT></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>def uses_all(word, required):
<TT>available</TT>, we can return <TT>False</TT>.
 
<CODE>uses_all</CODE> is similar except that we reverse the role
of the word and the string of letters:
<PRE CLASS="verbatim">def uses_all(word, required):
     for letter in required:  
     for letter in required:  
         if letter not in word:
         if letter not in word:
             return False
             return False
     return True
     return True
</FONT></FONT></PRE><P><FONT COLOR=black><FONT SIZE=3>Instead of traversing the letters in </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>word</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>, the loop
</PRE>
Instead of traversing the letters in <TT>word</TT>, the loop
traverses the required letters. If any of the required letters
traverses the required letters. If any of the required letters
do not appear in the word, we can return </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>False</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>.</FONT></FONT></P><P><A NAME="@default691"></A></P><P><FONT COLOR=black><FONT SIZE=3>If you were really thinking like a computer scientist, you would
do not appear in the word, we can return <TT>False</TT>.
have recognized that </FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3>uses_all</FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3> was an instance of a
 
previously-solved problem, and you would have written:</FONT></FONT></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>def uses_all(word, required):
If you were really thinking like a computer scientist, you would
have recognized that <CODE>uses_all</CODE> was an instance of a
previously-solved problem, and you would have written:
<PRE CLASS="verbatim">def uses_all(word, required):
     return uses_only(required, word)
     return uses_only(required, word)
</FONT></FONT></PRE><P><FONT COLOR=black><FONT SIZE=3>This is an example of a program development method called </FONT></FONT><FONT COLOR=black><FONT SIZE=3><B>problem
</PRE>
recognition</B></FONT></FONT><FONT COLOR=black><FONT SIZE=3>, which means that you recognize the problem you are
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
working on as an instance of a previously-solved problem, and apply a
previously-developed solution.</FONT></FONT></P><P><A NAME="@default692"></A><FONT COLOR=black><FONT SIZE=3>
previously-developed solution.
</FONT></FONT><A NAME="@default693"></A></P><H2 CLASS="section"><A NAME="toc101"></A><A NAME="htoc112"><FONT COLOR=black><FONT SIZE=3>9.4</FONT></FONT></A><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;Looping with indices</FONT></FONT></H2><P><A NAME="@default694"></A><FONT COLOR=black><FONT SIZE=3>
 
</FONT></FONT><A NAME="@default695"></A></P><P><FONT COLOR=black><FONT SIZE=3>I wrote the functions in the previous section with </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>for</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>
 
 
=== 9.4&#XA0;&#XA0;Looping with indices ===
 
 
 
 
I wrote the functions in the previous section with <TT>for</TT>
loops because I only needed the characters in the strings; I didn&#X2019;t
loops because I only needed the characters in the strings; I didn&#X2019;t
have to do anything with the indices.</FONT></FONT></P><P><FONT COLOR=black><FONT SIZE=3>For </FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3>is_abecedarian</FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3> we have to compare adjacent letters,
have to do anything with the indices.
which is a little tricky with a </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>for</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> loop:</FONT></FONT></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>def is_abecedarian(word):
 
For <CODE>is_abecedarian</CODE> we have to compare adjacent letters,
which is a little tricky with a <TT>for</TT> loop:
<PRE CLASS="verbatim">def is_abecedarian(word):
     previous = word[0]
     previous = word[0]
     for c in word:
     for c in word:
Line 157: Line 229:
         previous = c
         previous = c
     return True
     return True
</FONT></FONT></PRE><P><FONT COLOR=black><FONT SIZE=3>An alternative is to
</PRE>
use recursion:</FONT></FONT></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>def is_abecedarian(word):
An alternative is to
use recursion:
<PRE CLASS="verbatim">def is_abecedarian(word):
     if len(word) &lt;= 1:
     if len(word) &lt;= 1:
         return True
         return True
Line 164: Line 238:
         return False
         return False
     return is_abecedarian(word[1:])
     return is_abecedarian(word[1:])
</FONT></FONT></PRE><P><FONT COLOR=black><FONT SIZE=3>Another option is to use a </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>while</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> loop:</FONT></FONT></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>def is_abecedarian(word):
</PRE>
Another option is to use a <TT>while</TT> loop:
<PRE CLASS="verbatim">def is_abecedarian(word):
     i = 0
     i = 0
     while i &lt; len(word)-1:
     while i &lt; len(word)-1:
Line 171: Line 247:
         i = i+1
         i = i+1
     return True
     return True
</FONT></FONT></PRE><P><FONT COLOR=black><FONT SIZE=3>The loop starts at </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>i=0</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> and ends when </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>i=len(word)-1</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>. Each
</PRE>
time through the loop, it compares the </FONT></FONT><FONT COLOR=black><FONT SIZE=3><I>i</I></FONT></FONT><FONT COLOR=black><FONT SIZE=3>th character (which you can
The loop starts at <TT>i=0</TT> and ends when <TT>i=len(word)-1</TT>. Each
think of as the current character) to the </FONT></FONT><FONT COLOR=black><FONT SIZE=3><I>i</I>+1</FONT></FONT><FONT COLOR=black><FONT SIZE=3>th character (which you
time through the loop, it compares the <I>i</I>th character (which you can
can think of as the next).</FONT></FONT></P><P><FONT COLOR=black><FONT SIZE=3>If the next character is less than (alphabetically before) the current
think of as the current character) to the <I>i</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, and
one, then we have discovered a break in the abecedarian trend, and
we return </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>False</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>.</FONT></FONT></P><P><FONT COLOR=black><FONT SIZE=3>If we get to the end of the loop without finding a fault, then the
we return <TT>False</TT>.
 
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
word passes the test. To convince yourself that the loop ends
correctly, consider an example like </FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3>'flossy'</FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3>. The
correctly, consider an example like <CODE>'flossy'</CODE>. The
length of the word is 6, so
length of the word is 6, so
the last time the loop runs is when </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>i</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3> is 4, which is the
the last time the loop runs is when <TT>i</TT> is 4, which is the
index of the second-to-last character. On the last iteration,
index of the second-to-last character. On the last iteration,
it compares the second-to-last character to the last, which is
it compares the second-to-last character to the last, which is
what we want.</FONT></FONT></P><P><A NAME="@default696"></A></P><P><FONT COLOR=black><FONT SIZE=3>Here is a version of </FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3>is_palindrome</FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3> (see
what we want.
Exercise&#XA0;</FONT></FONT><A HREF="book007.html#palindrome"><FONT COLOR=black><FONT SIZE=3>6.6</FONT></FONT></A><FONT COLOR=black><FONT SIZE=3>) that uses two indices; one starts at the
 
beginning and goes up; the other starts at the end and goes down.</FONT></FONT></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>def is_palindrome(word):
Here is a version of <CODE>is_palindrome</CODE> (see
Exercise&#XA0;6.6) that uses two indices; one starts at the
beginning and goes up; the other starts at the end and goes down.
<PRE CLASS="verbatim">def is_palindrome(word):
     i = 0
     i = 0
     j = len(word)-1
     j = len(word)-1
Line 196: Line 280:


     return True
     return True
</FONT></FONT></PRE><P><FONT COLOR=black><FONT SIZE=3>Or, if you noticed that this is an instance of a previously-solved
</PRE>
problem, you might have written:</FONT></FONT></P><PRE CLASS="verbatim"><FONT COLOR=blue><FONT SIZE=4>def is_palindrome(word):
Or, if you noticed that this is an instance of a previously-solved
problem, you might have written:
<PRE CLASS="verbatim">def is_palindrome(word):
     return is_reverse(word, word)
     return is_reverse(word, word)
</FONT></FONT></PRE><P><A NAME="@default697"></A><FONT COLOR=black><FONT SIZE=3>
</PRE>
</FONT></FONT><A NAME="@default698"></A></P><P><FONT COLOR=black><FONT SIZE=3>Assuming you did Exercise&#XA0;</FONT></FONT><A HREF="book009.html#is_reverse"><FONT COLOR=black><FONT SIZE=3>8.8</FONT></FONT></A><FONT COLOR=black><FONT SIZE=3>.</FONT></FONT></P><H2 CLASS="section"><A NAME="toc102"></A><A NAME="htoc113"><FONT COLOR=black><FONT SIZE=3>9.5</FONT></FONT></A><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;Debugging</FONT></FONT></H2><P><A NAME="@default699"></A><FONT COLOR=black><FONT SIZE=3>
 
</FONT></FONT><A NAME="@default700"></A><FONT COLOR=black><FONT SIZE=3>
 
</FONT></FONT><A NAME="@default701"></A></P><P><FONT COLOR=black><FONT SIZE=3>Testing programs is hard. The functions in this chapter are
 
Assuming you did Exercise&#XA0;8.8.
=== 9.5&#XA0;&#XA0;Debugging ===
 
 
 
 
 
Testing programs is hard. The functions in this chapter are
relatively easy to test because you can check the results by hand.
relatively easy to test because you can check the results by hand.
Even so, it is somewhere between difficult and impossible to choose a
Even so, it is somewhere between difficult and impossible to choose a
set of words that test for all possible errors.</FONT></FONT></P><P><FONT COLOR=black><FONT SIZE=3>Taking </FONT></FONT><CODE><FONT COLOR=black><FONT SIZE=3>has_no_e</FONT></FONT></CODE><FONT COLOR=black><FONT SIZE=3> as an example, there are two obvious
set of words that test for all possible errors.
cases to check: words that have an &#X2019;e&#X2019; should return </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>False</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>;
 
words that don&#X2019;t should return </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>True</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>. You should have no
Taking <CODE>has_no_e</CODE> as an example, there are two obvious
trouble coming up with one of each.</FONT></FONT></P><P><FONT COLOR=black><FONT SIZE=3>Within each case, there are some less obvious subcases. Among the
cases to check: words that have an &#X2019;e&#X2019; should return <TT>False</TT>;
words that don&#X2019;t should return <TT>True</TT>. You should have no
trouble coming up with one of each.
 
Within each case, there are some less obvious subcases. Among the
words that have an &#X201C;e,&#X201D; you should test words with an &#X201C;e&#X201D; at the
words that have an &#X201C;e,&#X201D; you should test words with an &#X201C;e&#X201D; at the
beginning, the end, and somewhere in the middle. You should test long
beginning, the end, and somewhere in the middle. You should test long
words, short words, and very short words, like the empty string. The
words, short words, and very short words, like the empty string. The
empty string is an example of a </FONT></FONT><FONT COLOR=black><FONT SIZE=3><B>special case</B></FONT></FONT><FONT COLOR=black><FONT SIZE=3>, which is one of
empty string is an example of a '''special case''', which is one of
the non-obvious cases where errors often lurk.</FONT></FONT></P><P><A NAME="@default702"></A></P><P><FONT COLOR=black><FONT SIZE=3>In addition to the test cases you generate, you can also test
the non-obvious cases where errors often lurk.
your program with a word list like </FONT></FONT><FONT COLOR=black><FONT SIZE=3><TT>words.txt</TT></FONT></FONT><FONT COLOR=black><FONT SIZE=3>. By scanning
 
In addition to the test cases you generate, you can also test
your program with a word list like <TT>words.txt</TT>. By scanning
the output, you might be able to catch errors, but be careful:
the output, you might be able to catch errors, but be careful:
you might catch one kind of error (words that should not be
you might catch one kind of error (words that should not be
included, but are) and not another (words that should be included,
included, but are) and not another (words that should be included,
but aren&#X2019;t).</FONT></FONT></P><P><FONT COLOR=black><FONT SIZE=3>In general, testing can help you find bugs, but it is not easy to
but aren&#X2019;t).
 
In general, testing can help you find bugs, but it is not easy to
generate a good set of test cases, and even if you do, you can&#X2019;t
generate a good set of test cases, and even if you do, you can&#X2019;t
be sure your program is correct.</FONT></FONT></P><P><A NAME="@default703"></A></P><P><FONT COLOR=black><FONT SIZE=3>According to a legendary computer scientist:</FONT></FONT></P><BLOCKQUOTE CLASS="quote"><FONT COLOR=black><FONT SIZE=3>
be sure your program is correct.
 
According to a legendary computer scientist:
<BLOCKQUOTE CLASS="quote">
Program testing can be used to show the presence of bugs, but never to
Program testing can be used to show the presence of bugs, but never to
show their absence!</FONT></FONT><P><FONT COLOR=black><FONT SIZE=3>&#X2014; Edsger W. Dijkstra
show their absence!
</FONT></FONT></P></BLOCKQUOTE><P><A NAME="@default704"></A></P><H2 CLASS="section"><A NAME="toc103"></A><A NAME="htoc114"><FONT COLOR=black><FONT SIZE=3>9.6</FONT></FONT></A><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;Glossary</FONT></FONT></H2><DL CLASS="description"><DT CLASS="dt-description"><FONT COLOR=black><FONT SIZE=3><B>file object:</B></FONT></FONT></DT><DD CLASS="dd-description"><FONT COLOR=black><FONT SIZE=3> A value that represents an open file.
&#X2014; Edsger W. Dijkstra
</FONT></FONT><A NAME="@default705"></A><FONT COLOR=black><FONT SIZE=3>
 
</FONT></FONT><A NAME="@default706"></A></DD><DT CLASS="dt-description"><FONT COLOR=black><FONT SIZE=3><B>problem recognition:</B></FONT></FONT></DT><DD CLASS="dd-description"><FONT COLOR=black><FONT SIZE=3> A way of solving a problem by
</BLOCKQUOTE>
 
=== 9.6&#XA0;&#XA0;Glossary ===
 
<DL CLASS="description"><DT CLASS="dt-description">'''file object:'''</DT><DD CLASS="dd-description"> A value that represents an open file.
 
</DD><DT CLASS="dt-description">'''problem recognition:'''</DT><DD CLASS="dd-description"> A way of solving a problem by
expressing it as an instance of a previously-solved problem.
expressing it as an instance of a previously-solved problem.
</FONT></FONT><A NAME="@default707"></A></DD><DT CLASS="dt-description"><FONT COLOR=black><FONT SIZE=3><B>special case:</B></FONT></FONT></DT><DD CLASS="dd-description"><FONT COLOR=black><FONT SIZE=3> A test case that is atypical or non-obvious
</DD><DT CLASS="dt-description">'''special case:'''</DT><DD CLASS="dd-description"> A test case that is atypical or non-obvious
(and less likely to be handled correctly).
(and less likely to be handled correctly).
</FONT></FONT><A NAME="@default708"></A></DD></DL><H2 CLASS="section"><A NAME="toc104"></A><A NAME="htoc115"><FONT COLOR=black><FONT SIZE=3>9.7</FONT></FONT></A><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;Exercises</FONT></FONT></H2><DIV CLASS="theorem"><FONT COLOR=black><FONT SIZE=3><B>Exercise&#XA0;7</B></FONT></FONT><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;</FONT></FONT><P><A NAME="@default709"></A><FONT COLOR=black><FONT SIZE=3><EM>
</DD></DL>=== 9.7&#XA0;&#XA0;Exercises ===
</EM></FONT></FONT><A NAME="@default710"></A><FONT COLOR=black><FONT SIZE=3><EM>
 
</EM></FONT></FONT><A NAME="@default711"></A></P><P><FONT COLOR=black><FONT SIZE=3><EM>This question is based on a Puzzler that was broadcast on the radio
<DIV CLASS="theorem">'''Exercise&#XA0;7'''&#XA0;&#XA0;
program </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3>Car
''
Talk</FONT></FONT><SUP><A NAME="text17" HREF="#note17"><FONT COLOR=black><FONT SIZE=3><EM>2</EM></FONT></FONT></A></SUP><FONT COLOR=black><FONT SIZE=3><EM>:</EM></FONT></FONT></P><BLOCKQUOTE CLASS="quote"><FONT COLOR=black><FONT SIZE=3><EM>
''''
''
 
''This question is based on a Puzzler that was broadcast on the radio
program ''Car
Talk<SUP>''2''</SUP>'':''
<BLOCKQUOTE CLASS="quote">''
Give me a word with three consecutive double letters. I&#X2019;ll give you a
Give me a word with three consecutive double letters. I&#X2019;ll give you a
couple of words that almost qualify, but don&#X2019;t. For example, the word
couple of words that almost qualify, but don&#X2019;t. For example, the word
Line 242: Line 359:
be the only word. Of course there are probably 500 more but I can only
be the only word. Of course there are probably 500 more but I can only
think of one. What is the word?
think of one. What is the word?
</EM></FONT></FONT></BLOCKQUOTE><P><FONT COLOR=black><FONT SIZE=3><EM>Write a program to find it. You can see my solution at
''</BLOCKQUOTE>
</EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>thinkpython.com/code/cartalk.py</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM>.</EM></FONT></FONT></P></DIV><DIV CLASS="theorem"><FONT COLOR=black><FONT SIZE=3><B>Exercise&#XA0;8</B></FONT></FONT><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;<EM>
''Write a program to find it. You can see my solution at
Here&#X2019;s another </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3>Car Talk</FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM>
''''<TT>thinkpython.com/code/cartalk.py</TT>''''.''
Puzzler</EM></FONT></FONT><SUP><A NAME="text18" HREF="#note18"><FONT COLOR=black><FONT SIZE=3><EM>3</EM></FONT></FONT></A></SUP><FONT COLOR=black><FONT SIZE=3><EM>:</EM></FONT></FONT><P><A NAME="@default712"></A><FONT COLOR=black><FONT SIZE=3><EM>
</DIV><DIV CLASS="theorem">'''Exercise&#XA0;8'''&#XA0;&#XA0;''
</EM></FONT></FONT><A NAME="@default713"></A><FONT COLOR=black><FONT SIZE=3><EM>
Here&#X2019;s another ''Car Talk''
</EM></FONT></FONT><A NAME="@default714"></A><FONT COLOR=black><FONT SIZE=3><EM>
Puzzler''<SUP>''3''</SUP>'':''
</EM></FONT></FONT><A NAME="@default715"></A></P><BLOCKQUOTE CLASS="quote"><FONT COLOR=black><FONT SIZE=3><EM>
''
''''
''''
''
<BLOCKQUOTE CLASS="quote">''
&#X201C;I was driving on the highway the other day and I happened to
&#X201C;I was driving on the highway the other day and I happened to
notice my odometer. Like most odometers, it shows six digits,
notice my odometer. Like most odometers, it shows six digits,
in whole miles only. So, if my car had 300,000
in whole miles only. So, if my car had 300,000
miles, for example, I&#X2019;d see 3-0-0-0-0-0.</EM></FONT></FONT><P><FONT COLOR=black><FONT SIZE=3><EM>&#X201C;Now, what I saw that day was very interesting. I noticed that the
miles, for example, I&#X2019;d see 3-0-0-0-0-0.''
''&#X201C;Now, what I saw that day was very interesting. I noticed that the
last 4 digits were palindromic; that is, they read the same forward as
last 4 digits were palindromic; that is, they read the same forward as
backward. For example, 5-4-4-5 is a palindrome, so my odometer
backward. For example, 5-4-4-5 is a palindrome, so my odometer
could have read 3-1-5-4-4-5.</EM></FONT></FONT></P><P><FONT COLOR=black><FONT SIZE=3><EM>&#X201C;One mile later, the last 5 numbers were palindromic. For example, it
could have read 3-1-5-4-4-5.''
 
''&#X201C;One mile later, the last 5 numbers were palindromic. For example, it
could have read 3-6-5-4-5-6. One mile after that, the middle 4 out of
could have read 3-6-5-4-5-6. One mile after that, the middle 4 out of
6 numbers were palindromic. And you ready for this? One mile later,
6 numbers were palindromic. And you ready for this? One mile later,
all 6 were palindromic!</EM></FONT></FONT></P><P><FONT COLOR=black><FONT SIZE=3><EM>&#X201C;The question is, what was on the odometer when I first looked?&#X201D;
all 6 were palindromic!''
</EM></FONT></FONT></P></BLOCKQUOTE><P><FONT COLOR=black><FONT SIZE=3><EM>Write a Python program that tests all the six-digit numbers and prints
 
''&#X201C;The question is, what was on the odometer when I first looked?&#X201D;
''
</BLOCKQUOTE>
''Write a Python program that tests all the six-digit numbers and prints
any numbers that satisfy these requirements. You can see my solution
any numbers that satisfy these requirements. You can see my solution
at </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>thinkpython.com/code/cartalk.py</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM>.</EM></FONT></FONT></P></DIV><DIV CLASS="theorem"><FONT COLOR=black><FONT SIZE=3><B>Exercise&#XA0;9</B></FONT></FONT><FONT COLOR=black><FONT SIZE=3>&#XA0;&#XA0;<EM>
at ''''<TT>thinkpython.com/code/cartalk.py</TT>''''.''
Here&#X2019;s another </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3>Car Talk</FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM> Puzzler you can solve with a
</DIV><DIV CLASS="theorem">'''Exercise&#XA0;9'''&#XA0;&#XA0;''
search</EM></FONT></FONT><SUP><A NAME="text19" HREF="#note19"><FONT COLOR=black><FONT SIZE=3><EM>4</EM></FONT></FONT></A></SUP><FONT COLOR=black><FONT SIZE=3><EM>:</EM></FONT></FONT><P><A NAME="@default716"></A><FONT COLOR=black><FONT SIZE=3><EM>
Here&#X2019;s another ''Car Talk'' Puzzler you can solve with a
</EM></FONT></FONT><A NAME="@default717"></A><FONT COLOR=black><FONT SIZE=3><EM>
search''<SUP>''4''</SUP>'':''
</EM></FONT></FONT><A NAME="@default718"></A></P><BLOCKQUOTE CLASS="quote"><FONT COLOR=black><FONT SIZE=3><EM>
''
''''
''
<BLOCKQUOTE CLASS="quote">''
&#X201C;Recently I had a visit with my mom and we realized that
&#X201C;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
the two digits that make up my age when reversed resulted in her
age. For example, if she&#X2019;s 73, I&#X2019;m 37. We wondered how often this has
age. For example, if she&#X2019;s 73, I&#X2019;m 37. We wondered how often this has
happened over the years but we got sidetracked with other topics and
happened over the years but we got sidetracked with other topics and
we never came up with an answer.</EM></FONT></FONT><P><FONT COLOR=black><FONT SIZE=3><EM>&#X201C;When I got home I figured out that the digits of our ages have been
we never came up with an answer.''
''&#X201C;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&#X2019;re lucky it
reversible six times so far. I also figured out that if we&#X2019;re lucky it
would happen again in a few years, and if we&#X2019;re really lucky it would
would happen again in a few years, and if we&#X2019;re really lucky it would
happen one more time after that. In other words, it would have
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?&#X201D;</EM></FONT></FONT></P></BLOCKQUOTE><P><FONT COLOR=black><FONT SIZE=3><EM>Write a Python program that searches for solutions to this Puzzler.
happened 8 times over all. So the question is, how old am I now?&#X201D;''
Hint: you might find the string method </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>zfill</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM> useful.</EM></FONT></FONT></P><P><FONT COLOR=black><FONT SIZE=3><EM>You can see my solution at </EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM><TT>thinkpython.com/code/cartalk.py</TT></EM></FONT></FONT><FONT COLOR=black><FONT SIZE=3><EM>.</EM></FONT></FONT></P></DIV><HR CLASS="footnoterule"><DL CLASS="thefootnotes"><DT CLASS="dt-thefootnotes"><FONT COLOR=black><FONT SIZE=3>
</BLOCKQUOTE>
</FONT></FONT><A NAME="note16" HREF="#text16"><FONT COLOR=black><FONT SIZE=3>1</FONT></FONT></A></DT><DD CLASS="dd-thefootnotes"><FONT COLOR=black><FONT SIZE=3><TT>wikipedia.org/wiki/Moby_Project</TT>
''Write a Python program that searches for solutions to this Puzzler.
</FONT></FONT></DD><DT CLASS="dt-thefootnotes"><A NAME="note17" HREF="#text17"><FONT COLOR=black><FONT SIZE=3>2</FONT></FONT></A></DT><DD CLASS="dd-thefootnotes"><FONT COLOR=black><FONT SIZE=3><TT>www.cartalk.com/content/puzzler/transcripts/200725</TT>
Hint: you might find the string method ''''<TT>zfill</TT>'''' useful.''
</FONT></FONT></DD><DT CLASS="dt-thefootnotes"><A NAME="note18" HREF="#text18"><FONT COLOR=black><FONT SIZE=3>3</FONT></FONT></A></DT><DD CLASS="dd-thefootnotes"><FONT COLOR=black><FONT SIZE=3><TT>www.cartalk.com/content/puzzler/transcripts/200803</TT>
 
</FONT></FONT></DD><DT CLASS="dt-thefootnotes"><A NAME="note19" HREF="#text19"><FONT COLOR=black><FONT SIZE=3>4</FONT></FONT></A></DT><DD CLASS="dd-thefootnotes"><FONT COLOR=black><FONT SIZE=3><TT>www.cartalk.com/content/puzzler/transcripts/200813</TT>
''You can see my solution at ''''<TT>thinkpython.com/code/cartalk.py</TT>''''.''
</FONT></FONT></DD></DL>
</DIV><HR CLASS="footnoterule"><DL CLASS="thefootnotes"><DT CLASS="dt-thefootnotes">
1</DT><DD CLASS="dd-thefootnotes"><TT>wikipedia.org/wiki/Moby_Project</TT>
</DD><DT CLASS="dt-thefootnotes">2</DT><DD CLASS="dd-thefootnotes"><TT>www.cartalk.com/content/puzzler/transcripts/200725</TT>
</DD><DT CLASS="dt-thefootnotes">3</DT><DD CLASS="dd-thefootnotes"><TT>www.cartalk.com/content/puzzler/transcripts/200803</TT>
</DD><DT CLASS="dt-thefootnotes">4</DT><DD CLASS="dd-thefootnotes"><TT>www.cartalk.com/content/puzzler/transcripts/200813</TT>
</DD></DL>
<HR>
<HR>
<A HREF="book009.html"><IMG SRC="previous_motif.gif" ALT="Previous"></A>
<IMG SRC="previous_motif.gif" ALT="Previous">
<A HREF="index.html"><IMG SRC="contents_motif.gif" ALT="Up"></A>
<IMG SRC="contents_motif.gif" ALT="Up">
<A HREF="book011.html"><IMG SRC="next_motif.gif" ALT="Next"></A>
<IMG SRC="next_motif.gif" ALT="Next">
</BODY>
</HTML>

Latest revision as of 20:09, 18 May 2009

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 project1. 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 (as opposed to 'w' for writing).



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

=== 9.2  Exercises ===

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

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

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?

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 started 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 the end of the loop, we 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 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

I wrote the functions in the previous section with for loops because I only needed the characters in the strings; I didn’t have to do anything with the 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

An alternative is to use recursion:

def is_abecedarian(word):
    if len(word) <= 1:
        return True
    if word[0] > word[1]:
        return False
    return is_abecedarian(word[1:])

Another option is 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, and 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 last time the loop runs is when i is 4, which is the index of the second-to-last character. On the last iteration, it compares the second-to-last character to the last, which is what we want.

Here is a version of is_palindrome (see Exercise 6.6) that uses two indices; one starts at the beginning 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.8.

9.5  Debugging

Testing programs is hard. The functions in this chapter are relatively easy to test because you can check the results by hand. Even so, it is somewhere between difficult and impossible to choose a set of words that test for all possible errors.

Taking has_no_e as an example, there are two obvious cases to check: words that have an ’e’ should return False; words that don’t should return True. You should have no trouble coming up with one of each.

Within each case, there are some less obvious subcases. Among the words that have an “e,” you should test words with an “e” at the beginning, the end, and somewhere in the middle. You should test long words, short words, and very short words, like the empty string. The empty string is an example of a special case, which is one of the non-obvious cases where errors often lurk.

In addition to the test cases you generate, you can also test your program with a word list like words.txt. By scanning the output, you might be able to catch errors, but be careful: you might catch one kind of error (words that should not be included, but are) and not another (words that should be included, but aren’t).

In general, testing can help you find bugs, but it is not easy to generate a good set of test cases, and even if you do, you can’t be sure your program is correct.

According to a legendary computer scientist:

Program testing can be used to show the presence of bugs, but never to show their absence! — Edsger W. Dijkstra

9.6  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.
special case:
A test case that is atypical or non-obvious (and less likely to be handled correctly).

=== 9.7  Exercises ===

Exercise 7  

'

This question is based on a Puzzler that was broadcast on the radio program Car Talk2:

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?

Write a program to find it. You can see my solution at 'thinkpython.com/code/cartalk.py'.

Exercise 8  

Here’s another Car Talk Puzzler3: ' '

“I was driving on the highway the other day and I happened to notice my odometer. Like most odometers, it shows six digits, in whole miles only. So, if my car had 300,000 miles, for example, I’d see 3-0-0-0-0-0. “Now, what I saw that day was very interesting. I noticed that the last 4 digits were palindromic; that is, they read the same forward as backward. For example, 5-4-4-5 is a palindrome, so my odometer could have read 3-1-5-4-4-5.

“One mile later, the last 5 numbers were palindromic. For example, it could have read 3-6-5-4-5-6. One mile after that, the middle 4 out of 6 numbers were palindromic. And you ready for this? One mile later, all 6 were palindromic!

“The question is, what was on the odometer when I first looked?”

Write a Python program that tests all the six-digit numbers and prints any numbers that satisfy these requirements. You can see my solution at 'thinkpython.com/code/cartalk.py'.

Exercise 9  

Here’s another Car Talk Puzzler you can solve with a search4: '

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

You can see my solution at 'thinkpython.com/code/cartalk.py'.


1
wikipedia.org/wiki/Moby_Project
2
www.cartalk.com/content/puzzler/transcripts/200725
3
www.cartalk.com/content/puzzler/transcripts/200803
4
www.cartalk.com/content/puzzler/transcripts/200813

<IMG SRC="previous_motif.gif" ALT="Previous"> <IMG SRC="contents_motif.gif" ALT="Up"> <IMG SRC="next_motif.gif" ALT="Next">