diff --git a/ipynb/SpellingBee.ipynb b/ipynb/SpellingBee.ipynb
new file mode 100644
index 0000000..3d494e7
--- /dev/null
+++ b/ipynb/SpellingBee.ipynb
@@ -0,0 +1,1210 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
Peter Norvig, 3 Jan 2020
\n",
+ "\n",
+ "# Spelling Bee\n",
+ "\n",
+ "The [Jan. 3 2020 Riddler](https://fivethirtyeight.com/features/can-you-solve-the-vexing-vexillology/) concerns the popular [NYTimes Spelling Bee](https://www.nytimes.com/puzzles/spelling-bee) puzzle. As they describe the problem, given a \"honeycomb\" of 7 letters like this:\n",
+ "\n",
+ "
\n",
+ "\n",
+ "\n",
+ "*The goal is to identify as many words that meet the following criteria:*\n",
+ "1. *The word must be at least four letters long.*\n",
+ "2. *The word must include the central letter.*\n",
+ "3. *The word cannot include any letter beyond the seven given letters.*\n",
+ "\n",
+ "*Note that letters can be repeated. For example, the words GAME and AMALGAM are both acceptable words. Four-letter words are worth 1 point each, while five-letter words are worth 5 points, six-letter words are worth 6 points, seven-letter words are worth 7 points, etc. Words that use all of the seven letters in the honeycomb are known as “pangrams” and earn 7 bonus points (in addition to the points for the length of the word). So in the above example, MEGAPLEX is worth 15 points.*\n",
+ "\n",
+ "*Which seven-letter honeycomb results in the highest possible game score? To be a valid choice of seven letters, no letter can be repeated, it must not contain the letter S (that would be too easy) and there must be at least one pangram.*\n",
+ "\n",
+ "*For consistency, please use [this word list](https://norvig.com/ngrams/enable1.txt) to check your game score.*\n",
+ "\n",
+ "# Approach to a Solution\n",
+ "\n",
+ "Since the word list was on my web site (it is a standard Scrabble word list that I happen to host a copy of), I felt somewhat compelled to submit an answer. I had worked on word puzzles before, like Scrabble and Boggle. My first thought is that this puzzle is rather different because it deals with *unordered sets* of letters, not *ordered permutations* of letters. So I had a strategy that differs from past word puzzles: \n",
+ "\n",
+ "- Represent a word (and a honeycomb) as a set of letters, which I'll implement as a sorted string. So both \"GLAM\" and \"AMALGAM\" will be represented by the string `\"AGLM\"`.\n",
+ "- To represent a honeycomb, use the sorted letter set, but also keep track of the center. So the honeycomb in the image above would be represented by `('AEGLMPX', 'G')`.\n",
+ "- Since every honeycomb must contain a pangram, I can find the best honeycomb by considering all possible pangrams (with all possible centers) and taking the one that scores highest.\n",
+ "- I'm hoping there aren't too many candidate pangrams, but I can do some pre-computation to make it easier to find the best one."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Words, Word Scores, Pangrams, and Lettersets\n",
+ "\n",
+ "Here I load some modules and define four basic functions about words:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from itertools import combinations\n",
+ "from collections import Counter"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def Words(text: str) -> set:\n",
+ " \"\"\"All the valid words from a str of text.\"\"\"\n",
+ " return {w for w in text.upper().split() \n",
+ " if len(w) >= 4 and 'S' not in w and len(set(w)) <= 7}\n",
+ "\n",
+ "def word_score(word) -> int: \n",
+ " N = len(word)\n",
+ " bonus = (7 if is_pangram(word) else 0)\n",
+ " return (0 if N < 4 else 1 if N == 4 else N + bonus)\n",
+ "\n",
+ "def is_pangram(word) -> bool: \n",
+ " \"\"\"Does a word use all 7 letters (some maybe more than once)?\"\"\"\n",
+ " return len(set(word)) == 7\n",
+ "\n",
+ "def letterset(word) -> str:\n",
+ " \"\"\"The set of letters in a word, represented as a sorted str of letters.\"\"\"\n",
+ " return ''.join(sorted(set(word)))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "I'll make a tiny word list to experiment with: "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "words = Words('amalgam amalgamation game games gem glam maple megaplex pelagic I me')\n",
+ "words"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Note that `I`, `me` and `gem` are too short, `games` has an `s` which is not allowed, and `amalgamation` has too many distinct letters. Here are examples of the functions in action:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "{w: word_score(w) for w in words}"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "{w for w in words if is_pangram(w)}"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "{w: letterset(w) for w in words}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Notice that `AMALGAM` and `GLAM` have the same letterset, `AGLM`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# The enable1 Word List\n",
+ "\n",
+ "Now I will load in the `enable1` word list and see what we have:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " 172820 enable1.txt\r\n"
+ ]
+ }
+ ],
+ "source": [
+ "! [ -e enable1.txt ] || curl -O http://norvig.com/ngrams/enable1.txt\n",
+ "! wc -w enable1.txt"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "44585"
+ ]
+ },
+ "execution_count": 8,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "enable1 = Words(open('enable1.txt').read())\n",
+ "len(enable1)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Notice that only 1/4 of the words are valid: the others are either shorter than 4 letters in length, or contain an 'S', or have more than 7 distinct letters."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "['PUNGENCY',\n",
+ " 'MUCKRAKER',\n",
+ " 'BECUDGELED',\n",
+ " 'PERICARDIA',\n",
+ " 'PYRONINE',\n",
+ " 'HARBORED',\n",
+ " 'FIREDRAKE',\n",
+ " 'POUNDAL',\n",
+ " 'MIAULED',\n",
+ " 'TAGMEMIC']"
+ ]
+ },
+ "execution_count": 9,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "pangrams = [w for w in enable1 if is_pangram(w)]\n",
+ "pangrams[:10] # Just sample some of them"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "14741"
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "len(pangrams)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Only 14,741 candidate pangrams. I'm encouraged about my initial approach to a solution. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Efficiency\n",
+ "\n",
+ "The goal is to find the honeycomb that maximizes the total score of the words we can make. I've chosen to go down the path of considering all 14,741 pangrams, and all 7 centers for each pangram, for a total of 103,187 candidate honeycombs. \n",
+ "\n",
+ "I could check each honeycomb against each word. But there are 44,585 valid words in `enable1`.\n",
+ "I'll make things more efficient by *caching* some important information: \n",
+ "- For each center letter, I'll collect all the lettersets of words that include that letter (thereby excluding words that don't).\n",
+ "- For each of those lettersets, I'll precompute the points scored (possibly over several words with the same letterset).\n",
+ "\n",
+ "I put this in a dict that I call a *scoring table*. This is efficient because:\n",
+ "- We do the `scoring_table` calculation once, and then use it 103,187 times.\n",
+ "- For each candidate honeycomb we don't have to consider all the possible valid words. As we will see below, we will only need 64 table lookups per honeycomb, not 44,585."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "alphabet = 'ABCDEFGHIJKLMNOPQRTUVWXYZ'\n",
+ "\n",
+ "def scoring_table(words) -> dict:\n",
+ " \"\"\"Return a dict of {C: {letterset: sum_of_word_scores}} for all center letters C.\"\"\"\n",
+ " table = {C: Counter() for C in alphabet}\n",
+ " for w in words:\n",
+ " score = word_score(w)\n",
+ " s = letterset(w)\n",
+ " for C in s:\n",
+ " table[C][s] += score\n",
+ " return table"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'A': Counter({'AGLM': 8,\n",
+ " 'AEGLMPX': 15,\n",
+ " 'ACEGILP': 14,\n",
+ " 'AEGM': 1,\n",
+ " 'AELMP': 5}),\n",
+ " 'B': Counter(),\n",
+ " 'C': Counter({'ACEGILP': 14}),\n",
+ " 'D': Counter(),\n",
+ " 'E': Counter({'AEGLMPX': 15, 'ACEGILP': 14, 'AEGM': 1, 'AELMP': 5}),\n",
+ " 'F': Counter(),\n",
+ " 'G': Counter({'AGLM': 8, 'AEGLMPX': 15, 'ACEGILP': 14, 'AEGM': 1}),\n",
+ " 'H': Counter(),\n",
+ " 'I': Counter({'ACEGILP': 14}),\n",
+ " 'J': Counter(),\n",
+ " 'K': Counter(),\n",
+ " 'L': Counter({'AGLM': 8, 'AEGLMPX': 15, 'ACEGILP': 14, 'AELMP': 5}),\n",
+ " 'M': Counter({'AGLM': 8, 'AEGLMPX': 15, 'AEGM': 1, 'AELMP': 5}),\n",
+ " 'N': Counter(),\n",
+ " 'O': Counter(),\n",
+ " 'P': Counter({'AEGLMPX': 15, 'ACEGILP': 14, 'AELMP': 5}),\n",
+ " 'Q': Counter(),\n",
+ " 'R': Counter(),\n",
+ " 'T': Counter(),\n",
+ " 'U': Counter(),\n",
+ " 'V': Counter(),\n",
+ " 'W': Counter(),\n",
+ " 'X': Counter({'AEGLMPX': 15}),\n",
+ " 'Y': Counter(),\n",
+ " 'Z': Counter()}"
+ ]
+ },
+ "execution_count": 12,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "scoring_table(words)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'GLAM': 'AGLM',\n",
+ " 'AMALGAM': 'AGLM',\n",
+ " 'MEGAPLEX': 'AEGLMPX',\n",
+ " 'PELAGIC': 'ACEGILP',\n",
+ " 'GAME': 'AEGM',\n",
+ " 'MAPLE': 'AELMP'}"
+ ]
+ },
+ "execution_count": 13,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "{w: letterset(w) for w in words} # I repeat this here just to remind us of the `words`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Let's explain what `scoring_table(words)` produces. With the center letter being `'A'`, there are five entries:\n",
+ "`'AGLM'` scores 8 (7 for `'AMALGAM'` and 1 for `'GLAM'`), `'AEGLMPX'` scores 15 for `'MEGAPLEX'`, and so on. There are separate entries for each center letter; for example the center letter `'X'` has only one word, `'MEGAPLEX'`; it has letterset `'AEGLMPX'` and scores 15 points.\n",
+ "\n",
+ "# Computing the Game Score\n",
+ "\n",
+ "Given a honeycomb expressed as a (letterset, center letter) pair, we can efficiently compute the `game_score` (the total score of all words that can be made) using the precomputed scoring table:\n",
+ "- First we get the \"row\" of the table defined by the center letter.\n",
+ "- Next we consider every possible *subset* of the letters in the honeycomb. There are 7 letters in a honeycomb, but we must always include the central letter, so we're really asking for how many subsets there are of 6 letters, and the answer is $2^6 = 64$.\n",
+ "- We compute the subsets of letters with `letter_subsets`, fetch the 64 entries in the row, and add them up.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def game_score(letters, center, table):\n",
+ " \"The total score by this honeycomb (i.e., letters/center pair).\"\n",
+ " row = table[center]\n",
+ " subsets = letter_subsets(letters, center)\n",
+ " return sum(row[s] for s in subsets)\n",
+ "\n",
+ "def letter_subsets(letters, center):\n",
+ " \"\"\"All subsets of `letters` that contain `center` letter.\"\"\"\n",
+ " return [letterset(subset) \n",
+ " for n in range(1, 8) \n",
+ " for subset in combinations(letters, n) \n",
+ " if center in subset]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "64"
+ ]
+ },
+ "execution_count": 15,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "len(letter_subsets('AEGLMPX', 'G'))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "['C', 'AC', 'BC', 'CD', 'ABC', 'ACD', 'BCD', 'ABCD']"
+ ]
+ },
+ "execution_count": 16,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "letter_subsets('ABCD', 'C') # An example of `letter_subsets` computation."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Now we can compute a game score for a honeycomb:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "24"
+ ]
+ },
+ "execution_count": 17,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "game_score('AEGLMPX', 'G', scoring_table(words)) "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "153"
+ ]
+ },
+ "execution_count": 18,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "game_score('AEGLMPX', 'G', scoring_table(enable1)) "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# The Solution: The Best Honeycomb\n",
+ "\n",
+ "\n",
+ "Here's the function that will give us the solution. `best_honeycomb` searches through every possible pangram (and center) and finds the combination that gives the honeycomb with the highest game score:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def best_honeycomb(words) -> tuple: \n",
+ " \"\"\"Return (score, letters, center) for the honeycomb with highest score on these words.\"\"\"\n",
+ " table = scoring_table(words)\n",
+ " return max([game_score(pangram, center, table), pangram, center]\n",
+ " for center in alphabet\n",
+ " for pangram in table[center] \n",
+ " if len(pangram) == 7)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "First the solution for the small `words` list:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[29, 'AEGLMPX', 'M']"
+ ]
+ },
+ "execution_count": 20,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "best_honeycomb(words)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Now the solution for the big `enable1` word list:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPU times: user 4.35 s, sys: 6.58 ms, total: 4.36 s\n",
+ "Wall time: 4.36 s\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "[3898, 'AEGINRT', 'R']"
+ ]
+ },
+ "execution_count": 21,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%time best_honeycomb(enable1)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Wow. 3898** is a high score! And it took less than 5 seconds to find it.\n",
+ "\n",
+ "Still, this is a bit unsatisfying. I'd like to see the actual words, not just the score. If I had designed my program to be modular rather than to be efficient, I'd already have that. But as is, I need to somewhat repeat myself to create this report:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "metadata": {
+ "scrolled": false
+ },
+ "outputs": [],
+ "source": [
+ "def report(words):\n",
+ " \"\"\"Print stats and word scores for the best honeycomb on these words.\"\"\"\n",
+ " (score, letters, center) = best_honeycomb(words)\n",
+ " sw = scoring_words(letters, center, words)\n",
+ " print(f'For the word list of {len(words)} words, the highest-scoring honeycomb is:')\n",
+ " print(f' {letters} with center {center}')\n",
+ " print(f'It makes {len(sw)} words (for {score} points)',\n",
+ " f'with {sum(map(is_pangram, sw))} pangrams (*).\\n')\n",
+ " for w in sorted(sw):\n",
+ " print(f'{w} ({word_score(w)}) {\"*\" if is_pangram(w) else \"\"}')\n",
+ " \n",
+ "def scoring_words(letters, center, words):\n",
+ " \"\"\"What words can this honeycomb make?\"\"\"\n",
+ " subsets = letter_subsets(letters, center)\n",
+ " return {w for w in words if letterset(w) in subsets}"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "For the word list of 6 words, the highest-scoring honeycomb is:\n",
+ " AEGLMPX with center M\n",
+ "It makes 5 words (for 29 points) with 1 pangrams (*).\n",
+ "\n",
+ "AMALGAM (7) \n",
+ "GAME (1) \n",
+ "GLAM (1) \n",
+ "MAPLE (5) \n",
+ "MEGAPLEX (15) *\n"
+ ]
+ }
+ ],
+ "source": [
+ "report(words)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "metadata": {
+ "scrolled": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "For the word list of 44585 words, the highest-scoring honeycomb is:\n",
+ " AEGINRT with center R\n",
+ "It makes 537 words (for 3898 points) with 50 pangrams (*).\n",
+ "\n",
+ "AERATE (6) \n",
+ "AERATING (15) *\n",
+ "AERIE (5) \n",
+ "AERIER (6) \n",
+ "AGAR (1) \n",
+ "AGER (1) \n",
+ "AGGER (5) \n",
+ "AGGREGATE (9) \n",
+ "AGGREGATING (18) *\n",
+ "AGINNER (7) \n",
+ "AGRARIAN (8) \n",
+ "AGREE (5) \n",
+ "AGREEING (8) \n",
+ "AGRIA (5) \n",
+ "AIGRET (6) \n",
+ "AIGRETTE (8) \n",
+ "AIRER (5) \n",
+ "AIRIER (6) \n",
+ "AIRING (6) \n",
+ "AIRN (1) \n",
+ "AIRT (1) \n",
+ "AIRTING (7) \n",
+ "ANEAR (5) \n",
+ "ANEARING (8) \n",
+ "ANERGIA (7) \n",
+ "ANGARIA (7) \n",
+ "ANGER (5) \n",
+ "ANGERING (8) \n",
+ "ANGRIER (7) \n",
+ "ANTEATER (8) \n",
+ "ANTIAIR (7) \n",
+ "ANTIAR (6) \n",
+ "ANTIARIN (8) \n",
+ "ANTRA (5) \n",
+ "ANTRE (5) \n",
+ "AREA (1) \n",
+ "AREAE (5) \n",
+ "ARENA (5) \n",
+ "ARENITE (7) \n",
+ "ARETE (5) \n",
+ "ARGENT (6) \n",
+ "ARGENTINE (16) *\n",
+ "ARGENTITE (16) *\n",
+ "ARGININE (8) \n",
+ "ARIA (1) \n",
+ "ARIETTA (7) \n",
+ "ARIETTE (7) \n",
+ "ARRAIGN (7) \n",
+ "ARRAIGNING (10) \n",
+ "ARRANGE (7) \n",
+ "ARRANGER (8) \n",
+ "ARRANGING (9) \n",
+ "ARRANT (6) \n",
+ "ARREAR (6) \n",
+ "ARREARAGE (9) \n",
+ "ARTIER (6) \n",
+ "ATRIA (5) \n",
+ "ATTAINER (8) \n",
+ "ATTAR (5) \n",
+ "ATTIRE (6) \n",
+ "ATTIRING (8) \n",
+ "ATTRITE (7) \n",
+ "EAGER (5) \n",
+ "EAGERER (7) \n",
+ "EAGRE (5) \n",
+ "EARING (6) \n",
+ "EARN (1) \n",
+ "EARNER (6) \n",
+ "EARNING (7) \n",
+ "EARRING (7) \n",
+ "EATER (5) \n",
+ "EERIE (5) \n",
+ "EERIER (6) \n",
+ "EGER (1) \n",
+ "EGGAR (5) \n",
+ "EGGER (5) \n",
+ "EGRET (5) \n",
+ "ENGAGER (7) \n",
+ "ENGINEER (8) \n",
+ "ENGINEERING (11) \n",
+ "ENGIRT (6) \n",
+ "ENGRAIN (7) \n",
+ "ENGRAINING (10) \n",
+ "ENRAGE (6) \n",
+ "ENRAGING (8) \n",
+ "ENTER (5) \n",
+ "ENTERA (6) \n",
+ "ENTERER (7) \n",
+ "ENTERING (8) \n",
+ "ENTERTAIN (9) \n",
+ "ENTERTAINER (11) \n",
+ "ENTERTAINING (19) *\n",
+ "ENTIRE (6) \n",
+ "ENTRAIN (7) \n",
+ "ENTRAINER (9) \n",
+ "ENTRAINING (17) *\n",
+ "ENTRANT (7) \n",
+ "ENTREAT (7) \n",
+ "ENTREATING (17) *\n",
+ "ENTREE (6) \n",
+ "ERGATE (6) \n",
+ "ERNE (1) \n",
+ "ERRANT (6) \n",
+ "ERRATA (6) \n",
+ "ERRING (6) \n",
+ "ETAGERE (7) \n",
+ "ETERNE (6) \n",
+ "GAGER (5) \n",
+ "GAGGER (6) \n",
+ "GAINER (6) \n",
+ "GAITER (6) \n",
+ "GANGER (6) \n",
+ "GANGRENE (8) \n",
+ "GANGRENING (10) \n",
+ "GARAGE (6) \n",
+ "GARAGING (8) \n",
+ "GARGET (6) \n",
+ "GARNER (6) \n",
+ "GARNERING (9) \n",
+ "GARNET (6) \n",
+ "GARNI (5) \n",
+ "GARNIERITE (17) *\n",
+ "GARRET (6) \n",
+ "GARRING (7) \n",
+ "GARTER (6) \n",
+ "GARTERING (16) *\n",
+ "GEAR (1) \n",
+ "GEARING (7) \n",
+ "GENERA (6) \n",
+ "GENERATE (8) \n",
+ "GENERATING (17) *\n",
+ "GENRE (5) \n",
+ "GERENT (6) \n",
+ "GETTER (6) \n",
+ "GETTERING (9) \n",
+ "GINGER (6) \n",
+ "GINGERING (9) \n",
+ "GINNER (6) \n",
+ "GINNIER (7) \n",
+ "GIRN (1) \n",
+ "GIRNING (7) \n",
+ "GIRT (1) \n",
+ "GIRTING (7) \n",
+ "GITTERN (7) \n",
+ "GNAR (1) \n",
+ "GNARR (5) \n",
+ "GNARRING (8) \n",
+ "GNATTIER (15) *\n",
+ "GRAIN (5) \n",
+ "GRAINER (7) \n",
+ "GRAINIER (8) \n",
+ "GRAINING (8) \n",
+ "GRAN (1) \n",
+ "GRANA (5) \n",
+ "GRANGE (6) \n",
+ "GRANGER (7) \n",
+ "GRANITA (7) \n",
+ "GRANITE (14) *\n",
+ "GRANNIE (7) \n",
+ "GRANT (5) \n",
+ "GRANTEE (7) \n",
+ "GRANTER (7) \n",
+ "GRANTING (8) \n",
+ "GRAT (1) \n",
+ "GRATE (5) \n",
+ "GRATER (6) \n",
+ "GRATIN (6) \n",
+ "GRATINE (14) *\n",
+ "GRATINEE (15) *\n",
+ "GRATINEEING (18) *\n",
+ "GRATING (7) \n",
+ "GREAT (5) \n",
+ "GREATEN (7) \n",
+ "GREATENING (17) *\n",
+ "GREATER (7) \n",
+ "GREE (1) \n",
+ "GREEGREE (8) \n",
+ "GREEING (7) \n",
+ "GREEN (5) \n",
+ "GREENER (7) \n",
+ "GREENGAGE (9) \n",
+ "GREENIE (7) \n",
+ "GREENIER (8) \n",
+ "GREENING (8) \n",
+ "GREET (5) \n",
+ "GREETER (7) \n",
+ "GREETING (8) \n",
+ "GREGARINE (9) \n",
+ "GREIGE (6) \n",
+ "GRIG (1) \n",
+ "GRIGRI (6) \n",
+ "GRIN (1) \n",
+ "GRINNER (7) \n",
+ "GRINNING (8) \n",
+ "GRIT (1) \n",
+ "GRITTIER (8) \n",
+ "GRITTING (8) \n",
+ "IGNITER (7) \n",
+ "INANER (6) \n",
+ "INERRANT (8) \n",
+ "INERT (5) \n",
+ "INERTIA (7) \n",
+ "INERTIAE (8) \n",
+ "INGRAIN (7) \n",
+ "INGRAINING (10) \n",
+ "INGRATE (14) *\n",
+ "INGRATIATE (17) *\n",
+ "INGRATIATING (12) \n",
+ "INNER (5) \n",
+ "INTEGER (7) \n",
+ "INTEGRATE (16) *\n",
+ "INTEGRATING (18) *\n",
+ "INTENERATE (10) \n",
+ "INTENERATING (19) *\n",
+ "INTER (5) \n",
+ "INTERAGE (15) *\n",
+ "INTERGANG (16) *\n",
+ "INTERN (6) \n",
+ "INTERNE (7) \n",
+ "INTERNEE (8) \n",
+ "INTERNING (9) \n",
+ "INTERREGNA (17) *\n",
+ "INTERRING (9) \n",
+ "INTERTIE (8) \n",
+ "INTRANT (7) \n",
+ "INTREAT (7) \n",
+ "INTREATING (17) *\n",
+ "INTRIGANT (9) \n",
+ "IRATE (5) \n",
+ "IRATER (6) \n",
+ "IRING (5) \n",
+ "IRRIGATE (8) \n",
+ "IRRIGATING (10) \n",
+ "IRRITANT (8) \n",
+ "IRRITATE (8) \n",
+ "IRRITATING (10) \n",
+ "ITERANT (7) \n",
+ "ITERATE (7) \n",
+ "ITERATING (16) *\n",
+ "ITINERANT (9) \n",
+ "ITINERATE (9) \n",
+ "ITINERATING (18) *\n",
+ "NAGGER (6) \n",
+ "NAGGIER (7) \n",
+ "NAIRA (5) \n",
+ "NARINE (6) \n",
+ "NARRATE (7) \n",
+ "NARRATER (8) \n",
+ "NARRATING (9) \n",
+ "NATTER (6) \n",
+ "NATTERING (16) *\n",
+ "NATTIER (7) \n",
+ "NEAR (1) \n",
+ "NEARER (6) \n",
+ "NEARING (7) \n",
+ "NEATER (6) \n",
+ "NEGATER (7) \n",
+ "NETTER (6) \n",
+ "NETTIER (7) \n",
+ "NIGGER (6) \n",
+ "NITER (5) \n",
+ "NITERIE (7) \n",
+ "NITRATE (7) \n",
+ "NITRATING (9) \n",
+ "NITRE (5) \n",
+ "NITRITE (7) \n",
+ "NITTIER (7) \n",
+ "RAGA (1) \n",
+ "RAGE (1) \n",
+ "RAGEE (5) \n",
+ "RAGGEE (6) \n",
+ "RAGGING (7) \n",
+ "RAGI (1) \n",
+ "RAGING (6) \n",
+ "RAGTAG (6) \n",
+ "RAIA (1) \n",
+ "RAIN (1) \n",
+ "RAINIER (7) \n",
+ "RAINING (7) \n",
+ "RANEE (5) \n",
+ "RANG (1) \n",
+ "RANGE (5) \n",
+ "RANGER (6) \n",
+ "RANGIER (7) \n",
+ "RANGING (7) \n",
+ "RANI (1) \n",
+ "RANT (1) \n",
+ "RANTER (6) \n",
+ "RANTING (7) \n",
+ "RARE (1) \n",
+ "RARER (5) \n",
+ "RARING (6) \n",
+ "RATAN (5) \n",
+ "RATATAT (7) \n",
+ "RATE (1) \n",
+ "RATER (5) \n",
+ "RATINE (6) \n",
+ "RATING (6) \n",
+ "RATITE (6) \n",
+ "RATTAN (6) \n",
+ "RATTEEN (7) \n",
+ "RATTEN (6) \n",
+ "RATTENER (8) \n",
+ "RATTENING (16) *\n",
+ "RATTER (6) \n",
+ "RATTIER (7) \n",
+ "RATTING (7) \n",
+ "REAGENT (7) \n",
+ "REAGGREGATE (11) \n",
+ "REAGGREGATING (20) *\n",
+ "REAGIN (6) \n",
+ "REAR (1) \n",
+ "REARER (6) \n",
+ "REARING (7) \n",
+ "REARRANGE (9) \n",
+ "REARRANGING (11) \n",
+ "REATA (5) \n",
+ "REATTAIN (8) \n",
+ "REATTAINING (18) *\n",
+ "REEARN (6) \n",
+ "REEARNING (9) \n",
+ "REENGAGE (8) \n",
+ "REENGAGING (10) \n",
+ "REENGINEER (10) \n",
+ "REENGINEERING (13) \n",
+ "REENTER (7) \n",
+ "REENTERING (10) \n",
+ "REENTRANT (9) \n",
+ "REGAIN (6) \n",
+ "REGAINER (8) \n",
+ "REGAINING (9) \n",
+ "REGATTA (7) \n",
+ "REGEAR (6) \n",
+ "REGEARING (9) \n",
+ "REGENERATE (10) \n",
+ "REGENERATING (19) *\n",
+ "REGENT (6) \n",
+ "REGGAE (6) \n",
+ "REGINA (6) \n",
+ "REGINAE (7) \n",
+ "REGNA (5) \n",
+ "REGNANT (7) \n",
+ "REGRANT (7) \n",
+ "REGRANTING (17) *\n",
+ "REGRATE (7) \n",
+ "REGRATING (16) *\n",
+ "REGREEN (7) \n",
+ "REGREENING (10) \n",
+ "REGREET (7) \n",
+ "REGREETING (10) \n",
+ "REGRET (6) \n",
+ "REGRETTER (9) \n",
+ "REGRETTING (10) \n",
+ "REIGN (5) \n",
+ "REIGNING (8) \n",
+ "REIGNITE (8) \n",
+ "REIGNITING (10) \n",
+ "REIN (1) \n",
+ "REINING (7) \n",
+ "REINITIATE (10) \n",
+ "REINITIATING (19) *\n",
+ "REINTEGRATE (18) *\n",
+ "REINTEGRATING (20) *\n",
+ "REINTER (7) \n",
+ "REINTERRING (11) \n",
+ "REITERATE (9) \n",
+ "REITERATING (18) *\n",
+ "RENEGE (6) \n",
+ "RENEGER (7) \n",
+ "RENEGING (8) \n",
+ "RENIG (5) \n",
+ "RENIGGING (9) \n",
+ "RENIN (5) \n",
+ "RENITENT (8) \n",
+ "RENNET (6) \n",
+ "RENNIN (6) \n",
+ "RENT (1) \n",
+ "RENTE (5) \n",
+ "RENTER (6) \n",
+ "RENTIER (7) \n",
+ "RENTING (7) \n",
+ "RERAN (5) \n",
+ "RERIG (5) \n",
+ "RERIGGING (9) \n",
+ "RETAG (5) \n",
+ "RETAGGING (16) *\n",
+ "RETAIN (6) \n",
+ "RETAINER (8) \n",
+ "RETAINING (16) *\n",
+ "RETARGET (8) \n",
+ "RETARGETING (18) *\n",
+ "RETE (1) \n",
+ "RETEAR (6) \n",
+ "RETEARING (16) *\n",
+ "RETENE (6) \n",
+ "RETIA (5) \n",
+ "RETIARII (8) \n",
+ "RETIE (5) \n",
+ "RETINA (6) \n",
+ "RETINAE (7) \n",
+ "RETINE (6) \n",
+ "RETINENE (8) \n",
+ "RETINITE (8) \n",
+ "RETINT (6) \n",
+ "RETINTING (9) \n",
+ "RETIRANT (8) \n",
+ "RETIRE (6) \n",
+ "RETIREE (7) \n",
+ "RETIRER (7) \n",
+ "RETIRING (8) \n",
+ "RETRAIN (7) \n",
+ "RETRAINING (17) *\n",
+ "RETREAT (7) \n",
+ "RETREATANT (10) \n",
+ "RETREATER (9) \n",
+ "RETREATING (17) *\n",
+ "RETTING (7) \n",
+ "RIANT (5) \n",
+ "RIATA (5) \n",
+ "RIGGER (6) \n",
+ "RIGGING (7) \n",
+ "RING (1) \n",
+ "RINGENT (7) \n",
+ "RINGER (6) \n",
+ "RINGGIT (7) \n",
+ "RINGING (7) \n",
+ "RINNING (7) \n",
+ "RITE (1) \n",
+ "RITTER (6) \n",
+ "TAGGER (6) \n",
+ "TAGRAG (6) \n",
+ "TANAGER (7) \n",
+ "TANGERINE (16) *\n",
+ "TANGIER (14) *\n",
+ "TANNER (6) \n",
+ "TANTARA (7) \n",
+ "TANTRA (6) \n",
+ "TARE (1) \n",
+ "TARGE (5) \n",
+ "TARGET (6) \n",
+ "TARGETING (16) *\n",
+ "TARING (6) \n",
+ "TARN (1) \n",
+ "TARRE (5) \n",
+ "TARRIER (7) \n",
+ "TARRING (7) \n",
+ "TART (1) \n",
+ "TARTAN (6) \n",
+ "TARTANA (7) \n",
+ "TARTAR (6) \n",
+ "TARTER (6) \n",
+ "TARTING (7) \n",
+ "TARTRATE (8) \n",
+ "TATAR (5) \n",
+ "TATER (5) \n",
+ "TATTER (6) \n",
+ "TATTERING (16) *\n",
+ "TATTIER (7) \n",
+ "TEAR (1) \n",
+ "TEARER (6) \n",
+ "TEARIER (7) \n",
+ "TEARING (14) *\n",
+ "TEENAGER (8) \n",
+ "TEENER (6) \n",
+ "TEENIER (7) \n",
+ "TEETER (6) \n",
+ "TEETERING (9) \n",
+ "TENNER (6) \n",
+ "TENTER (6) \n",
+ "TENTERING (9) \n",
+ "TENTIER (7) \n",
+ "TERAI (5) \n",
+ "TERETE (6) \n",
+ "TERGA (5) \n",
+ "TERGITE (7) \n",
+ "TERN (1) \n",
+ "TERNATE (7) \n",
+ "TERNE (5) \n",
+ "TERRA (5) \n",
+ "TERRAE (6) \n",
+ "TERRAIN (7) \n",
+ "TERRANE (7) \n",
+ "TERRARIA (8) \n",
+ "TERREEN (7) \n",
+ "TERRENE (7) \n",
+ "TERRET (6) \n",
+ "TERRIER (7) \n",
+ "TERRINE (7) \n",
+ "TERRIT (6) \n",
+ "TERTIAN (7) \n",
+ "TETRA (5) \n",
+ "TETTER (6) \n",
+ "TIARA (5) \n",
+ "TIER (1) \n",
+ "TIERING (7) \n",
+ "TIGER (5) \n",
+ "TINIER (6) \n",
+ "TINNER (6) \n",
+ "TINNIER (7) \n",
+ "TINTER (6) \n",
+ "TIRE (1) \n",
+ "TIRING (6) \n",
+ "TITER (5) \n",
+ "TITRANT (7) \n",
+ "TITRATE (7) \n",
+ "TITRATING (9) \n",
+ "TITRE (5) \n",
+ "TITTER (6) \n",
+ "TITTERER (8) \n",
+ "TITTERING (9) \n",
+ "TRAGI (5) \n",
+ "TRAIN (5) \n",
+ "TRAINEE (7) \n",
+ "TRAINER (7) \n",
+ "TRAINING (8) \n",
+ "TRAIT (5) \n",
+ "TREAT (5) \n",
+ "TREATER (7) \n",
+ "TREATING (15) *\n",
+ "TREE (1) \n",
+ "TREEING (7) \n",
+ "TREEN (5) \n",
+ "TRET (1) \n",
+ "TRIAGE (6) \n",
+ "TRIAGING (8) \n",
+ "TRIENE (6) \n",
+ "TRIENNIA (8) \n",
+ "TRIER (5) \n",
+ "TRIG (1) \n",
+ "TRIGGER (7) \n",
+ "TRIGGERING (10) \n",
+ "TRIGGING (8) \n",
+ "TRINE (5) \n",
+ "TRINING (7) \n",
+ "TRINITARIAN (11) \n",
+ "TRITE (5) \n",
+ "TRITER (6) \n"
+ ]
+ }
+ ],
+ "source": [
+ "report(enable1)"
+ ]
+ }
+ ],
+ "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.7.2"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}