--- /dev/null
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "import string\n",
+ "import re\n",
+ "import random\n",
+ "import collections\n",
+ "import copy\n",
+ "\n",
+ "from enum import Enum\n",
+ "Direction = Enum('Direction', 'left right up down upleft upright downleft downright')\n",
+ " \n",
+ "delta = {Direction.left: (0, -1),Direction.right: (0, 1), \n",
+ " Direction.up: (-1, 0), Direction.down: (1, 0), \n",
+ " Direction.upleft: (-1, -1), Direction.upright: (-1, 1), \n",
+ " Direction.downleft: (1, -1), Direction.downright: (1, 1)}\n",
+ "\n",
+ "cat = ''.join\n",
+ "wcat = ' '.join\n",
+ "lcat = '\\n'.join"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "# all_words = [w.strip() for w in open('/usr/share/dict/british-english').readlines()\n",
+ "# if all(c in string.ascii_lowercase for c in w.strip())]\n",
+ "# words = [w for w in all_words\n",
+ "# if not any(w in w2 for w2 in all_words if w != w2)]\n",
+ "# open('wordsearch-words', 'w').write(lcat(words))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "['aardvarks',\n",
+ " 'abaci',\n",
+ " 'abacuses',\n",
+ " 'abaft',\n",
+ " 'abalones',\n",
+ " 'abandoned',\n",
+ " 'abandoning',\n",
+ " 'abandonment',\n",
+ " 'abandons',\n",
+ " 'abased']"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "ws_words = [w.strip() for w in open('wordsearch-words').readlines()\n",
+ " if all(c in string.ascii_lowercase for c in w.strip())]\n",
+ "ws_words[:10]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def empty_grid(w, h):\n",
+ " return [['.' for c in range(w)] for r in range(h)]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def show_grid(grid):\n",
+ " return lcat(cat(r) for r in grid)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "..........\n",
+ "..........\n",
+ "..........\n",
+ "..........\n",
+ "..........\n",
+ "..........\n",
+ "..........\n",
+ "..........\n",
+ "..........\n",
+ "..........\n"
+ ]
+ }
+ ],
+ "source": [
+ "grid = empty_grid(10, 10)\n",
+ "print(show_grid(grid))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def indices(grid, r, c, l, d):\n",
+ " dr, dc = delta[d]\n",
+ " w = len(grid[0])\n",
+ " h = len(grid)\n",
+ " inds = [(r + i * dr, c + i * dc) for i in range(l)]\n",
+ " return [(i, j) for i, j in inds\n",
+ " if i >= 0\n",
+ " if j >= 0\n",
+ " if i < h\n",
+ " if j < w]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def gslice(grid, r, c, l, d):\n",
+ " return [grid[i][j] for i, j in indices(grid, r, c, l, d)]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def set_grid(grid, r, c, d, word):\n",
+ " for (i, j), l in zip(indices(grid, r, c, len(word), d), word):\n",
+ " grid[i][j] = l\n",
+ " return grid"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "..........\n",
+ "..........\n",
+ "...t......\n",
+ "....e.....\n",
+ ".....s....\n",
+ "......t...\n",
+ ".......w..\n",
+ "........o.\n",
+ ".........r\n",
+ "..........\n"
+ ]
+ }
+ ],
+ "source": [
+ "set_grid(grid, 2, 3, Direction.downright, 'testword')\n",
+ "print(show_grid(grid))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'..e.....'"
+ ]
+ },
+ "execution_count": 12,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "cat(gslice(grid, 3, 2, 15, Direction.right))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "<_sre.SRE_Match object; span=(0, 4), match='keen'>"
+ ]
+ },
+ "execution_count": 13,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "re.match(cat(gslice(grid, 3, 2, 4, Direction.right)), 'keen')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "<_sre.SRE_Match object; span=(0, 3), match='kee'>"
+ ]
+ },
+ "execution_count": 14,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "re.match(cat(gslice(grid, 3, 2, 3, Direction.right)), 'keen')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "re.fullmatch(cat(gslice(grid, 3, 2, 3, Direction.right)), 'keen')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "re.match(cat(gslice(grid, 3, 2, 4, Direction.right)), 'kine')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def could_add(grid, r, c, d, word):\n",
+ " s = gslice(grid, r, c, len(word), d)\n",
+ " return re.fullmatch(cat(s), word)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "<_sre.SRE_Match object; span=(0, 4), match='keen'>"
+ ]
+ },
+ "execution_count": 18,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "could_add(grid, 3, 2, Direction.right, 'keen')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "could_add(grid, 3, 2, Direction.right, 'kine')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "<Direction.up: 3>"
+ ]
+ },
+ "execution_count": 20,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "random.choice(list(Direction))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def fill_grid(grid, words, word_count, max_attempts= 1000):\n",
+ " attempts = 0\n",
+ " added_words = []\n",
+ " w = len(grid[0])\n",
+ " h = len(grid)\n",
+ " while len(added_words) < word_count and attempts < max_attempts:\n",
+ " attempts += 1\n",
+ " r = random.randrange(w)\n",
+ " c = random.randrange(h)\n",
+ " word = random.choice(words)\n",
+ " d = random.choice(list(Direction))\n",
+ " if could_add(grid, r, c, d, word):\n",
+ " set_grid(grid, r, c, d, word)\n",
+ " added_words += [word]\n",
+ " attempts = 0\n",
+ " return grid, added_words"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "35"
+ ]
+ },
+ "execution_count": 22,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "g = empty_grid(20, 20)\n",
+ "g, ws = fill_grid(g, ws_words, 40)\n",
+ "len(ws)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "......swoopingg.l.up\n",
+ "..dunsnapped.n.i..ne\n",
+ ".cee.gninarci.m...er\n",
+ "sotpt......k.pmv..mv\n",
+ "euirca.d..c.n.a...pa\n",
+ "snduo.o.eo.e.lgs..ld\n",
+ "itndny.ctks.i.nos.oe\n",
+ "rroev.lsrsss..ifr.ys\n",
+ "eycno.eb.aeub.ttebas\n",
+ "tmetyr..asgetmuemebe\n",
+ "nerie....tvuu.dsraln\n",
+ "in.adbdmbecls.etocei\n",
+ "w..loeu.lilu..s.fh.d\n",
+ "...rtl.e.ec.l...eimw\n",
+ "..oac.d.v..y.e..rnao\n",
+ ".nrhgniknilsc.n..gyd\n",
+ ".pignippay...l.i..f.\n",
+ ".n..skcenrehtael..l.\n",
+ "g....popinjays.s..y.\n",
+ "gnimmugspuds.relppus\n",
+ "35 words added\n",
+ "ineluctably limpness countrymen slinking beaching restocking vellum convoyed winterises tusked leathernecks sugarcoated mayfly mulching popinjays magnitudes unsnapped prudential yapping spuds softest boron craning unemployable reformers bicycles swooping recondite dowdiness gumming pervades beveled valises suppler prated\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(show_grid(g))\n",
+ "print(len(ws), 'words added')\n",
+ "print(wcat(ws))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def present(grid, word):\n",
+ " w = len(grid[0])\n",
+ " h = len(grid)\n",
+ " for r in range(h):\n",
+ " for c in range(w):\n",
+ " for d in Direction:\n",
+ " if cat(gslice(grid, r, c, len(word), d)) == word:\n",
+ " return True, r, c, d\n",
+ " return False, 0, 0, list(Direction)[0]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "metadata": {
+ "collapsed": false,
+ "scrolled": true
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "ineluctably (True, 16, 15, <Direction.upleft: 5>)\n",
+ "limpness (True, 0, 16, <Direction.downleft: 7>)\n",
+ "countrymen (True, 2, 1, <Direction.down: 4>)\n",
+ "slinking (True, 15, 11, <Direction.left: 1>)\n",
+ "beaching (True, 8, 17, <Direction.down: 4>)\n",
+ "restocking (True, 9, 5, <Direction.upright: 6>)\n",
+ "vellum (True, 14, 8, <Direction.upright: 6>)\n",
+ "convoyed (True, 4, 4, <Direction.down: 4>)\n",
+ "winterises (True, 12, 0, <Direction.up: 3>)\n",
+ "tusked (True, 9, 12, <Direction.upleft: 5>)\n",
+ "leathernecks (True, 17, 15, <Direction.left: 1>)\n",
+ "sugarcoated (True, 11, 12, <Direction.upleft: 5>)\n",
+ "mayfly (True, 13, 18, <Direction.down: 4>)\n",
+ "mulching (True, 11, 7, <Direction.downleft: 7>)\n",
+ "popinjays (True, 18, 5, <Direction.right: 2>)\n",
+ "magnitudes (True, 3, 14, <Direction.down: 4>)\n",
+ "unsnapped (True, 1, 3, <Direction.right: 2>)\n",
+ "prudential (True, 3, 3, <Direction.down: 4>)\n",
+ "yapping (True, 16, 9, <Direction.left: 1>)\n",
+ "spuds (True, 19, 7, <Direction.right: 2>)\n",
+ "softest (True, 5, 15, <Direction.down: 4>)\n",
+ "boron (True, 11, 5, <Direction.downleft: 7>)\n",
+ "craning (True, 2, 11, <Direction.left: 1>)\n",
+ "unemployable (True, 0, 18, <Direction.down: 4>)\n",
+ "reformers (True, 14, 16, <Direction.up: 3>)\n",
+ "bicycles (True, 11, 8, <Direction.downright: 8>)\n",
+ "swooping (True, 0, 6, <Direction.right: 2>)\n",
+ "recondite (True, 10, 2, <Direction.up: 3>)\n",
+ "dowdiness (True, 15, 19, <Direction.up: 3>)\n",
+ "gumming (True, 19, 6, <Direction.left: 1>)\n",
+ "pervades (True, 0, 19, <Direction.down: 4>)\n",
+ "beveled (True, 8, 12, <Direction.downleft: 7>)\n",
+ "valises (True, 3, 15, <Direction.downleft: 7>)\n",
+ "suppler (True, 19, 19, <Direction.left: 1>)\n",
+ "prated (True, 16, 1, <Direction.upright: 6>)\n"
+ ]
+ }
+ ],
+ "source": [
+ "for w in ws:\n",
+ " print(w, present(g, w))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 26,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def interesting(grid, words):\n",
+ " dirs = set(present(grid, w)[3] for w in words)\n",
+ " return len(words) > 35 and len(words) < 40 and len(dirs) + 1 >= len(delta)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 27,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "execution_count": 27,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "interesting(g, ws)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 28,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def interesting_grid():\n",
+ " boring = True\n",
+ " while boring:\n",
+ " grid = empty_grid(20, 20)\n",
+ " grid, words = fill_grid(grid, ws_words, 40)\n",
+ " boring = not interesting(grid, words)\n",
+ " return grid, words"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 29,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "..reittonk..ss......\n",
+ "tinctured.wcee.....w\n",
+ "serutats.oyozm....o.\n",
+ "b....s..l.eoia...m.r\n",
+ "e.b.y.lf..lpsd..bgye\n",
+ "a.ist.no..less.ssrgm\n",
+ "m.gtfi.lo.orae.n.ura\n",
+ "edaei..i.cwi.mo..mor\n",
+ "demrn..b..in.m...psk\n",
+ "epya...e..sgm....ile\n",
+ "slsg...l..hi.....nrd\n",
+ "tekisyassesdepeebeum\n",
+ "rtec.gninretni...sfo\n",
+ "oiinsetse..baggy.snd\n",
+ "ynn....p..sebircsaui\n",
+ "egs.noitasiretupmocf\n",
+ "r.....artefacts....y\n",
+ "s.....seilaog.winosi\n",
+ ".....eyelidsegener.n\n",
+ "regicidesesopatxuj.g\n",
+ "38 words added; 7 directions\n",
+ "wombs persimmons computerisation ascribes coopering goalies beamed modifying insets cigarets statures libels remarked baggy juxtaposes mesdames grumpiness artefacts skeins assizes inflow depleting beeped reneges interning yellowish regicides eyelids cools orgy nifty knottier destroyers unfurls tinctured bigamy winos essays\n"
+ ]
+ }
+ ],
+ "source": [
+ "g, ws = interesting_grid()\n",
+ "print(show_grid(g))\n",
+ "print(len(ws), 'words added; ', len(set(present(g, w)[3] for w in ws)), 'directions')\n",
+ "print(wcat(ws))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 30,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def datafile(name, sep='\\t'):\n",
+ " \"\"\"Read key,value pairs from file.\n",
+ " \"\"\"\n",
+ " with open(name) as f:\n",
+ " for line in f:\n",
+ " splits = line.split(sep)\n",
+ " yield [splits[0], int(splits[1])]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def normalise(frequencies):\n",
+ " \"\"\"Scale a set of frequencies so they sum to one\n",
+ " \n",
+ " >>> sorted(normalise({1: 1, 2: 0}).items())\n",
+ " [(1, 1.0), (2, 0.0)]\n",
+ " >>> sorted(normalise({1: 1, 2: 1}).items())\n",
+ " [(1, 0.5), (2, 0.5)]\n",
+ " >>> sorted(normalise({1: 1, 2: 1, 3: 1}).items()) # doctest: +ELLIPSIS\n",
+ " [(1, 0.333...), (2, 0.333...), (3, 0.333...)]\n",
+ " >>> sorted(normalise({1: 1, 2: 2, 3: 1}).items())\n",
+ " [(1, 0.25), (2, 0.5), (3, 0.25)]\n",
+ " \"\"\"\n",
+ " length = sum(f for f in frequencies.values())\n",
+ " return collections.defaultdict(int, ((k, v / length) \n",
+ " for (k, v) in frequencies.items()))\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "english_counts = collections.Counter(dict(datafile('count_1l.txt')))\n",
+ "normalised_english_counts = normalise(english_counts)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 34,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "wordsearch_counts = collections.Counter(cat(ws_words))\n",
+ "normalised_wordsearch_counts = normalise(wordsearch_counts)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def weighted_choice(d):\n",
+ " \"\"\"Generate random item from a dictionary of item counts\n",
+ " \"\"\"\n",
+ " target = random.uniform(0, sum(d.values()))\n",
+ " cuml = 0.0\n",
+ " for (l, p) in d.items():\n",
+ " cuml += p\n",
+ " if cuml > target:\n",
+ " return l\n",
+ " return None\n",
+ "\n",
+ "def random_english_letter():\n",
+ " \"\"\"Generate a random letter based on English letter counts\n",
+ " \"\"\"\n",
+ " return weighted_choice(normalised_english_counts)\n",
+ "\n",
+ "def random_wordsearch_letter():\n",
+ " \"\"\"Generate a random letter based on wordsearch letter counts\n",
+ " \"\"\"\n",
+ " return weighted_choice(normalised_wordsearch_counts)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 36,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'aaaaaaaabcccddddeeeeeeeeeeeeeeefffggghhhhhiiiiiillllmnnnnnnoooooooooprrsssssssssssssttttttttuuuvwwwy'"
+ ]
+ },
+ "execution_count": 36,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "cat(sorted(random_english_letter() for i in range(100)))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 37,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'aaaaaabcccddddddeeeeeeggggghhiiiiiiiiiiiiklllmmmmnnnnnnnnnnoooooooooppprrrrrrrrrssssssttttttuuuwwwyy'"
+ ]
+ },
+ "execution_count": 37,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "cat(sorted(random_wordsearch_letter() for i in range(100)))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 38,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'a'"
+ ]
+ },
+ "execution_count": 38,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "random_wordsearch_letter()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 39,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def pad_grid(g0):\n",
+ " grid = copy.deepcopy(g0)\n",
+ " w = len(grid[0])\n",
+ " h = len(grid)\n",
+ " for r in range(h):\n",
+ " for c in range(w):\n",
+ " if grid[r][c] == '.':\n",
+ " grid[r][c] = random_wordsearch_letter()\n",
+ " return grid"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 40,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "streittonkorsssatnal\n",
+ "tincturedswceedrlnuw\n",
+ "serutatsloyozmeieiot\n",
+ "baanfsollleoiasnlmar\n",
+ "ewblyhlfetlpsdyvbgye\n",
+ "aeistonoeilessassrgm\n",
+ "mlgtfitloioraeenwura\n",
+ "edaeiupiscwiamoygmor\n",
+ "demrnasbhcinsmiiapsk\n",
+ "epyakraedrsgmolsnile\n",
+ "slsgtuoloihireneonrd\n",
+ "tekisyassesdepeebeum\n",
+ "rtecigninretnincesfo\n",
+ "oiinsetseddbaggydsnd\n",
+ "ynnnsfapcfsebircsaui\n",
+ "egsonoitasiretupmocf\n",
+ "raioelartefactseawfy\n",
+ "speonsseilaogrwinosi\n",
+ "wrndfeyelidsegenerln\n",
+ "regicidesesopatxujrg\n"
+ ]
+ }
+ ],
+ "source": [
+ "padded = pad_grid(g)\n",
+ "print(show_grid(padded))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 41,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "..reittonk..ss......\n",
+ "tinctured.wcee.....w\n",
+ "serutats.oyozm....o.\n",
+ "b....s..l.eoia...m.r\n",
+ "e.b.y.lf..lpsd..bgye\n",
+ "a.ist.no..less.ssrgm\n",
+ "m.gtfi.lo.orae.n.ura\n",
+ "edaei..i.cwi.mo..mor\n",
+ "demrn..b..in.m...psk\n",
+ "epya...e..sgm....ile\n",
+ "slsg...l..hi.....nrd\n",
+ "tekisyassesdepeebeum\n",
+ "rtec.gninretni...sfo\n",
+ "oiinsetse..baggy.snd\n",
+ "ynn....p..sebircsaui\n",
+ "egs.noitasiretupmocf\n",
+ "r.....artefacts....y\n",
+ "s.....seilaog.winosi\n",
+ ".....eyelidsegener.n\n",
+ "regicidesesopatxuj.g\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(show_grid(g))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 42,
+ "metadata": {
+ "collapsed": false,
+ "scrolled": true
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "wombs (True, 1, 19, <Direction.downleft: 7>)\n",
+ "persimmons (True, 14, 7, <Direction.upright: 6>)\n",
+ "computerisation (True, 15, 18, <Direction.left: 1>)\n",
+ "ascribes (True, 14, 17, <Direction.left: 1>)\n",
+ "coopering (True, 1, 11, <Direction.down: 4>)\n",
+ "goalies (True, 17, 12, <Direction.left: 1>)\n",
+ "beamed (True, 3, 0, <Direction.down: 4>)\n",
+ "modifying (True, 11, 19, <Direction.down: 4>)\n",
+ "insets (True, 13, 2, <Direction.right: 2>)\n",
+ "cigarets (True, 12, 3, <Direction.up: 3>)\n",
+ "statures (True, 2, 7, <Direction.left: 1>)\n",
+ "libels (True, 6, 7, <Direction.down: 4>)\n",
+ "remarked (True, 3, 19, <Direction.down: 4>)\n",
+ "baggy (True, 13, 11, <Direction.right: 2>)\n",
+ "juxtaposes (True, 19, 17, <Direction.left: 1>)\n",
+ "mesdames (True, 7, 13, <Direction.up: 3>)\n",
+ "grumpiness (True, 4, 17, <Direction.down: 4>)\n",
+ "artefacts (True, 16, 6, <Direction.right: 2>)\n",
+ "skeins (True, 10, 2, <Direction.down: 4>)\n",
+ "assizes (True, 6, 12, <Direction.up: 3>)\n",
+ "inflow (True, 6, 5, <Direction.upright: 6>)\n",
+ "depleting (True, 7, 1, <Direction.down: 4>)\n",
+ "beeped (True, 11, 16, <Direction.left: 1>)\n",
+ "reneges (True, 18, 17, <Direction.left: 1>)\n",
+ "interning (True, 12, 13, <Direction.left: 1>)\n",
+ "yellowish (True, 2, 10, <Direction.down: 4>)\n",
+ "regicides (True, 19, 0, <Direction.right: 2>)\n",
+ "eyelids (True, 18, 5, <Direction.right: 2>)\n",
+ "cools (True, 7, 9, <Direction.upleft: 5>)\n",
+ "orgy (True, 7, 18, <Direction.up: 3>)\n",
+ "nifty (True, 8, 4, <Direction.up: 3>)\n",
+ "knottier (True, 0, 9, <Direction.left: 1>)\n",
+ "destroyers (True, 8, 0, <Direction.down: 4>)\n",
+ "unfurls (True, 14, 18, <Direction.up: 3>)\n",
+ "tinctured (True, 1, 0, <Direction.right: 2>)\n",
+ "bigamy (True, 4, 2, <Direction.down: 4>)\n",
+ "winos (True, 17, 14, <Direction.right: 2>)\n",
+ "essays (True, 11, 9, <Direction.left: 1>)\n"
+ ]
+ }
+ ],
+ "source": [
+ "for w in ws:\n",
+ " print(w, present(padded, w))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 43,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def decoys(grid, words, all_words, limit=60):\n",
+ " decoy_words = []\n",
+ " while len(words) + len(decoy_words) < limit:\n",
+ " d = random.choice(all_words)\n",
+ " if d not in words and not present(grid, d)[0]:\n",
+ " decoy_words += [d]\n",
+ " return decoy_words"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 45,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "['blindfolding',\n",
+ " 'televised',\n",
+ " 'climaxed',\n",
+ " 'autumns',\n",
+ " 'aquaria',\n",
+ " 'bilks',\n",
+ " 'psychologies',\n",
+ " 'sparkled',\n",
+ " 'dorkiest',\n",
+ " 'corollas',\n",
+ " 'polygons',\n",
+ " 'accessioning',\n",
+ " 'bubbled',\n",
+ " 'astringency',\n",
+ " 'debunking',\n",
+ " 'cannery',\n",
+ " 'exhilarates',\n",
+ " 'overzealous',\n",
+ " 'primping',\n",
+ " 'geckos',\n",
+ " 'admiration',\n",
+ " 'misconstructions']"
+ ]
+ },
+ "execution_count": 45,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "ds = decoys(padded, ws, ws_words)\n",
+ "ds"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 46,
+ "metadata": {
+ "collapsed": false,
+ "scrolled": true
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "wombs (True, 1, 19, <Direction.downleft: 7>)\n",
+ "persimmons (True, 14, 7, <Direction.upright: 6>)\n",
+ "computerisation (True, 15, 18, <Direction.left: 1>)\n",
+ "ascribes (True, 14, 17, <Direction.left: 1>)\n",
+ "coopering (True, 1, 11, <Direction.down: 4>)\n",
+ "goalies (True, 17, 12, <Direction.left: 1>)\n",
+ "beamed (True, 3, 0, <Direction.down: 4>)\n",
+ "modifying (True, 11, 19, <Direction.down: 4>)\n",
+ "insets (True, 13, 2, <Direction.right: 2>)\n",
+ "cigarets (True, 12, 3, <Direction.up: 3>)\n",
+ "statures (True, 2, 7, <Direction.left: 1>)\n",
+ "libels (True, 6, 7, <Direction.down: 4>)\n",
+ "remarked (True, 3, 19, <Direction.down: 4>)\n",
+ "baggy (True, 13, 11, <Direction.right: 2>)\n",
+ "juxtaposes (True, 19, 17, <Direction.left: 1>)\n",
+ "mesdames (True, 7, 13, <Direction.up: 3>)\n",
+ "grumpiness (True, 4, 17, <Direction.down: 4>)\n",
+ "artefacts (True, 16, 6, <Direction.right: 2>)\n",
+ "skeins (True, 10, 2, <Direction.down: 4>)\n",
+ "assizes (True, 6, 12, <Direction.up: 3>)\n",
+ "inflow (True, 6, 5, <Direction.upright: 6>)\n",
+ "depleting (True, 7, 1, <Direction.down: 4>)\n",
+ "beeped (True, 11, 16, <Direction.left: 1>)\n",
+ "reneges (True, 18, 17, <Direction.left: 1>)\n",
+ "interning (True, 12, 13, <Direction.left: 1>)\n",
+ "yellowish (True, 2, 10, <Direction.down: 4>)\n",
+ "regicides (True, 19, 0, <Direction.right: 2>)\n",
+ "eyelids (True, 18, 5, <Direction.right: 2>)\n",
+ "cools (True, 7, 9, <Direction.upleft: 5>)\n",
+ "orgy (True, 7, 18, <Direction.up: 3>)\n",
+ "nifty (True, 8, 4, <Direction.up: 3>)\n",
+ "knottier (True, 0, 9, <Direction.left: 1>)\n",
+ "destroyers (True, 8, 0, <Direction.down: 4>)\n",
+ "unfurls (True, 14, 18, <Direction.up: 3>)\n",
+ "tinctured (True, 1, 0, <Direction.right: 2>)\n",
+ "bigamy (True, 4, 2, <Direction.down: 4>)\n",
+ "winos (True, 17, 14, <Direction.right: 2>)\n",
+ "essays (True, 11, 9, <Direction.left: 1>)\n",
+ "blindfolding (False, 0, 0, <Direction.left: 1>)\n",
+ "televised (False, 0, 0, <Direction.left: 1>)\n",
+ "climaxed (False, 0, 0, <Direction.left: 1>)\n",
+ "autumns (False, 0, 0, <Direction.left: 1>)\n",
+ "aquaria (False, 0, 0, <Direction.left: 1>)\n",
+ "bilks (False, 0, 0, <Direction.left: 1>)\n",
+ "psychologies (False, 0, 0, <Direction.left: 1>)\n",
+ "sparkled (False, 0, 0, <Direction.left: 1>)\n",
+ "dorkiest (False, 0, 0, <Direction.left: 1>)\n",
+ "corollas (False, 0, 0, <Direction.left: 1>)\n",
+ "polygons (False, 0, 0, <Direction.left: 1>)\n",
+ "accessioning (False, 0, 0, <Direction.left: 1>)\n",
+ "bubbled (False, 0, 0, <Direction.left: 1>)\n",
+ "astringency (False, 0, 0, <Direction.left: 1>)\n",
+ "debunking (False, 0, 0, <Direction.left: 1>)\n",
+ "cannery (False, 0, 0, <Direction.left: 1>)\n",
+ "exhilarates (False, 0, 0, <Direction.left: 1>)\n",
+ "overzealous (False, 0, 0, <Direction.left: 1>)\n",
+ "primping (False, 0, 0, <Direction.left: 1>)\n",
+ "geckos (False, 0, 0, <Direction.left: 1>)\n",
+ "admiration (False, 0, 0, <Direction.left: 1>)\n",
+ "misconstructions (False, 0, 0, <Direction.left: 1>)\n"
+ ]
+ }
+ ],
+ "source": [
+ "for w in ws + ds:\n",
+ " print(w, present(padded, w))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 47,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "tuiababblerssknalbft\n",
+ "gnewerodeswskrowerif\n",
+ "nballooningosfriedum\n",
+ "iuebksidesaddlessldt\n",
+ "mrncdbmnnyllasuaceet\n",
+ "udiesnettirwtsohglli\n",
+ "uepoeyremeenoyreveum\n",
+ "cnuselitnegfatterbdp\n",
+ "asslmulleveiadieneer\n",
+ "visnmewsrelrsbtleddi\n",
+ "crouchinghogetrzooms\n",
+ "dstrihstaewseteaotoo\n",
+ "vkssrdovulatesrldgon\n",
+ "snnvengefullyuiawets\n",
+ "uiofendelleuferrcosi\n",
+ "ahsdenoixelpmocluitt\n",
+ "vcdauthorisesnfsnenp\n",
+ "eyoelmferociouslypng\n",
+ "lngimmpauperisedtnon\n",
+ "ytnthstnemecitnehhpc\n",
+ "38 words added; 7 directions\n",
+ "Present: abrades authorises babblers ballooning blanks causally chinks complexioned crouching deluded emery enticements erodes everyone fatter ferociously fireworks fried gentiles ghostwritten godsons imprisons mews ovulates owlets pauperised refuelled retracing sidesaddles suavely supine sweatshirts unburdens vacuuming vellum vengefully yeti zooms\n",
+ "Decoys: chastened doodling ethnologists executrixes freelancing generously halfheartedness harries kinswoman mangles narrowly optimising oversimplifies polystyrene recounts riffing slicking smackers squashy thirsting tiebreakers unexampled\n"
+ ]
+ }
+ ],
+ "source": [
+ "g, ws = interesting_grid()\n",
+ "p = pad_grid(g)\n",
+ "ds = decoys(p, ws, ws_words)\n",
+ "print(show_grid(p))\n",
+ "print(len(ws), 'words added; ', len(set(present(g, w)[3] for w in ws)), 'directions')\n",
+ "print('Present:', wcat(sorted(ws)))\n",
+ "print('Decoys:', wcat(sorted(ds)))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 49,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "0\n",
+ "1\n",
+ "2\n",
+ "3\n",
+ "4\n",
+ "5\n",
+ "6\n",
+ "7\n",
+ "8\n",
+ "9\n",
+ "10\n",
+ "11\n",
+ "12\n",
+ "13\n",
+ "14\n",
+ "15\n",
+ "16\n",
+ "17\n",
+ "18\n",
+ "19\n"
+ ]
+ }
+ ],
+ "source": [
+ "for i in range(20):\n",
+ " print(i)\n",
+ " g, ws = interesting_grid()\n",
+ " p = pad_grid(g)\n",
+ " ds = decoys(p, ws, ws_words)\n",
+ " with open('wordsearch{:02}.txt'.format(i), 'w') as f:\n",
+ " f.write('20x20\\n')\n",
+ " f.write(show_grid(p))\n",
+ " f.write('\\n')\n",
+ " f.write(lcat(sorted(ws + ds)))\n",
+ " with open('wordsearch-solution{:02}.txt'.format(i), 'w') as f:\n",
+ " f.write('20x20\\n')\n",
+ " f.write(show_grid(g))\n",
+ " f.write('\\n')\n",
+ " f.write(lcat(sorted(ws)) + '\\n\\n')\n",
+ " f.write(lcat(sorted(ds)))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "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.5.2+"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
--- /dev/null
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 106,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "import string\n",
+ "import re\n",
+ "import collections\n",
+ "import copy\n",
+ "import os\n",
+ "\n",
+ "from enum import Enum\n",
+ "Direction = Enum('Direction', 'left right up down upleft upright downleft downright')\n",
+ " \n",
+ "delta = {Direction.left: (0, -1),Direction.right: (0, 1), \n",
+ " Direction.up: (-1, 0), Direction.down: (1, 0), \n",
+ " Direction.upleft: (-1, -1), Direction.upright: (-1, 1), \n",
+ " Direction.downleft: (1, -1), Direction.downright: (1, 1)}\n",
+ "\n",
+ "cat = ''.join\n",
+ "wcat = ' '.join\n",
+ "lcat = '\\n'.join"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 46,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def empty_grid(w, h):\n",
+ " return [['.' for c in range(w)] for r in range(h)]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 47,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def show_grid(grid):\n",
+ " return lcat(cat(r) for r in grid)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 48,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def indices(grid, r, c, l, d):\n",
+ " dr, dc = delta[d]\n",
+ " w = len(grid[0])\n",
+ " h = len(grid)\n",
+ " inds = [(r + i * dr, c + i * dc) for i in range(l)]\n",
+ " return [(i, j) for i, j in inds\n",
+ " if i >= 0\n",
+ " if j >= 0\n",
+ " if i < h\n",
+ " if j < w]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 49,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def gslice(grid, r, c, l, d):\n",
+ " return [grid[i][j] for i, j in indices(grid, r, c, l, d)]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 50,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def set_grid(grid, r, c, d, word):\n",
+ " for (i, j), l in zip(indices(grid, r, c, len(word), d), word):\n",
+ " grid[i][j] = l\n",
+ " return grid"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 51,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def could_add(grid, r, c, d, word):\n",
+ " s = gslice(grid, r, c, len(word), d)\n",
+ " return re.fullmatch(cat(s), word)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 52,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def present(grid, word):\n",
+ " w = len(grid[0])\n",
+ " h = len(grid)\n",
+ " for r in range(h):\n",
+ " for c in range(w):\n",
+ " for d in Direction:\n",
+ " if cat(gslice(grid, r, c, len(word), d)) == word:\n",
+ " return True, r, c, d\n",
+ " return False, 0, 0, list(Direction)[0]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 53,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def pad_grid(g0):\n",
+ " grid = copy.deepcopy(g0)\n",
+ " w = len(grid[0])\n",
+ " h = len(grid)\n",
+ " for r in range(h):\n",
+ " for c in range(w):\n",
+ " if grid[r][c] == '.':\n",
+ " grid[r][c] = random_wordsearch_letter()\n",
+ " return grid"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 54,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "def read_wordsearch(fn):\n",
+ " lines = [l.strip() for l in open(fn).readlines()]\n",
+ " w, h = [int(s) for s in lines[0].split('x')]\n",
+ " grid = lines[1:h+1]\n",
+ " words = lines[h+1:]\n",
+ " return w, h, grid, words"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 77,
+ "metadata": {
+ "collapsed": false,
+ "scrolled": true
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(['iisnoitpircserpoacos',\n",
+ " 'eohgiodnpgbeautypoar',\n",
+ " 'arsllorcsnestdomofne',\n",
+ " 'irfdeemseirrgarnlfrb',\n",
+ " 'tclumpingkoeasevoedm',\n",
+ " 'hetobsibecgftdcmgrvi',\n",
+ " 'isesrepoeuelmsriyset',\n",
+ " 'eknaodouusnoedetoxes',\n",
+ " 'vldwiarsoiogsstiedue',\n",
+ " 'ehealcegdsuidsesuifi',\n",
+ " 'dirrsiidnesepmdaelnc',\n",
+ " 'nierrrsdeibuwpegriga',\n",
+ " 'sprirrsyebniednusirb',\n",
+ " 'eeboraisahmieeaycnnw',\n",
+ " 'irrrnbrnepcmanhriuug',\n",
+ " 'tjasdypeykuanppetiot',\n",
+ " 'nuvaaaicssndonrppmer',\n",
+ " 'areeioassenyocoaooze',\n",
+ " 'hellesoamalabruptest',\n",
+ " 'csygsfyosinightclubs'],\n",
+ " ['abruptest',\n",
+ " 'apology',\n",
+ " 'assumed',\n",
+ " 'barricades',\n",
+ " 'beauty',\n",
+ " 'bravely',\n",
+ " 'bravos',\n",
+ " 'burlier',\n",
+ " 'chanties',\n",
+ " 'clumping',\n",
+ " 'coached',\n",
+ " 'coffers',\n",
+ " 'coyness',\n",
+ " 'decriminalisation',\n",
+ " 'detoxes',\n",
+ " 'differs',\n",
+ " 'duelled',\n",
+ " 'duplicating',\n",
+ " 'elaborates',\n",
+ " 'embroils',\n",
+ " 'encirclement',\n",
+ " 'erogenous',\n",
+ " 'facsimiled',\n",
+ " 'festers',\n",
+ " 'flickering',\n",
+ " 'fusible',\n",
+ " 'gluiest',\n",
+ " 'golfers',\n",
+ " 'interpolations',\n",
+ " 'involved',\n",
+ " 'irony',\n",
+ " 'lithographed',\n",
+ " 'nabbed',\n",
+ " 'nightclubs',\n",
+ " 'oblongs',\n",
+ " 'optics',\n",
+ " 'orphaned',\n",
+ " 'overstates',\n",
+ " 'paining',\n",
+ " 'papery',\n",
+ " 'perjures',\n",
+ " 'prescriptions',\n",
+ " 'prissier',\n",
+ " 'rallies',\n",
+ " 'rebated',\n",
+ " 'reneges',\n",
+ " 'saleswomen',\n",
+ " 'scrolls',\n",
+ " 'searing',\n",
+ " 'slobbering',\n",
+ " 'soups',\n",
+ " 'sucking',\n",
+ " 'tenderer',\n",
+ " 'thieved',\n",
+ " 'timbers',\n",
+ " 'toiletries',\n",
+ " 'unabashed',\n",
+ " 'warriors',\n",
+ " 'wimpy',\n",
+ " 'wriest'])"
+ ]
+ },
+ "execution_count": 77,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "width, height, g, ws = read_wordsearch('wordsearch1.txt')\n",
+ "g, ws"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 78,
+ "metadata": {
+ "collapsed": false,
+ "scrolled": true
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "abruptest (True, 18, 11, <Direction.right: 2>)\n",
+ "apology (True, 0, 16, <Direction.down: 4>)\n",
+ "assumed (True, 18, 7, <Direction.upright: 6>)\n",
+ "barricades (True, 14, 5, <Direction.up: 3>)\n",
+ "beauty (True, 1, 10, <Direction.right: 2>)\n",
+ "bravely (True, 13, 2, <Direction.down: 4>)\n",
+ "bravos (False, 0, 0, <Direction.left: 1>)\n",
+ "burlier (False, 0, 0, <Direction.left: 1>)\n",
+ "chanties (True, 19, 0, <Direction.up: 3>)\n",
+ "clumping (True, 4, 1, <Direction.right: 2>)\n",
+ "coached (True, 17, 13, <Direction.upleft: 5>)\n",
+ "coffers (True, 0, 17, <Direction.down: 4>)\n",
+ "coyness (True, 17, 13, <Direction.left: 1>)\n",
+ "decriminalisation (False, 0, 0, <Direction.left: 1>)\n",
+ "detoxes (True, 7, 13, <Direction.right: 2>)\n",
+ "differs (False, 0, 0, <Direction.left: 1>)\n",
+ "duelled (False, 0, 0, <Direction.left: 1>)\n",
+ "duplicating (False, 0, 0, <Direction.left: 1>)\n",
+ "elaborates (False, 0, 0, <Direction.left: 1>)\n",
+ "embroils (True, 3, 4, <Direction.down: 4>)\n",
+ "encirclement (False, 0, 0, <Direction.left: 1>)\n",
+ "erogenous (True, 2, 10, <Direction.down: 4>)\n",
+ "facsimiled (False, 0, 0, <Direction.left: 1>)\n",
+ "festers (False, 0, 0, <Direction.left: 1>)\n",
+ "flickering (False, 0, 0, <Direction.left: 1>)\n",
+ "fusible (False, 0, 0, <Direction.left: 1>)\n",
+ "gluiest (True, 11, 18, <Direction.upleft: 5>)\n",
+ "golfers (True, 8, 11, <Direction.up: 3>)\n",
+ "interpolations (False, 0, 0, <Direction.left: 1>)\n",
+ "involved (False, 0, 0, <Direction.left: 1>)\n",
+ "irony (True, 11, 1, <Direction.downright: 8>)\n",
+ "lithographed (False, 0, 0, <Direction.left: 1>)\n",
+ "nabbed (True, 14, 7, <Direction.upright: 6>)\n",
+ "nightclubs (True, 19, 10, <Direction.right: 2>)\n",
+ "oblongs (False, 0, 0, <Direction.left: 1>)\n",
+ "optics (True, 17, 16, <Direction.up: 3>)\n",
+ "orphaned (True, 17, 14, <Direction.up: 3>)\n",
+ "overstates (False, 0, 0, <Direction.left: 1>)\n",
+ "paining (True, 15, 13, <Direction.upleft: 5>)\n",
+ "papery (True, 18, 15, <Direction.up: 3>)\n",
+ "perjures (True, 12, 1, <Direction.down: 4>)\n",
+ "prescriptions (True, 0, 14, <Direction.left: 1>)\n",
+ "prissier (True, 15, 6, <Direction.up: 3>)\n",
+ "rallies (False, 0, 0, <Direction.left: 1>)\n",
+ "rebated (False, 0, 0, <Direction.left: 1>)\n",
+ "reneges (False, 0, 0, <Direction.left: 1>)\n",
+ "saleswomen (False, 0, 0, <Direction.left: 1>)\n",
+ "scrolls (True, 2, 8, <Direction.left: 1>)\n",
+ "searing (True, 8, 13, <Direction.downright: 8>)\n",
+ "slobbering (False, 0, 0, <Direction.left: 1>)\n",
+ "soups (True, 9, 9, <Direction.upleft: 5>)\n",
+ "sucking (True, 7, 9, <Direction.up: 3>)\n",
+ "tenderer (True, 5, 2, <Direction.down: 4>)\n",
+ "thieved (True, 4, 0, <Direction.down: 4>)\n",
+ "timbers (True, 6, 19, <Direction.up: 3>)\n",
+ "toiletries (False, 0, 0, <Direction.left: 1>)\n",
+ "unabashed (False, 0, 0, <Direction.left: 1>)\n",
+ "warriors (True, 8, 3, <Direction.down: 4>)\n",
+ "wimpy (True, 11, 12, <Direction.downleft: 7>)\n",
+ "wriest (True, 13, 19, <Direction.upleft: 5>)\n"
+ ]
+ }
+ ],
+ "source": [
+ "for w in ws:\n",
+ " print(w, present(g, w))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Which words are present?"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 79,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "['abruptest',\n",
+ " 'apology',\n",
+ " 'assumed',\n",
+ " 'barricades',\n",
+ " 'beauty',\n",
+ " 'bravely',\n",
+ " 'chanties',\n",
+ " 'clumping',\n",
+ " 'coached',\n",
+ " 'coffers',\n",
+ " 'coyness',\n",
+ " 'detoxes',\n",
+ " 'embroils',\n",
+ " 'erogenous',\n",
+ " 'gluiest',\n",
+ " 'golfers',\n",
+ " 'irony',\n",
+ " 'nabbed',\n",
+ " 'nightclubs',\n",
+ " 'optics',\n",
+ " 'orphaned',\n",
+ " 'paining',\n",
+ " 'papery',\n",
+ " 'perjures',\n",
+ " 'prescriptions',\n",
+ " 'prissier',\n",
+ " 'scrolls',\n",
+ " 'searing',\n",
+ " 'soups',\n",
+ " 'sucking',\n",
+ " 'tenderer',\n",
+ " 'thieved',\n",
+ " 'timbers',\n",
+ " 'warriors',\n",
+ " 'wimpy',\n",
+ " 'wriest']"
+ ]
+ },
+ "execution_count": 79,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "[w for w in ws if present(g, w)[0]]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "What is the longest word that is present?"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 88,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'prescriptions'"
+ ]
+ },
+ "execution_count": 88,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "sorted([w for w in ws if present(g, w)[0]], key=len)[-1]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "What is the longest word that is absent?"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 89,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'decriminalisation'"
+ ]
+ },
+ "execution_count": 89,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "sorted([w for w in ws if not present(g, w)[0]], key=len)[-1]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "How many letters are unused?"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 82,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "143"
+ ]
+ },
+ "execution_count": 82,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "g0 = empty_grid(width, height)\n",
+ "for w in ws:\n",
+ " p, r, c, d = present(g, w)\n",
+ " if p:\n",
+ " set_grid(g0, r, c, d, w)\n",
+ "len([c for c in cat(cat(l) for l in g0) if c == '.'])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "What is the longest word you can make form the leftover letters?"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 83,
+ "metadata": {
+ "collapsed": false,
+ "scrolled": true
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Counter({'a': 11,\n",
+ " 'b': 2,\n",
+ " 'c': 3,\n",
+ " 'd': 10,\n",
+ " 'e': 21,\n",
+ " 'f': 3,\n",
+ " 'g': 4,\n",
+ " 'h': 2,\n",
+ " 'i': 15,\n",
+ " 'k': 2,\n",
+ " 'l': 3,\n",
+ " 'm': 7,\n",
+ " 'n': 10,\n",
+ " 'o': 13,\n",
+ " 'p': 3,\n",
+ " 'r': 9,\n",
+ " 's': 12,\n",
+ " 't': 2,\n",
+ " 'u': 6,\n",
+ " 'v': 2,\n",
+ " 'y': 2,\n",
+ " 'z': 1})"
+ ]
+ },
+ "execution_count": 83,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "unused_letters = [l for l, u in zip((c for c in cat(cat(l) for l in g)), (c for c in cat(cat(l) for l in g0)))\n",
+ " if u == '.']\n",
+ "unused_letter_count = collections.Counter(unused_letters)\n",
+ "unused_letter_count"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 84,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "['bravos',\n",
+ " 'burlier',\n",
+ " 'decriminalisation',\n",
+ " 'differs',\n",
+ " 'duelled',\n",
+ " 'duplicating',\n",
+ " 'elaborates',\n",
+ " 'encirclement',\n",
+ " 'facsimiled',\n",
+ " 'festers',\n",
+ " 'flickering',\n",
+ " 'fusible',\n",
+ " 'interpolations',\n",
+ " 'involved',\n",
+ " 'lithographed',\n",
+ " 'oblongs',\n",
+ " 'overstates',\n",
+ " 'rallies',\n",
+ " 'rebated',\n",
+ " 'reneges',\n",
+ " 'saleswomen',\n",
+ " 'slobbering',\n",
+ " 'toiletries',\n",
+ " 'unabashed']"
+ ]
+ },
+ "execution_count": 84,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "unused_words = [w for w in ws if not present(g, w)[0]]\n",
+ "unused_words"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 85,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "*bravos Counter({'v': 1, 'b': 1, 'a': 1, 's': 1, 'o': 1, 'r': 1})\n",
+ "*burlier Counter({'r': 2, 'b': 1, 'u': 1, 'l': 1, 'i': 1, 'e': 1})\n",
+ "*decriminalisation Counter({'i': 4, 'n': 2, 'a': 2, 's': 1, 'l': 1, 'd': 1, 'e': 1, 'r': 1, 't': 1, 'm': 1, 'o': 1, 'c': 1})\n",
+ "*differs Counter({'f': 2, 's': 1, 'i': 1, 'd': 1, 'e': 1, 'r': 1})\n",
+ "*duelled Counter({'d': 2, 'e': 2, 'l': 2, 'u': 1})\n",
+ "*duplicating Counter({'i': 2, 'g': 1, 'n': 1, 'a': 1, 'l': 1, 'd': 1, 't': 1, 'p': 1, 'u': 1, 'c': 1})\n",
+ "*elaborates Counter({'a': 2, 'e': 2, 'b': 1, 's': 1, 'l': 1, 'o': 1, 'r': 1, 't': 1})\n",
+ "*encirclement Counter({'e': 3, 'n': 2, 'c': 2, 'm': 1, 't': 1, 'l': 1, 'i': 1, 'r': 1})\n",
+ "*facsimiled Counter({'i': 2, 's': 1, 'm': 1, 'a': 1, 'f': 1, 'l': 1, 'd': 1, 'e': 1, 'c': 1})\n",
+ "*festers Counter({'s': 2, 'e': 2, 'f': 1, 'r': 1, 't': 1})\n",
+ "*flickering Counter({'i': 2, 'g': 1, 'n': 1, 'f': 1, 'l': 1, 'e': 1, 'k': 1, 'r': 1, 'c': 1})\n",
+ "*fusible Counter({'s': 1, 'e': 1, 'f': 1, 'b': 1, 'i': 1, 'l': 1, 'u': 1})\n",
+ "*interpolations Counter({'n': 2, 'i': 2, 'o': 2, 't': 2, 's': 1, 'a': 1, 'l': 1, 'e': 1, 'p': 1, 'r': 1})\n",
+ "*involved Counter({'v': 2, 'n': 1, 'l': 1, 'i': 1, 'o': 1, 'e': 1, 'd': 1})\n",
+ "*lithographed Counter({'h': 2, 'g': 1, 'a': 1, 'r': 1, 'l': 1, 'i': 1, 'o': 1, 'e': 1, 'p': 1, 'd': 1, 't': 1})\n",
+ "*oblongs Counter({'o': 2, 'b': 1, 'g': 1, 'n': 1, 'l': 1, 's': 1})\n",
+ "*overstates Counter({'s': 2, 'e': 2, 't': 2, 'v': 1, 'a': 1, 'o': 1, 'r': 1})\n",
+ "*rallies Counter({'l': 2, 's': 1, 'a': 1, 'i': 1, 'e': 1, 'r': 1})\n",
+ "*rebated Counter({'e': 2, 'b': 1, 'a': 1, 'd': 1, 'r': 1, 't': 1})\n",
+ "*reneges Counter({'e': 3, 's': 1, 'g': 1, 'n': 1, 'r': 1})\n",
+ "saleswomen Counter({'s': 2, 'e': 2, 'm': 1, 'n': 1, 'a': 1, 'l': 1, 'o': 1, 'w': 1})\n",
+ "*slobbering Counter({'b': 2, 's': 1, 'g': 1, 'n': 1, 'i': 1, 'o': 1, 'e': 1, 'l': 1, 'r': 1})\n",
+ "*toiletries Counter({'i': 2, 'e': 2, 't': 2, 's': 1, 'l': 1, 'o': 1, 'r': 1})\n",
+ "*unabashed Counter({'a': 2, 'b': 1, 'n': 1, 'h': 1, 's': 1, 'd': 1, 'e': 1, 'u': 1})\n"
+ ]
+ }
+ ],
+ "source": [
+ "makeable_words = []\n",
+ "for w in unused_words:\n",
+ " unused_word_count = collections.Counter(w)\n",
+ " if all(l in unused_letter_count and unused_word_count[l] <= unused_letter_count[l] for l in unused_word_count):\n",
+ " makeable_words += [w]\n",
+ " print('*', end='')\n",
+ " print(w, unused_word_count)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 86,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "17"
+ ]
+ },
+ "execution_count": 86,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "max(len(w) for w in makeable_words)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 90,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'decriminalisation'"
+ ]
+ },
+ "execution_count": 90,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "sorted(makeable_words, key=len)[-1]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 103,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "def do_wordsearch_tasks(fn):\n",
+ " width, height, grid, words = read_wordsearch(fn)\n",
+ " unused_words = [w for w in words if not present(grid, w)[0]]\n",
+ " lwp = sorted([w for w in words if present(grid, w)[0]], key=len)[-1]\n",
+ " lwa = sorted(unused_words, key=len)[-1]\n",
+ " g0 = empty_grid(width, height)\n",
+ " for w in words:\n",
+ " p, r, c, d = present(grid, w)\n",
+ " if p:\n",
+ " set_grid(g0, r, c, d, w) \n",
+ " unused_letters = [l for l, u in zip((c for c in cat(cat(l) for l in grid)), (c for c in cat(cat(l) for l in g0)))\n",
+ " if u == '.']\n",
+ " unused_letter_count = collections.Counter(unused_letters)\n",
+ " makeable_words = []\n",
+ " for w in unused_words:\n",
+ " unused_word_count = collections.Counter(w)\n",
+ " if all(l in unused_letter_count and unused_word_count[l] <= unused_letter_count[l] for l in unused_word_count):\n",
+ " makeable_words += [w]\n",
+ " lwm = sorted(makeable_words, key=len)[-1]\n",
+ " print('\\n{}'.format(fn))\n",
+ " print('Longest word present: {}, {} letters'.format(lwp, len(lwp)))\n",
+ " print('Longest word absent: {}, {} letters'.format(lwa, len(lwa)))\n",
+ " print('{} unused letters'.format(len([c for c in cat(cat(l) for l in g0) if c == '.'])))\n",
+ " print('Longest makeable word: {}, {}'.format(lwm, len(lwm)))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 111,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "wordsearch00.txt\n",
+ "Longest word present: testamentary, 12 letters\n",
+ "Longest word absent: decontamination, 15 letters\n",
+ "133 unused letters\n",
+ "Longest makeable word: decontamination, 15\n",
+ "\n",
+ "wordsearch01.txt\n",
+ "Longest word present: corresponded, 12 letters\n",
+ "Longest word absent: antihistamines, 14 letters\n",
+ "115 unused letters\n",
+ "Longest makeable word: universality, 12\n",
+ "\n",
+ "wordsearch02.txt\n",
+ "Longest word present: presupposing, 12 letters\n",
+ "Longest word absent: chloroformed, 12 letters\n",
+ "114 unused letters\n",
+ "Longest makeable word: chloroformed, 12\n",
+ "\n",
+ "wordsearch03.txt\n",
+ "Longest word present: pasteurisation, 14 letters\n",
+ "Longest word absent: spotlessness, 12 letters\n",
+ "115 unused letters\n",
+ "Longest makeable word: spotlessness, 12\n",
+ "\n",
+ "wordsearch04.txt\n",
+ "Longest word present: domestication, 13 letters\n",
+ "Longest word absent: discountenances, 15 letters\n",
+ "123 unused letters\n",
+ "Longest makeable word: discountenances, 15\n",
+ "\n",
+ "wordsearch05.txt\n",
+ "Longest word present: refinancing, 11 letters\n",
+ "Longest word absent: polytechnics, 12 letters\n",
+ "124 unused letters\n",
+ "Longest makeable word: polytechnics, 12\n",
+ "\n",
+ "wordsearch06.txt\n",
+ "Longest word present: playwrights, 11 letters\n",
+ "Longest word absent: conglomerations, 15 letters\n",
+ "137 unused letters\n",
+ "Longest makeable word: conglomerations, 15\n",
+ "\n",
+ "wordsearch07.txt\n",
+ "Longest word present: negotiations, 12 letters\n",
+ "Longest word absent: electroplating, 14 letters\n",
+ "110 unused letters\n",
+ "Longest makeable word: electroplating, 14\n",
+ "\n",
+ "wordsearch08.txt\n",
+ "Longest word present: schoolhouses, 12 letters\n",
+ "Longest word absent: tonsillectomies, 15 letters\n",
+ "129 unused letters\n",
+ "Longest makeable word: tonsillectomies, 15\n",
+ "\n",
+ "wordsearch09.txt\n",
+ "Longest word present: ecumenically, 12 letters\n",
+ "Longest word absent: vulgarisation, 13 letters\n",
+ "136 unused letters\n",
+ "Longest makeable word: vulgarisation, 13\n",
+ "\n",
+ "wordsearch10.txt\n",
+ "Longest word present: cosmological, 12 letters\n",
+ "Longest word absent: flabbergasting, 14 letters\n",
+ "115 unused letters\n",
+ "Longest makeable word: perversions, 11\n",
+ "\n",
+ "wordsearch11.txt\n",
+ "Longest word present: penultimates, 12 letters\n",
+ "Longest word absent: slaughterhouses, 15 letters\n",
+ "122 unused letters\n",
+ "Longest makeable word: unfriendliness, 14\n",
+ "\n",
+ "wordsearch12.txt\n",
+ "Longest word present: unintelligibly, 14 letters\n",
+ "Longest word absent: equestriennes, 13 letters\n",
+ "124 unused letters\n",
+ "Longest makeable word: unresponsive, 12\n",
+ "\n",
+ "wordsearch13.txt\n",
+ "Longest word present: fearlessness, 12 letters\n",
+ "Longest word absent: impoverishing, 13 letters\n",
+ "130 unused letters\n",
+ "Longest makeable word: impoverishing, 13\n",
+ "\n",
+ "wordsearch14.txt\n",
+ "Longest word present: separations, 11 letters\n",
+ "Longest word absent: perambulated, 12 letters\n",
+ "159 unused letters\n",
+ "Longest makeable word: perambulated, 12\n",
+ "\n",
+ "wordsearch15.txt\n",
+ "Longest word present: undisciplined, 13 letters\n",
+ "Longest word absent: noncontagious, 13 letters\n",
+ "128 unused letters\n",
+ "Longest makeable word: noncontagious, 13\n",
+ "\n",
+ "wordsearch16.txt\n",
+ "Longest word present: intergalactic, 13 letters\n",
+ "Longest word absent: beautification, 14 letters\n",
+ "107 unused letters\n",
+ "Longest makeable word: pharmacopeias, 13\n",
+ "\n",
+ "wordsearch17.txt\n",
+ "Longest word present: undershirts, 11 letters\n",
+ "Longest word absent: photographing, 13 letters\n",
+ "138 unused letters\n",
+ "Longest makeable word: photographing, 13\n",
+ "\n",
+ "wordsearch18.txt\n",
+ "Longest word present: humanitarians, 13 letters\n",
+ "Longest word absent: environmentalists, 17 letters\n",
+ "120 unused letters\n",
+ "Longest makeable word: proportionality, 15\n",
+ "\n",
+ "wordsearch19.txt\n",
+ "Longest word present: regurgitates, 12 letters\n",
+ "Longest word absent: unsatisfactory, 14 letters\n",
+ "122 unused letters\n",
+ "Longest makeable word: unsatisfactory, 14\n"
+ ]
+ }
+ ],
+ "source": [
+ "for fn in sorted(os.listdir()):\n",
+ " if re.match('wordsearch\\d\\d\\.txt', fn):\n",
+ " do_wordsearch_tasks(fn)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "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.5.2+"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}