Add files via upload

This commit is contained in:
Peter Norvig authored and GitHub committed 2017-09-29 22:14:04 -07:00
1 parent bfbfc756c6
commit adb324b443
1 file changed
+559
+559
View File
@@ -0,0 +1,559 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Palindromic Panama Sentences\n",
"\n",
"I [previously](http://norvig.com/palindrome.html) wrote a program to find long palindromic sentences. Starting with the sentence:\n",
"\n",
" A man, a plan, a canal, Panama\n",
" \n",
"the program added words to the middle, one comma-separated phrase at a time, backtracking when a choice didn't work out. The program is able to find palindromes of over 15,000 words that look like:\n",
"\n",
" A man, a plan, a caddy, Ore, Lee, ..., Steele, Roydd, a canal, Panama.\n",
"\n",
"In this notebook I revisit the problem, but this time progressing one *letter* at a time rather than one phrase. That should allow me, at each choice point, to pick the letter that is most likely to lead to a complete phrase,\n",
"perhaps leading to longer palindromes.\n",
"\n",
"# Phrase Dictionary\n",
"\n",
"First, I will build a dictionary of phrases. I start with a file, [npdict.txt](npdict.txt), which lists noun phrases —proper nouns like `'Panama'`, plural nouns like `'dogs'`, and two-word noun phrases like `'a plan'`. Capitalization, whitespace and punctuation don't count in palindromes, just letters, so I use the function `letters` to extract just the letters of each phrase and create a dict of entries like `{'donaldeknuth': 'Donald E. Knuth'}`. In addition, to decide what letter to choose next, I will want to know if a given sequence of letters forms a *prefix* or *suffix* of other words. For example, if I have formed the partial phrase `'am'`, then adding `'a'` gives us `'ama'`, which is a prefix of over 1000 phrases in the dictionary, but adding `'d'` gives us `'amd'`, which is not a prefix of any phrase, and thus can be rejected.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"! [ -e npdict.txt ] || curl -O http://norvig.com/npdict.txt"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 1.88 s, sys: 92.8 ms, total: 1.97 s\n",
"Wall time: 2.04 s\n"
]
},
{
"data": {
"text/plain": [
"'ok'"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from collections import Counter, deque\n",
"import re\n",
"\n",
"class PhraseDict(dict):\n",
" \"\"\"A dictionary of {letters: phrase}, such as {'donaldeknuth': 'Donald E. Knuth'}, with:\n",
" .prefixes: Counter of {'pre': n} where n is the number of keys that start with 'pre'\n",
" .suffixes: Counter of {'xes': n} where n is the number of keys that end with 'xes'\"\"\"\n",
" def __init__(self, phrases):\n",
" for phrase in phrases:\n",
" phrase = phrase.strip()\n",
" self[letters(phrase)] = phrase\n",
" self.prefixes = Counter(x for p in self for x in prefixes(p))\n",
" self.suffixes = Counter(x for p in self for x in suffixes(p))\n",
" \n",
"def prefixes(phrase): return [phrase[:i] for i in range(1, len(phrase) + 1)]\n",
"\n",
"def suffixes(phrase): return [phrase[-i:] for i in range(1, len(phrase) + 1)]\n",
"\n",
"def letters(phrase, sub=re.compile(r'[\\W]+').sub):\n",
" \"Remove all the non-letters from phrase; return lowercase version.\"\n",
" return sub('', phrase).lower()\n",
"\n",
"def test1():\n",
" assert prefixes('hello') == ['h', 'he', 'hel', 'hell', 'hello']\n",
" assert suffixes('hello') == ['o', 'lo', 'llo', 'ello', 'hello']\n",
" assert letters('a man') == 'aman'\n",
" assert letters('an elk') == 'anelk'\n",
" assert letters('Mr. T') == 'mrt'\n",
" assert letters('Donald E. Knuth') == 'donaldeknuth'\n",
" assert len(DICT) == 125512\n",
" assert 'panama' in DICT\n",
" assert 'aman' in DICT\n",
" assert 'threemen' not in DICT\n",
" assert DICT['acanal'] == 'a canal'\n",
" return 'ok'\n",
"\n",
"%time DICT = PhraseDict(open('npdict.txt'))\n",
"test1()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Search for Panama Palindromes\n",
"\n",
"Now the `Panama` class will search for a palindrome. I'll show the code first, then explain it."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'ok'"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import time\n",
"\n",
"class Panama:\n",
" \"\"\"Panama represents a palindrome, or a state in searching for one.\n",
" It has .left and .right to hold the phrases that are chosen,\n",
" and .L and .R to hold the current partial phrases in the middle (still working on these).\n",
" Also, a .set of all complete phrases, and the .dict of allowable phrases to choose from.\"\"\"\n",
" \n",
" def __init__(self, left=['aman', 'aplan'], L='aca', R='', right=['acanal', 'panama'], dict=DICT):\n",
" assert cat(left + [L]) == cat([R] + right)[::-1]\n",
" self.left = list(left) # list of complete phrases on left\n",
" self.L = L # an incomplete phrase on left\n",
" self.R = R # an incomplete phrase on right\n",
" self.right = deque(right) # deque of complete phrases on right\n",
" self.dict = dict # a {letters: actual_phrase} mapping\n",
" self.set = set(left + right) # a set of all complete phrases in palindrome\n",
" self.best = [] # list of phrases in longest palindrome found\n",
" self.Nshown = 0 # the number of phrases shown in the previous printout\n",
" self.i = 0 # the number of steps taken in the search\n",
" self.check()\n",
"\n",
" def __str__(self): return self.original_phrases(self.best)\n",
" \n",
" def original_phrases(self, phrases): return ', '.join(self.dict[phrase] for phrase in phrases)\n",
"\n",
" def search(self, steps=10**5):\n",
" \"\"\"Depth-first search for palindromes. From the current state, find all applicable actions.\n",
" Do the first one, and put on the stack reminders to undo it and try the others,\n",
" but first search deeper from the result of the first action.\"\"\"\n",
" self.t0 = time.time()\n",
" stack = [self.applicable_actions()]\n",
" for self.i in range(steps):\n",
" if not stack: \n",
" return\n",
" command = stack.pop()\n",
" if isinstance(command, UndoCommand):\n",
" self.undo(command)\n",
" elif command:\n",
" act = command.pop()\n",
" self.do(act)\n",
" self.check()\n",
" stack.extend([command, UndoCommand(act), self.applicable_actions()])\n",
" \n",
" def do(self, act):\n",
" \"Modify the current state by adding a letter, or finishing a phrase.\"\n",
" if act == ',': # finish phrase on left\n",
" self.set.add(self.L)\n",
" self.left.append(self.L)\n",
" self.L = ''\n",
" elif act == ';': # finish phrase on right\n",
" self.set.add(self.R)\n",
" self.right.appendleft(self.R)\n",
" self.R = ''\n",
" else: # add a letter\n",
" self.L = self.L + act \n",
" self.R = act + self.R\n",
" \n",
" def undo(self, act):\n",
" \"Modify the current state by undoing an action that was previously done.\"\n",
" if act == ',': # unfinish phrase on left\n",
" assert self.L == ''\n",
" self.L = self.left.pop()\n",
" self.set.remove(self.L)\n",
" elif act == ';': # unfinish phrase on right\n",
" assert self.R == ''\n",
" self.R = self.right.popleft()\n",
" self.set.remove(self.R)\n",
" else: # remove a letter\n",
" self.L = self.L[:-1]\n",
" self.R = self.R[1:]\n",
" \n",
" def check(self):\n",
" \"Check to see if current state is a palindrome, and if so, record it and maybe print.\"\n",
" if not self.is_palindrome(): return\n",
" N = len(self.left) + len(self.right) \n",
" if N > len(self.best):\n",
" self.best = self.left + list(self.right)\n",
" if N - self.Nshown > 1000 or N > 16000:\n",
" self.Nshown = N\n",
" print(self.report())\n",
" \n",
" def report(self):\n",
" N = len(self.best)\n",
" nwords = N + sum(self.dict[p].count(' ') for p in self.best)\n",
" nletters = sum(len(p) for p in self.best)\n",
" return ('Pal: {:6,d} phrases, {:6,d} words, {:6,d} letters ({:,d} steps, {} seconds)'\n",
" .format(N, nwords, nletters, self.i+1, round(time.time() - self.t0)))\n",
" \n",
" def applicable_actions(self):\n",
" L, R, D = self.L, self.R, self.dict\n",
" actions = []\n",
" def score(A): return D.prefixes[L+A] * D.suffixes[A+R]\n",
" if self.is_allowed(L):\n",
" actions.append(',')\n",
" if self.is_allowed(R):\n",
" actions.append(';')\n",
" for A in sorted(alphabet, key=score):\n",
" if score(A) > 0:\n",
" actions.append(A) \n",
" return actions\n",
" \n",
" def is_allowed(self, phrase): return phrase in self.dict and phrase not in self.set\n",
" \n",
" def is_palindrome(self): \n",
" \"Is this a palindrome? (Does any extra .L or .R match the other side?)\"\n",
" return ((self.L == '' and self.left[-1].endswith(self.R)) or \n",
" (self.R == '' and self.right[0].startswith(self.L)))\n",
"\n",
"alphabet = 'abcdefghijklmnopqrstuvwxyz'\n",
"cat = ''.join\n",
"UndoCommand = str\n",
"DoCommand = list\n",
" \n",
"def test2():\n",
" p1 = Panama()\n",
" assert p1.is_palindrome()\n",
" assert str(p1) == 'a man, a plan, a canal, Panama'\n",
" p2 = Panama(['aman','aplan'], 'acadd','dd', ['acanal', 'panama'])\n",
" assert not p2.is_palindrome()\n",
" p3 = Panama(['maya'], '', '', ['ayam'])\n",
" assert p3.is_palindrome()\n",
" assert str(p3) == 'Maya, a yam'\n",
" return 'ok'\n",
"\n",
"test2()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The main point of the `Panama` class is to allow the `.search()` method to search for a long palindrome. The [overall strategy](http://norvig.com/pal-alg.html#v3) is explained elsewhere. The search\n",
"adds a lett1er at a time to both left and right side, backtracking when necessary. The state of the search is represented by the four fields `.left, .L, .R, .right`, for example:\n",
" \n",
" A man, a plan, a cam, a yak, a yam, a canal, Panama\n",
" \n",
"gets encoded into the four fields like this:\n",
" \n",
" .left: ['aman', 'aplan', 'acam', 'ayak']\n",
" .L: ''\n",
" .R: 'k'\n",
" .right: ['ayam', 'acanal', 'panama']\n",
" \n",
"We always maintain the invariant:\n",
"\n",
" cat(left + [L]) == cat([R] + right)[::-1]\n",
" \n",
"The `search` method would be more straightforward if we could write it is a recursive function. But Python does not perform well when recursing to 100 million levels deep. So instead I manually manage a *stack* of *commands* that tell the search what to do and to undo. A command can be a `DoCommand`, which consists of a list of letters describing all the possible actions we could take at this point. Actions include letters that could be added (only the letters that make a legitimate prefix of a known phrase in `.L` and a legitimate suffix of a known phrase in `.R`). Actions can also include the character `','`, which is used to dignal that `.L` is a complete word and should be moved ontot he `.left` list, or `';'` to signal the same for `.R/.right`. A command can also be a `UndoCommand`, which consists of a single character; one that was previously chosen to be done. So the list of characters in a `DoCommand` constitutes a choice point; we first choose one, and continue deeper from there, but we put on the command stack an `UndoCommand` to reverse the effects of the action, and put back the remaining characters to try instead, if the first character doesn't work out. Note that we pop characters off the end of a `DoCommand`, so the last character is the first one tried.\n",
"\n",
"Let's see how it works:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Pal: 1,001 phrases, 1,395 words, 6,246 letters (30,023 steps, 1 seconds)\n",
"Pal: 2,003 phrases, 2,802 words, 12,375 letters (112,614 steps, 3 seconds)\n",
"CPU times: user 5.61 s, sys: 17.4 ms, total: 5.63 s\n",
"Wall time: 5.65 s\n"
]
}
],
"source": [
"p = Panama()\n",
"%time p.search(200000)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'Pal: 2,791 phrases, 3,876 words, 17,101 letters (200,000 steps, 4 seconds)'"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"p.report()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"That seemed to go ok; let's try a longer search (100 million steps):"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Pal: 1,001 phrases, 1,395 words, 6,246 letters (30,023 steps, 1 seconds)\n",
"Pal: 2,003 phrases, 2,802 words, 12,375 letters (112,614 steps, 2 seconds)\n",
"Pal: 3,004 phrases, 4,162 words, 18,341 letters (227,472 steps, 5 seconds)\n",
"Pal: 4,005 phrases, 5,507 words, 24,199 letters (386,226 steps, 8 seconds)\n",
"Pal: 5,019 phrases, 6,890 words, 30,137 letters (619,790 steps, 12 seconds)\n",
"Pal: 6,021 phrases, 8,235 words, 35,931 letters (877,024 steps, 17 seconds)\n",
"Pal: 7,023 phrases, 9,568 words, 41,593 letters (1,183,926 steps, 23 seconds)\n",
"Pal: 8,024 phrases, 10,878 words, 47,259 letters (1,561,761 steps, 30 seconds)\n",
"Pal: 9,026 phrases, 12,183 words, 52,965 letters (1,947,540 steps, 38 seconds)\n",
"Pal: 10,028 phrases, 13,467 words, 58,583 letters (2,480,848 steps, 48 seconds)\n",
"Pal: 11,029 phrases, 14,730 words, 64,117 letters (3,206,870 steps, 61 seconds)\n",
"Pal: 12,030 phrases, 16,018 words, 69,588 letters (4,044,269 steps, 77 seconds)\n",
"Pal: 13,031 phrases, 17,293 words, 74,967 letters (5,272,674 steps, 99 seconds)\n",
"Pal: 14,033 phrases, 18,519 words, 80,203 letters (7,091,166 steps, 133 seconds)\n",
"Pal: 15,034 phrases, 19,753 words, 85,307 letters (10,753,057 steps, 201 seconds)\n",
"Pal: 16,003 phrases, 20,886 words, 90,015 letters (27,084,693 steps, 506 seconds)\n",
"Pal: 16,004 phrases, 20,886 words, 90,015 letters (27,149,605 steps, 508 seconds)\n",
"Pal: 16,005 phrases, 20,890 words, 90,017 letters (27,316,698 steps, 511 seconds)\n",
"Pal: 16,009 phrases, 20,896 words, 90,039 letters (27,350,925 steps, 511 seconds)\n",
"Pal: 16,010 phrases, 20,896 words, 90,039 letters (27,412,633 steps, 513 seconds)\n",
"Pal: 16,011 phrases, 20,897 words, 90,045 letters (27,427,058 steps, 513 seconds)\n",
"Pal: 16,013 phrases, 20,898 words, 90,049 letters (27,567,249 steps, 516 seconds)\n",
"Pal: 16,014 phrases, 20,898 words, 90,046 letters (27,592,882 steps, 516 seconds)\n",
"Pal: 16,016 phrases, 20,900 words, 90,055 letters (27,595,464 steps, 516 seconds)\n",
"Pal: 16,019 phrases, 20,903 words, 90,070 letters (27,616,478 steps, 517 seconds)\n",
"Pal: 16,020 phrases, 20,904 words, 90,073 letters (27,670,352 steps, 518 seconds)\n",
"Pal: 16,022 phrases, 20,908 words, 90,081 letters (27,690,556 steps, 518 seconds)\n",
"Pal: 16,024 phrases, 20,908 words, 90,096 letters (27,750,047 steps, 519 seconds)\n",
"Pal: 16,025 phrases, 20,911 words, 90,097 letters (27,902,094 steps, 522 seconds)\n",
"Pal: 16,028 phrases, 20,916 words, 90,113 letters (27,913,484 steps, 523 seconds)\n",
"Pal: 16,029 phrases, 20,916 words, 90,119 letters (28,000,410 steps, 524 seconds)\n",
"Pal: 16,030 phrases, 20,916 words, 90,115 letters (28,199,063 steps, 528 seconds)\n",
"Pal: 16,033 phrases, 20,920 words, 90,131 letters (28,207,363 steps, 528 seconds)\n",
"Pal: 16,035 phrases, 20,924 words, 90,143 letters (28,222,377 steps, 529 seconds)\n",
"Pal: 16,036 phrases, 20,924 words, 90,145 letters (28,245,911 steps, 529 seconds)\n",
"Pal: 16,038 phrases, 20,928 words, 90,155 letters (28,319,574 steps, 530 seconds)\n",
"Pal: 16,039 phrases, 20,927 words, 90,165 letters (28,357,649 steps, 531 seconds)\n",
"Pal: 16,044 phrases, 20,933 words, 90,179 letters (28,398,480 steps, 532 seconds)\n",
"Pal: 16,045 phrases, 20,936 words, 90,187 letters (28,669,877 steps, 537 seconds)\n",
"Pal: 16,046 phrases, 20,936 words, 90,193 letters (28,756,803 steps, 538 seconds)\n",
"Pal: 16,047 phrases, 20,937 words, 90,195 letters (28,951,247 steps, 542 seconds)\n",
"Pal: 16,048 phrases, 20,937 words, 90,195 letters (29,168,799 steps, 546 seconds)\n",
"Pal: 16,049 phrases, 20,939 words, 90,197 letters (29,304,707 steps, 548 seconds)\n",
"Pal: 16,050 phrases, 20,940 words, 90,200 letters (29,402,659 steps, 550 seconds)\n",
"Pal: 16,055 phrases, 20,946 words, 90,223 letters (29,483,409 steps, 552 seconds)\n",
"Pal: 16,060 phrases, 20,951 words, 90,241 letters (29,703,353 steps, 556 seconds)\n",
"Pal: 16,062 phrases, 20,951 words, 90,257 letters (29,935,767 steps, 560 seconds)\n",
"Pal: 16,065 phrases, 20,957 words, 90,261 letters (30,401,372 steps, 568 seconds)\n",
"Pal: 16,066 phrases, 20,957 words, 90,267 letters (30,488,256 steps, 570 seconds)\n",
"Pal: 16,067 phrases, 20,961 words, 90,271 letters (30,742,860 steps, 575 seconds)\n",
"Pal: 16,068 phrases, 20,961 words, 90,277 letters (30,829,744 steps, 576 seconds)\n",
"Pal: 16,069 phrases, 20,965 words, 90,279 letters (31,091,412 steps, 581 seconds)\n",
"Pal: 16,070 phrases, 20,965 words, 90,285 letters (31,178,296 steps, 583 seconds)\n",
"Pal: 16,071 phrases, 20,969 words, 90,287 letters (31,452,702 steps, 588 seconds)\n",
"Pal: 16,072 phrases, 20,969 words, 90,293 letters (31,539,586 steps, 589 seconds)\n",
"Pal: 16,075 phrases, 20,973 words, 90,299 letters (31,899,517 steps, 597 seconds)\n",
"Pal: 16,077 phrases, 20,975 words, 90,305 letters (32,079,651 steps, 600 seconds)\n",
"Pal: 16,079 phrases, 20,978 words, 90,317 letters (32,418,194 steps, 606 seconds)\n",
"Pal: 16,080 phrases, 20,977 words, 90,327 letters (32,456,269 steps, 607 seconds)\n",
"Pal: 16,085 phrases, 20,983 words, 90,341 letters (32,497,076 steps, 608 seconds)\n",
"Pal: 16,087 phrases, 20,985 words, 90,351 letters (32,651,838 steps, 611 seconds)\n",
"Pal: 16,090 phrases, 20,986 words, 90,365 letters (32,782,954 steps, 613 seconds)\n",
"Pal: 16,091 phrases, 20,988 words, 90,363 letters (33,528,985 steps, 627 seconds)\n",
"Pal: 16,093 phrases, 20,992 words, 90,375 letters (33,696,891 steps, 630 seconds)\n",
"Pal: 16,094 phrases, 20,992 words, 90,381 letters (33,783,775 steps, 631 seconds)\n",
"Pal: 16,095 phrases, 20,992 words, 90,379 letters (34,151,848 steps, 638 seconds)\n",
"Pal: 16,096 phrases, 20,995 words, 90,389 letters (34,283,539 steps, 640 seconds)\n",
"Pal: 16,098 phrases, 20,996 words, 90,397 letters (34,342,030 steps, 642 seconds)\n",
"Pal: 16,099 phrases, 20,998 words, 90,397 letters (34,699,703 steps, 648 seconds)\n",
"Pal: 16,100 phrases, 21,001 words, 90,405 letters (36,050,560 steps, 673 seconds)\n",
"Pal: 16,102 phrases, 21,002 words, 90,413 letters (36,109,051 steps, 674 seconds)\n",
"Pal: 16,103 phrases, 21,004 words, 90,413 letters (36,466,496 steps, 680 seconds)\n",
"Pal: 16,105 phrases, 21,005 words, 90,411 letters (37,036,344 steps, 691 seconds)\n",
"Pal: 16,106 phrases, 21,004 words, 90,423 letters (76,003,720 steps, 1522 seconds)\n",
"Pal: 16,107 phrases, 21,006 words, 90,423 letters (76,361,384 steps, 1530 seconds)\n",
"Pal: 16,108 phrases, 21,009 words, 90,431 letters (77,712,187 steps, 1559 seconds)\n",
"Pal: 16,110 phrases, 21,010 words, 90,439 letters (77,770,678 steps, 1560 seconds)\n",
"Pal: 16,111 phrases, 21,012 words, 90,439 letters (78,128,114 steps, 1568 seconds)\n",
"CPU times: user 33min 11s, sys: 13 s, total: 33min 24s\n",
"Wall time: 33min 38s\n"
]
}
],
"source": [
"p = Panama()\n",
"%time p.search(10**8)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"a man, a plan, a caretaker, a moksha, Lufkin, a jacinth, Gile, Daniell, Ivanov, an odor, a negativeness, a tsarevna, melanomas, an ire, Gib, Olga, Lata Narayanan, a meld, an elecampane, an evonymus, an anatase, Rachael, an anabasis, an anaesthetics, an anamniote, an ani, DRE, Fredra, Cal, Parette, Serang, Isaacs, a ramark, IHD, a nonacademic, an obstet, Sellers, Sanalda, Erbe, Eba, an ane, Zaneski, Red, Erfurt, Sinai, Sale, Gene Roddenberry, a nonactive, Cenaean, Namangan, a mell, Everes, a nonanimal, a sanitarian, a non-American, Gat, Noman, an arista, Lattimer, a nonemendation, Eberta, Poe, LCT, an anaerobe, Ranita, Marella, an antired, ipsissima verba, a nan, a manille, Tiv, a nonet, an one, Kenney, a caret, Alon, Omar Epps, an analemma, an enami, Barents, Regen, A. H. Taub, a self, Farant, Caresse, RdAc, a nil, a nonrevocation, a melanite, Ranite, Canarese, NEbE, an allanite, Caressa, an all, IJssel, DNA, Carolee Carmello, Sara, Pareto, Oran, a mel, Dissenter, Cesare, Tema, an alt, Evslin, an alienist, anabases, a torpedoman, a meta, Garett, a csk, Cabaeus, an aloneness, Emelita, an alerion, Eleni, a cong, Ilan Adler, a keratoderma, an alg, Roe, generalities, a nonrateability, a hellebore, an anatta, Ranitta, petits, a baresark, unities, an iman, a non-Indian, a non-Israelite, Delano, Ita, Ranit, Ame, a hanaper, Tanah, an alarum, an alpha, Lacedaemonian, a tab, Cenac, an alette, Levona, an airbrasive, Lody, Nnadia Chan, a naira, an airstream, Macedon, an anatexis, an aileron, a nail, arts, UART, San, a nong, a morceau, dice, Delavan, a mete, Parent, Artemas, Esau, an ora, Hsingan, Amero, FAD, Naman, Amery, Bareilly, Hwang, Inanna, an aider, Acre, Vona, an ait, an aisle, a decare, billies, Banares, ARE, Nanakuli, codette, Lemon, a nonmember, a jaconet, an aigrette, Sang, Tanagra, Genet, a portal, an ore, Pip, a non-Uralian, Bohaty, a parent, Sikang, Amorette, Locrian, Cameron, Anna, Carole, Delaware, Temenus, an iterativeness, Enos, an aim, a danaite, Ahrens, Enarete, Mona, Marelda, an ataractic, a noise, lanais, anatexes, a noisette, Long, APS, Enalus, Omari, a natl, Usanis, an attestator, Gean, an oiliness, an oilskin, Numanus, Ari, Hellene, RCAF, a caresser, Pare, Tem, a torsel, a nat, a kokanee, LaSorella, Gipsies, Irby, an aperient, Mareld, an enallage, a decalcomania, an argali, an arbalester, an imagnableness, an arith, a nonsensitiveness, Anam, Kramer, a caress, a parens, Asel, Ujiji, Emelina, PMA, Carena, ELAS, an it's, Estella, Marela, Edana, Melba, Tsana, Mel, Bonar, a nidana, Meri, Fareham, an arum, a receivable, Gene Geter, a grammarian, Alanah, Ana, Hsian, a lenity, an aug, an oil, a decane, an arrest, Ibanez, an acosmist, an absorptance, a noisemaker, a metall, a gasser, an acuteness, Ender, an ossa, Maria, Teria, an acetal, a manilla, Marie, Benilda, Erb, a no-side, an amah, Drava, Lat, Ragan, Amer, O'Fallon, an amasser, Tai, Tini, Nanak, a nine, Leninabad, an enargite, Gilles, a benignancy, Lanam, an ami, Vanessa, LCI, Kikelia, Delamare, Terr, Efahan, a visitor, a nies, a nit, OFris, an amenorrhea, Laramie, Lipsey, an augend, Averell, a carelessness, oogoninia, Lad, a nose, Majel Barrett, an amen, AHSA, Lari, Oka, Hsi, Namara, an enate, Caren, a career, Gena, Jenna von Oy, a material, Lebanon, an abaca, an abalone, an abaser, a nebris, a benison, Aug, an osnaburg, a nosh, Tim, Selle, Baram, an aba, Hellen, a decal, a paretta, Marelya, Teri Polo, a predicate, leva, Warenne, R. Bayer, Fred Astaire, an abr, Unit, a legatee, RFA, Nananne, Kenner, Rawley, a greensand, Ronald, Barea, Benildas, an abb, inamoratas, an abuser, an illegalisation, a mess, a heller, a decapitation, a hello, Helles, Ignatia, Hsia-men, a hcl, a north, Gilda, Edan, a mesa, Barotse, Hts, an ill, a green, OPA, Ewan, a menu, Bret, epithalami, an adder, a nos, an ordainer, an adagio, Marion, a menses, a tsi, Perelman, a taker-in, Ume, Arp, an ord, a ceder, ACS, an oracy, Aneto, O'Kelley, Na-Dene, Lassell, a caramel, a derisiveness, a cudgel, an orang, Farewell, a beek, Sella, Geneva, Dela, Derian, a men, Arcadian, a meg, Rabassa, an adenoma, Gareth, Gilbart, Sandy, a hell, a mix, Elam, a danewort, an emanation, Matsys, a gala, Mario, Marilyn, a tob, an origan, a media, Sello, Cinerama, an asonia, Rania, Redeemer, an oddness, endia, Tsan, a medullation, a colin, a mee, RFD, a raft, a tsarina, Venita, an aulos, a ferret, Edna, Rgen, Alena, Idelle, Baras, an isotach, an oceanography, an orinasal, a bander, a jar, Tepic, a carabin, ANG, a mannan, A Mei, paralipses, ATC, Enola, a goner, a caracal, a spur-rowel, an agron, an oribi, an adenine, Lger, a wet, Agana, an agromania, Lehet, an oca, EdB, USAR, an ell, a veritas, an orca, Marena, Elgan, a morale, a m/s, inamoratos, a rasse, Irene, Csel, a dep, Ohara, a ballade, Paramatman, an agr, Efren, a ramie, Wenona, tubas, an ipm, a caravaner, a decanter, a nitrogen, a morn, Onalaska, a sinapine, LAC, Sellma, Lamas, an agar, Ihab, a rani, Artamas, an aga, Nana Visitor, anLine truncated
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"str(p)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Success! We found a palindrome that is longer than any found before. We can do about 50,000 steps per second. I'll stop here, but there are many things to try; feel free to explore on your own:\n",
"\n",
"- I order the actions by the product of the number of prefixes and number of suffixes formed. In other words, choose first the character that forms the most possible completions. But maybe, at least in the early goings, I should choose more difficult combinations of letters, to use up the \"hard\" phrases and save the \"easy\" phrases for later.\n",
"- I'm not sure when to end a word and when to try to continue to a longer word; experimentation here would be useful.\n",
"- The program is deterministic, and thus always finds the same plaindrome. that's boring; can some randomness be introduced?\n",
"- Can we make more interesting phrases? Include determiners other than \"a\" and \"an\"; include adjectives, etc.\n",
"- The counts of prefixes and suffixes include all the phrases in the dictionary. But we are not allowed to repeat a phrase, so can we modify the code to subtract one from the counts every time a phrase is used (and add onr back in when we backtrack over the phrase)?\n",
"\n",
"Perhaps you can find other areas to explore.\n",
"\n",
"# Indefinite Articles\n",
"\n",
"What if every noun phrase (except \"Panama\") has to start with an indefinite article, as in \"an X\" or \"a Y\"? Let's see:"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [],
"source": [
"! [ -e anpdict-short.txt ] || curl -O http://norvig.com/anpdict-short.txt"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"DICT2 = PhraseDict(open('anpdict-short.txt'))"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Pal: 1,001 phrases, 2,001 words, 5,649 letters (473,669 steps, 13 seconds)\n",
"CPU times: user 44min 43s, sys: 25.2 s, total: 45min 8s\n",
"Wall time: 46min 55s\n"
]
},
{
"data": {
"text/plain": [
"'Pal: 1,237 phrases, 2,473 words, 6,787 letters (100,000,000 steps, 2816 seconds)'"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"p = Panama(dict=DICT2)\n",
"%time p.search(10**8)\n",
"p.report()"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {
"scrolled": true
},
"outputs": [
{
"data": {
"text/plain": [
"\"a man, a plan, a caret, a rec, a manana, an anabasis, a tsar, a tanager, an araban, a mat, an aromatic, an amadan, a melanoma, an anatman, an amowt, a barege, a jar, a tav, an arr, a parataxis, an amora, an agron, an ami, a caress, a paracasein, a salad, an allopath, a baronet, an all, a darer, a caravan, an alg, a ratal, an alfa, an agnate, an arg, an arcs, an arcanum, an air, an alp, a nail, a run-on, an aid, a ratan, an anabas, a pat, an ain, a baron, a manakin, a natator, a tarok, an attar, an apa, a nag, an animator, an apr, a tannin, a nap, a janitor, a carapa, an abaca, an atabal, an agal, an abb, a ratel, a salal, a harem, an anime, a hareem, an anim, a gala, a para, a maniac, an ann, a manit, a salep, a laniard, an acaridan, an aced, an adat, an abater, a lca, an amalaka, an aim, a daraf, an anion, a galliwasp, a caraway, a redan, a mow, a nae, a paretic, an avail, a guna, an iter, a nay, a daric, an aug, an origan, a mot, an orinasal, a barret, an ordn, an anil, a torc, an ord, a haram, a darts, an ani, a rater, an imaret, a rut, a sale, a taler, an orc, a nanowatt, an annot, an ore, an angina, a nit, a nos, a nonet, a nogg, a warb, an anus, a nog, a rap, a nome, a dater, a bat, a nom, a let, an ottar, an oil, a tale, a balata, an imam, a rate, a harim, an alapa, a nim, a lar, a cort, an oca, a nil, a saliva, an ips, an oxen, a nod, a rat, an alarum, an oak, an ital, a caracara, a hallah, a nipa, an elem, a nip, a latah, an isallobar, a core, an alias, an inner, a nib, a saloop, a nies, a carol, a varec, a radiov, an emagram, an etalon, a tuba, an evader, a nett, a parol, a jam, a lama, a net, a carob, a tamis, an egg, a lati, an alif, a neep, an eel, a ballot, an alley, an eyalet, a corban, a minah, a regal, a cariama, a nork, a nek, a wallaba, a nomad, an omasum, a lac, an ecad, an ogam, a lame, a haloid, a neb, an esu, a halo, a gallop, a nef, an uta, an okay, a radar, a fanegada, a var, a civ, a nunatak, a zap, an acarid, an upas, a pariah, an udal, a colat, a rabal, a cedar, a gallet, a nub, a retail, a kalam, an alegar, a geb, a retem, a gem, a redd, an alt, an alb, an attabal, a tepal, a timer, a redleg, a repp, an atlas, a ballup, a rep, a carioca, a nut, a resaw, a sal, a tip, a carrot, a resp, a lambda, a tat, a bal, a tot, a resin, a tob, a res, a radula, a tam, a tamas, an amatol, a removal, a sis, a rem, a gall, a carpus, a revel, a rev, a rad, a canalis, a bani, a rps, a recap, a rennet, a reed, a reel, a reek, a fanon, a canikin, a manioc, a reflet, a refl, a dep, a ref, a wall, a cerat, a tub, a reward, a reb, a saros, a vav, a rein, a pannus, a rex, a wanigan, a myriad, a rag, an amah, a rail, a rank, a raob, a rota, a tag, an amurca, a tass, a canoe, an assam, a catalpa, a tad, a room, a rood, a roll, a palais, a rod, a rim, a roc, a rial, a rip, a tapa, a jad, a catnap, a rub-a-dub, a tarp, a tanga, a tenaim, a rani, a tapis, a taps, a taka, a teff, a tap, a lanai, a snob, a tax, a taw, a tennis, a tew, a teg, a teel, a trapan, a mrem, a tram, a trad, a trot, a troop, a trid, a trf, a topaz, a galiot, a topsail, a manuf, a top, a catnip, a tool, a galop, a toom, a tonk, a tom-tom, a tog, a manway, a today, a rom, a titan, a trap, a tid, an assent, a pall, a mar, a bec, a fan, a lad, a til, a tum, a gal, a noma, a tef, a tug, a tufa, a tolan, a rasa, a margay, a datum, a tsi, a waterfall, a far, a mac, a jass, a lag, a batik, a tpd, a sard, a matins, a sav, a samaj, a tabard, a saga, a mallam, a dam, a sakai, a rum, a sari, a palikar, a misaim, a kalema, a mam, a sima, a mag, an assoc, a sumac, a lila, a molar, a peh, a sum, a ramrod, a supp, a pataca, a moc, a matt, a wadi, a matsu, a haeres, a sup, a subah, a lap, a calix, an ass, a pastel, a sepal, a mahaleb, a tabu, a dat, a cuda, a mob, a sex, an ashcan, a rub, a jasey, a strops, a stob, a sora, a minimax, a satsuma, a mix, a mass, a sadi, a ragi, a tab, a lulav, a lay, a wad, a cyc, a sorb, a soda, a mud, a sop, a snug, a slag, a scf, a lack, a laic, a fats, a cassaba, a recast, a hwan, a cep, a laud, a lev, a rayah, a leek, a vat, a gam, a slab, a leg, a baas, a camaca, a ruse, a camoca, a rug, an asb, an aery, an agnail, a lex, an abac, a leud, a liard, a lip, a lied, a lid, a loofah, a loom, a loca, a ripsaw, a swob, a lob, a lub, a bayard, a lyc, a nabob, a cadi, a sag, a dagoba, a lek, an app, a yapok, a dargah, a mib, a daud, a dap, a dab, a dig, a draw, an actg, a drib, a drub, a def, a deb, a dewan, a diam, a dopa, a lune, a papaya, a lug, a doom, a dorp, a dom, a doh, a dog, a doc, a duc, a marga, a liza, a capa, a cup, a marc, a madrigal, a pipa, a babassu, a gad, a dak, an oka, a belga, a grub, a maya, a gib, a map, a mesa, a goy, a mug, a misc, a fat, a qadi, a cay, a sap, a papaw, a myg, a mpg, a pagan, a gob, a powan, a wog, a pika, a pup, a pig, a proc, a peek, a plug, a pcf, a cav, a caf, a cig, a may, a tagrag, a garb, a gaw, a guv, a bob, a kabaka, a dis, a qaid, a kaki, a pay, a bai, a law, a rLine truncated
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"str(p)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.5.3"
}
},
"nbformat": 4,
"nbformat_minor": 1
}