From bfbfc756c68c95e7b8562e5980277b5dae23e837 Mon Sep 17 00:00:00 2001 From: Peter Norvig Date: Fri, 29 Sep 2017 21:59:55 -0700 Subject: [PATCH] Add files via upload --- Ghost.ipynb | 339 ++++++++++++++++++++-------------------------------- 1 file changed, 128 insertions(+), 211 deletions(-) diff --git a/Ghost.ipynb b/Ghost.ipynb index e902c6c..245e391 100644 --- a/Ghost.ipynb +++ b/Ghost.ipynb @@ -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,