{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3.6.2 (default, Jul 17 2017, 16:44:45) \n", "[GCC 4.2.1 Compatible Apple LLVM 8.1.0 (clang-802.0.42)]\n" ] } ], "source": [ "import sys\n", "print(sys.version)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Common Boggle Words" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What words appear most often in a game of Boggle?\n", "\n", "[Naively](https://english.stackexchange.com/questions/12747/is-it-spelt-na%C3%AFve-or-naive), we can count $16! \\cdot 6^{16} \\approx 5.9 \\cdot 10^{25}$ different boards, not taking duplicate letters into account. This many boards is too a numerous; even if discovering words per board was as fast as $\\mathcal{O}(\\mathtt{pass})$, it would be too much." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "ename": "OverflowError", "evalue": "normalized days too large to fit in a C int", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mOverflowError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 18\u001b[0m \u001b[0ma\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mb\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mpolyfit\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mx\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0my\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mdeg\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 19\u001b[0m \u001b[0mseconds\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0ma\u001b[0m\u001b[1;33m*\u001b[0m\u001b[1;36m1e25\u001b[0m \u001b[1;33m-\u001b[0m \u001b[0mb\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 20\u001b[1;33m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mtimedelta\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mseconds\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mOverflowError\u001b[0m: normalized days too large to fit in a C int" ] } ], "source": [ "import time\n", "import numpy as np\n", "from datetime import timedelta\n", "\n", "\n", "def simple_loop(n):\n", " \"\"\"Return how many seconds a simple loop takes.\"\"\"\n", " start = time.time()\n", " for _ in range(n):\n", " pass\n", " return time.time() - start\n", "\n", "\n", "\n", "x = np.arange(start=1, stop=1e6, step=1e4)\n", "y = [simple_loop(int(n)) for n in x]\n", "\n", "a, b = np.polyfit(x, y, deg=1)\n", "seconds = a*1e25 - b\n", "print(timedelta(seconds))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Great Scott!" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "6.769637001126942" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from math import log10\n", "\n", "\n", "C_MAX_INT = 2**31\n", "log10(C_MAX_INT/365)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So we will recourse to sampling!\n", "\n", "The game Boggle has a square board, with 16 slots for cubes. Each cube has letters on its 6 faces. Starting each game, the board is shuffled so that each cube is in a random slot, _and_ each cube has a random face showing.\n", "\n", "Players compete to find most words showing on the resultant board. A player's score is based on the number of words that no other players found. Where longer words that no other players found are scored higher.\n", "\n", "Words can be constructed from any non-repeating contiguous sequence of horizontal, vertical, or diagonal cubes. But sequences can't wrap around the board. Note too that the same letter may appear twice in a path, but not the same cube.\n", "\n", "\n", "Lastly, the words must belong to an agreed upon dictionary, and be at least some minimum length." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Simulating Boggle" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There are 16 cubes one will find in a standard Boggle board:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "cubes = [\n", " ('a', 'a', 'e', 'e', 'g', 'n'),\n", " ('a', 'b', 'b', 'j', 'o', 'o'), \n", " ('a', 'c', 'h', 'o', 'p', 's'),\n", " ('a', 'f', 'f', 'k', 'p', 's'),\n", " ('a', 'o', 'o', 't', 't', 'w'),\n", " ('c', 'i', 'm', 'o', 't', 'u'),\n", " ('d', 'e', 'i', 'l', 'r', 'x'),\n", " ('d', 'e', 'l', 'r', 'v', 'y'),\n", " ('d', 'i', 's', 't', 't', 'y'),\n", " ('e', 'e', 'g', 'h', 'n', 'w'),\n", " ('e', 'e', 'i', 'n', 's', 'u'),\n", " ('e', 'h', 'r', 't', 'v', 'w'),\n", " ('e', 'i', 'o', 's', 's', 't'),\n", " ('e', 'l', 'r', 't', 't', 'y'),\n", " ('h', 'i', 'm', 'n', 'qu', 'u'),\n", " ('h', 'l', 'n', 'n', 'r', 'z'),\n", "]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To simulate the process of shuffling a board, we uniformly choose a slots and faces." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from random import choice, shuffle\n", "from itertools import islice\n", "\n", "\n", "def rand_board():\n", " board = []\n", " shuffle(cubes)\n", " cube_order = iter(cubes)\n", " for i in range(4):\n", " row = islice(cube_order, 4)\n", " board.append([choice(cube) for cube in row])\n", " return board" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[['w', 't', 'i', 'p'],\n", " ['n', 'x', 'e', 'n'],\n", " ['r', 'e', 's', 's'],\n", " ['t', 'b', 'n', 't']]" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rand_board()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To find viable words on a board, we'll recursively traverse paths across the showing letters. Once a sequence goes out of bounds or repeats a cube, traversal in that direction will stop. \n", "\n", "Now, at each step in this process we could look up the sequence in a hash table. But, it may be the case that the current sequence is not a prefix of any word in the dictionary. So, it will be more efficient to store the dictionary in a trie. [Here](http://stackoverflow.com/a/11016430) is an inspiring elegant implementation." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "def make_trie(words):\n", " root = {}\n", " for word in words:\n", " node = root\n", " for letter in word:\n", " node = node.setdefault(letter, {})\n", " node[None] = None # signify that node is a word\n", " return root" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"a\": {\n", " \"p\": {\n", " \"p\": {\n", " \"l\": {\n", " \"y\": {\n", " \"null\": null\n", " },\n", " \"e\": {\n", " \"null\": null\n", " }\n", " }\n", " },\n", " \"t\": {\n", " \"null\": null\n", " }\n", " }\n", " }\n", "}\n" ] } ], "source": [ "import json\n", "\n", "\n", "t = make_trie(['apply', 'apple', 'apt'])\n", "print(json.dumps(t, indent=2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's write that recursive function that generates unique words on a given board, of at least some length." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "from itertools import product\n", "\n", "\n", "def get_words(board, trie, min_len=3):\n", " \"\"\"Yield possible words from a given board and trie.\"\"\"\n", " offsets = [(-1, -1), (-1, 0), (-1, 1),\n", " (0, -1), (0, 1),\n", " (1, -1), (1, 0), (1, 1)]\n", " \n", " def recur(i, j, path, seen, node):\n", " if (i, j) in seen: # path can't cross itself\n", " return\n", " if not (0 <= i < 4) or not (0 <= j < 4): # path can't wrap around board\n", " return\n", " letter = board[i][j]\n", " path += letter\n", " seen.add((i, j))\n", " if letter in node: # path is a prefix\n", " if None in node[letter]: # path is a word\n", " yield path\n", " for i_off, j_off in offsets: # traverse neighbors even if path is word\n", " yield from recur(i + i_off, j + j_off, path, seen.copy(), node[letter])\n", " \n", " seen = set()\n", " for i, j in product(range(4), range(4)):\n", " for word in recur(i, j, '', set(), trie):\n", " if len(word) >= min_len and word not in seen:\n", " yield word\n", " seen.add(word)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finding words necessitates a dictionary, and a _Scrabble_-esque dictionary seems fitting. Conveniently, the _Words With Friends_ game's dictionary is [publicly available](http://gaming.stackexchange.com/a/7163): the Enhanced North American Benchmark Lexicon." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "with open('enable1.txt') as f:\n", " trie = make_trie(word.strip() for word in f)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Estimating Word Probabilities" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To estimate word frequencies, we'll gather relative frequencies from random boards. But how many boards to sample from?\n", "\n", "We can view the appearance of a specific word as a Bernoulli random variable with some unknown probability. We'd like to sample enough boards so that the resultant $k$ most common words is are in fact the most common, and in order.\n", "\n", "In the land of probability bounds, where Markov and Chebyshev wander wide, one called Hoeffding's inequality is particularly powerful when it comes to Bernoulli variables. It can tell us how tight an $\\epsilon$-sized confidence interval should be with $n$ samples.\n", "\n", "Specifically, let $X_1, \\ldots, X_n \\sim \\mathrm{Bernoulli}(p)$. Then, for any $\\epsilon > 0$,\n", "\n", "$$P(\\left| \\overline{X_n} - p \\right| > \\epsilon) \\le 2 e^{-2n\\epsilon^2}$$\n", "\n", "So, to be $\\alpha$ confident that some variable is within error $\\epsilon$ of it's true value, we'll need\n", "\n", "$$n \\ge \\frac{1}{2\\epsilon^2}\\log{\\frac{2}{\\alpha}}$$\n", "\n", "samples.\n", "\n", "This applies to any Bernoullie variable, and each random Boggle board gives a Bernoulli value to every word (it appears or it doesn't). So, we can just sample a bunch of boards, which will probably produce a tight enough bound.\n", "\n", "However, we'll have to be very diligent about choosing our confidence $\\alpha$, and tightness $\\epsilon$. We can't guarantee that the top $k$ words are in fact the top $k$, and in order, but it does ossify those top probabilities with a diligently chosen $\\epsilon$.\n", "\n", "Experimenting with 1000 boards showed that top 10 words' probabilities overlap, suggesting that we need $\\epsilon < 0.001$ to be, say, $\\alpha = 0.1$ confident that the ordering is correct. Unfortunately, this would lead to $n \\ge 1.5 \\times 10^6$ boards, which is too many hours of computation. Instead we'll shoot for just having the same set of top words, rather than correct ranking. The difference in probability between the empirical most common word and the 20th (we choose $k$ arbitrarily) was about 0.01, yielding $n \\ge 14979$ boards, which is more manageable." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " Rank Word Frequency\n", "------ ------ -----------\n", " 1 teen 0.0642\n", " 2 tees 0.0639\n", " 3 toes 0.0575\n", " 4 note 0.0575\n", " 5 teat 0.0573\n", " 6 tent 0.0565\n", " 7 tone 0.0565\n", " 8 toea 0.0558\n", " 9 neat 0.0525\n", " 10 test 0.0524\n", " 11 nets 0.0517\n", " 12 sent 0.0511\n", " 13 nest 0.0510\n", " 14 teas 0.0508\n", " 15 thee 0.0505\n", " 16 seta 0.0502\n", " 17 ates 0.0502\n", " 18 tens 0.0501\n", " 19 ante 0.0501\n", " 20 etna 0.0501\n", "CPU times: user 52.8 s, sys: 562 ms, total: 53.4 s\n", "Wall time: 1min 13s\n" ] } ], "source": [ "%%time\n", "from collections import Counter\n", "from tabulate import tabulate\n", "import multiprocessing as mp\n", "\n", "NBOARDS = 14979\n", "\n", "\n", "def update_counts(_):\n", " board = rand_board()\n", " return Counter(get_words(board, trie, min_len=4))\n", "\n", "\n", "counts = Counter()\n", "nprocs = mp.cpu_count()\n", "with mp.Pool(processes=nprocs) as pool:\n", " it = pool.imap_unordered(update_counts, range(NBOARDS),\n", " chunksize=500)\n", " for sampled_counts in it:\n", " counts += sampled_counts\n", "\n", "\n", "table = [(i, word, count/NBOARDS) for i, (word, count)\n", " in enumerate(counts.most_common(20), start=1)]\n", "print(tabulate(table, headers=['Rank', 'Word', 'Frequency'], floatfmt='.4f'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Common and Ordinary Word Ranking" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The most common list is interesting, but some of the words are so ordinary that other players will probably know them and also find them. To improve one's chances of winning Boggle, it would be nice to have a ranking that takes into account both a word's probability of occurence, *and ordinariness*.\n", "\n", "This is reminiscent [tf-idf weighting](http://nlp.stanford.edu/IR-book/html/htmledition/tf-idf-weighting-1.html)! Our current list gives us the *term frequency*, so all that remains is to find each term's *inverse document frequency*. Here, we assume that usage corresponds to how likely it is for a word to be known.\n", "\n", "We'll use word usage counts from [American National Corpus](http://www.anc.org/data/anc-second-release/frequency-data/). However, some of the words from Boggle might have various endings (e.g. \"apple\", \"apples\"), and those should add in frequency for \"ordinariness.\" So we'll stem the words for lookup." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "from nltk.stem import SnowballStemmer\n", "from collections import defaultdict\n", "\n", "\n", "snow = SnowballStemmer('english')\n", "freqs = defaultdict(int)\n", "min_freq = 1 # smallest frequency found, for use with Boggle words not in list\n", "\n", "with open('ANC-token-count.txt', encoding='ISO-8859-1') as f: # there's weird characters\n", " for word, *_, freq in map(str.split, f):\n", " if word == 'Total': # there was a total count of words at the end\n", " continue\n", " word, freq = snow.stem(word), float(freq)\n", " freqs[word] += freq\n", " min_freq = freqs[word]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, we just need to calculate the scores of the words output a new sorted list." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " Rank Word Score\n", "------ ------ -------\n", " 1 toea 14157\n", " 2 seta 12736.3\n", " 3 nett 12668.6\n", " 4 teat 12641.7\n", " 5 stet 12228.8\n", " 6 tret 11721.4\n", " 7 rete 11719.2\n", " 8 sett 11653.8\n", " 9 tees 11617.2\n", " 10 etna 11493.7\n", " 11 teel 11078.7\n", " 12 hest 10994.1\n", " 13 teth 10981.5\n", " 14 haet 10841.9\n", " 15 nite 10547.4\n", " 16 hent 10419\n", " 17 eath 10419\n", " 18 tine 10407.1\n", " 19 leet 10359.1\n", " 20 sate 10339.6\n" ] } ], "source": [ "from math import log\n", "\n", "\n", "tfs, idfs = [], []\n", "for word, count in counts.items():\n", " word = snow.stem(word)\n", " tfs.append(1 + count)\n", " idfs.append(log(1 + 1/freqs.get(word, min_freq)))\n", "\n", "scores = sorted([(tf*idf, word) for tf, idf, word in zip(tfs, idfs, counts.keys())], reverse=True)\n", "table = [(i, word, score) for i, (score, word) in enumerate(scores[:20], start=1)]\n", "print(tabulate(table, headers=['Rank', 'Word', 'Score']))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "# Epilogue\n", "\n", "After completing this task, several sub-topics came to mind. We address some of those here. Unlike in previous sections, we will be lax proving the correct number of boards." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Number of Possible Words Distribution" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It would be interesting to see the minimum, maximum, mean, etc. of the number of words found over all boards. For the sake of brevity, rather than fitting the distribution and devising estimators, we'll just plot a simple histogram. And I choose an arbitrarily large number of boards to sample from." ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAgQAAAFtCAYAAAB4EXKZAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3XtAVHX+//HXwHCRi4purF/JBSP9qpEUuGYihC5s5oZB\nxoq3pDSTzeqXsou0rKFm5LZuromVW7vuagVaoda2Uahp3hVXDVPX8JKX0ig1BhSQOb8//DKJIozm\ngDDPx1/MOZ855/2Zg86Lc/l8TIZhGAIAAE7NpakLAAAATY9AAAAACAQAAIBAAAAARCAAAAAiEAAA\nABEI0EwdPXpU3bp10zvvvFNr+d/+9jelp6dfs/0MGDBAu3btumbbq4/FYlFSUpLi4uJUUFBQa116\nerqioqKUkJCg+++/X3FxcXrsscf03XffXdMaioqK9OSTT9r2+fe//73Odt26ddOpU6fs3u7YsWO1\ncOFC2+uDBw+qW7duevHFF23LvvvuO916662yWCxXWb2Un5+vUaNG2d3+6NGj6tGjhxISEpSQkKDB\ngwdr2LBh+ve//21rM2fOHC1btqze7WRnZ2vlypV1rrvw/Vf6uUnSZ599pmeeeUZS7eMDXGvmpi4A\nuFouLi6aOXOmwsPDFRQU1NTl/Gi7d+/WyZMnlZ+fX+f6hx56SA899JDt9cyZM5WZmak5c+ZcsxpC\nQkL0l7/8pcF2JpPpirYbFRWlTZs22b6sV61apQEDBmjlypV66qmnJEkbN25UeHi4fHx8rrzwH1Gb\np6en8vLybK+PHTum5ORkmc1mxcbG6oknnmhwGxs3blSXLl3qXHfh+6+0Nknat2+fjh8/Lsn+4wNc\nDQIBmi0PDw899NBDmjhxohYvXiyzufavc3p6urp27Wr7Er3w9YABAxQXF6eNGzfq+++/15gxY7Rt\n2zbt2rVLbm5uevnll3XDDTdIkhYtWqS9e/eqqqpKycnJGjJkiKTzX2ovv/yyzp07J09PT6WlpSk0\nNFRz587Vf/7zH504cULdu3fXH//4x1p1FRQUKDs7W4ZhyNvbW2lpafL19dXvf/97nThxQgkJCcrN\nzZW7u3u9/e/Tp4/+9Kc/STr/pTF9+nSdOnVKLi4uSk5OVnx8vMrLy5Wenq4vv/xSJpNJISEhmjZt\n2mWXb968WdOnT9d7770nSdq6das+/PBDlZWVqW/fvpo8ebJcXFx04Xhmb7/9tt58801JUtu2bZWR\nkaGbbrqpVq1RUVHKzs62vV65cqUmTZqkiRMn6vDhw+rUqZM2bNig6OjoevuzefNmzZgxQ61atdKZ\nM2f09ttva968eXr//ffl5+enn/3sZ7Z9bN26VTNnzpTVapXJZNKjjz6q2NjYej9TSerYsaOeeOIJ\nvf7664qNja31ezNnzhytWLFCbm5uatu2rbKysvTRRx+pqKhIf/zjH+Xi4qIVK1bo1KlTOnLkiKKj\no1VSUmJ7v2EY+vOf/6yioiIZhqEnn3xS0dHRysvLU35+vl555RVJsr3OzMzUSy+9JIvFoqefflrx\n8fG242OxWDR16lTt2bNHJpNJkZGRmjRpklxcXNSzZ0+NGzdOa9euVUlJicaMGaNhw4Y12Hc4NwIB\nmi2TyaSUlBStX79es2bNUlpa2hW9v7KyUrm5ufrggw+UmpqqpUuXqmvXrpowYYLy8vI0btw4SVKr\nVq307rvv2r6sb7vtNpnNZv35z3/WokWL1KZNG33xxRdKTk62ner/6quv9K9//euSvwj379+vzMxM\n5ebmKiAgQBs3btRvfvMb5efn69lnn9X06dNr/bV6OWfPntWyZcvUp08fVVdX6ze/+Y3S0tIUExOj\nEydOKDExUZ07d9bBgwdVXl6uvLw8Wa1WZWZm6vDhw9q2bVudyy92/PhxvfHGG3J1ddXDDz+sxYsX\nKykpybZ+y5YtWrp0qd566y15eHho3bp1mjBhgj744INa2wkKClKbNm20Z88edezYUQcPHtRtt92m\nyMhIrVy5UqNHj9aGDRs0ZsyYevsjSV988YVWrFihDh06qKCgQAUFBVq+fLk8PDz0m9/8xrbPuXPn\n6qGHHtKgQYO0d+9eLV682K5AIJ0/tb93795ay77++mv985//1IYNG+Tm5qYFCxZo586dGjFihD78\n8EM9+OCDiomJ0YoVK1RRUWELVRdfwgoMDNS0adO0b98+jRw5Uh9++OFl6+jQoYOeeOIJ5efn67nn\nntPmzZtt66ZPny4/Pz+99957qqqq0vjx4/X666/rkUceUWVlpdq1a6ecnBzt2rVLw4YN05AhQxoM\nmXBuBAI0ey+88ILi4+MVGRl5Re/75S9/KUn62c9+phtuuEFdu3aVJHXq1KnWdd6hQ4dKkvz9/dWv\nXz9t2LBBLi4u+uabb5ScnGz7a9lsNuvQoUOSpNDQ0DpPD2/cuFF33nmnAgICJJ3/K/8nP/mJXfcp\n/P3vf9fy5ctlGIaqq6vVu3dvTZw4UQcPHlRlZaViYmJsdf7yl7/Up59+qoSEBM2ePVujRo1SRESE\nRo8erU6dOslkMtW5/Kuvvqq1z/vuu08eHh6SpMGDB2vNmjW1AsEnn3yiL7/8UklJSbbPobS0VN9/\n/71at25da1tRUVHavHmz2rVrp4iICElS//799eabbyomJkYuLi4KCgpScXHxZfvTu3dvdejQQR06\ndLB9nrGxsWrVqpUkaciQIbZ7Fe655x5NmzZNK1euVN++fW2XJuxhMpls26zx05/+VN27d1dCQoIi\nIyMVFRWlO++807b+wrMmYWFhl912zefXpUsXdenSRdu3b7e7rgt9+umnysnJkSS5ublp2LBh+sc/\n/qFHHnlEkvSLX/xCknTLLbeoqqpKZ86cIRCgXgQCNHsdOnTQ1KlTlZaWpvj4+FrrLvxPurKysta6\nC/9zvPhyw4VcXV1tP1dXV8tsNuvcuXPq27ev/vznP9vWff311/L399fHH38sb2/vOrdVc/r6QtXV\n1Tp37ly9NUiX3kNw4fsv3qZhGKqqqlJAQIDy8/O1ZcsWbdy4UaNHj9azzz6r6OjoOpd7eXnV2o6L\ni0utbdbUWLM/q9Wq++67T5MmTbK1O378+CVhQJIiIyP19ttvy93d3faX+p133qmMjAytX79ed911\nV4P9kXRJjRce4wuP1dChQzVgwACtW7dOa9as0dy5c/Xhhx/a9aW4c+dOW0CsYTKZtHDhQhUVFWn9\n+vXKyspSVFSUUlNTL3n/5Y6/VPszrfl9uri/NX2tz8W/S1arVefOnbO9rglyNZi2Bg3hKQM0Wxf+\nB3f33XcrKipK//jHP2zL2rVrp6KiIknnv6S2bNlyVft59913JZ2/2azmL/w777xT69at0/79+yVJ\nq1ev1n333aeKiop6t3XnnXdq7dq1OnLkiCRpw4YNOn78uHr27HlVtUnSTTfdJLPZbLtccfz4cX30\n0UeKiIjQW2+9pfT0dEVERGjSpEmKjIzUrl27Lrv8Yh988IEqKytVUVGhvLw8RUVFSfrhs+/Xr5/+\n9a9/6ZtvvpEkvfHGG0pOTq6zzjvuuEO7d+/W1q1b1a9fP0nnv7RuueUWvfHGG7b7B+rqT35+vu2s\nwoUiIyP14YcfqrS0VFartdbTAElJSfr8888VHx+vadOmqbS0VCUlJZds4+IvygMHDujll1/Www8/\n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\nPZu46mvnwv5//vnnGj9+vIKCgiRJw4YN0z333NMi+3/u3Dk9/fTTOnr0qKqqqjR+/HjdfPPNTnP8\n6+p/hw4dnOb4W61WZWRk6MCBAzKbzXruuedkGIbTHP+6+l9aWnptjr/Rwnz00UfG5MmTDcMwjO3b\ntxspKSlNXJHjVVRUGAkJCbWW3Xfffcbhw4cNwzCMRx55xPj888+bojSH+etf/2rce++9xtChQw3D\nMIzx48cbW7ZsMQzDMKZMmWJ8/PHHxq5du4zRo0cbhmEYx44dM4YMGdJU5V5zF/d/8eLFxt///vda\nbVpq/9955x3jueeeMwzDME6dOmVER0c71fG/sP8nT540oqOjjSVLljjN8f/444+Np59+2jAMw9i0\naZORkpLiVMe/rv5fq3//Le6SQX3DJbdUe/bsUXl5ucaMGaPk5GRt3bpVVVVVuvHGGyVJ/fr104YN\nG5q4ymsrMDBQ2dnZtte7du1Sr169JJ0f8nr9+vUqLCxURESEJOl//ud/ZLVadfLkySap91qrq/+f\nfPKJRo4cqYyMDJWVlbXY/t9zzz168sknJZ3/a8nV1VWff/650xz/C/tvGIbMZrN27dqlVatWOcXx\nj4mJ0fTp0yVJx44d009+8hOnOv4X9v/o0aP6yU9+cs2Of4sLBJcbLrkl8/T01JgxY/T6668rMzNT\n6enp8vT0tK2vGQK6JYmNja01IYlxwdOz9Q15feGIl83Zxf0PDQ3V7373Oy1atEidOnXS3LlzW2z/\nW7VqZevLk08+qaeeesqpjv/F/f9//+//qWfPnkpLS3OK4y9JLi4umjx5sp599lndfffdTnX8pR/6\nP2PGDA0cOFChoaHX5Pi3uEBQ33DJLVVQUJAGDx5s+9nX11enT5+2rbd3qOfm7MJjXFZWpjZt2tQ5\n5PWF/0BakpiYGPXo0cP28+7du1t0/7/66iuNHj1aCQkJ+tWvfuV0x//i/jvb8Zek559/Xvn5+crI\nyFBFRYVtuTMcf6l2/yMiIq7J8W9x35Q1Qx9LumS45JbqnXfe0fPPPy9JOn78uM6cOaNWrVrp8OHD\nMgxDa9eubfFDOffo0UNbtmyRJK1Zs0bh4eG6/fbbtW7dOhmGoWPHjskwDLVt27aJK3WMMWPG6LPP\nPpMkbdiwQSEhIQoLC9PatWtbXP9LSko0ZswY/fa3v1VCQoIkqXv37k5z/OvqvzMd/2XLlmn+/PmS\nJA8PD7m4uCgkJESbN2+W1PKP/8X9N5lMevzxx7Vz505JP+74t7inDGJjY7Vu3TolJSVJOj9cckv3\nwAMPKD09XcOHD5eLi4uysrLk4uKi1NRUWa1WRUREtIi7a+uTlpamP/zhD6qqqlJwcLAGDhwok8mk\n8PBwDR06VIZhaMqUKU1dpsNkZmZq2rRpcnd31w033KBp06bJ29tbvXr1anH9f/XVV/X9999r3rx5\nys7Olslk0u9//3s9++yzTnH86+p/enq6ZsyY4RTH/5e//KXS09M1cuRInTt3ThkZGbrpppuUkZHh\nFMe/rv536NBBU6dO/dHHn6GLAQBAy7tkAAAArhyBAAAAEAgAAACBAAAAiEAAAABEIAAAACIQAM3G\n0aNH1a1bt0vmpRgwYICOHTv2o7d/rbZTn6+++koDBw5UQkKCysvLHbovSerWrZvD9wG0FAQCoBkx\nm83KyMio9WVqMpmuybav1Xbqs2nTJoWEhCgvL09eXl4O319j9AloKQgEQDPi7++viIgI21DV0g8T\nO23evFmjRo2yLU9PT9fSpUt19OhRxcfHa+LEiYqLi9PkyZOVm5urpKQkDRo0SPv377dt56WXXlJC\nQoKSkpK0d+9eSdK3336rxx57TEOGDFFiYqLtDMXcuXM1duxY3XvvvcrJyalV58GDBzVq1CgNHjxY\nSUlJ+uyzz7Rnzx795S9/0aeffqrMzExb2++++05RUVG211FRUfr3v/8tSZo/f77+9re/6ezZs0pN\nTVVcXJzuu+8+LV26VJKUl5enBx98UIMHD9aLL76oo0ePavjw4UpISNAzzzxj2+aGDRt0//3364EH\nHtCYMWN06tSpH30sgJaGQAA0IyaTSWlpaVq7dm2dU1pf7i/ivXv3aty4cXrvvfe0bds2HTt2TDk5\nORo0aJAWL15sa9e5c2fl5eUpJSVFkydPliTNmDFDDzzwgN555x3NmzdPU6ZMsZ2hqKys1Pvvv28b\nKrzGb3/7W40ePVrLly9Xenq6nnzySQUHB+uJJ57QgAEDagWCdu3aqWPHjvriiy+0f/9+VVdX2+Yl\n+PTTTxUdHa2XXnpJfn5+eu+997RgwQLNnTtX//3vfyWdn79j2bJleuqppzR9+nQNGTJEeXl5CgsL\ns+3j5Zdf1rRp0/T222+rb9+++vzzz6/i0wdaNgIB0Mx4e3tr+vTptnnP7XHDDTfYrqf/9Kc/VZ8+\nfSRJAQEBtWbGfOCBByRJd911l44dOyaLxaL169drzpw5io+P1yOPPKLq6mp9+eWXks5Pu3yx8vJy\nffnll4qJibG1adu2rQ4cOHDZ+mrmsN+4caNGjx6trVu3ymKx6Ntvv9VNN92kjRs32mrz8/NTTEyM\nbTKbW265xRaENm3apHvuuUeSNHjwYJnN56drGTBggB577DFNnz5d3bt3V9++fe363ABnQiAAmqGI\niAhFRERo5syZti9Dk8lUa174qqoq289ubm613l/zRXkxV1fXS15brVb94x//0NKlS7V06VLl5uba\nZhH18PC4ZBtWq7XOZdXV1Zftz1133aX169dr8+bNio2Nlaurq9577z3169dPknTxlCtWq1Xnzp27\npAYXFxfb/k0mk60/ycnJWrRokQIDA/XCCy/o1VdfvWwtgLMiEADNyIVfjL/73e+0du1affPNN5LO\n/+V85MgRVVZW6tSpUyosLKzzffV57733JEkff/yxbrrpJrVq1Up9+vTRG2+8IUn64osvdO+99+rs\n2bOX3YaPj486deqkgoICSeenIS8pKVGXLl0u+55bbrlFBw8e1MGDB9W5c2f17t1bL7/8svr37y9J\nuuOOO/T2229LOn/PwYoVK3THHXdcsp2+fftq2bJlkqT8/HxVVFRIkn7961/LYrHowQcf1OjRo7Vr\n1y67Pg/AmbS46Y+BluzCewR8fHw0ffp0jR07VpJ0880366677tK9996rgIAA9erVq873Xe4+A5PJ\npIMHDyo+Pl4+Pj6aOXOmJCkjI0NTpkzR4MGDJUmzZs1q8AmBF154QVOmTNFf/vIXeXh4KDs7+7Jn\nJWr06tVLZ86ckST16dNHb7/9tn7+859Lkh577DFNnTpVcXFxMgxDKSkp6t69u/bs2VNrGxkZGfrd\n736nJUuWKCQkRD4+PpKkp556SpMnT5arq6u8vb01Y8aMemsBnBHTHwMAAC4ZAAAAAgEAABCBAAAA\niEAAAABEIAAAACIQAAAAEQgAAIAIBAAAQNL/ByillHimjLNlAAAAAElFTkSuQmCC\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import matplotlib.pyplot as plt\n", "import seaborn\n", "\n", "\n", "%matplotlib inline\n", "seaborn.set_context('notebook')\n", "seaborn.set_style('whitegrid')\n", "\n", "num_words = []\n", "for _ in range(2000):\n", " num_words.append(len(list(get_words(rand_board(), trie))))\n", " \n", "plt.hist(num_words, bins=40, normed=True)\n", "plt.title('Number of Possible Words Distribution')\n", "plt.xlabel('Number of words')\n", "plt.ylabel('Proportion')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Best Proportion of Vowels" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A friend gave the comment that the \"best\" Boggle boards are comprised of 40% vowels. Best, here, was intended to mean highest number of word solutions. Now to experimentally prove this claim." ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "from itertools import chain\n", "from collections import defaultdict\n", "\n", "\n", "NBOARDS = 1000\n", "data = defaultdict(int) # store how many words found per vowel, summed over boards\n", "vowels = set('aeiou') # for simplicity we assume that these are the only vowels\n", "\n", "for _ in range(NBOARDS):\n", " num_vowels = 0\n", " board = rand_board()\n", " for letter in chain(*board):\n", " if letter in vowels:\n", " num_vowels += 1\n", " num_words = len(list(get_words(board, trie, min_len=4)))\n", " data[num_vowels] += num_words" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now to plot the mean number of found words per number of vowels:" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "image/png": "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\nVaKiorL4Toh4F7edm13EWxUoUIBx48YxYsQIVq1alanvue+++6hWrRoAJUqUsC+6UapUKXbu3Gl/\nXseOHQFo3LgxgwYN4urVq2zdupWTJ0/y1ltvAWZL+vTp0wAEBQXddqxr165x+vRpmjdvbn9O4cKF\nOXnyZIb52rZty8SJE+natSuff/45AwYM4IcffiAoKIgHHngAgL///e/MmzePChUqcPXqVS5fvkxU\nVBTPPfccO3fuJF++fDRu3JhTp05x+vRpevfube+1uPX6/GOPPcYrr7zCwYMHadKkCV27ds3Uz1HE\nW6mYi1ggJCSEkJAQJk2aZG+N22y2NGsXx8fH2x/nypUrzfcnryp2K39//9u2k5KSeP/99+0rT/3+\n++8ULVqU9evXkydPntteI70VypKSkkhMTMzwfGrVqsUff/zB/v37OXfuHEFBQXzzzTdpzif1azRs\n2JB169bh5+dHs2bNePPNN7HZbPTr14/ExETKli3LihUrAPPywfnz59Mcr27duqxZs4YNGzbw5Zdf\nsmLFChYsWJBhPhFvp252ERdKXdwGDRrE5s2b+f333wEIDAwkOjqauLg4Ll26lKbrOLPrISUvnbhu\n3ToqVqxIvnz5qF+/Ph9++CEAx44dIzQ0lBs3bmT4GgULFqRMmTKsX78egL1793L+/HkqV658x2OH\nhoYyatQoQkNDAbNFv2/fPvsI+48//ph69eoBZs/B3Llzefjhh6lWrRrHjx/n1KlTVKtWjYoVK/LH\nH3/w/fffA2aX+sCBA9Mca/LkyaxcuZL27dszcuRIDh06lKmfj4i3UstcxIVSXxMvWLAg48aN44UX\nXgCgUqVKNG7cmNDQUEqVKsXDDz+c7vdldF3dZrNx6tQp2rdvT8GCBZk0aRIAI0aMICIignbt2gEw\nZcoUhyPRJ0+eTEREBG+99f/t3aENgwAQQNGfkIAHxwYsAFswAgvgWAEwKCBhDRZjA4Koqqho/TX/\nTXCnfnLmNrIs4ziOr9eAt7Zt2feddV0BKIqCcRzp+57neSjLknmeAWiahuu6qOsagKqqyPMcgDRN\n2baNaZq47/tjl/fuXdcxDAPneZIkCcuy/JxN+ne+QJUkKTjP7JIkBWfMJUkKzphLkhScMZckKThj\nLklScMZckqTgjLkkScEZc0mSgnsBu2ZdyyHBZUEAAAAASUVORK5CYII=\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "x, y = zip(*sorted(data.items()))\n", "\n", "plt.plot(x, [t/NBOARDS for t in y], 'bo:')\n", "plt.title(r'Number of Words in Board per Vowel')\n", "plt.xlabel('Number of vowels')\n", "plt.ylabel('Total number of found words')\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.375" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "6/16" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Would you look at that, 40% was pretty spot on for an off the cuff comment." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Super Big Boggle" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "collapsed": true }, "outputs": [], "source": [ "big_cubes = [\n", " ('s', 's', 's', 'u', 'n', 'e'),\n", " ('qu', 'j', 'k', 'z', 'w', 'x'),\n", " ('a', 'a', 'a', 's', 'f', 'r'),\n", " ('h', 'o', 'r', 'd', 'l', 'n'),\n", " ('g', 'o', 'r', 'w', 'r', 'v'),\n", " ('f', 's', 'i', 'r', 'a', 'a'),\n", " ('an', 'in', 'er', 'he', 'th', 'qu'),\n", " ('a', 'e', 'a', 'e', 'e', 'e'),\n", " ('l', 'i', 'e', 'a', 'n', 'm'),\n", " ('g', 'f', 'n', 'u', 'y', 'c'),\n", " ('a', 'e', 'm', 'e', 'e', 'e'),\n", " ('h', 'd', 't', 'n', 'o', 'd'),\n", " ('h', 's', 'e', 'r', 'i', 'l'),\n", " ('n', 'u', 'e', 'o', 'i', 'a'),\n", " ('o', 'n', 'd', 'w', 'h', 'h'),\n", " ('a', 'o', 'i', 'e', 'b', 'd'),\n", " ('l', 'h', 'h', 'r', 'd', 'o'),\n", " ('g', 'n', 'a', 'm', 'n', 'e'),\n", " ('s', 't', 'r', 'h', 'p', 'o'),\n", " ('n', 'd', 'e', 'a', 'n', 'n'),\n", " ('o', 'o', 'a', 'e', 'a', 'e'),\n", " ('t', 'm', 't', 'e', 'o', 't'),\n", " ('t', 'i', 's', 'r', 'v', 'h'),\n", " ('a', 'e', 'm', 'g', 'u', 'e'),\n", " ('t', 'i', 'l', 's', 'p', 'e'),\n", " ('n', 'o', 'w', 'o', 'u', 't'),\n", " ('y', 'r', 'y', 's', 'i', 'p'),\n", " ('e', 's', 'p', 't', 'i', 'c'),\n", " ('c', 't', 'n', 's', 'e', 'c'),\n", " ('y', 'r', 'f', 's', 'i', 'a'),\n", " ('l', 'c', 'd', 'd', 'n', 'n'),\n", " ('t', 'i', 't', 'c', 't', 'e', 'i'),\n", " ('e', 't', 'i', 'l', 'i', 's'),\n", " ('j', 'b', 'b', 'z', 'x', 'k'),\n", " ('o', 'o', 'o', 't', 'u', 't'),\n", " ('o', 'i', None, None, 'e', None)\n", "]" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.2" } }, "nbformat": 4, "nbformat_minor": 1 }