Files
dive-into-python3/advanced-iterators.html
T
2009-04-21 00:03:51 -04:00

424 lines
26 KiB
HTML

<!DOCTYPE html>
<head>
<meta charset=utf-8>
<title>Advanced Iterators - Dive into Python 3</title>
<link rel=stylesheet type=text/css href=dip3.css>
<style>
body{counter-reset:h1 7}
</style>
<link rel=stylesheet type=text/css media='only screen and (max-device-width: 480px)' href=mobile.css>
</head>
<form action=http://www.google.com/cse><div><input type=hidden name=cx value=014021643941856155761:l5eihuescdw><input type=hidden name=ie value=UTF-8>&nbsp;<input name=q size=25>&nbsp;<input type=submit name=sa value=Search></div></form>
<p>You are here: <a href=index.html>Home</a> <span>&#8227;</span> <a href=table-of-contents.html#advanced-iterators>Dive Into Python 3</a> <span>&#8227;</span>
<p id=level title=advanced>Difficulty level: <span>&#x2666;</span><span>&#x2666;</span><span>&#x2666;</span><span>&#x2666;</span><span>&#x2662;</span>
<h1>Advanced Iterators</h1>
<blockquote class=q>
<p><span>&#x275D;</span> Great fleas have little fleas upon their backs to bite &#8217;em,<br>And little fleas have lesser fleas, and so ad infinitum. <span>&#x275E;</span><br>&mdash; Augustus De Morgan
</blockquote>
<p id=toc>&nbsp;
<h2 id=divingin>Diving In</h2>
<p class=f>H<code>AWAII + IDAHO + IOWA + OHIO = STATES</code>. Or, to put it another way, <code>510199 + 98153 + 9301 + 3593 == 621246</code>. Am I speaking in tongues? No, it's just a puzzle.
<p>Let me spell it out for you.
<pre><code>HAWAII + IDAHO + IOWA + OHIO = STATES
510199 + 98153 + 9301 + 3593 == 621246
H = 5
A = 1
W = 0
I = 9
D = 8
O = 3
S = 6
T = 2
E = 4</code></pre>
<p>Puzzles like this are called <i>cryptarithms</i> or <i>alphametics</i>. The letters spell out actual words, but if you replace each letter with a digit from <code>0&ndash;9</code>, it also &#8220;spells&#8221; an arithmetic equation. The trick is to figure out which letter maps to each digit. All the occurrences of each letter must map to the same digit, no digit can be repeated, and no &#8220;word&#8221; can start with the digit <code>0</code>.
<p>The most well-known alphametic puzzle is <code>SEND + MORE = MONEY</code>.
<p>In this chapter, we'll dive into an incredible Python program originally written by Raymond Hettinger. This program solves alphametic puzzles <em>in just 14 lines of code</em>.
<p class=d>[<a href=examples/alphametics.py>download <code>alphametics.py</code></a>]
<pre><code>import re
import itertools
def solve(puzzle):
words = re.findall('[A-Z]+', puzzle.upper())
unique_characters = {c for c in ''.join(words)}
assert len(unique_characters) <= 10
first_letters = {word[0] for word in words}
n = len(first_letters)
sorted_characters = ''.join(first_letters) + \
''.join(unique_characters - first_letters)
characters = tuple(ord(c) for c in sorted_characters)
digits = tuple(ord(c) for c in '0123456789')
zero = digits[0]
for guess in itertools.permutations(digits, len(characters)):
if zero not in guess[:n]:
equation = puzzle.translate(dict(zip(characters, guess)))
if eval(equation):
return equation
if __name__ == '__main__':
import sys
for puzzle in sys.argv[1:]:
print(puzzle)
solution = solve(puzzle)
if solution:
print(solution)</code></pre>
<pre class=screen>
<samp class=p>you@localhost:~$ </samp><kbd>python3 alphametics.py "HAWAII + IDAHO + IOWA + OHIO = STATES"</kbd>
<samp>HAWAII + IDAHO + IOWA + OHIO = STATES
510199 + 98153 + 9301 + 3593 == 621246</samp>
<samp class=p>you@localhost:~$ </samp><kbd>python3 alphametics.py "I + LOVE + YOU == DORA"</kbd>
<samp>I + LOVE + YOU == DORA
1 + 2784 + 975 == 3760</samp>
<samp class=p>you@localhost:~$ </samp><kbd>python3 alphametics.py "SEND + MORE == MONEY"</kbd>
<samp>SEND + MORE == MONEY
9567 + 1085 == 10652</samp></pre>
<h2 id=re-findall>Finding all occurrences of a pattern</h2>
<p>FIXME
<pre class=screen>
<samp class=p>>>> </samp><kbd>import re</kbd>
<samp class=p>>>> </samp><kbd>re.findall('[A-Z]+', 'SEND + MORE == MONEY')</kbd>
<samp>['SEND', 'MORE', 'MONEY']</samp></pre>
<p>FIXME
<h2 id=unique-items>Finding the unique items in a sequence</h2>
<p>Set comprehensions make it trivial to find the unique items in a sequence. [FIXME-not sure if I'm going to cover set comprehensions in an earlier chapter; if not, this is certainly an abrupt and inadequate introduction to the topic.]
<pre class=screen>
<samp class=p>>>> </samp><kbd>a_list = ['a', 'c', 'b', 'a', 'd', 'b']</kbd>
<a><samp class=p>>>> </samp><kbd>{c for c in a_list}</kbd> <span>&#x2460;</span></a>
<samp>{'a', 'c', 'b', 'd'}</samp>
<samp class=p>>>> </samp><kbd>a_string = 'EAST IS EAST'</kbd>
<a><samp class=p>>>> </samp><kbd>{c for c in a_string}</kbd> <span>&#x2461;</span></a>
<samp>{'A', ' ', 'E', 'I', 'S', 'T'}</samp>
<samp class=p>>>> </samp><kbd>words = ['SEND', 'MORE', 'MONEY']</kbd>
<a><samp class=p>>>> </samp><kbd>''.join(words)</kbd> <span>&#x2462;</span></a>
<samp>'SENDMOREMONEY'</samp>
<a><samp class=p>>>> </samp><kbd>{c for c in ''.join(words)}</kbd> <span>&#x2463;</span></a>
<samp>{'E', 'D', 'M', 'O', 'N', 'S', 'R', 'Y'}</samp></pre>
<ol>
<li>Given a list of several strings, a set comprehension with the identity function will return a set of unique strings from the list. This makes sense if you think of it like a <code>for</code> loop. Take the first item from the list, put it in the set. Second. Third. Fourth &mdash; wait, that's in the set already, so it only gets listed once. Fifth. Sixth &mdash; again, a duplicate, so it only gets listed once. The end result? All the unique items in the original list, without any duplicates. The original list doesn't even need to be sorted first.
<li>The same technique works with strings, since a string is just a sequence of characters.
<li>Given a list of strings, <code>''.join(<var>a_list</var>)</code> concatenates all the strings together into one.
<li>So, given a list of strings, this set comprehension returns all the unique characters across all the strings, with no duplicates.
</ol>
<p>The alphametics solver uses this technique to get a list of all the unique characters in the puzzle.
<pre><code>unique_characters = {c for c in ''.join(words)}</code></pre>
<p>This list is later used to assign digits to characters as the solver iterates through the possible solutions.
<h2 id=assert>Making assertions</h2>
<p>Like many programming languages, Python has an <code>assert</code> statement. Here's how it works.
<pre class=screen>
<a><samp class=p>>>> </samp><kbd>assert 1 + 1 == 2</kbd> <span>&#x2460;</span></a>
<a><samp class=p>>>> </samp><kbd>assert 1 + 1 == 3</kbd> <span>&#x2461;</span></a>
<samp class=traceback>Traceback (most recent call last):
File "&lt;stdin>", line 1, in <module>
AssertionError</samp></pre>
<ol>
<li>The <code>assert</code> statement is followed by any valid Python expression. In this case, the expression <code>1 + 1 == 2</code> evaluates to <code>True</code>, so the <code>assert</code> statement does nothing.
<li>However, if the Python expression evaluates to <code>False</code>, the <code>assert</code> statement will raise an <code>AssertionError</code>.
</ol>
<p>Therefore, this line of code:
<pre><code>assert len(unique_characters) <= 10</code></pre>
<p>&hellip;is equivalent to&hellip;
<pre><code>if len(unique_characters) > 10:
raise AssertionError</code></pre>
<p>But a bit easier to read and write.
<p>The alphametics solver uses this exact <code>assert</code> statement to bail out early if the puzzle contains more than ten unique letters. Since each letter is assigned a unique digit, and there are only ten digits, a puzzle with more than ten unique letters is unsolvable.
<h2 id=generator-expressions>Generator expressions</h2>
<p>FIXME
<pre class=screen>
<samp>>>> </samp><kbd>unique_characters = {'E', 'D', 'M', 'O', 'N', 'S', 'R', 'Y'}</kbd>
<samp>>>> </samp><kbd>gen = (ord(c) for c in unique_characters)</kbd>
<samp>>>> </samp><kbd>gen</kbd>
<samp>&lt;generator object &lt;genexpr> at 0x00BADC10></samp>
<samp>>>> </samp><kbd>next(gen)</kbd>
<samp>69</samp>
<samp>>>> </samp><kbd>next(gen)</kbd>
<samp>68</samp>
<samp>>>> </samp><kbd>tuple(ord(c) for c in unique_characters)</kbd>
<samp>(69, 68, 77, 79, 78, 83, 82, 89)</samp></pre>
<p>FIXME
<h2 id=permutations>Calculating Permutations&hellip; The Lazy Way!</h2>
<p>First of all, what the heck are permutations? Permutations are a mathematical concept. (There are actually several definitions, depending on what kind of math you're doing. Here I'm talking about combinatorics, but if that doesn't mean anything to you, don't worry about it. As always, <a href="http://en.wikipedia.org/wiki/Permutation">Wikipedia is your friend</a>.)
<p>The idea is that you take a list of things (could be numbers, could be letters, could be dancing bears) and find all the possible ways to split them up into smaller lists. All the smaller lists have the same size, which can be as small as 1 and as large as the total number of items. Oh, and nothing can be repeated. Mathematicians say things like "let's find the permutations of 3 different items taken 2 at a time," which means you have a sequence of 3 items and you want to find all the possible ordered pairs.
<pre class=screen>
<a><samp class=p>>>> </samp><kbd>import itertools</kbd> <span>&#x2460;</span></a>
<a><samp class=p>>>> </samp><kbd>perms = itertools.permutations([1, 2, 3], 2)</kbd> <span>&#x2461;</span></a>
<a><samp class=p>>>> </samp><kbd>next(perms)</kbd> <span>&#x2462;</span></a>
<samp>(1, 2)</samp>
<samp class=p>>>> </samp><kbd>next(perms)</kbd>
<samp>(1, 3)</samp>
<samp class=p>>>> </samp><kbd>next(perms)</kbd>
<a><samp>(2, 1)</samp> <span>&#x2463;</span></a>
<samp class=p>>>> </samp><kbd>next(perms)</kbd>
<samp>(2, 3)</samp>
<samp class=p>>>> </samp><kbd>next(perms)</kbd>
<samp>(3, 1)</samp>
<samp class=p>>>> </samp><kbd>next(perms)</kbd>
<samp>(3, 2)</samp>
<a><samp class=p>>>> </samp><kbd>next(perms)</kbd> <span>&#x2464;</span></a>
<samp class=traceback>Traceback (most recent call last):
File "&lt;stdin>", line 1, in <module>
StopIteration</samp></pre>
<ol>
<li>The <code>itertools</code> module has all kinds of fun stuff in it, including a <ocde>permutations()</code> function that does all the hard work of finding permutations.
<li>The <code>permutations()</code> function takes a sequence (here a list of three integers) and a number, which is the number of items you want in each smaller group. The function returns an iterator, which you can use in a <code>foor</code> loop or any old place that iterates. Here I'll step through the iterator manually to show all the values.
<li>The first permutation of <code>[1, 2, 3]</code> taken 2 at a time is <code>(1, 2)</code>.
<li>Note that permutations are ordered: <code>(2, 1)</code> is different than <code>(1, 2)</code>.
<li>That's it! Those are all the permutations of <code>[1, 2, 3]</code> taken 2 at a time. Pairs like <code>(1, 1)</code> and <code>(2, 2)</code> never show up, because they contain repeats so they aren't valid permutations. When there are no more permutations, the iterator raises a <code>StopIteration</code> exception.
</ol>
<p>The <code>permutations()</code> function doesn't have to take a list. It can take any sequence &mdash; even a string.
<pre class=screen>
<samp class=p>>>> </samp><kbd>import itertools</kbd>
<a><samp class=p>>>> </samp><kbd>perms = itertools.permutations('ABC', 3)</kbd> <span>&#x2460;</span></a>
<samp class=p>>>> </samp><kbd>next(perms)</kbd>
<a><samp>('A', 'B', 'C')</samp> <span>&#x2461;</span></a>
<samp class=p>>>> </samp><kbd>next(perms)</kbd>
<samp>('A', 'C', 'B')</samp>
<samp class=p>>>> </samp><kbd>next(perms)</kbd>
<samp>('B', 'A', 'C')</samp>
<samp class=p>>>> </samp><kbd>next(perms)</kbd>
<samp>('B', 'C', 'A')</samp>
<samp class=p>>>> </samp><kbd>next(perms)</kbd>
<samp>('C', 'A', 'B')</samp>
<samp class=p>>>> </samp><kbd>next(perms)</kbd>
<samp>('C', 'B', 'A')</samp>
<samp class=p>>>> </samp><kbd>next(perms)</kbd>
<samp class=traceback>Traceback (most recent call last):
File "&lt;stdin>", line 1, in <module>
StopIteration</samp>
<a><samp class=p>>>> </samp><kbd>list(itertools.permutations('ABC', 3))</kbd> <span>&#x2462;</span></a>
<samp>[('A', 'B', 'C'), ('A', 'C', 'B'),
('B', 'A', 'C'), ('B', 'C', 'A'),
('C', 'A', 'B'), ('C', 'B', 'A')]</samp></pre>
<ol>
<li>A string is just a sequence of characters. For the purposes of finding permutations, the string <code>'ABC'</code> is equivalent to the list <code>['A', 'B', 'C']</code>.
<li>The first permutation of the 3 items <code>['A', 'B', 'C']</code>, taken 3 at a time, is <code>('A', 'B', 'C')</code>. There are five other permutations &mdash; the same three characters in every conceivable order.
<li>Since the <code>permutations()</code> function always returns an iterator, an easy way to debug permutations is to pass that iterator to the built-in <code>list()</code> function to see all the permutations immediately.
</ol>
<h2 id=more-itertools>Other Fun Stuff in the <code>itertools</code> Module</h2>
<pre class=screen>
<samp class=p>>>> </samp><kbd>import itertools</kbd>
<a><samp class=p>>>> </samp><kbd>list(itertools.product('ABC', '123'))</kbd> <span>&#x2460;</span></a>
<samp>[('A', '1'), ('A', '2'), ('A', '3'),
('B', '1'), ('B', '2'), ('B', '3'),
('C', '1'), ('C', '2'), ('C', '3')]</samp>
<a><samp class=p>>>> </samp><kbd>list(itertools.combinations('ABC', 2))</kbd> <span>&#x2461;</span></a>
<samp>[('A', 'B'), ('A', 'C'), ('B', 'C')]</samp></pre>
<ol>
<li>The <code>itertools.product()</code> function returns an iterator containing the Cartesian product of two sequences.
<li>The <code>itertools.combinations()</code> function returns an iterator containing all the possible combinations of the given sequence of the given length. This is like the <code>itertools.permutations()</code> function, except combinations don't include items that are duplicates of other items in a different order. So <code>itertools.permutations('ABC', 2)</code> will return both <code>('A', 'B')</code> and <code>('B', 'A')</code> (among others), but <code>itertools.combinations('ABC', 2)</code> will not return <code>('B', 'A')</code> because it is a duplicate of <code>('A', 'B')</code> in a different order.
</ol>
<p class=d>[<a href=examples/favorite-people.txt>download <code>favorite-people.txt</code></a>]
<pre class=screen>
<a><samp class=p>>>> </samp><kbd>names = list(open('examples/favorite-people.txt'))</kbd> <span>&#x2460;</span></a>
<samp class=p>>>> </samp><kbd>names</kbd>
<samp>['Dora\n', 'Ethan\n', 'Wesley\n', 'John\n', 'Anne\n',
'Mike\n', 'Chris\n', 'Sarah\n', 'Alex\n', 'Lizzie\n']</samp>
<a><samp class=p>>>> </samp><kbd>names = [name.rstrip() for name in names]</kbd> <span>&#x2461;</span></a>
<samp class=p>>>> </samp><kbd>names</kbd>
<samp>['Dora', 'Ethan', 'Wesley', 'John', 'Anne',
'Mike', 'Chris', 'Sarah', 'Alex', 'Lizzie']</samp>
<a><samp class=p>>>> </samp><kbd>names = sorted(names)</kbd> <span>&#x2462;</span></a>
<samp class=p>>>> </samp><kbd>names</kbd>
<samp>['Alex', 'Anne', 'Chris', 'Dora', 'Ethan',
'John', 'Lizzie', 'Mike', 'Sarah', 'Wesley']</samp>
<a><samp class=p>>>> </samp><kbd>names = sorted(names, key=len)</kbd> <span>&#x2463;</span></a>
<samp class=p>>>> </samp><kbd>names</kbd>
<samp>['Alex', 'Anne', 'Dora', 'John', 'Mike',
'Chris', 'Ethan', 'Sarah', 'Lizzie', 'Wesley']</samp></pre>
<ol>
<li>This idiom returns a list of the lines in a text file.
<li>Unfortunately (for this example), the <code>list(open(<var>filename</var>))</code> idiom also includes the carriage returns at the end of each line. This list comprehension uses the <code>rstrip()</code> string method to strip trailing whitespace from each line.
<li>The <code>sorted()</code> function takes a list and returns it sorted. By default, it sorts alphabetically.
<li>But the <code>sorted()</code> function can also take a function as the <var>key</var> parameter, and it sorts by that key. In this case, the sort function is <code>len()</code>, so it sorts by <code>len(<var>each item</var>)</code>. Shorter names come first, then longer, then longest.
</ol>
<p>What does this have to do with the <code>itertools</code> module? I'm glad you asked.
<pre class=screen>
<p>&hellip;continuing from the previous interactive shell&hellip;
<samp class=p>>>> </samp><kbd>import itertools</kbd>
<a><samp class=p>>>> </samp><kbd>groups = itertools.groupby(names, len)</kbd> <span>&#x2460;</span></a>
<samp class=p>>>> </samp><kbd>groups</kbd>
<samp>&lt;itertools.groupby object at 0x00BB20C0></samp>
<samp class=p>>>> </samp><kbd>list(groups)</kbd>
<samp>[(4, &lt;itertools._grouper object at 0x00BA8BF0>),
(5, &lt;itertools._grouper object at 0x00BB4050>),
(6, &lt;itertools._grouper object at 0x00BB4030>)]</samp>
<samp class=p>>>> </samp><kbd>groups = itertools.groupby(names, len)</kbd>
<a><samp class=p>>>> </samp><kbd>for name_length, name_iter in groups:</kbd> <span>&#x2461;</span></a>
<samp class=p>... </samp><kbd> print('Names with {0:d} letters:'.format(name_length))</kbd>
<samp class=p>... </samp><kbd> for name in name_iter:</kbd>
<samp class=p>... </samp><kbd> print(name)</kbd>
<samp class=p>... </samp>
<samp>Names with 4 letters:
Alex
Anne
Dora
John
Mike
Names with 5 letters:
Chris
Ethan
Sarah
Names with 6 letters:
Lizzie
Wesley</samp></pre>
<ol>
<li>The <code>itertools.groupby()</code> function takes a sequence and a key function, and returns an iterator that generates pairs. Each pair contains the result of <code>key_function(<var>each item</var>)</code> and another iterator containing all the items that shared that key result.
<li>In this example, given a list of names sorted by length, <code>itertools.groupby(names, len)</code> will put all the 4-letter names in one iterator, all the 5-letter names in another iterator, and so on. The <code>groupby()</code> function is completely generic; it could group strings by first letter, numbers by their number of factors, or any other key function you can think of.
</ol>
<!-- YO DAWG, WE HEARD YOU LIKE LOOPING, SO WE PUT AN ITERATOR IN YOUR ITERATOR SO YOU CAN LOOP WHILE YOU LOOP. -->
<p>Are you watching closely?
<pre class=screen>
<samp class=p>>>> </samp><kbd>list(range(0, 3))</kbd>
<samp>[0, 1, 2]</samp>
<samp class=p>>>> </samp><kbd>list(range(10, 13))</kbd>
<samp>[10, 11, 12]</samp>
<a><samp class=p>>>> </samp><kbd>list(itertools.chain(range(0, 3), range(10, 13)))</kbd> <span>&#x2460;</span></a>
<samp>[0, 1, 2, 10, 11, 12]</samp>
<a><samp class=p>>>> </samp><kbd>list(zip(range(0, 3), range(10, 13)))</kbd> <span>&#x2461;</span></a>
<samp>[(0, 10), (1, 11), (2, 12)]</samp>
<a><samp class=p>>>> </samp><kbd>list(zip(range(0, 3), range(10, 14)))</kbd> <span>&#x2462;</span></a>
<samp>[(0, 10), (1, 11), (2, 12)]</samp>
<a><samp class=p>>>> </samp><kbd>list(itertools.zip_longest(range(0, 3), range(10, 14)))</kbd> <span>&#x2463;</span></a>
<samp>[(0, 10), (1, 11), (2, 12), (None, 13)]</samp></pre>
<ol>
<li>The <code>itertools.chain()</code> function takes two iterators and returns an iterator that contains all the items from the first iterator, followed by all the items from the second iterator. (Actually, it can take any number of iterators, and it chains them all in the order they were passed to the function.)
<li>The <code>zip()</code> function does something prosaic that turns out to be extremely useful: it any number of sequences and returns an iterator with the first items of each sequence, then the second items of each, then the third, and so on.
<li>The <code>zip()</code> function stops at the end of the shortest sequence. <code>range(10, 14)</code> has 4 items (10, 11, 12, and 13), but <code>range(0, 3)</code> only has 3, so the <code>zip()</code> function returns an iterator of 3 items.
<li>On the other hand, the <code>itertools.zip_longest()</code> function stops at the end of the <em>longest</em> sequence, inserting <code>None</code> values for items past the end of the shorter sequences.
</ol>
<p>OK, that was all very interesting, but how does it relate to the alphametics solver? Here's how:
<pre class=screen>
<samp class=p>>>> </samp><kbd>characters = ('S', 'M', 'E', 'D', 'O', 'N', 'R', 'Y')</kbd>
<samp class=p>>>> </samp><kbd>guess = ('1', '2', '0', '3', '4', '5', '6', '7')</kbd>
<a><samp class=p>>>> </samp><kbd>tuple(zip(characters, guess))</kbd> <span>&#x2460;</span></a>
<samp>(('S', '1'), ('M', '2'), ('E', '0'), ('D', '3'),
('O', '4'), ('N', '5'), ('R', '6'), ('Y', '7'))</samp>
<a><samp class=p>>>> </samp><kbd>dict(zip(characters, guess))</kbd> <span>&#x2461;</span></a>
<samp>{'E': '0', 'D': '3', 'M': '2', 'O': '4',
'N': '5', 'S': '1', 'R': '6', 'Y': '7'}</samp></pre>
<ol>
<li>Given a list of letters and a list of digits (each represented here as 1-character strings), the <code>zip</code> function will create a pairing of letters and digits, in order.
<li>Why is that cool? Because that data structure happens to be exactly the right structure to pass to the <code>dict()</code> function to create a dictionary that uses letters as keys and their associated digits as values. Although the printed representation of the dictionary lists the pairs in a different order (dictionaries have no "order" per se), you can see that each letter is associated with the digit, based on the ordering of the original <var>characters</var> and <var>guess</var> sequences.
</ol>
<p>The alphametics solver uses this technique to create a dictionary that maps letters in the puzzle to digits in the solution, for each possible solution.
<pre><code>characters = tuple(ord(c) for c in sorted_characters)
digits = tuple(ord(c) for c in '0123456789')
...
for guess in itertools.permutations(digits, len(characters)):
...
<mark> equation = puzzle.translate(dict(zip(characters, guess)))</mark></code></pre>
<p>But what is this <code>translate()</code> method? Ah, now you're getting to the <em>really</em> fun part.
<h2 id=string-translate>A New Kind Of String Manipulation</h2>
<p>FIXME
<pre class=screen>
<samp class=p>>>> </samp><kbd>characters = tuple(ord(c) for c in 'SMEDONRY')</kbd>
<samp class=p>>>> </samp><kbd>characters</kbd>
<samp>(83, 77, 69, 68, 79, 78, 82, 89)</samp>
<samp class=p>>>> </samp><kbd>digits = tuple(ord(c) for c in '0123456789')</kbd>
<samp class=p>>>> </samp><kbd>digits</kbd>
<samp>(48, 49, 50, 51, 52, 53, 54, 55, 56, 57)</samp>
<samp class=p>>>> </samp><kbd>guess = (49, 50, 48, 51, 52, 53, 54, 55)</kbd>
<samp class=p>>>> </samp><kbd>translation_table = dict(zip(characters, guess))</kbd>
<samp class=p>>>> </samp><kbd>translation_table</kbd>
<samp>{68: 51, 69: 48, 77: 50, 78: 53, 79: 52, 82: 54, 83: 49, 89: 55}</samp>
<samp class=p>>>> </samp><kbd>"SEND + MORE == MONEY".translate(translation_table)</kbd>
<samp>'1053 + 2460 == 24507'</samp></pre>
<p>FIXME
<pre class=screen>
<samp class=p>>>> </samp><kbd>translation_table = {ord("A"): ord("O")}</kbd>
<samp class=p>>>> </samp><kbd>translation_table</kbd>
<samp>{65: 79}</samp>
<samp class=p>>>> </samp><kbd>'MARK'.translate(translation_table)</kbd>
<samp>'MORK'</samp></pre>
<p>FIXME
<h2 id=eval>Evaluating Arbitrary Strings As Python Expressions</h2>
<p>FIXME
<h2 id=alphametics-finale>Putting It All Together</h2>
<p>To recap: this program solves alphametic puzzles by brute force, <i>i.e.</i> through an exhaustive search of all possible solutions. To do this, it&hellip;
<ol>
<li><a href=#re-findall>Finds all the letters in the puzzle</a> with the <code>re.findall()</code> function
<li><a href=#unique-items>Find all the <em>unique</em> letters in the puzzle</a> with set comprehensions
<li><a href=#assert>Checks if there are more than 10 unique letters</a> (meaning the puzzle is definitely unsolvable) with an <code>assert</code> statement
<li>FIXME sorts the letters with a set difference operation
<li><a href=#generator-objects>Converts the letters to their ASCII equivalents</a> with a generator object
<li><a href=#permutations>Calculates all the possible solutions</a> with the <code>itertools.permutations()</code> function
<li><a href=#string-translate>Converts each possible solution to a Python expression</a> with the <code>translate()</code> string method
<li><a href=#eval>Tests each possible solution by evaluating the Python expression</a> with the <code>eval()</code> function
<li>Returns the first solution that evaluates to <code>True</code>
</ol>
<p>&hellip;in just 14 lines of code.
<h2 id=furtherreading>Further Reading</h2>
<ul>
<li><a href="http://blip.tv/file/1947373/">Watch Raymond Hettinger's "Easy AI with Python" talk</a> at PyCon 2009
<li><a href="http://code.activestate.com/recipes/576615/">Recipe 576615: Alphametics solver</a>, Raymond Hettinger's original alphametics solver for Python 2
<li><a href="http://code.activestate.com/recipes/users/178123/">More of Raymond Hettinger's recipes</a> in the ActiveState Code repository
<li><a href="http://en.wikipedia.org/wiki/Verbal_arithmetic">Alphametics on Wikipedia</a>
<li><a href="http://www.tkcs-collins.com/truman/alphamet/index.shtml">Alphametics Index</a>, including <a href="http://www.tkcs-collins.com/truman/alphamet/alphamet.shtml">lots of puzzles</a> and <a href="http://www.tkcs-collins.com/truman/alphamet/alpha_gen.shtml">a generator to make your own</a>
</ul>
<p>Many, many thanks to Raymond Hettinger for agreeing to relicense his code so I could port it to Python 3 and use it as the basis for this chapter.
<p class=nav><a rel=prev href=iterators.html title="back to &#8220;Iterators&#8221;"><span>&#x261C;</a> <a rel=next href=unit-testing.html title="onward to &#8220;Unit Testing&#8221;"><span>&#x261E;</span></a>
<p class=c>&copy; 2001&ndash;9 <a href=about.html>Mark Pilgrim</a>
<script src=jquery.js></script>
<script src=dip3.js></script>