From ff6fbe5359c326d085cdd0f7e6e7f359c0d99064 Mon Sep 17 00:00:00 2001 From: Neil Smith Date: Fri, 23 Jan 2015 07:28:47 +0000 Subject: [PATCH] Finished the text --- SIGNED.md | 38 +- hangman/02-hangman-guesser.ipynb | 419 +-- hangman/03-hangman-both.ipynb | 423 ++- ...n-better.ipynb => 04-hangman-better.ipynb} | 2411 +++++++---------- ...logging.ipynb => 05-hangman-logging.ipynb} | 0 hangman/hangman.py | 8 +- 6 files changed, 1419 insertions(+), 1880 deletions(-) rename hangman/{05-hangman-better.ipynb => 04-hangman-better.ipynb} (60%) rename hangman/{06-hangman-logging.ipynb => 05-hangman-logging.ipynb} (100%) diff --git a/SIGNED.md b/SIGNED.md index 4fa48b2..447be38 100644 --- a/SIGNED.md +++ b/SIGNED.md @@ -3,19 +3,19 @@ -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 -iQIcBAABAgAGBQJUsmqUAAoJEJPB2e07Pgbq7roP/3c7WymRzq3CLhcdFURrX8yN -lNf8ZktWzUkmJPee7T8Hjh44MPk7itt5RuRqltZA8COOJIRRFIRnV7eebG5LhBof -ynDZqQKOBUshqWo67b8U7wQnSVZN1KmMqbEapks67xtOXkJqD8WSKM5mtnM123tU -Z8pc1mwt8od3U26tjHP/aLSvXgxqv38ws6fMt1DzriPd5t2G/p5dCRngZRquAlXz -y2uFtUFZ2e51Ji3D6ntNe2BRgdEOF4I6AH1YbM8C+Irv3BUv2ez7x3D8RH79Lk7b -hmEnhqwcHTFPzn29K+2swzn1q+youfXmtVxhwQsBPYpamQdkn/7CBmPHa6SEeXOD -0Dcs3/R1YBPpj81VAYA9ZZs2pUGWtoJDxtpabZ93zfReFOYNq6DbgzfA8SW+sB7U -Bxf1DBQr6C8PnrQdp2lxsHKIXN/4cQNqwOP3NmuIAo38RpLtKiEfLls7zzIDbpAz -RN2EfL6le85gUyAD/mp56guwvYgXImvDBVZ5RUDlw67AaKqeTfvr7kTLo1vICdUp -kQwdG7fUcg4zSpNPZnYcVApPHxZI6nojWwfJDHSErkFmsJBK6YpNOpFM7nmxnR9T -nrNehH6I67tjNU0thM8HXzG9cO/QBH0Ms8tXAeUQMvI1RgbeMOXNBzyh3FJrzQET -kKgyU5dzDmAK1Bg4AQ05 -=7t35 +iQIcBAABAgAGBQJUwfg1AAoJEJPB2e07PgbqpYEP/3HVphHcvq977tiPTB854NAY +YDM2QPnKQ7kQetphTX9Y/U0qwGeYph+Rd00lqdJgBJLhJr15UZHEHd8SLF+yB58x +m39e6YBwdzsyilpHl/GHKKtDjcXuMsnNvCVGdpeCP/319i0FTSzT/kBpkyBQAXK0 +TCBocPo5GaeSAgxdZ1DJA73wMlCiQzJsv9Vjf7rg3APwetuFwY8ZpVwfcslglnsX +vGu1lfbdIvVpbhyjGxyI0zaXzehVuCIyBb6Hu0lw0FGU060ntN75Pz++Rf3LrE+U +0XnCeYjbbWLTzVE436mWB45MJnc48dTzdsLMDMEE5z0dGifJf1nbtNgqsVRi1vZQ +i9yGCCwKPP6rhZ1ezfNfpmzjQ6riF26RXYq9V4w6oCDTbIN9vjPFNC0+5YYYDjM1 +ROcccxFvUKTkb91NECcAbZGqPFWovRvaFX7KzGyizEWCGMoD7/5rpRw3YrY/x14C +lhZ6n5AwEAqL9RBZk+Mrubz+YgNSDbTM/+QcL5PHGb8mRC41PD4xSo4PCPU6AB+y +vDSW523YNkhHB3IC3uAijZppimyk484IYsuw1WY9uVdClgCsUcs2VT8E3GOQOemQ +34ZaPgeZR/JLKVpLo3lCuxbgI2JsNvGYmFw8moTcW4mTlO3dmPSi6s3bns3/GNA8 +OTu2ZMrmO+Lff9JwTr5L +=SFX3 -----END PGP SIGNATURE----- ``` @@ -41,6 +41,11 @@ size exec file contents 21947 euler-11.ipynb 81780dca17496751894f582e8691935ee6f94949017fb3135087056489f8ad88 24903 euler-14.ipynb 3652a2be86ec270420431814efa453f8679ad054ae28c015d34232c48dac16bd hangman/ +18717 01-hangman-setter.ipynb 72cebe4f36ddcd4c856c4520d4566e5e6f4793ad3d9f716a7f9ab1e52a7b9f71 +9100 02-hangman-guesser.ipynb 1a74d6cc7065cb756d8d0ecfd58b41da6d75f79c76def20aab0b23725ac47fbf +34900 03-hangman-both.ipynb f75a5c2934f9385dc02db3efaf71fca8ed91cee86173730a3f99445b8841a223 +55675 04-hangman-better.ipynb d123307c6004a2b34433904ca541edb9e4ceba54877e27b1e65a428d68f284e3 +258871 05-hangman-logging.ipynb 5f94f25200c6f0c01ff27e3392afd0ef172dab0df5557aebb4a76692052bd167 106328 adaptive_excluded.csv 72ea6bb3f6f0301d7f5592766b7854c1a4c46ab01ca0677357aa367e1f2b6783|92e30fc15f783e701721a38039b2cd54d96bccd5cfdc37e92f70e174d6e941eb 78326 adaptive_included.csv 39e4947fc840ea9e7c7ee8c658c65deb196ffed86a1b2e5e3f6c3770259e6a3f|78597f1124040a884533f157b99d898c4cd1b372bb20432652a5376d9de5090d 109806 adaptive_length.csv 296ddbe817525f33b15e7489cc0f0ff2e7852adcd5720673632673d3d6c04a2f|b151fc20e1399e517f03be172e22c115934af891b6f6509b94381b2e15b613dc @@ -50,13 +55,8 @@ size exec file contents 118543 fixed_alphabetical_reversed.csv 1a5bc0d94947f4260bb2bb0f6bfd21434f2876e75907b49669bf49356fb59967|d62bb39d0a775abf63ba29e22decc208ff0810f70f0761729eb92a6cc809b9a4 109497 fixed_dict_order.csv c593bb2ff8eb353906d591d34a3d5b6dcf399a414424dbae1c245fa7588e829a|6c97d8cc9db5ba4a674c11e669527cfb8e39e8339b7b3aea1b12b107db249d7e 112991 fixed_order.csv 61c803950fb824ea20f91c328450d80ce69a65e0d5cfa24e961019f795a75feb|3a2d6099afe1785434d314d2eefc1249cacb15afb2751e75fa448887a8f22b41 -53469 hangman-better.ipynb 04adeb21f6e3688123ea37f41135affb4b81e76e572e31da04fc1c94edc02045 -27923 hangman-both.ipynb 279221758793f80048923b335037ba7639cd299814878142ba15746b627dbd63 -9419 hangman-guesser.ipynb d867d7f57b1b59b9cf74f4594fe4b6fd5761e93799292abfb65dcf35381f25e1 -258871 hangman-logging.ipynb 5f94f25200c6f0c01ff27e3392afd0ef172dab0df5557aebb4a76692052bd167 -12247 hangman-setter.ipynb bc8d8d7f0bddbe128d273ccd174bbf4ad338771c9159b5b07b262a3e5fede892 44670 hangman-split-player.ipynb b72ff88817c6cce83d26a701e7e3b17d4332f10fa6cf97520dd7dd6ee6e00464 -6437 hangman.py 90895564bcf4c5b8b6f06c6e80209c749947ebbb38505ca2f441e338e59eb16a +6173 hangman.py ba4ac6cbeb4691433b304e1301190c67674f82ce9ba7b62637bf342c4504cabd 10890 word_filter_comparison.ipynb 9b80fff718b9ff5620d9441123ebbf60073ea337d14e1e4825ef7860f4e1346b 5286 hangman.html b9a4e8d3f96c57d64dd678fecb1dbd453ee6b415a0bb5a1c232e4e224310eb88 4087 hangman2.html 18e1678e58a1d89f5d2e9fd68de94152f9f34bb2ad93023b6fb69b970bf53a4a diff --git a/hangman/02-hangman-guesser.ipynb b/hangman/02-hangman-guesser.ipynb index 8f482b1..66f3b49 100644 --- a/hangman/02-hangman-guesser.ipynb +++ b/hangman/02-hangman-guesser.ipynb @@ -1,7 +1,7 @@ { "metadata": { "name": "", - "signature": "sha256:393162de43635426518dd3dca8100dca49558a55f990aede7c52f3a98de60612" + "signature": "sha256:eb35fa497310659186b8d082192529c5f3ce0083adaa1704282cce83da804db6" }, "nbformat": 3, "nbformat_minor": 0, @@ -45,6 +45,75 @@ "Someone needs to keep track of the incorrect guesses so that the guesser doesn't keep making the same wrong guess. As the setter is already keeping track of the rest of the state, it's probably easier if the setter also keeps track of the wrong letters. That might allow us to keep the guesser state-free, which will keep it simpler. " ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Getting a game state\n", + "If we need the guesser to interact with a human, let's get it to read the game state from a human.\n", + "\n", + "Remember that we want to return two lists of characters, not two strings.\n", + "\n", + "We'll also not do any input validation. Life's too short (for this example)." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "def read_game():\n", + " discovered = input('Enter the discovered word: ')\n", + " missed = input('Enter the wrong guesses: ')\n", + " return list(discovered), list(missed)" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 18 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "read_game()" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "stream": "stdout", + "text": [ + "Enter the discovered word: __pp_\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "stream": "stdout", + "text": [ + "Enter the wrong guesses: xv\n" + ] + }, + { + "metadata": {}, + "output_type": "pyout", + "prompt_number": 19, + "text": [ + "(['_', '_', 'p', 'p', '_'], ['x', 'v'])" + ] + } + ], + "prompt_number": 19 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That was easy." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -64,7 +133,7 @@ "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 1 + "prompt_number": 20 }, { "cell_type": "markdown", @@ -88,13 +157,13 @@ { "metadata": {}, "output_type": "pyout", - "prompt_number": 3, + "prompt_number": 21, "text": [ "Counter({'e': 53111, 't': 38981, 'a': 35137, 'o': 33512, 'i': 30140, 'h': 29047, 'n': 28682, 's': 27194, 'r': 24508, 'd': 18563, 'l': 17145, 'u': 13116, 'm': 11787, 'w': 11266, 'c': 10499, 'y': 9431, 'f': 8975, 'g': 7887, 'p': 6835, 'b': 6362, 'v': 4452, 'k': 3543, 'x': 549, 'j': 452, 'q': 426, 'z': 149})" ] } ], - "prompt_number": 3 + "prompt_number": 21 }, { "cell_type": "markdown", @@ -115,7 +184,7 @@ { "metadata": {}, "output_type": "pyout", - "prompt_number": 6, + "prompt_number": 22, "text": [ "[('e', 53111),\n", " ('t', 38981),\n", @@ -146,7 +215,7 @@ ] } ], - "prompt_number": 6 + "prompt_number": 22 }, { "cell_type": "code", @@ -161,95 +230,7 @@ { "metadata": {}, "output_type": "pyout", - "prompt_number": 4, - "text": [ - "['e',\n", - " 't',\n", - " 'a',\n", - " 'o',\n", - " 'i',\n", - " 'h',\n", - " 'n',\n", - " 's',\n", - " 'r',\n", - " 'd',\n", - " 'l',\n", - " 'u',\n", - " 'm',\n", - " 'w',\n", - " 'c',\n", - " 'y',\n", - " 'f',\n", - " 'g',\n", - " 'p',\n", - " 'b',\n", - " 'v',\n", - " 'k',\n", - " 'x',\n", - " 'j',\n", - " 'q',\n", - " 'z']" - ] - } - ], - "prompt_number": 4 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def make_guess():\n", - " guessed_letters = read_game()\n", - " unguessed_letters_in_order = ordered_subtract(letters_in_order, guessed_letters)\n", - " print('My guess is:', unguessed_letters_in_order[0])" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 29 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def read_game():\n", - " discovered = input('Enter the discovered word: ')\n", - " missed = input('Enter the wrong guesses: ')\n", - " return [l for l in lower(discovered + missed) if l in string.ascii_letters]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 30 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def ordered_subtract(ordered_list, to_remove):\n", - " for r in to_remove:\n", - " if r in ordered_list:\n", - " ordered_list.remove(r)\n", - " return ordered_list" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 19 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "letters_in_order" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 20, + "prompt_number": 23, "text": [ "['e',\n", " 't',\n", @@ -280,192 +261,35 @@ ] } ], - "prompt_number": 20 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "ordered_subtract(letters_in_order, 'etaoin')" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 21, - "text": [ - "['h',\n", - " 's',\n", - " 'r',\n", - " 'd',\n", - " 'l',\n", - " 'u',\n", - " 'm',\n", - " 'w',\n", - " 'c',\n", - " 'y',\n", - " 'f',\n", - " 'g',\n", - " 'p',\n", - " 'b',\n", - " 'v',\n", - " 'k',\n", - " 'x',\n", - " 'j',\n", - " 'q',\n", - " 'z']" - ] - } - ], - "prompt_number": 21 + "prompt_number": 23 }, { - "cell_type": "code", - "collapsed": false, - "input": [ - "letters_in_order" - ], - "language": "python", + "cell_type": "markdown", "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 22, - "text": [ - "['h',\n", - " 's',\n", - " 'r',\n", - " 'd',\n", - " 'l',\n", - " 'u',\n", - " 'm',\n", - " 'w',\n", - " 'c',\n", - " 'y',\n", - " 'f',\n", - " 'g',\n", - " 'p',\n", - " 'b',\n", - " 'v',\n", - " 'k',\n", - " 'x',\n", - " 'j',\n", - " 'q',\n", - " 'z']" - ] - } - ], - "prompt_number": 22 + "source": [ + "## Make a guess\n", + "How to make a guess? We want to guess the most common letter that hasn't already been guessed. \n", + "\n", + "That means we want to filter the available letters by removing the ones already guessed, and then take the first letter of the ones that are left. " + ] }, { "cell_type": "code", "collapsed": false, "input": [ - "def ordered_subtract(ordered_list, to_remove):\n", - " for r in to_remove:\n", - " if r in ordered_list:\n", - " ri = ordered_list.index(r)\n", - " ordered_list = ordered_list[:ri] + ordered_list[ri+1:]\n", - " return ordered_list" + "def make_guess(discovered, missed):\n", + " return [l for l in letters_in_order if l not in (discovered + missed)][0]" ], "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 26 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "letters_in_order = [p[0] for p in letter_counts.most_common()]\n", - "letters_in_order" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 24, - "text": [ - "['e',\n", - " 't',\n", - " 'a',\n", - " 'o',\n", - " 'i',\n", - " 'h',\n", - " 'n',\n", - " 's',\n", - " 'r',\n", - " 'd',\n", - " 'l',\n", - " 'u',\n", - " 'm',\n", - " 'w',\n", - " 'c',\n", - " 'y',\n", - " 'f',\n", - " 'g',\n", - " 'p',\n", - " 'b',\n", - " 'v',\n", - " 'k',\n", - " 'x',\n", - " 'j',\n", - " 'q',\n", - " 'z']" - ] - } - ], - "prompt_number": 24 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "ordered_subtract(letters_in_order, 'etaoin')" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 27, - "text": [ - "['h',\n", - " 's',\n", - " 'r',\n", - " 'd',\n", - " 'l',\n", - " 'u',\n", - " 'm',\n", - " 'w',\n", - " 'c',\n", - " 'y',\n", - " 'f',\n", - " 'g',\n", - " 'p',\n", - " 'b',\n", - " 'v',\n", - " 'k',\n", - " 'x',\n", - " 'j',\n", - " 'q',\n", - " 'z']" - ] - } - ], "prompt_number": 27 }, { "cell_type": "code", "collapsed": false, "input": [ - "letters_in_order" + "make_guess(list('__pp_'), list('exv'))" ], "language": "python", "metadata": {}, @@ -473,73 +297,22 @@ { "metadata": {}, "output_type": "pyout", - "prompt_number": 28, + "prompt_number": 26, "text": [ - "['e',\n", - " 't',\n", - " 'a',\n", - " 'o',\n", - " 'i',\n", - " 'h',\n", - " 'n',\n", - " 's',\n", - " 'r',\n", - " 'd',\n", - " 'l',\n", - " 'u',\n", - " 'm',\n", - " 'w',\n", - " 'c',\n", - " 'y',\n", - " 'f',\n", - " 'g',\n", - " 'p',\n", - " 'b',\n", - " 'v',\n", - " 'k',\n", - " 'x',\n", - " 'j',\n", - " 'q',\n", - " 'z']" + "'t'" ] } ], - "prompt_number": 28 + "prompt_number": 26 }, { - "cell_type": "code", - "collapsed": false, - "input": [ - "make_guess()" - ], - "language": "python", + "cell_type": "markdown", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "stream": "stdout", - "text": [ - "Enter the discovered word: _a__y\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "stream": "stdout", - "text": [ - "Enter the wrong guesses: eit\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "My guess is: o\n" - ] - } - ], - "prompt_number": 31 + "source": [ + "...and that's it. We don't need to keep track of game end: the setting player can do that.\n", + "\n", + "It's easy to see how we could have different strategies, depending on the order of letters used in the `make_guess` procedure. But that's for next time." + ] }, { "cell_type": "code", diff --git a/hangman/03-hangman-both.ipynb b/hangman/03-hangman-both.ipynb index d9e565b..543d832 100644 --- a/hangman/03-hangman-both.ipynb +++ b/hangman/03-hangman-both.ipynb @@ -1,13 +1,51 @@ { "metadata": { "name": "", - "signature": "sha256:5e9f030b42097ebbcce2654cdb1d9370fde38b61bdbfa85a0cc700e76d5a8a71" + "signature": "sha256:8dfb2bb1cf596c711c837f4c7c2d8706f9dcacc1a0ffc3dc21c1d0fedd3fc6bf" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#Putting both halves together\n", + "We now have systems that can do both halves of the Hangman game. Now it's time to put them together so that we can get a machine to play itself. At the moment, let's concentrate on just the plumbing: let's get the two halves working together. In the next step, we'll think about different strategies and compare their effectiveness.\n", + "\n", + "The word of this section is \"encapsulation.\" \n", + "\n", + "First, a change in terminology. We'll call a `player` the thing that makes the guesses (the guesser player from before). We'll call a `game` the thing that handles receiving guesses, updating `discovered` words, updating lives and all of that. That's what was the setting player from before, but without the bit about picking a random word. We'll have the `game` accept the `target` as a parameter, so that we can test everything more easily (if we know what word the `player` is trying to guess, we can predict what it should do).\n", + "\n", + "Back to encapsulation.\n", + "\n", + "We want to bundle all the state information for a `game` into one 'thing' so that we don't need to worry about global variables. We will also want to bundle up everything to do with a `player` into one 'thing' so we can tell the game about it. For the `player`, the thing will include both any state it needs and the definition of that `player`'s guessing strategy. We're also going to have a bunch of related players, all doing much the same thing but with different strategies.\n", + "\n", + "In this case, objects seem like a natural fit.\n", + "\n", + "(Insert Neil's aikido instructor story here.)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##Object-orientation in Python\n", + "There are a few things to remember about object orientation in Python.\n", + "\n", + "1. It's an ugly kludge that was bolted on long after the language was defined.\n", + "\n", + "2. `self` needs to be passed in to all object methods as the first parameter, though it doesn't appear when you call the method.\n", + "\n", + "3. Use `self.whatever` to refer to the instance variable `whatever`\n", + "\n", + "4. Use `self.however(arg1, arg2)` when one method calls another in the same object.\n", + "\n", + "Apart from that, it's pretty straightforward." + ] + }, { "cell_type": "code", "collapsed": false, @@ -20,7 +58,7 @@ "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 71 + "prompt_number": 43 }, { "cell_type": "code", @@ -32,7 +70,7 @@ "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 72 + "prompt_number": 44 }, { "cell_type": "code", @@ -43,7 +81,22 @@ "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 73 + "prompt_number": 45 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is pretty much the same code as for the `setter` part, just with plenty of `self` statements scatter around. A few things to note:\n", + "\n", + "* `initialise()` is now named `__init__()` (note the double underscores fore and aft).\n", + "* `player` and `lives` are parameters to the `game`, but with default values.\n", + "* I've added two new flags, `game_won` and `game_lost`.\n", + "* `do_turn` asks the player for a guess, if there is a player.\n", + "* The `guess()` method of the `player` is passed the `lives`, in case the guess should change depending on the progress of the game (the `player` can always ignore it).\n", + "* After a guess has been processed, `do_turn` has a few more statements to set the relevant flags to represent the state of the game.\n", + "* `report_on_game()` has been added for when the game is being played against a human." + ] }, { "cell_type": "code", @@ -112,7 +165,14 @@ "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 74 + "prompt_number": 46 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's test it!" + ] }, { "cell_type": "code", @@ -123,7 +183,7 @@ "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 75 + "prompt_number": 47 }, { "cell_type": "code", @@ -137,13 +197,13 @@ { "metadata": {}, "output_type": "pyout", - "prompt_number": 76, + "prompt_number": 48, "text": [ - "'strife'" + "'distension'" ] } ], - "prompt_number": 76 + "prompt_number": 48 }, { "cell_type": "code", @@ -157,13 +217,13 @@ { "metadata": {}, "output_type": "pyout", - "prompt_number": 77, + "prompt_number": 49, "text": [ - "['_', '_', '_', '_', '_', '_']" + "['_', '_', '_', '_', '_', '_', '_', '_', '_', '_']" ] } ], - "prompt_number": 77 + "prompt_number": 49 }, { "cell_type": "code", @@ -178,7 +238,7 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Word: _ _ _ _ _ _ : Lives = 10 , wrong guesses: \n" + "Word: _ _ _ _ _ _ _ _ _ _ : Lives = 10 , wrong guesses: \n" ] }, { @@ -190,7 +250,7 @@ ] } ], - "prompt_number": 78 + "prompt_number": 50 }, { "cell_type": "code", @@ -204,13 +264,13 @@ { "metadata": {}, "output_type": "pyout", - "prompt_number": 79, + "prompt_number": 51, "text": [ "10" ] } ], - "prompt_number": 79 + "prompt_number": 51 }, { "cell_type": "code", @@ -224,13 +284,13 @@ { "metadata": {}, "output_type": "pyout", - "prompt_number": 80, + "prompt_number": 52, "text": [ "[]" ] } ], - "prompt_number": 80 + "prompt_number": 52 }, { "cell_type": "code", @@ -245,7 +305,7 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Word: _ _ _ _ _ e : Lives = 10 , wrong guesses: \n" + "Word: _ _ _ _ e _ _ _ _ _ : Lives = 10 , wrong guesses: \n" ] }, { @@ -253,11 +313,11 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Enter letter: d\n" + "Enter letter: q\n" ] } ], - "prompt_number": 81 + "prompt_number": 53 }, { "cell_type": "code", @@ -271,13 +331,13 @@ { "metadata": {}, "output_type": "pyout", - "prompt_number": 82, + "prompt_number": 54, "text": [ "9" ] } ], - "prompt_number": 82 + "prompt_number": 54 }, { "cell_type": "code", @@ -291,13 +351,13 @@ { "metadata": {}, "output_type": "pyout", - "prompt_number": 83, + "prompt_number": 55, "text": [ - "['_', '_', '_', '_', '_', 'e']" + "['_', '_', '_', '_', 'e', '_', '_', '_', '_', '_']" ] } ], - "prompt_number": 83 + "prompt_number": 55 }, { "cell_type": "code", @@ -311,13 +371,13 @@ { "metadata": {}, "output_type": "pyout", - "prompt_number": 84, + "prompt_number": 56, "text": [ - "['d']" + "['q']" ] } ], - "prompt_number": 84 + "prompt_number": 56 }, { "cell_type": "code", @@ -333,7 +393,7 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Word: _ _ _ _ _ _ _ : Lives = 10 , wrong guesses: \n" + "Word: _ _ _ _ _ _ _ _ : Lives = 10 , wrong guesses: \n" ] }, { @@ -341,14 +401,14 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Enter letter: d\n" + "Enter letter: e\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ - "Word: _ _ _ _ _ _ d : Lives = 10 , wrong guesses: \n" + "Word: e _ e _ _ _ _ e : Lives = 10 , wrong guesses: \n" ] }, { @@ -356,14 +416,14 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Enter letter: e\n" + "Enter letter: x\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ - "Word: _ e _ _ _ e d : Lives = 10 , wrong guesses: \n" + "Word: e _ e _ _ _ _ e : Lives = 9 , wrong guesses: x\n" ] }, { @@ -371,14 +431,14 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Enter letter: f\n" + "Enter letter: s\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ - "Word: _ e _ _ _ e d : Lives = 9 , wrong guesses: f\n" + "Word: e _ e _ _ _ _ e : Lives = 8 , wrong guesses: s x\n" ] }, { @@ -386,14 +446,14 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Enter letter: e\n" + "Enter letter: l\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ - "Word: _ e _ _ _ e d : Lives = 9 , wrong guesses: f\n" + "Word: e _ e _ _ _ _ e : Lives = 7 , wrong guesses: l s x\n" ] }, { @@ -401,14 +461,14 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Enter letter: s\n" + "Enter letter: t\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ - "Word: _ e _ _ _ e d : Lives = 8 , wrong guesses: f s\n" + "Word: e _ e _ _ _ _ e : Lives = 6 , wrong guesses: l s t x\n" ] }, { @@ -416,14 +476,14 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Enter letter: t\n" + "Enter letter: m\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ - "Word: _ e _ _ _ e d : Lives = 7 , wrong guesses: f s t\n" + "Word: e _ e _ _ _ _ e : Lives = 5 , wrong guesses: l m s t x\n" ] }, { @@ -431,14 +491,14 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Enter letter: n\n" + "Enter letter: h\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ - "Word: _ e _ _ _ e d : Lives = 6 , wrong guesses: f n s t\n" + "Word: e _ e _ _ _ _ e : Lives = 4 , wrong guesses: h l m s t x\n" ] }, { @@ -446,14 +506,14 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Enter letter: m\n" + "Enter letter: i\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ - "Word: _ e _ _ _ e d : Lives = 5 , wrong guesses: f m n s t\n" + "Word: e _ e _ _ _ _ e : Lives = 3 , wrong guesses: h i l m s t x\n" ] }, { @@ -461,14 +521,14 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Enter letter: h\n" + "Enter letter: a\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ - "Word: _ e _ _ _ e d : Lives = 4 , wrong guesses: f h m n s t\n" + "Word: e _ e _ _ _ _ e : Lives = 2 , wrong guesses: a h i l m s t x\n" ] }, { @@ -483,7 +543,7 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Word: _ e _ _ _ e d : Lives = 3 , wrong guesses: f h m n o s t\n" + "Word: e _ e _ _ o _ e : Lives = 2 , wrong guesses: a h i l m s t x\n" ] }, { @@ -491,14 +551,14 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Enter letter: i\n" + "Enter letter: n\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ - "Word: _ e _ i _ e d : Lives = 3 , wrong guesses: f h m n o s t\n" + "Word: e _ e _ _ o n e : Lives = 2 , wrong guesses: a h i l m s t x\n" ] }, { @@ -513,7 +573,7 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Word: r e _ i r e d : Lives = 3 , wrong guesses: f h m n o s t\n" + "Word: e _ e r _ o n e : Lives = 2 , wrong guesses: a h i l m s t x\n" ] }, { @@ -521,26 +581,41 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Enter letter: w\n" + "Enter letter: v\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "Word: e v e r _ o n e : Lives = 2 , wrong guesses: a h i l m s t x\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "stream": "stdout", + "text": [ + "Enter letter: y\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ - "You won! The word was rewired\n" + "You won! The word was everyone\n" ] }, { "metadata": {}, "output_type": "pyout", - "prompt_number": 85, + "prompt_number": 57, "text": [ "True" ] } ], - "prompt_number": 85 + "prompt_number": 57 }, { "cell_type": "code", @@ -556,7 +631,7 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Word: _ _ _ _ _ : Lives = 10 , wrong guesses: \n" + "Word: _ _ _ _ _ _ _ _ _ : Lives = 10 , wrong guesses: \n" ] }, { @@ -564,14 +639,14 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Enter letter: e\n" + "Enter letter: q\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ - "Word: _ _ _ _ _ : Lives = 9 , wrong guesses: e\n" + "Word: _ _ _ _ _ _ _ _ _ : Lives = 9 , wrong guesses: q\n" ] }, { @@ -579,14 +654,14 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Enter letter: s\n" + "Enter letter: z\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ - "Word: _ _ _ _ s : Lives = 9 , wrong guesses: e\n" + "Word: _ _ _ _ _ _ _ _ _ : Lives = 8 , wrong guesses: q z\n" ] }, { @@ -594,14 +669,14 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Enter letter: t\n" + "Enter letter: x\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ - "Word: _ _ _ _ s : Lives = 8 , wrong guesses: e t\n" + "Word: _ _ _ _ _ _ _ _ _ : Lives = 7 , wrong guesses: q x z\n" ] }, { @@ -609,14 +684,14 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Enter letter: o\n" + "Enter letter: e\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ - "Word: _ _ _ _ s : Lives = 7 , wrong guesses: e o t\n" + "Word: _ _ _ _ _ _ _ _ _ : Lives = 6 , wrong guesses: e q x z\n" ] }, { @@ -624,14 +699,14 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Enter letter: i\n" + "Enter letter: a\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ - "Word: _ _ _ _ s : Lives = 6 , wrong guesses: e i o t\n" + "Word: _ _ a _ _ _ _ _ _ : Lives = 6 , wrong guesses: e q x z\n" ] }, { @@ -639,14 +714,14 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Enter letter: u\n" + "Enter letter: v\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ - "Word: _ _ _ _ s : Lives = 5 , wrong guesses: e i o t u\n" + "Word: _ _ a _ _ _ _ _ _ : Lives = 5 , wrong guesses: e q v x z\n" ] }, { @@ -654,14 +729,14 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Enter letter: a\n" + "Enter letter: b\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ - "Word: _ _ _ _ s : Lives = 4 , wrong guesses: a e i o t u\n" + "Word: _ _ a _ _ _ _ _ _ : Lives = 4 , wrong guesses: b e q v x z\n" ] }, { @@ -669,14 +744,14 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Enter letter: y\n" + "Enter letter: k\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ - "Word: _ y _ _ s : Lives = 4 , wrong guesses: a e i o t u\n" + "Word: _ _ a _ k _ k _ _ : Lives = 4 , wrong guesses: b e q v x z\n" ] }, { @@ -684,14 +759,14 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Enter letter: w\n" + "Enter letter: l\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ - "Word: _ y _ _ s : Lives = 3 , wrong guesses: a e i o t u w\n" + "Word: _ _ a _ k _ k _ _ : Lives = 3 , wrong guesses: b e l q v x z\n" ] }, { @@ -699,14 +774,14 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Enter letter: h\n" + "Enter letter: j\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ - "Word: h y _ _ s : Lives = 3 , wrong guesses: a e i o t u w\n" + "Word: _ _ a _ k _ k _ _ : Lives = 2 , wrong guesses: b e j l q v x z\n" ] }, { @@ -714,14 +789,14 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Enter letter: m\n" + "Enter letter: p\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ - "Word: h y m _ s : Lives = 3 , wrong guesses: a e i o t u w\n" + "Word: _ _ a _ k _ k _ _ : Lives = 1 , wrong guesses: b e j l p q v x z\n" ] }, { @@ -729,26 +804,26 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Enter letter: n\n" + "Enter letter: d\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ - "You won! The word was hymns\n" + "You lost. The word was sharkskin\n" ] }, { "metadata": {}, "output_type": "pyout", - "prompt_number": 86, + "prompt_number": 58, "text": [ - "True" + "False" ] } ], - "prompt_number": 86 + "prompt_number": 58 }, { "cell_type": "code", @@ -762,13 +837,23 @@ { "metadata": {}, "output_type": "pyout", - "prompt_number": 108, + "prompt_number": 59, "text": [ - "'six'" + "'sharkskin'" ] } ], - "prompt_number": 108 + "prompt_number": 59 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Player objects\n", + "Let's work ourselves gently into this.\n", + "\n", + "In the first instance, the only thing a `player` needs is to respond to the `guess` method. Let's make objects with just this method. Let's even make it simpler than the player from the `guesser` stage, and have it just guess letters in alphabetical order." + ] }, { "cell_type": "code", @@ -776,13 +861,21 @@ "input": [ "class PlayerAlphabetical:\n", " def guess(self, discovered, missed, lives):\n", - " guessed_letters = [l.lower() for l in discovered + missed if l in string.ascii_letters]\n", - " return [l for l in string.ascii_lowercase if l not in guessed_letters][0]" + " return [l for l in string.ascii_lowercase if l not in discovered + missed][0]" ], "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 109 + "prompt_number": 60 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That wasn't too hard, was it?\n", + "\n", + "Let's use it to play a game." + ] }, { "cell_type": "code", @@ -793,7 +886,7 @@ "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 110 + "prompt_number": 69 }, { "cell_type": "code", @@ -807,13 +900,13 @@ { "metadata": {}, "output_type": "pyout", - "prompt_number": 111, + "prompt_number": 70, "text": [ "False" ] } ], - "prompt_number": 111 + "prompt_number": 70 }, { "cell_type": "code", @@ -827,13 +920,13 @@ { "metadata": {}, "output_type": "pyout", - "prompt_number": 112, + "prompt_number": 71, "text": [ - "['l', 'e', 'g', 'a', 'l', 'l', '_']" + "['c', 'e', '_', '_', 'i', 'f', 'i', 'c', 'a', '_', 'e', '_']" ] } ], - "prompt_number": 112 + "prompt_number": 71 }, { "cell_type": "code", @@ -847,13 +940,20 @@ { "metadata": {}, "output_type": "pyout", - "prompt_number": 113, + "prompt_number": 72, "text": [ - "'legally'" + "'certificates'" ] } ], - "prompt_number": 113 + "prompt_number": 72 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Bored with looking at all the state of the game with different cells. Let's have a `print()` statement that tells us what we need." + ] }, { "cell_type": "code", @@ -870,28 +970,35 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Target: cavalry ; Discovered: ['c', 'a', '_', 'a', 'l', '_', '_'] ; Lives remaining: 0\n" + "Target: turnoffs ; Discovered: ['_', '_', '_', '_', '_', 'f', 'f', '_'] ; Lives remaining: 0\n" ] } ], - "prompt_number": 114 + "prompt_number": 74 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Another player\n", + "Let's reimplement our player that does letters in frequency order. Again, this is essentially code reused from earlier." + ] }, { "cell_type": "code", "collapsed": false, "input": [ - "LETTER_COUNTS = collections.Counter(l.lower() for l in open('sherlock-holmes.txt').read() if l in string.ascii_letters)\n", + "LETTER_COUNTS = collections.Counter(l.lower() for l in open('../sherlock-holmes.txt').read() if l in string.ascii_letters)\n", "LETTERS_IN_ORDER = [p[0] for p in LETTER_COUNTS.most_common()]\n", "\n", "class PlayerFreqOrdered:\n", " def guess(self, discovered, missed, lives):\n", - " guessed_letters = [l.lower() for l in discovered + missed if l in string.ascii_letters]\n", - " return [l for l in LETTERS_IN_ORDER if l not in guessed_letters][0]" + " return [l for l in LETTERS_IN_ORDER if l not in discovered + missed][0]" ], "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 115 + "prompt_number": 76 }, { "cell_type": "code", @@ -908,11 +1015,11 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Target: deathblow ; Discovered: ['d', 'e', 'a', 't', 'h', '_', 'l', 'o', 'w'] ; Lives remaining: 0\n" + "Target: cartons ; Discovered: ['c', 'a', 'r', 't', 'o', 'n', 's'] ; Lives remaining: 2\n" ] } ], - "prompt_number": 116 + "prompt_number": 77 }, { "cell_type": "code", @@ -929,11 +1036,11 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Target: littered ; Discovered: ['l', 'i', 't', 't', 'e', 'r', 'e', 'd'] ; Lives remaining: 5\n" + "Target: douching ; Discovered: ['d', 'o', 'u', 'c', 'h', 'i', 'n', '_'] ; Lives remaining: 0\n" ] } ], - "prompt_number": 117 + "prompt_number": 78 }, { "cell_type": "code", @@ -950,11 +1057,23 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Target: licenced ; Discovered: ['l', 'i', 'c', 'e', 'n', 'c', 'e', 'd'] ; Lives remaining: 1\n" + "Target: minimisation ; Discovered: ['m', 'i', 'n', 'i', 'm', 'i', 's', 'a', 't', 'i', 'o', 'n'] ; Lives remaining: 4\n" ] } ], - "prompt_number": 118 + "prompt_number": 79 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Player subclasses and inheritance\n", + "Time to DRY the code a little. Both of these players (`PlayerAlphabetical` and `PlayerFreqOrdered`) are essentially the same. The only difference is the set of letters used in the middle of the `guess` method. \n", + "\n", + "Let's create a generic \"letters in a fixed order\" class that has that implementation of `guess` and is passed in a set of letters in order. The two players we already have are subclasses of that, where we have pre-packaged letter orders.\n", + "\n", + "Here's the generic player. Note that the ordered letters are passed in as a parameter to the `__init__()` method, and `guess()` uses that letter sequence." + ] }, { "cell_type": "code", @@ -965,13 +1084,19 @@ " self.ordered_letters = ordered_letters\n", " \n", " def guess(self, discovered, missed, lives):\n", - " guessed_letters = [l.lower() for l in discovered + missed if l in string.ascii_letters]\n", - " return [l for l in self.ordered_letters if l not in guessed_letters][0]" + " return [l for l in self.ordered_letters if l not in discovered + missed][0]" ], "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 119 + "prompt_number": 85 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now specify the subclasses, using the fixed order. The initialisation of the subclasses now takes no additional parameter, but we pass the hardcoded order to the superclass's `__init__()`." + ] }, { "cell_type": "code", @@ -988,7 +1113,14 @@ "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 120 + "prompt_number": 86 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Does it work?" + ] }, { "cell_type": "code", @@ -1005,11 +1137,11 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Target: unworthier ; Discovered: ['u', 'n', 'w', 'o', 'r', 't', 'h', 'i', 'e', 'r'] ; Lives remaining: 5\n" + "Target: yogi ; Discovered: ['_', 'o', '_', 'i'] ; Lives remaining: 0\n" ] } ], - "prompt_number": 121 + "prompt_number": 87 }, { "cell_type": "code", @@ -1026,11 +1158,11 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Target: kneel ; Discovered: ['_', 'n', 'e', 'e', 'l'] ; Lives remaining: 0\n" + "Target: slimmest ; Discovered: ['s', 'l', 'i', 'm', 'm', 'e', 's', 't'] ; Lives remaining: 3\n" ] } ], - "prompt_number": 122 + "prompt_number": 88 }, { "cell_type": "code", @@ -1047,11 +1179,18 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Target: provisoes ; Discovered: ['_', '_', '_', '_', 'i', '_', '_', 'e', '_'] ; Lives remaining: 0\n" + "Target: diseases ; Discovered: ['d', 'i', '_', 'e', 'a', '_', 'e', '_'] ; Lives remaining: 0\n" ] } ], - "prompt_number": 123 + "prompt_number": 89 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Can we use the generic version directly?" + ] }, { "cell_type": "code", @@ -1068,11 +1207,11 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Target: invincible ; Discovered: ['_', '_', 'v', '_', '_', '_', '_', '_', '_', '_'] ; Lives remaining: 0\n" + "Target: pardon ; Discovered: ['p', '_', 'r', '_', 'o', 'n'] ; Lives remaining: 0\n" ] } ], - "prompt_number": 124 + "prompt_number": 90 }, { "cell_type": "code", @@ -1089,11 +1228,21 @@ "output_type": "stream", "stream": "stdout", "text": [ - "Target: clogging ; Discovered: ['_', '_', '_', '_', '_', '_', '_', '_'] ; Lives remaining: 0\n" + "Target: grouchier ; Discovered: ['_', 'r', 'o', 'u', '_', '_', '_', '_', 'r'] ; Lives remaining: 0\n" ] } ], - "prompt_number": 125 + "prompt_number": 91 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##Mass gaming\n", + "Now we can get machines playing each other, we get get them playing *lots* of games and just tell us the summaries. \n", + "\n", + "Which of these three methods (alphabetical, frequency ordered, reverse alphabetical) is the best? Let's get each player to play 100 random games and see which gets the most wins." + ] }, { "cell_type": "code", @@ -1114,11 +1263,11 @@ "output_type": "stream", "stream": "stdout", "text": [ - "44\n" + "49\n" ] } ], - "prompt_number": 126 + "prompt_number": 92 }, { "cell_type": "code", @@ -1139,11 +1288,11 @@ "output_type": "stream", "stream": "stdout", "text": [ - "320\n" + "309\n" ] } ], - "prompt_number": 127 + "prompt_number": 96 }, { "cell_type": "code", @@ -1164,11 +1313,20 @@ "output_type": "stream", "stream": "stdout", "text": [ - "7\n" + "6\n" ] } ], - "prompt_number": 128 + "prompt_number": 94 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Frequency ordered is much better than the others, but still only wins about 30% of the time. I'm sure we can do better.\n", + "\n", + "That's the next part..." + ] }, { "cell_type": "code", @@ -1176,8 +1334,7 @@ "input": [], "language": "python", "metadata": {}, - "outputs": [], - "prompt_number": 128 + "outputs": [] } ], "metadata": {} diff --git a/hangman/05-hangman-better.ipynb b/hangman/04-hangman-better.ipynb similarity index 60% rename from hangman/05-hangman-better.ipynb rename to hangman/04-hangman-better.ipynb index 20a12e0..f524dae 100644 --- a/hangman/05-hangman-better.ipynb +++ b/hangman/04-hangman-better.ipynb @@ -1,13 +1,23 @@ { "metadata": { "name": "", - "signature": "sha256:32b3b300745f022158bfde0b3fc0a50b11d36551211371d50140e9f9d2a7b8e1" + "signature": "sha256:f96f8d6bd00204692b51b22bb9f514c926594fc7c3ed2ee8cc31f44119a3e59c" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##Better strategies\n", + "We've got the plumbing all working. Now to play and come up with some better playing strategies. \n", + "\n", + "First, some repetition from the previous section." + ] + }, { "cell_type": "code", "collapsed": false, @@ -38,7 +48,7 @@ "cell_type": "code", "collapsed": false, "input": [ - "LETTER_COUNTS = collections.Counter(l.lower() for l in open('sherlock-holmes.txt').read() if l in string.ascii_letters)\n", + "LETTER_COUNTS = collections.Counter(l.lower() for l in open('../sherlock-holmes.txt').read() if l in string.ascii_letters)\n", "LETTERS_IN_ORDER = [p[0] for p in LETTER_COUNTS.most_common()]" ], "language": "python", @@ -134,8 +144,7 @@ " self.ordered_letters = ordered_letters\n", " \n", " def guess(self, discovered, missed, lives):\n", - " guessed_letters = [l.lower() for l in discovered + missed if l in string.ascii_letters]\n", - " return [l for l in self.ordered_letters if l not in guessed_letters][0]" + " return [l for l in self.ordered_letters if l not in discovered + missed][0]" ], "language": "python", "metadata": {}, @@ -159,6 +168,14 @@ "outputs": [], "prompt_number": 7 }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##Timing runs\n", + "Use the IPython `timeit` cellmagic to time runs." + ] + }, { "cell_type": "code", "collapsed": false, @@ -180,16 +197,8 @@ "output_type": "stream", "stream": "stdout", "text": [ - "50\n", - "59" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "33" + "49\n", + "45" ] }, { @@ -197,7 +206,7 @@ "stream": "stdout", "text": [ "\n", - "53" + "54" ] }, { @@ -205,7 +214,7 @@ "stream": "stdout", "text": [ "\n", - "49" + "54" ] }, { @@ -213,15 +222,35 @@ "stream": "stdout", "text": [ "\n", - "42" + "1 loops, best of 3: 207 ms per loop\n" ] - }, + } + ], + "prompt_number": 8 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "%%timeit\n", + "\n", + "wins = 0\n", + "for _ in range(1000):\n", + " g = Game(random.choice(WORDS), player=PlayerFreqOrdered())\n", + " g.play_game()\n", + " if g.game_won:\n", + " wins += 1\n", + "print(wins)" + ], + "language": "python", + "metadata": {}, + "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ - "\n", - "43" + "305\n", + "331" ] }, { @@ -229,7 +258,7 @@ "stream": "stdout", "text": [ "\n", - "54" + "327" ] }, { @@ -237,7 +266,7 @@ "stream": "stdout", "text": [ "\n", - "49" + "316" ] }, { @@ -245,15 +274,35 @@ "stream": "stdout", "text": [ "\n", - "54" + "1 loops, best of 3: 212 ms per loop\n" ] - }, + } + ], + "prompt_number": 9 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "%%timeit\n", + "\n", + "wins = 0\n", + "for _ in range(1000):\n", + " g = Game(random.choice(WORDS), player=PlayerFixedOrder(list(reversed(string.ascii_lowercase))))\n", + " g.play_game()\n", + " if g.game_won:\n", + " wins += 1\n", + "print(wins)" + ], + "language": "python", + "metadata": {}, + "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ - "\n", - "45" + "8\n", + "8" ] }, { @@ -261,7 +310,7 @@ "stream": "stdout", "text": [ "\n", - "46" + "15" ] }, { @@ -269,7 +318,7 @@ "stream": "stdout", "text": [ "\n", - "44" + "8" ] }, { @@ -277,39 +326,150 @@ "stream": "stdout", "text": [ "\n", - "45" + "1 loops, best of 3: 198 ms per loop\n" ] - }, + } + ], + "prompt_number": 10 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Better strategy 1: letters in dictionary frequency order\n", + "When we're looking at letter frequencies, we've been looking at running English text. But the words being preseted to the player aren't drawn randomly from novels: they're taken from the dictionary. That means our frequencies are off because we're looking at the wrong distribution of words.\n", + "\n", + "For example, 'e' and 't' are common in running text because words like 'the' occur very frequently. But 'the' only occurs once in the dictionary. Let's sample letters from the dictionary and see if that makes a difference." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "DICT_COUNTS = collections.Counter(''.join(WORDS))\n", + "DICT_LETTERS_IN_ORDER = [p[0] for p in DICT_COUNTS.most_common()]" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 11 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "DICT_COUNTS" + ], + "language": "python", + "metadata": {}, + "outputs": [ { - "output_type": "stream", - "stream": "stdout", + "metadata": {}, + "output_type": "pyout", + "prompt_number": 12, "text": [ - "\n", - "48" + "Counter({'e': 60732, 's': 47997, 'i': 45470, 'a': 38268, 'r': 37522, 'n': 36868, 't': 36009, 'o': 31004, 'l': 26902, 'c': 21061, 'd': 20764, 'u': 17682, 'g': 16560, 'p': 15240, 'm': 13855, 'h': 11662, 'b': 9866, 'y': 7877, 'f': 7430, 'v': 5325, 'k': 4829, 'w': 4757, 'x': 1425, 'q': 1020, 'z': 979, 'j': 965})" ] - }, + } + ], + "prompt_number": 12 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "print(DICT_LETTERS_IN_ORDER)\n", + "print(LETTERS_IN_ORDER)" + ], + "language": "python", + "metadata": {}, + "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ - "\n", - "51" + "['e', 's', 'i', 'a', 'r', 'n', 't', 'o', 'l', 'c', 'd', 'u', 'g', 'p', 'm', 'h', 'b', 'y', 'f', 'v', 'k', 'w', 'x', 'q', 'z', 'j']\n", + "['e', 't', 'a', 'o', 'i', 'h', 'n', 's', 'r', 'd', 'l', 'u', 'm', 'w', 'c', 'y', 'f', 'g', 'p', 'b', 'v', 'k', 'x', 'j', 'q', 'z']\n" ] - }, + } + ], + "prompt_number": 13 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The letter orders are certainly different. Let's see if it make the player more effective than the ~30% of `FreqOrder`." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "wins = 0\n", + "for _ in range(1000):\n", + " g = Game(random.choice(WORDS), player=PlayerFixedOrder(DICT_LETTERS_IN_ORDER))\n", + " g.play_game()\n", + " if g.game_won:\n", + " wins += 1\n", + "print(wins)" + ], + "language": "python", + "metadata": {}, + "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ - "\n", - "46" + "463\n" ] - }, + } + ], + "prompt_number": 14 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For convenience, we can wrap this new behaviour up in its own class." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "class PlayerDictOrder(PlayerFixedOrder):\n", + " def __init__(self):\n", + " super().__init__(DICT_LETTERS_IN_ORDER)" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 15 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "%%timeit\n", + "\n", + "wins = 0\n", + "for _ in range(1000):\n", + " g = Game(random.choice(WORDS), player=PlayerDictOrder())\n", + " g.play_game()\n", + " if g.game_won:\n", + " wins += 1\n", + "print(wins)" + ], + "language": "python", + "metadata": {}, + "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ - "\n", - "54" + "474\n", + "460" ] }, { @@ -317,7 +477,7 @@ "stream": "stdout", "text": [ "\n", - "49" + "490" ] }, { @@ -325,7 +485,7 @@ "stream": "stdout", "text": [ "\n", - "44" + "467" ] }, { @@ -333,23 +493,107 @@ "stream": "stdout", "text": [ "\n", - "42" + "1 loops, best of 3: 220 ms per loop\n" ] - }, + } + ], + "prompt_number": 16 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That was worth it!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##Better strategy 2: paying attention the target word\n", + "The strategies so far haven't paid any attention to the target word. Can we use information about the target to help refine our guesses?\n", + "\n", + "We've just seen that sampling letters from the dictionary works better than other approaches. But if we're presented with a seven letter word, we don't need to pay attention to the letter occurences in three- or ten-letter words. \n", + "\n", + "Let's build our first \"adaptive\" player, one that adapts its behaviour to what's happening in this game. \n", + "\n", + "The idea is that the player will hold internally a set of `candidate_words` that could match what it knows about the target. It will then count the letters in that set and pick the most frequent, but so-far-unguessed, letter.\n", + "\n", + "When the player is created, the it's give `all_words` it can chose from, but `candidate_words` is non-existent. As soon as it's given a word, it can filter `all_words` so that `candidate_words` are just those words with the same length as the `discovered` word. From the `candidate_words`, the player can create its own set of `ordered_letters`. Guesses are pulled from the `ordered_letters` the same as before." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "class PlayerAdaptiveLength:\n", + " def __init__(self, words):\n", + " self.all_words = words\n", + " self.candidate_words = None\n", + " \n", + " def guess(self, discovered, missed, lives):\n", + " if not self.candidate_words:\n", + " self.set_ordered_letters(len(discovered))\n", + " return [l for l in self.ordered_letters if l not in discovered + missed][0]\n", + " \n", + " def set_ordered_letters(self, word_len):\n", + " self.candidate_words = [w for w in self.all_words if len(w) == word_len]\n", + " counts = collections.Counter(l.lower() for l in ''.join(self.candidate_words) if l in string.ascii_letters)\n", + " self.ordered_letters = [p[0] for p in counts.most_common()]" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 17 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "wins = 0\n", + "for _ in range(1000):\n", + " g = Game(random.choice(WORDS), player=PlayerAdaptiveLength(WORDS))\n", + " g.play_game()\n", + " if g.game_won:\n", + " wins += 1\n", + "print(wins)" + ], + "language": "python", + "metadata": {}, + "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ - "\n", - "44" + "452\n" ] - }, + } + ], + "prompt_number": 18 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "%%timeit\n", + "\n", + "wins = 0\n", + "for _ in range(1000):\n", + " g = Game(random.choice(WORDS), player=PlayerAdaptiveLength(WORDS))\n", + " g.play_game()\n", + " if g.game_won:\n", + " wins += 1\n", + "print(wins)" + ], + "language": "python", + "metadata": {}, + "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ - "\n", - "48" + "480\n", + "444" ] }, { @@ -357,7 +601,7 @@ "stream": "stdout", "text": [ "\n", - "39" + "504" ] }, { @@ -365,7 +609,7 @@ "stream": "stdout", "text": [ "\n", - "46" + "463" ] }, { @@ -373,1111 +617,203 @@ "stream": "stdout", "text": [ "\n", - "43" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "50" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "47" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "52" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "46" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "55" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "47" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "39" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "55" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "40" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "46" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "53" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "43" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "40" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "53" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "41" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "10 loops, best of 3: 64.2 ms per loop\n" - ] - } - ], - "prompt_number": 54 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%timeit\n", - "\n", - "wins = 0\n", - "for _ in range(1000):\n", - " g = Game(random.choice(WORDS), player=PlayerFreqOrdered())\n", - " g.play_game()\n", - " if g.game_won:\n", - " wins += 1\n", - "print(wins)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "334\n", - "342" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "318" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "313" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "353" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "304" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "332" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "313" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "335" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "339" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "328" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "334" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "322" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "347" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "334" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "340" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "319" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "365" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "315" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "307" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "314" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "317" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "310" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "324" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "313" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "318" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "314" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "324" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "297" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "335" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "335" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "343" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "342" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "318" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "306" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "353" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "332" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "330" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "334" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "307" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "306" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "10 loops, best of 3: 96.2 ms per loop\n" - ] - } - ], - "prompt_number": 56 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%timeit\n", - "\n", - "wins = 0\n", - "for _ in range(1000):\n", - " g = Game(random.choice(WORDS), player=PlayerFixedOrder(list(reversed(string.ascii_lowercase))))\n", - " g.play_game()\n", - " if g.game_won:\n", - " wins += 1\n", - "print(wins)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "7\n", - "5" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "6" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "7" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "7" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "10" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "8" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "5" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "5" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "5" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "9" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "3" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "8" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "13" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "8" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "10" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "9" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "9" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "12" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "6" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "6" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "14" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "9" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "1" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "8" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "8" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "7" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "9" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "10" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "6" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "7" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "6" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "4" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "6" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "3" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "7" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "7" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "10" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "11" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "6" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "4" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "10 loops, best of 3: 74.6 ms per loop\n" - ] - } - ], - "prompt_number": 57 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "DICT_COUNTS = collections.Counter(l.lower() for l in open('/usr/share/dict/british-english').read() if l in string.ascii_letters)\n", - "DICT_LETTERS_IN_ORDER = [p[0] for p in DICT_COUNTS.most_common()]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 11 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "DICT_COUNTS" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 12, - "text": [ - "Counter({'s': 91332, 'e': 88692, 'i': 66900, 'a': 64468, 'r': 57460, 'n': 57128, 't': 52949, 'o': 49121, 'l': 40995, 'c': 31854, 'd': 28505, 'u': 26372, 'g': 22693, 'm': 22549, 'p': 22249, 'h': 19337, 'b': 15540, 'y': 12652, 'f': 10679, 'k': 8386, 'v': 8000, 'w': 7505, 'x': 2125, 'z': 2058, 'j': 1950, 'q': 1536})" - ] - } - ], - "prompt_number": 12 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "print(DICT_LETTERS_IN_ORDER)\n", - "print(LETTERS_IN_ORDER)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "['s', 'e', 'i', 'a', 'r', 'n', 't', 'o', 'l', 'c', 'd', 'u', 'g', 'm', 'p', 'h', 'b', 'y', 'f', 'k', 'v', 'w', 'x', 'z', 'j', 'q']\n", - "['e', 't', 'a', 'o', 'i', 'h', 'n', 's', 'r', 'd', 'l', 'u', 'm', 'w', 'c', 'y', 'f', 'g', 'p', 'b', 'v', 'k', 'x', 'j', 'q', 'z']\n" + "1 loops, best of 3: 18.5 s per loop\n" ] } ], - "prompt_number": 13 + "prompt_number": 19 }, { - "cell_type": "code", - "collapsed": false, - "input": [ - "wins = 0\n", - "for _ in range(1000):\n", - " g = Game(random.choice(WORDS), player=PlayerFixedOrder(DICT_LETTERS_IN_ORDER))\n", - " g.play_game()\n", - " if g.game_won:\n", - " wins += 1\n", - "print(wins)" - ], - "language": "python", + "cell_type": "markdown", "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "440\n" - ] - } - ], - "prompt_number": 14 + "source": [ + "It's a bit better, but quite a lot slower. Can we improve the performance, to offset the cost of the slower game?" + ] }, { - "cell_type": "code", - "collapsed": false, - "input": [ - "class PlayerAdaptiveLength:\n", - " def __init__(self, words):\n", - " self.all_words = words\n", - " self.candidate_words = None\n", - " \n", - " def guess(self, discovered, missed, lives):\n", - " if not self.candidate_words:\n", - " self.set_ordered_letters(len(discovered))\n", - " guessed_letters = [l.lower() for l in discovered + missed if l in string.ascii_letters]\n", - " return [l for l in self.ordered_letters if l not in guessed_letters][0]\n", - " \n", - " def set_ordered_letters(self, word_len):\n", - " self.candidate_words = [w for w in self.all_words if len(w) == word_len]\n", - " counts = collections.Counter(l.lower() for l in ''.join(self.candidate_words) if l in string.ascii_letters)\n", - " self.ordered_letters = [p[0] for p in counts.most_common()]" - ], - "language": "python", + "cell_type": "markdown", "metadata": {}, - "outputs": [], - "prompt_number": 33 + "source": [ + "##Better strategy 3: using discovered letters\n", + "Once we've made some successful guesses, we know more about the `target` and we can use that information to further filter the `candiate_words`. For instance, if we know the `target` is seven letters with an 'e' in the second and fourth positions, there are probably very few candiate words to choose from.\n", + "\n", + "The challenge here is to filter the `candidate_words` depending on the letter pattern. That's non-trivial, so we'll look at how to do that bit first.\n", + "\n", + "(This matching is the sort of thing that regular expressions are designed for, but I think there are enough new ideas in this material so far. See the end of this notebook for implementations of the adaptive players that use regexes.)" + ] }, { - "cell_type": "code", - "collapsed": false, - "input": [ - "wins = 0\n", - "for _ in range(1000):\n", - " g = Game(random.choice(WORDS), player=PlayerAdaptiveLength(WORDS))\n", - " g.play_game()\n", - " if g.game_won:\n", - " wins += 1\n", - "print(wins)" - ], - "language": "python", + "cell_type": "markdown", "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "474\n" - ] - } - ], - "prompt_number": 34 + "source": [ + "### Matching\n", + "The problem: given a pattern and a target word, determine if the word matches the pattern. The return value of `match` should be a Boolean.\n", + "\n", + "A target is a sequence of letters. \n", + "\n", + "A pattern is a sequence of either a letter or an underscore.\n", + "\n", + "A target matches a pattern if the corresponding elements of both match.\n", + "\n", + "* A target letter always matches an underscore.\n", + "\n", + "* A target letter matches a pattern letter if they are the same.\n", + "\n", + "If any element doesn't match, the whole match fails.\n", + "\n", + "If the target and pattern are different lengths, the match fails.\n", + "\n", + "The built-in `zip` will allow us to pair up the elements of the pattern and target." + ] }, { "cell_type": "code", "collapsed": false, "input": [ - "class PlayerAdaptiveIncludedLetters:\n", - " def __init__(self, words):\n", - " self.candidate_words = words\n", - " \n", - " def guess(self, discovered, missed, lives):\n", - " self.filter_candidate_words(discovered)\n", - " self.set_ordered_letters()\n", - " guessed_letters = [l.lower() for l in discovered + missed if l in string.ascii_letters]\n", - " return [l for l in self.ordered_letters if l not in guessed_letters][0]\n", - " \n", - " def filter_candidate_words(self, discovered):\n", - " exp = re.compile('^' + ''.join(discovered).replace('_', '.') + '$')\n", - " self.candidate_words = [w for w in self.candidate_words if exp.match(w)]\n", - " \n", - " def set_ordered_letters(self):\n", - " counts = collections.Counter(l.lower() for l in ''.join(self.candidate_words) if l in string.ascii_letters)\n", - " self.ordered_letters = [p[0] for p in counts.most_common()]" + "def match(pattern, target):\n", + " if len(pattern) != len(target):\n", + " return False\n", + " for m, c in zip(pattern, target):\n", + " if m == '_':\n", + " # true\n", + " pass\n", + " elif m != '_' and m == c:\n", + " # true\n", + " pass\n", + " else:\n", + " return False\n", + " return True " ], "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 35 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "wins = 0\n", - "for _ in range(1000):\n", - " g = Game(random.choice(WORDS), player=PlayerAdaptiveIncludedLetters(WORDS))\n", - " g.play_game()\n", - " if g.game_won:\n", - " wins += 1\n", - "print(wins)" + "prompt_number": 20 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "match('__pp_', 'happy')" ], "language": "python", "metadata": {}, "outputs": [ { - "output_type": "stream", - "stream": "stdout", + "metadata": {}, + "output_type": "pyout", + "prompt_number": 21, "text": [ - "982\n" + "True" ] } ], - "prompt_number": 36 + "prompt_number": 21 }, { "cell_type": "code", "collapsed": false, "input": [ - "re.match('^[^xaz]*$', 'happy')" + "match('__pp_', 'hippo')" ], "language": "python", "metadata": {}, - "outputs": [], - "prompt_number": 37 + "outputs": [ + { + "metadata": {}, + "output_type": "pyout", + "prompt_number": 22, + "text": [ + "True" + ] + } + ], + "prompt_number": 22 }, { "cell_type": "code", "collapsed": false, "input": [ - "class PlayerAdaptiveExcludedLetters:\n", - " def __init__(self, words):\n", - " self.candidate_words = words\n", - " \n", - " def guess(self, discovered, missed, lives):\n", - " self.filter_candidate_words(missed)\n", - " self.set_ordered_letters()\n", - " guessed_letters = [l.lower() for l in discovered + missed if l in string.ascii_letters]\n", - " return [l for l in self.ordered_letters if l not in guessed_letters][0]\n", - " \n", - " def filter_candidate_words(self, missed):\n", - " if missed:\n", - " exp = re.compile('^[^' + ''.join(missed) + ']*$')\n", - " self.candidate_words = [w for w in self.candidate_words if exp.match(w)]\n", - " \n", - " def set_ordered_letters(self):\n", - " counts = collections.Counter(l.lower() for l in ''.join(self.candidate_words) if l in string.ascii_letters)\n", - " self.ordered_letters = [p[0] for p in counts.most_common()]" + "match('__pp_', 'hello')" ], "language": "python", "metadata": {}, - "outputs": [], - "prompt_number": 38 + "outputs": [ + { + "metadata": {}, + "output_type": "pyout", + "prompt_number": 23, + "text": [ + "False" + ] + } + ], + "prompt_number": 23 }, { "cell_type": "code", "collapsed": false, "input": [ - "wins = 0\n", - "for _ in range(1000):\n", - " g = Game(random.choice(WORDS), player=PlayerAdaptiveExcludedLetters(WORDS))\n", - " g.play_game()\n", - " if g.game_won:\n", - " wins += 1\n", - "print(wins)" + "match('__pp_', 'happier')" ], "language": "python", "metadata": {}, "outputs": [ { - "output_type": "stream", - "stream": "stdout", + "metadata": {}, + "output_type": "pyout", + "prompt_number": 24, "text": [ - "502\n" + "False" ] } ], - "prompt_number": 39 + "prompt_number": 24 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That all seems to be working. Let's put it in a `player` object." + ] }, { "cell_type": "code", "collapsed": false, "input": [ - "class PlayerAdaptivePattern:\n", + "class PlayerAdaptiveIncludedLetters:\n", " def __init__(self, words):\n", " self.candidate_words = words\n", " \n", " def guess(self, discovered, missed, lives):\n", - " self.filter_candidate_words(discovered, missed)\n", + " self.filter_candidate_words(discovered)\n", " self.set_ordered_letters()\n", - " guessed_letters = [l.lower() for l in discovered + missed if l in string.ascii_letters]\n", - " return [l for l in self.ordered_letters if l not in guessed_letters][0]\n", + " return [l for l in self.ordered_letters if l not in discovered + missed][0]\n", " \n", - " def filter_candidate_words(self, discovered, missed):\n", - " attempted_letters = list(set(l.lower() for l in discovered + missed if l in string.ascii_letters))\n", - " if attempted_letters:\n", - " exclusion_pattern = '[^' + ''.join(attempted_letters) + ']'\n", - " else:\n", - " exclusion_pattern = '.'\n", - " exp = re.compile('^' + ''.join(discovered).replace('_', exclusion_pattern) + '$')\n", - " self.candidate_words = [w for w in self.candidate_words if exp.match(w)]\n", + " def filter_candidate_words(self, discovered):\n", + " self.candidate_words = [w for w in self.candidate_words if self.match(discovered, w)]\n", " \n", " def set_ordered_letters(self):\n", " counts = collections.Counter(l.lower() for l in ''.join(self.candidate_words) if l in string.ascii_letters)\n", - " self.ordered_letters = [p[0] for p in counts.most_common()]\n" + " self.ordered_letters = [p[0] for p in counts.most_common()]\n", + " \n", + " def match(self, pattern, target):\n", + " if len(pattern) != len(target):\n", + " return False\n", + " for m, c in zip(pattern, target):\n", + " if m == '_':\n", + " # true\n", + " pass\n", + " elif m != '_' and m == c:\n", + " # true\n", + " pass\n", + " else:\n", + " return False\n", + " return True " ], "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 40 + "prompt_number": 25 }, { "cell_type": "code", @@ -1485,7 +821,7 @@ "input": [ "wins = 0\n", "for _ in range(1000):\n", - " g = Game(random.choice(WORDS), player=PlayerAdaptivePattern(WORDS))\n", + " g = Game(random.choice(WORDS), player=PlayerAdaptiveIncludedLetters(WORDS))\n", " g.play_game()\n", " if g.game_won:\n", " wins += 1\n", @@ -1498,11 +834,11 @@ "output_type": "stream", "stream": "stdout", "text": [ - "993\n" + "981\n" ] } ], - "prompt_number": 41 + "prompt_number": 26 }, { "cell_type": "code", @@ -1512,7 +848,7 @@ "\n", "wins = 0\n", "for _ in range(1000):\n", - " g = Game(random.choice(WORDS), player=PlayerAdaptivePattern(WORDS))\n", + " g = Game(random.choice(WORDS), player=PlayerAdaptiveIncludedLetters(WORDS))\n", " g.play_game()\n", " if g.game_won:\n", " wins += 1\n", @@ -1525,8 +861,8 @@ "output_type": "stream", "stream": "stdout", "text": [ - "994\n", - "993" + "982\n", + "987" ] }, { @@ -1534,7 +870,7 @@ "stream": "stdout", "text": [ "\n", - "987" + "984" ] }, { @@ -1542,7 +878,7 @@ "stream": "stdout", "text": [ "\n", - "993" + "982" ] }, { @@ -1550,173 +886,185 @@ "stream": "stdout", "text": [ "\n", - "1 loops, best of 3: 30.6 s per loop\n" + "1 loops, best of 3: 51.7 s per loop\n" ] } ], - "prompt_number": 42 + "prompt_number": 27 }, { - "cell_type": "code", - "collapsed": false, - "input": [ - "class PlayerAdaptive:\n", - " def __init__(self, words):\n", - " self.candidate_words = words\n", - " \n", - " def guess(self, discovered, missed, lives):\n", - " self.filter_candidate_words(discovered, missed)\n", - " self.set_ordered_letters()\n", - " guessed_letters = [l.lower() for l in discovered + missed if l in string.ascii_letters]\n", - " return [l for l in self.ordered_letters if l not in guessed_letters][0]\n", - " \n", - " def filter_candidate_words(self, discovered, missed):\n", - " pass\n", - " \n", - " def set_ordered_letters(self):\n", - " counts = collections.Counter(l.lower() \n", - " for l in ''.join(self.candidate_words) + string.ascii_lowercase \n", - " if l in string.ascii_letters)\n", - " self.ordered_letters = [p[0] for p in counts.most_common()]" - ], - "language": "python", + "cell_type": "markdown", "metadata": {}, - "outputs": [], - "prompt_number": 43 + "source": [ + "Quite an improvement!\n", + "\n", + "But let's see if we can do better." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Better strategy 4: using wrong guesses\n", + "If we make a guess that's wrong, we can also use that to eliminate `candidate_words`. If we know the `target` doesn't contain an 'e', we can eliminate all `candidate_words` that contain an 'e'. If we also know that the word contains and 'e' in the third positon, we can also eliminate all words that contain and 'e' in any other position.\n", + "\n", + "This requires an different verion of `match`, so that a word matches a pattern if an underscore in the pattern does not match any of the known-incorrect letters." + ] }, { "cell_type": "code", "collapsed": false, "input": [ - "class PlayerAdaptiveLength(PlayerAdaptive):\n", - " def __init__(self, words):\n", - " super().__init__(words)\n", - " self.word_len = None\n", - " self.ordered_letters = None\n", - " \n", - " def filter_candidate_words(self, discovered, missed):\n", - " if not self.word_len:\n", - " self.word_len = len(discovered)\n", - " self.candidate_words = [w for w in self.candidate_words if len(w) == self.word_len]\n", - " \n", - " def set_ordered_letters(self):\n", - " if not self.ordered_letters:\n", - " super().set_ordered_letters()" + "def match_exclusion(pattern, target, excluded=None):\n", + " if not excluded:\n", + " excluded = ''\n", + " if len(pattern) != len(target):\n", + " return False\n", + " for m, c in zip(pattern, target):\n", + " if m == '_' and c not in excluded:\n", + " # true\n", + " pass\n", + " elif m != '_':\n", + " # true\n", + " pass\n", + " else:\n", + " return False\n", + " return True " ], "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 44 + "prompt_number": 28 }, { "cell_type": "code", "collapsed": false, "input": [ - "class PlayerAdaptiveIncludedLetters(PlayerAdaptive):\n", - " def filter_candidate_words(self, discovered, missed):\n", - " exp = re.compile('^' + ''.join(discovered).replace('_', '.') + '$')\n", - " self.candidate_words = [w for w in self.candidate_words if exp.match(w)]" + "match_exclusion('__pp_', 'happy', 'x')" ], "language": "python", "metadata": {}, - "outputs": [], - "prompt_number": 45 + "outputs": [ + { + "metadata": {}, + "output_type": "pyout", + "prompt_number": 29, + "text": [ + "True" + ] + } + ], + "prompt_number": 29 }, { "cell_type": "code", "collapsed": false, "input": [ - "class PlayerAdaptiveExcludedLetters(PlayerAdaptive):\n", - " def filter_candidate_words(self, discovered, missed):\n", - " if missed:\n", - " exp = re.compile('^[^' + ''.join(missed) + ']*$')\n", - " self.candidate_words = [w for w in self.candidate_words if exp.match(w)] " + "match_exclusion('__pp_', 'happy', 'a')" ], "language": "python", "metadata": {}, - "outputs": [], - "prompt_number": 46 + "outputs": [ + { + "metadata": {}, + "output_type": "pyout", + "prompt_number": 30, + "text": [ + "False" + ] + } + ], + "prompt_number": 30 }, { "cell_type": "code", "collapsed": false, "input": [ - "class PlayerAdaptivePattern(PlayerAdaptive):\n", - " def filter_candidate_words(self, discovered, missed):\n", - " attempted_letters = [l for l in discovered if l != '_'] + missed\n", - " if attempted_letters:\n", - " exclusion_pattern = '[^' + ''.join(attempted_letters) + ']'\n", - " else:\n", - " exclusion_pattern = '.'\n", - " exp = re.compile('^' + ''.join(discovered).replace('_', exclusion_pattern) + '$')\n", - " self.candidate_words = [w for w in self.candidate_words if exp.match(w)]" + "match_exclusion('__pp_', 'happy', 'p')" ], "language": "python", "metadata": {}, - "outputs": [], - "prompt_number": 47 + "outputs": [ + { + "metadata": {}, + "output_type": "pyout", + "prompt_number": 31, + "text": [ + "True" + ] + } + ], + "prompt_number": 31 }, { "cell_type": "code", "collapsed": false, "input": [ - "%%timeit\n", - "\n", - "wins = 0\n", - "for _ in range(1000):\n", - " g = Game(random.choice(WORDS), player=PlayerAdaptiveLength(WORDS))\n", - " g.play_game()\n", - " if g.game_won:\n", - " wins += 1\n", - "print(wins)" + "match_exclusion('__pp_', 'happy', 'hp')" ], "language": "python", "metadata": {}, "outputs": [ { - "output_type": "stream", - "stream": "stdout", - "text": [ - "479\n", - "455" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "460" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "498" - ] - }, - { - "output_type": "stream", - "stream": "stdout", + "metadata": {}, + "output_type": "pyout", + "prompt_number": 32, "text": [ - "\n", - "1 loops, best of 3: 15.9 s per loop\n" + "False" ] } ], - "prompt_number": 48 + "prompt_number": 32 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "class PlayerAdaptiveExcludedLetters:\n", + " def __init__(self, words):\n", + " self.candidate_words = words\n", + " \n", + " def guess(self, discovered, missed, lives):\n", + " self.filter_candidate_words(discovered, missed)\n", + " self.set_ordered_letters()\n", + " return [l for l in self.ordered_letters if l not in discovered + missed][0]\n", + " \n", + " def filter_candidate_words(self, discovered, missed):\n", + " attempted_letters = list(set(l.lower() for l in discovered + missed if l in string.ascii_letters))\n", + " self.candidate_words = [w for w in self.candidate_words if self.match_exclusion(discovered, w, attempted_letters)]\n", + " \n", + " def set_ordered_letters(self):\n", + " counts = collections.Counter(l.lower() for l in ''.join(self.candidate_words) if l in string.ascii_letters)\n", + " self.ordered_letters = [p[0] for p in counts.most_common()]\n", + " \n", + " def match_exclusion(self, pattern, target, excluded=None):\n", + " if not excluded:\n", + " excluded = ''\n", + " if len(pattern) != len(target):\n", + " return False\n", + " for m, c in zip(pattern, target):\n", + " if m == '_' and c not in excluded:\n", + " # true\n", + " pass\n", + " elif m != '_':\n", + " # true\n", + " pass\n", + " else:\n", + " return False\n", + " return True " + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 33 }, { "cell_type": "code", "collapsed": false, "input": [ - "%%timeit\n", - "\n", "wins = 0\n", "for _ in range(1000):\n", - " g = Game(random.choice(WORDS), player=PlayerAdaptiveIncludedLetters(WORDS))\n", + " g = Game(random.choice(WORDS), player=PlayerAdaptiveExcludedLetters(WORDS))\n", " g.play_game()\n", " if g.game_won:\n", " wins += 1\n", @@ -1729,36 +1077,11 @@ "output_type": "stream", "stream": "stdout", "text": [ - "981\n", - "983" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "980" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "980" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "1 loops, best of 3: 36.9 s per loop\n" + "737\n" ] } ], - "prompt_number": 49 + "prompt_number": 34 }, { "cell_type": "code", @@ -1781,8 +1104,8 @@ "output_type": "stream", "stream": "stdout", "text": [ - "521\n", - "484" + "735\n", + "748" ] }, { @@ -1790,7 +1113,7 @@ "stream": "stdout", "text": [ "\n", - "491" + "742" ] }, { @@ -1798,7 +1121,7 @@ "stream": "stdout", "text": [ "\n", - "518" + "748" ] }, { @@ -1806,18 +1129,73 @@ "stream": "stdout", "text": [ "\n", - "1 loops, best of 3: 7min 18s per loop\n" + "1 loops, best of 3: 1min 4s per loop\n" ] } ], - "prompt_number": 50 + "prompt_number": 35 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That's better than the non-adaptive players, but not as good as the `IncludedLetters` player. But we could combine the two approaches. Will that work better?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Better strategy 5: using both correct and wrong guesses\n", + "Not much to say. Let's just merge the two previous approaches." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "class PlayerAdaptivePattern:\n", + " def __init__(self, words):\n", + " self.candidate_words = words\n", + " \n", + " def guess(self, discovered, missed, lives):\n", + " self.filter_candidate_words(discovered, missed)\n", + " self.set_ordered_letters()\n", + " return [l for l in self.ordered_letters if l not in discovered + missed][0]\n", + " \n", + " def filter_candidate_words(self, discovered, missed):\n", + " attempted_letters = list(set(l.lower() for l in discovered + missed if l in string.ascii_letters))\n", + " self.candidate_words = [w for w in self.candidate_words if self.match(discovered, w, attempted_letters)]\n", + " \n", + " def set_ordered_letters(self):\n", + " counts = collections.Counter(l.lower() for l in ''.join(self.candidate_words) if l in string.ascii_letters)\n", + " self.ordered_letters = [p[0] for p in counts.most_common()]\n", + " \n", + " def match(self, pattern, target, excluded=None):\n", + " if not excluded:\n", + " excluded = ''\n", + " if len(pattern) != len(target):\n", + " return False\n", + " for m, c in zip(pattern, target):\n", + " if m == '_' and c not in excluded:\n", + " # true\n", + " pass\n", + " elif m != '_' and m == c:\n", + " # true\n", + " pass\n", + " else:\n", + " return False\n", + " return True" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 36 }, { "cell_type": "code", "collapsed": false, "input": [ - "%%timeit\n", - "\n", "wins = 0\n", "for _ in range(1000):\n", " g = Game(random.choice(WORDS), player=PlayerAdaptivePattern(WORDS))\n", @@ -1833,46 +1211,25 @@ "output_type": "stream", "stream": "stdout", "text": [ - "987\n", - "988" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "996" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "995" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "1 loops, best of 3: 31.1 s per loop\n" + "993\n" ] } ], - "prompt_number": 51 + "prompt_number": 37 }, { "cell_type": "code", "collapsed": false, "input": [ + "%%timeit\n", + "\n", + "wins = 0\n", "for _ in range(1000):\n", " g = Game(random.choice(WORDS), player=PlayerAdaptivePattern(WORDS))\n", " g.play_game()\n", - " if not g.game_won:\n", - " print(g.target, g.discovered, g.wrong_letters)" + " if g.game_won:\n", + " wins += 1\n", + "print(wins)" ], "language": "python", "metadata": {}, @@ -1881,139 +1238,57 @@ "output_type": "stream", "stream": "stdout", "text": [ - "naked ['_', 'a', '_', 'e', 'd'] ['s', 'r', 'w', 'c', 't', 'g', 'l', 'f', 'p', 'm']\n", - "wound" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - " ['_', 'o', 'u', 'n', 'd'] ['s', 'e', 'a', 'y', 'p', 'm', 'f', 'h', 'b', 'r']\n", - "hut" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - " ['_', 'u', 't'] ['a', 'o', 'e', 'i', 'b', 'g', 'n', 'p', 'm', 'c']\n", - "fucker" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - " ['_', 'u', '_', '_', 'e', 'r'] ['d', 's', 'i', 'a', 'o', 't', 'b', 'n', 'm', 'l']\n", - "fox" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - " ['_', 'o', '_'] ['a', 't', 'b', 'd', 'w', 'p', 'n', 's', 'g', 'y']\n", - "wills" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - " ['_', 'i', 'l', 'l', 's'] ['e', 'o', 'a', 'g', 'p', 'm', 'k', 'f', 'h', 'd']\n", - "bunny" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - " ['_', 'u', 'n', 'n', 'y'] ['s', 'e', 'a', 'o', 'i', 'm', 'l', 'g', 'f', 't']\n", - "curving" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - " ['c', 'u', '_', '_', 'i', 'n', 'g'] ['e', 'a', 'o', 'l', 's', 'f', 'p', 'b', 't', 'm']\n", - "hoe" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - " ['_', 'o', '_'] ['a', 't', 'b', 'd', 'w', 'p', 'n', 's', 'g', 'y']\n", - "butt" + "990\n", + "995" ] }, { "output_type": "stream", "stream": "stdout", "text": [ - " ['_', 'u', '_', '_'] ['e', 's', 'o', 'a', 'i', 'l', 'f', 'r', 'n', 'm']\n", - "mucked" + "\n", + "992" ] }, { "output_type": "stream", "stream": "stdout", "text": [ - " ['_', 'u', '_', '_', 'e', 'd'] ['a', 'o', 'i', 'l', 's', 't', 'f', 'p', 'g', 'b']\n", - "flaw" + "\n", + "994" ] }, { "output_type": "stream", "stream": "stdout", "text": [ - " ['f', 'l', 'a', '_'] ['e', 's', 'o', 'r', 'y', 'g', 'k', 'x', 'p', 'n']\n" + "\n", + "1 loops, best of 3: 47.1 s per loop\n" ] } ], - "prompt_number": 52 + "prompt_number": 38 }, { - "cell_type": "code", - "collapsed": false, - "input": [ - "iterations = 10000\n", - "wins = 0\n", - "for _ in range(iterations):\n", - " g = Game(random.choice(WORDS), player=PlayerAdaptivePattern(WORDS))\n", - " g.play_game()\n", - " if g.game_won:\n", - " wins += 1\n", - "print(wins / iterations)" - ], - "language": "python", + "cell_type": "markdown", "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "0.9936\n" - ] - } - ], - "prompt_number": 53 + "source": [ + "##DRYing it up\n", + "You've probably noticed some duplicate code above. " + ] }, { "cell_type": "code", "collapsed": false, "input": [ - "class PlayerAdaptiveNoRegex:\n", + "class PlayerAdaptive:\n", " def __init__(self, words):\n", " self.candidate_words = words\n", " \n", " def guess(self, discovered, missed, lives):\n", " self.filter_candidate_words(discovered, missed)\n", " self.set_ordered_letters()\n", - " guessed_letters = [l.lower() for l in discovered + missed if l in string.ascii_letters]\n", - " return [l for l in self.ordered_letters if l not in guessed_letters][0]\n", + " return [l for l in self.ordered_letters if l not in discovered + missed][0]\n", " \n", " def filter_candidate_words(self, discovered, missed):\n", " pass\n", @@ -2043,13 +1318,13 @@ "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 59 + "prompt_number": 39 }, { "cell_type": "code", "collapsed": false, "input": [ - "class PlayerAdaptiveLengthNoRegex(PlayerAdaptiveNoRegex):\n", + "class PlayerAdaptiveLength(PlayerAdaptive):\n", " def __init__(self, words):\n", " super().__init__(words)\n", " self.word_len = None\n", @@ -2067,26 +1342,26 @@ "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 60 + "prompt_number": 40 }, { "cell_type": "code", "collapsed": false, "input": [ - "class PlayerAdaptiveIncludedLettersNoRegex(PlayerAdaptiveNoRegex):\n", + "class PlayerAdaptiveIncludedLetters(PlayerAdaptive):\n", " def filter_candidate_words(self, discovered, missed):\n", " self.candidate_words = [w for w in self.candidate_words if self.match(discovered, w)]" ], "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 61 + "prompt_number": 41 }, { "cell_type": "code", "collapsed": false, "input": [ - "class PlayerAdaptiveExcludedLettersNoRegex(PlayerAdaptiveNoRegex):\n", + "class PlayerAdaptiveExcludedLetters(PlayerAdaptive):\n", " def filter_candidate_words(self, discovered, missed):\n", " if missed:\n", " empty_target = '_' * len(discovered)\n", @@ -2095,21 +1370,73 @@ "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 66 + "prompt_number": 42 }, { "cell_type": "code", "collapsed": false, "input": [ - "class PlayerAdaptivePatternNoRegex(PlayerAdaptiveNoRegex):\n", + "class PlayerAdaptivePattern(PlayerAdaptive):\n", " def filter_candidate_words(self, discovered, missed):\n", " attempted_letters = [l for l in discovered if l != '_'] + missed\n", " self.candidate_words = [w for w in self.candidate_words if self.match(discovered, w, attempted_letters)]" ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 67 + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 43 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "%%timeit\n", + "\n", + "wins = 0\n", + "for _ in range(1000):\n", + " g = Game(random.choice(WORDS), player=PlayerAdaptiveLength(WORDS))\n", + " g.play_game()\n", + " if g.game_won:\n", + " wins += 1\n", + "print(wins)" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "472\n", + "460" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "\n", + "462" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "\n", + "484" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "\n", + "1 loops, best of 3: 17 s per loop\n" + ] + } + ], + "prompt_number": 44 }, { "cell_type": "code", @@ -2119,7 +1446,7 @@ "\n", "wins = 0\n", "for _ in range(1000):\n", - " g = Game(random.choice(WORDS), player=PlayerAdaptiveLengthNoRegex(WORDS))\n", + " g = Game(random.choice(WORDS), player=PlayerAdaptiveIncludedLetters(WORDS))\n", " g.play_game()\n", " if g.game_won:\n", " wins += 1\n", @@ -2132,8 +1459,8 @@ "output_type": "stream", "stream": "stdout", "text": [ - "471\n", - "492" + "988\n", + "985" ] }, { @@ -2141,7 +1468,7 @@ "stream": "stdout", "text": [ "\n", - "502" + "987" ] }, { @@ -2149,7 +1476,7 @@ "stream": "stdout", "text": [ "\n", - "469" + "988" ] }, { @@ -2157,11 +1484,11 @@ "stream": "stdout", "text": [ "\n", - "1 loops, best of 3: 16 s per loop\n" + "1 loops, best of 3: 49.3 s per loop\n" ] } ], - "prompt_number": 69 + "prompt_number": 45 }, { "cell_type": "code", @@ -2171,7 +1498,7 @@ "\n", "wins = 0\n", "for _ in range(1000):\n", - " g = Game(random.choice(WORDS), player=PlayerAdaptiveIncludedLettersNoRegex(WORDS))\n", + " g = Game(random.choice(WORDS), player=PlayerAdaptiveExcludedLetters(WORDS))\n", " g.play_game()\n", " if g.game_won:\n", " wins += 1\n", @@ -2184,8 +1511,8 @@ "output_type": "stream", "stream": "stdout", "text": [ - "978\n", - "979" + "575\n", + "611" ] }, { @@ -2193,7 +1520,7 @@ "stream": "stdout", "text": [ "\n", - "983" + "583" ] }, { @@ -2201,7 +1528,7 @@ "stream": "stdout", "text": [ "\n", - "979" + "607" ] }, { @@ -2209,11 +1536,11 @@ "stream": "stdout", "text": [ "\n", - "1 loops, best of 3: 48 s per loop\n" + "1 loops, best of 3: 5min 10s per loop\n" ] } ], - "prompt_number": 70 + "prompt_number": 46 }, { "cell_type": "code", @@ -2223,7 +1550,7 @@ "\n", "wins = 0\n", "for _ in range(1000):\n", - " g = Game(random.choice(WORDS), player=PlayerAdaptiveExcludedLettersNoRegex(WORDS))\n", + " g = Game(random.choice(WORDS), player=PlayerAdaptivePattern(WORDS))\n", " g.play_game()\n", " if g.game_won:\n", " wins += 1\n", @@ -2236,8 +1563,8 @@ "output_type": "stream", "stream": "stdout", "text": [ - "582\n", - "595" + "990\n", + "992" ] }, { @@ -2245,7 +1572,7 @@ "stream": "stdout", "text": [ "\n", - "587" + "995" ] }, { @@ -2253,7 +1580,7 @@ "stream": "stdout", "text": [ "\n", - "611" + "991" ] }, { @@ -2261,11 +1588,94 @@ "stream": "stdout", "text": [ "\n", - "1 loops, best of 3: 4min 59s per loop\n" + "1 loops, best of 3: 39.1 s per loop\n" + ] + } + ], + "prompt_number": 47 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "for _ in range(1000):\n", + " g = Game(random.choice(WORDS), player=PlayerAdaptivePattern(WORDS))\n", + " g.play_game()\n", + " if not g.game_won:\n", + " print(g.target, g.discovered, g.wrong_letters)" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "puffy ['p', 'u', '_', '_', 'y'] ['s', 'e', 'a', 'o', 'i', 'm', 'l', 'n', 'r', 't']\n", + "jar" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + " ['_', 'a', 'r'] ['t', 'p', 'g', 'w', 'd', 'm', 'b', 'y', 'f', 'o']\n", + "fate" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + " ['_', 'a', '_', 'e'] ['l', 'r', 'm', 'p', 's', 'g', 'd', 'k', 'v', 'b']\n", + "robs" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + " ['_', 'o', 'b', 's'] ['e', 'a', 't', 'h', 'g', 'f', 'l', 'c', 'm', 'j']\n" ] } ], - "prompt_number": 71 + "prompt_number": 48 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##Better strategy 6: divide and conquer\n", + "This is a general approach to solving big problems: divide the candidate set into two equal halves, so that a simple test can discard half the candidates. \n", + "\n", + "For Hangman, we want to guess the letter that will eliminate half the `candidate_words`. For each letter that's present in all of the candidate words, score the letter +1 for every word it appears in, and -1 for every word it doesn't appear in. The closer the total is to zero, the closer letter is to being in half the `candidate_words`. We then pick the unguesses letter with the score closest to zero.\n", + "\n", + "As the `PlayerAdaptivePattern` seems to work well, we'll use that to keep track of the `candidate_words`. This variant changes the order of the letters, so it's a rewrite of `set_ordered_letters()`." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "class PlayerAdaptiveSplit(PlayerAdaptivePattern):\n", + " def set_ordered_letters(self):\n", + " def letter_diff(l):\n", + " total = 0\n", + " for w in self.candidate_words:\n", + " if l in w:\n", + " total += 1\n", + " else:\n", + " total -= 1\n", + " return total\n", + " # return abs(sum(1 if l in w else -1 for w in self.candidate_words))\n", + " possible_letters = set(''.join(self.candidate_words))\n", + " letter_diffs = [(l, letter_diff(l)) for l in possible_letters]\n", + " self.ordered_letters = [p[0] for p in sorted(letter_diffs, key=lambda p: p[1])]" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 49 }, { "cell_type": "code", @@ -2275,7 +1685,7 @@ "\n", "wins = 0\n", "for _ in range(1000):\n", - " g = Game(random.choice(WORDS), player=PlayerAdaptivePatternNoRegex(WORDS))\n", + " g = Game(random.choice(WORDS), player=PlayerAdaptiveSplit(WORDS))\n", " g.play_game()\n", " if g.game_won:\n", " wins += 1\n", @@ -2288,8 +1698,8 @@ "output_type": "stream", "stream": "stdout", "text": [ - "991\n", - "992" + "113\n", + "114" ] }, { @@ -2297,7 +1707,7 @@ "stream": "stdout", "text": [ "\n", - "993" + "131" ] }, { @@ -2305,7 +1715,7 @@ "stream": "stdout", "text": [ "\n", - "994" + "132" ] }, { @@ -2313,20 +1723,221 @@ "stream": "stdout", "text": [ "\n", - "1 loops, best of 3: 37.9 s per loop\n" + "1 loops, best of 3: 2min 28s per loop\n" ] } ], - "prompt_number": 72 + "prompt_number": 50 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Oh.\n", + "\n", + "Why is this happening?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## See below for the regex-using versions" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "class PlayerAdaptiveIncludedLettersRegex:\n", + " def __init__(self, words):\n", + " self.candidate_words = words\n", + " \n", + " def guess(self, discovered, missed, lives):\n", + " self.filter_candidate_words(discovered)\n", + " self.set_ordered_letters()\n", + " return [l for l in self.ordered_letters if l not in discovered + missed][0]\n", + " \n", + " def filter_candidate_words(self, discovered):\n", + " exp = re.compile('^' + ''.join(discovered).replace('_', '.') + '$')\n", + " self.candidate_words = [w for w in self.candidate_words if exp.match(w)]\n", + " \n", + " def set_ordered_letters(self):\n", + " counts = collections.Counter(l.lower() for l in ''.join(self.candidate_words) if l in string.ascii_letters)\n", + " self.ordered_letters = [p[0] for p in counts.most_common()]" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 51 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "class PlayerAdaptiveExcludedLettersRegex:\n", + " def __init__(self, words):\n", + " self.candidate_words = words\n", + " \n", + " def guess(self, discovered, missed, lives):\n", + " self.filter_candidate_words(missed)\n", + " self.set_ordered_letters()\n", + " return [l for l in self.ordered_letters if l not in discovered + missed][0]\n", + " \n", + " def filter_candidate_words(self, missed):\n", + " if missed:\n", + " exp = re.compile('^[^' + ''.join(missed) + ']*$')\n", + " self.candidate_words = [w for w in self.candidate_words if exp.match(w)]\n", + " \n", + " def set_ordered_letters(self):\n", + " counts = collections.Counter(l.lower() for l in ''.join(self.candidate_words) if l in string.ascii_letters)\n", + " self.ordered_letters = [p[0] for p in counts.most_common()]" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 52 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "re.match('^[^xaz]*$', 'happy')" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 53 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "class PlayerAdaptivePatternRegex:\n", + " def __init__(self, words):\n", + " self.candidate_words = words\n", + " \n", + " def guess(self, discovered, missed, lives):\n", + " self.filter_candidate_words(discovered, missed)\n", + " self.set_ordered_letters()\n", + " return [l for l in self.ordered_letters if l not in discovered + missed][0]\n", + " \n", + " def filter_candidate_words(self, discovered, missed):\n", + " attempted_letters = list(set(l.lower() for l in discovered + missed if l in string.ascii_letters))\n", + " if attempted_letters:\n", + " exclusion_pattern = '[^' + ''.join(attempted_letters) + ']'\n", + " else:\n", + " exclusion_pattern = '.'\n", + " exp = re.compile('^' + ''.join(discovered).replace('_', exclusion_pattern) + '$')\n", + " self.candidate_words = [w for w in self.candidate_words if exp.match(w)]\n", + " \n", + " def set_ordered_letters(self):\n", + " counts = collections.Counter(l.lower() for l in ''.join(self.candidate_words) if l in string.ascii_letters)\n", + " self.ordered_letters = [p[0] for p in counts.most_common()]\n" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 54 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "class PlayerAdaptiveRegex:\n", + " def __init__(self, words):\n", + " self.candidate_words = words\n", + " \n", + " def guess(self, discovered, missed, lives):\n", + " self.filter_candidate_words(discovered, missed)\n", + " self.set_ordered_letters()\n", + " return [l for l in self.ordered_letters if l not in discovered + missed][0]\n", + " \n", + " def filter_candidate_words(self, discovered, missed):\n", + " pass\n", + " \n", + " def set_ordered_letters(self):\n", + " counts = collections.Counter(l.lower() \n", + " for l in ''.join(self.candidate_words) + string.ascii_lowercase \n", + " if l in string.ascii_letters)\n", + " self.ordered_letters = [p[0] for p in counts.most_common()]" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 55 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "class PlayerAdaptiveLengthRegex(PlayerAdaptiveRegex):\n", + " def __init__(self, words):\n", + " super().__init__(words)\n", + " self.word_len = None\n", + " self.ordered_letters = None\n", + " \n", + " def filter_candidate_words(self, discovered, missed):\n", + " if not self.word_len:\n", + " self.word_len = len(discovered)\n", + " self.candidate_words = [w for w in self.candidate_words if len(w) == self.word_len]\n", + " \n", + " def set_ordered_letters(self):\n", + " if not self.ordered_letters:\n", + " super().set_ordered_letters()" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 56 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "class PlayerAdaptiveIncludedLettersRegex(PlayerAdaptiveRegex):\n", + " def filter_candidate_words(self, discovered, missed):\n", + " exp = re.compile('^' + ''.join(discovered).replace('_', '.') + '$')\n", + " self.candidate_words = [w for w in self.candidate_words if exp.match(w)]" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 57 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "class PlayerAdaptiveExcludedLettersRegex(PlayerAdaptiveRegex):\n", + " def filter_candidate_words(self, discovered, missed):\n", + " if missed:\n", + " exp = re.compile('^[^' + ''.join(missed) + ']*$')\n", + " self.candidate_words = [w for w in self.candidate_words if exp.match(w)] " + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 58 }, { "cell_type": "code", "collapsed": false, - "input": [], + "input": [ + "class PlayerAdaptivePatternRegex(PlayerAdaptiveRegex):\n", + " def filter_candidate_words(self, discovered, missed):\n", + " attempted_letters = [l for l in discovered if l != '_'] + missed\n", + " if attempted_letters:\n", + " exclusion_pattern = '[^' + ''.join(attempted_letters) + ']'\n", + " else:\n", + " exclusion_pattern = '.'\n", + " exp = re.compile('^' + ''.join(discovered).replace('_', exclusion_pattern) + '$')\n", + " self.candidate_words = [w for w in self.candidate_words if exp.match(w)]" + ], "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 72 + "prompt_number": 59 } ], "metadata": {} diff --git a/hangman/06-hangman-logging.ipynb b/hangman/05-hangman-logging.ipynb similarity index 100% rename from hangman/06-hangman-logging.ipynb rename to hangman/05-hangman-logging.ipynb diff --git a/hangman/hangman.py b/hangman/hangman.py index 1eac3a5..f9a634c 100644 --- a/hangman/hangman.py +++ b/hangman/hangman.py @@ -9,7 +9,7 @@ WORDS = [w.strip() for w in open('/usr/share/dict/british-english').readlines() LETTER_COUNTS = collections.Counter(l.lower() for l in open('../sherlock-holmes.txt').read() if l in string.ascii_letters) LETTERS_IN_ORDER = [p[0] for p in LETTER_COUNTS.most_common()] -DICT_COUNTS = collections.Counter(l.lower() for l in open('/usr/share/dict/british-english').read() if l in string.ascii_letters) +DICT_COUNTS = collections.Counter(''.join(WORDS)) DICT_LETTERS_IN_ORDER = [p[0] for p in DICT_COUNTS.most_common()] STARTING_LIVES = 10 @@ -79,8 +79,7 @@ class PlayerFixedOrder: self.ordered_letters = ordered_letters def guess(self, discovered, missed, lives): - guessed_letters = [l.lower() for l in discovered + missed if l in string.ascii_letters] - return [l for l in self.ordered_letters if l not in guessed_letters][0] + return [l for l in self.ordered_letters if l not in discovered + missed][0] class PlayerAlphabetical(PlayerFixedOrder): def __init__(self): @@ -106,8 +105,7 @@ class PlayerAdaptive: def guess(self, discovered, missed, lives): self.filter_candidate_words(discovered, missed) self.set_ordered_letters() - guessed_letters = [l.lower() for l in discovered + missed if l in string.ascii_letters] - return [l for l in self.ordered_letters if l not in guessed_letters][0] + return [l for l in self.ordered_letters if l not in discovered + missed][0] def filter_candidate_words(self, discovered, missed): pass -- 2.34.1