Add files via upload

This commit is contained in:
Peter Norvig
2017-09-29 21:59:55 -07:00
committed by GitHub
parent 8fd57ec9a9
commit bfbfc756c6

View File

@@ -15,7 +15,7 @@
"- **Words**: I will read a standard online word list, `enable1`, and make a set of all the words of sufficient length.\n",
"- **Fragment**: a fragment is a `str` of letters, such as `'gho'`.\n",
"- **Beginning**: each word has a set of valid beginnings: for `ghost` it is `{'', g, gh, gho, ghos, ghost}`. \"Prefix\" is a synonym of \"beginning\".\n",
"- **Vocabulary**: `Vocabulary(words)` is an object with: a set of all the `words`, and a set of all the valid `fragments` (beginnings) of the words.\n",
"- **Vocabulary**: A `Vocabulary` object holds a set of all the `words` in a dictionary, as well as all the valid `fragments` (beginnings) of the words.\n",
"- **Player**: The first player will be called player `0`; the second player `1`. \n",
"- **Play**: A play is a new fragment formed by adding one letter to the end of the existing fragment.\n",
"- **Legal Play**: A play that is a valid prefix of some word. `enable1.legal_plays('gho') = {'ghos, 'ghou'}`.\n",
@@ -30,7 +30,7 @@
"source": [
"# Vocabulary: Words, Fragments, Legal Plays, and `enable1`\n",
"\n",
"`Vocabulary(text)` takes a collection of words as input, stores the words as a set, and also stores all the legal fragments of those words (that is, the beginnings). `legal_plays(fragments)` gives a set of all plays that can be formed by adding a letter to create a legal word fragment (where 'fragment' includes complete words). I also define the function `words` to split any string into component words."
"`Vocabulary(words)` takes a collection of words as input, stores the words as a set, and also stores all the legal fragments of those words. `legal_plays(fragments)` gives a set of all plays that can be formed by adding a letter to create a legal word fragment. I also define the function `words` to split any string into component words."
]
},
{
@@ -65,9 +65,7 @@
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"data": {
@@ -89,9 +87,7 @@
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"data": {
@@ -129,9 +125,7 @@
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [],
"source": [
"enable1 = Vocabulary(words(open('enable1.txt').read()))"
@@ -141,15 +135,13 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Here I explore `enable1`:"
"Let's explore `enable1`:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"data": {
@@ -169,9 +161,7 @@
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"data": {
@@ -191,9 +181,7 @@
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"data": {
@@ -213,9 +201,7 @@
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"data": {
@@ -263,21 +249,19 @@
"Who wins a game if both players play rationally? To answer that, consider the general situation at some point\n",
"during the game where\n",
"a player is presented with a fragment. That player can win if either:\n",
"- The fragment is a word (meaning the other player formed the word, and lost).\n",
"- The fragment is not a legal fragment (meaning the other player made a mistake, and lost).\n",
"- The fragment is a complete word (meaning the other player just formed the word, and thereby lost).\n",
"- The fragment is not a legal fragment (meaning the other player made a mistake, and thereby lost).\n",
"- At least one of the legal plays in this position puts the opponent in a position from which they *cannot* win.\n",
"\n",
"The function `win(vocab, fragment)` implements this. It returns a winning fragment if there is one, otherwise `False`. In particular, it returns `fragment` if the current player has already won (because `fragment` is\n",
"a word or illegal fragment), and it returns one of the legal plays if the play leads to a position from\n",
"a word or illegal fragment), and it returns one of the legal plays if that play leads to a position from\n",
"which the opponent *cannot* `win`."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [],
"source": [
"def win(vocab, fragment=''):\n",
@@ -301,9 +285,7 @@
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"data": {
@@ -324,9 +306,7 @@
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"data": {
@@ -340,16 +320,14 @@
}
],
"source": [
"# 'g' is a winning play (but 'c' wuld be a loser)\n",
"# 'g' is a winning play (but 'c' would be a loser)\n",
"win(Vocabulary(words('cat camel goat gerbil'))) "
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"data": {
@@ -363,16 +341,14 @@
}
],
"source": [
"# No winning play; dommed to 'camel' or 'gar'\n",
"# No winning play; doomed to 'camel' or 'gar'\n",
"win(Vocabulary(words('cat camel goat gecko gerbil gar'))) "
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"data": {
@@ -386,7 +362,7 @@
}
],
"source": [
"# 'g' wins because 'ga' can be answered with 'gan'\n",
"# 'g' wins because 'ga' can be answered with 'gan' and 'ge' with 'ge'\n",
"win(Vocabulary(words('cat camel goat gecko gerbil gar gannet'))) "
]
},
@@ -402,9 +378,7 @@
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"data": {
@@ -434,22 +408,12 @@
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 6.35 ms, sys: 179 µs, total: 6.53 ms\n",
"Wall time: 7.14 ms\n"
]
},
{
"data": {
"text/plain": [
"'h'"
"'n'"
]
},
"execution_count": 17,
@@ -460,7 +424,7 @@
"source": [
"enable1_4 = Vocabulary(enable1.words, 4)\n",
"\n",
"%time win(enable1_4)"
"win(enable1_4)"
]
},
{
@@ -482,9 +446,7 @@
{
"cell_type": "code",
"execution_count": 18,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [],
"source": [
"import random\n",
@@ -495,22 +457,20 @@
"\n",
"def ask(name):\n",
" \"Return a strategy that asks for the next letter.\"\n",
" return (lambda _, fragment: input('Player {}, given \"{}\", plays? '.format(name, fragment)))"
" return lambda _, fragment: input('Player ' + name + ' given \"' + fragment + '\" plays? ')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here is a function to play a game. You give it a vocabulary, two (or potentially more) strategies, and optionally a `verbose` keyword to say if you want a line printed for each play or not."
"Here is a function to play a game. You give it a vocabulary, two (or possibly more) strategies, and optionally a `verbose` keyword to say if you want a line printed for each play or not."
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [],
"source": [
"from itertools import cycle\n",
@@ -533,14 +493,12 @@
{
"cell_type": "code",
"execution_count": 20,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(1, 'llama')"
"(1, 'odyssey')"
]
},
"execution_count": 20,
@@ -555,33 +513,31 @@
{
"cell_type": "code",
"execution_count": 21,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[(1, 'dreck'),\n",
" (1, 'mho'),\n",
"[(1, 'truancies'),\n",
" (1, 'xebec'),\n",
" (1, 'ods'),\n",
" (1, 'vying'),\n",
" (1, 'ewe'),\n",
" (1, 'bwana'),\n",
" (1, 'vying'),\n",
" (1, 'knell'),\n",
" (1, 'zloty'),\n",
" (1, 'flirt'),\n",
" (1, 'zlote'),\n",
" (1, 'bwana'),\n",
" (1, 'aal'),\n",
" (1, 'gjetost'),\n",
" (1, 'oquassa'),\n",
" (1, 'oquassa'),\n",
" (1, 'yperite'),\n",
" (1, 'yperite'),\n",
" (1, 'plica'),\n",
" (1, 'wrest'),\n",
" (1, 'qursh'),\n",
" (1, 'aal'),\n",
" (1, 'aas'),\n",
" (1, 'tweak'),\n",
" (1, 'uintahite'),\n",
" (1, 'uintahite'),\n",
" (1, 'hmm'),\n",
" (1, 'uintahite'),\n",
" (1, 'jnana')]"
" (1, 'pliancies'),\n",
" (1, 'flounce'),\n",
" (1, 'ufologist'),\n",
" (1, 'flour'),\n",
" (1, 'dweeb'),\n",
" (1, 'flirt'),\n",
" (1, 'ignatia')]"
]
},
"execution_count": 21,
@@ -598,14 +554,12 @@
{
"cell_type": "code",
"execution_count": 22,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(0, 'hugs')"
"(0, 'nyctalopia')"
]
},
"execution_count": 22,
@@ -620,23 +574,22 @@
{
"cell_type": "code",
"execution_count": 23,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Player 0, given \"\", plays? d\n",
"Player 0, given \"dr\", plays? dri\n",
"Player 0, given \"drif\", plays? drift\n"
"Player P given \"\" plays? d\n",
"Player P given \"dw\" plays? dwa\n",
"Player P given \"dwar\" plays? dwarv\n",
"Player P given \"dwarve\" plays? dwarves\n"
]
},
{
"data": {
"text/plain": [
"(1, 'drift')"
"(1, 'dwarves')"
]
},
"execution_count": 23,
@@ -645,7 +598,7 @@
}
],
"source": [
"play(enable1, ask(0), rational)"
"play(enable1, ask('P'), rational)"
]
},
{
@@ -665,9 +618,7 @@
{
"cell_type": "code",
"execution_count": 24,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [],
"source": [
"import textwrap \n",
@@ -717,9 +668,7 @@
{
"cell_type": "code",
"execution_count": 25,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"name": "stdout",
@@ -745,16 +694,14 @@
{
"cell_type": "code",
"execution_count": 26,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Outcomes (7) for player 0:\n",
"Winners (7): nays nene ngultrum nirvanic nolo null nyctalopia\n"
"Winners (7): naan nene ngultrum nirvanic nolo null nyctalopia\n"
]
}
],
@@ -778,21 +725,19 @@
{
"cell_type": "code",
"execution_count": 27,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Outcomes (55) for player 1:\n",
"Winners (55): aah aal aargh aas bwana cwm drake dreck drift droit\n",
"druse dry ewe fjeld fjord gjetost hmm ihram jnana kwashiorkor llama\n",
"mho nth oquassa praam prequel prill pro prurigo pry qua quell quiff\n",
"quondam qursh rhamnus rheum rhizoid rho rhumb rhyolitic sjambok\n",
"tchotchke uhlan vroom wrang wrest wrick wrong wrung wry xanthic\n",
"xanthin yperite zucchetto\n"
"Winners (55): aah aal aargh aas bwana cwm drave dreck drink droit\n",
"druid dry ewe fjeld fjord gjetost hmm ihram jnana kwashiorkor llano\n",
"mho nth oquassa prawn prequel prill pro prurigo pry qua quell quiff\n",
"quomodo qursh rhamnus rheum rhizoid rho rhumb rhyolitic squoosh\n",
"tchotchke uhlan vying wrath wrest wrist wrong wrung wry xanthic\n",
"xanthin yttrium zucchetto\n"
]
}
],
@@ -812,26 +757,24 @@
{
"cell_type": "code",
"execution_count": 28,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Outcomes (85) for player 1:\n",
"Losers (4): hybrid hyphen hyte ngultrum\n",
"Losers (4): hybris hyphen hyte ngultrum\n",
"Winners (81): aquiculture aquifer aquilegia aquiver bwana cnidarian\n",
"drake dreck drift droit druse drywall eschatologies eschatology\n",
"escheat eserine eskar esophagus esplanade esquire esquiring essay\n",
"drave dreck drink droit druid dryness eschatologies eschatology\n",
"escheat eserine esker esophagus esplanade esquire esquiring essay\n",
"estuarine estuary esurience fjeld fjord gjetost hyaenic hydatid hyena\n",
"hyenine hyenoid hygeist hying hylozoism hylozoist hymen hyoid hypha\n",
"hyraces hyrax hyson ihram jnana kwashiorkor llama mbira ngwee oquassa\n",
"plaza plethoric plica plonk pluck plyer quaff quell quiff quondam\n",
"qursh rhamnus rheum rhizoid rhomb rhumb rhyolitic sjambok tchotchke\n",
"uhlan vroom wrang wrest wrick wrong wrung wryly xanthic xanthin\n",
"yperite zucchetto\n"
"hyraces hyrax hyson ihram jnana kwashiorkor llano mbira ngwee oquassa\n",
"plaza plethoric plica plonk pluck plyer quaff quell quiff quomodo\n",
"qursh rhamnus rheum rhizoid rhomb rhumb rhyolitic squoosh tchotchke\n",
"uhlan vying wrath wrest wrist wrong wrung wryly xanthic xanthin\n",
"yttrium zucchetto\n"
]
}
],
@@ -858,9 +801,7 @@
{
"cell_type": "code",
"execution_count": 29,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [],
"source": [
"class SuperVocabulary(Vocabulary):\n",
@@ -886,21 +827,17 @@
{
"cell_type": "code",
"execution_count": 30,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [],
"source": [
"enable1s = SuperVocabulary(enable1.words, 3)\n",
"enable1_4s = SuperVocabulary(enable1.words, 4)"
"enable1_3super = SuperVocabulary(enable1.words, 3)\n",
"enable1_4super = SuperVocabulary(enable1.words, 4)"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"data": {
@@ -932,20 +869,18 @@
],
"source": [
"# Example legal plays\n",
"enable1s.legal_plays('crop')"
"enable1_3super.legal_plays('crop')"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'y'"
"'u'"
]
},
"execution_count": 32,
@@ -956,20 +891,18 @@
"source": [
"# Can the first player win in SuperGhost with 3-letter words?\n",
"\n",
"win(enable1s)"
"win(enable1_3super)"
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'y'"
"'u'"
]
},
"execution_count": 33,
@@ -980,7 +913,7 @@
"source": [
"# How about with a 4-letter limit?\n",
"\n",
"win(enable1_4s)"
"win(enable1_4super)"
]
},
{
@@ -993,24 +926,22 @@
{
"cell_type": "code",
"execution_count": 34,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Player 0, given \"\", plays? q\n",
"Player 0, given \"mq\", plays? mqu\n",
"Player 0, given \"umqu\", plays? umqua\n",
"Player 0, given \"cumqua\", plays? cumquat\n"
"Player P given \"\" plays? q\n",
"Player P given \"cq\" plays? cqu\n",
"Player P given \"cque\" plays? acque\n",
"Player P given \"acques\" plays? acquest\n"
]
},
{
"data": {
"text/plain": [
"(1, 'cumquat')"
"(1, 'acquest')"
]
},
"execution_count": 34,
@@ -1019,7 +950,7 @@
}
],
"source": [
"play(enable1s, ask(0), rational)"
"play(enable1_3super, ask('P'), rational)"
]
},
{
@@ -1047,9 +978,7 @@
" \n",
" def legal_plays(self, fragment):\n",
" \"All plays that form a valid infix; optionally reverse fragment first.\"\n",
" def all4(frag, L): \n",
" return (frag + L, frag[::-1] + L, \n",
" L + frag, L + frag[::-1])\n",
" def all4(f, L): return (f + L, f[::-1] + L, L + f, L + f[::-1])\n",
" return {p for L in alphabet for p in all4(fragment, L)} & self.fragments"
]
},
@@ -1061,16 +990,14 @@
},
"outputs": [],
"source": [
"enable1sd = SuperDuperVocabulary(enable1.words)\n",
"enable1_4sd = SuperDuperVocabulary(enable1.words, 4)"
"enable1_3duper = SuperDuperVocabulary(enable1.words)\n",
"enable1_4duper = SuperDuperVocabulary(enable1.words, 4)"
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"data": {
@@ -1108,22 +1035,20 @@
],
"source": [
"# Example legal plays\n",
"enable1sd.legal_plays('crop')"
"enable1_3duper.legal_plays('crop')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we should check who wins. But I'm impateint: I tried `win(enable1sd)`, waited a minute, and it still hadn't returned, so I interrupted that computation and threw in a `lru_cache` decorator; which stops `win` from wasting time repeating the same computation over and over. This brings the time down to about 2 seconds."
"Now we should check who wins. But I'm impateint: I tried `win(enable1_3duper)`, waited a minute, and it still hadn't returned, so I interrupted that computation and threw in a `lru_cache` decorator; which stops `win` from wasting time repeating the same computation over and over. This brings the time down to about 2 seconds."
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [],
"source": [
"from functools import lru_cache\n",
@@ -1143,14 +1068,12 @@
{
"cell_type": "code",
"execution_count": 39,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'y'"
"'p'"
]
},
"execution_count": 39,
@@ -1159,20 +1082,18 @@
}
],
"source": [
"win(enable1sd)"
"win(enable1_3duper)"
]
},
{
"cell_type": "code",
"execution_count": 40,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'y'"
"'p'"
]
},
"execution_count": 40,
@@ -1181,7 +1102,7 @@
}
],
"source": [
"win(enable1_4sd)"
"win(enable1_4duper)"
]
},
{
@@ -1194,30 +1115,28 @@
{
"cell_type": "code",
"execution_count": 41,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Player 0, given \"\", plays \"y\".\n",
"Player 1, given \"y\", plays \"zy\".\n",
"Player 0, given \"zy\", plays \"izy\".\n",
"Player 1, given \"izy\", plays \"mizy\".\n",
"Player 0, given \"mizy\", plays \"mizyg\".\n",
"Player 1, given \"mizyg\", plays \"emizyg\".\n",
"Player 0, given \"emizyg\", plays \"emizygo\".\n",
"Player 1, given \"emizygo\", plays \"emizygou\".\n",
"Player 0, given \"emizygou\", plays \"hemizygou\".\n",
"Player 1, given \"hemizygou\", plays \"hemizygous\".\n"
"Player 0, given \"\", plays \"p\".\n",
"Player 1, given \"p\", plays \"pr\".\n",
"Player 0, given \"pr\", plays \"lpr\".\n",
"Player 1, given \"lpr\", plays \"rple\".\n",
"Player 0, given \"rple\", plays \"rplex\".\n",
"Player 1, given \"rplex\", plays \"rplexe\".\n",
"Player 0, given \"rplexe\", plays \"urplexe\".\n",
"Player 1, given \"urplexe\", plays \"urplexes\".\n",
"Player 0, given \"urplexes\", plays \"ourplexes\".\n",
"Player 1, given \"ourplexes\", plays \"fourplexes\".\n"
]
},
{
"data": {
"text/plain": [
"(0, 'hemizygous')"
"(0, 'fourplexes')"
]
},
"execution_count": 41,
@@ -1226,7 +1145,7 @@
}
],
"source": [
"play(enable1sd, rational, rational, verbose=True)"
"play(enable1_3duper, rational, rational, verbose=True)"
]
},
{
@@ -1239,9 +1158,7 @@
{
"cell_type": "code",
"execution_count": 42,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"data": {
@@ -1256,7 +1173,7 @@
],
"source": [
"[len(v.fragments) \n",
" for v in [enable1, enable1_4, enable1s, enable1_4s, enable1sd, enable1_4sd]]"
" for v in [enable1, enable1_4, enable1_3super, enable1_4super, enable1_3duper, enable1_4duper]]"
]
},
{
@@ -1268,7 +1185,7 @@
"Here's a summary of what we have learned. (*Note:* the bold **qursh** means it is a losing word):\n",
"\n",
"| Variant \t| Shortest \t| Winner \t| First Player Forced Outcomes | 2nd Outcomes | Fragments\n",
"|----\t|---:\t |---\t |--- |--- |---:\n",
"|:----:\t|:---:\t |:---:\t |:---: |:---: |---:\n",
"| Ghost | 3 \t | Second \t| qaid qiviut qoph **qursh** qurush qwerty | 55 words | 387,878\n",
"| Ghost | 4 \t | First \t| naan nene ngultrum nirvanic nolo null nyctalopia | 85 words | 387,844\n",
"| Super | 3\t| First \t| ? | ? | 1,076,434\n",
@@ -1306,7 +1223,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.0"
"version": "3.5.3"
}
},
"nbformat": 4,