Updated word chain problem
authorNeil Smith <neil.git@njae.me.uk>
Mon, 22 May 2017 13:59:24 +0000 (14:59 +0100)
committerNeil Smith <neil.git@njae.me.uk>
Mon, 22 May 2017 13:59:24 +0000 (14:59 +0100)
word-chains/explore-word-chain-4.ipynb
word-chains/word-chain-solution.ipynb

index 6e230ed52e040b245f97620dbfdf9115b0a1f2d9..5f624fd65885f27cbae4e71c725cbe14d5821496 100644 (file)
    },
    "outputs": [],
    "source": [
-    "def extend(chain):\n",
-    "    return [chain + [s] for s in neighbours[chain[-1]]\n",
+    "def extend(chain, closed=None):\n",
+    "    if closed:\n",
+    "        nbrs = set(neighbours[chain[-1]]) - closed\n",
+    "    else:\n",
+    "        nbrs = neighbours[chain[-1]]\n",
+    "    return [chain + [s] for s in nbrs\n",
     "           if s not in chain]"
    ]
   },
    },
    "outputs": [],
    "source": [
-    "def bfs_search(start, target, debug=False):\n",
-    "    return bfs([[start]], target, debug=debug)"
+    "def bfs_search(start, goal, debug=False):\n",
+    "    agenda = [[start]]\n",
+    "    finished = False\n",
+    "    while not finished and agenda:\n",
+    "        current = agenda[0]\n",
+    "        if debug:\n",
+    "            print(current)\n",
+    "        if current[-1] == goal:\n",
+    "            finished = True\n",
+    "        else:\n",
+    "            successors = extend(current)\n",
+    "            agenda = agenda[1:] + successors\n",
+    "    if agenda:\n",
+    "        return current\n",
+    "    else:\n",
+    "        return None        "
    ]
   },
   {
    },
    "outputs": [],
    "source": [
-    "def bfs(agenda, goal, debug=False):\n",
+    "def bfs_search_closed(start, goal, debug=False):\n",
+    "    agenda = [[start]]\n",
+    "    closed = set()\n",
     "    finished = False\n",
     "    while not finished and agenda:\n",
     "        current = agenda[0]\n",
     "        if current[-1] == goal:\n",
     "            finished = True\n",
     "        else:\n",
-    "            successors = extend(current)\n",
+    "            closed.add(current[-1])\n",
+    "            successors = extend(current, closed)\n",
     "            agenda = agenda[1:] + successors\n",
     "    if agenda:\n",
     "        return current\n",
    },
    "outputs": [],
    "source": [
-    "def dfs_search(start, target, debug=False):\n",
-    "    return dfs([[start]], target, debug=debug)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 19,
-   "metadata": {
-    "collapsed": true
-   },
-   "outputs": [],
-   "source": [
-    "def dfs(agenda, goal, debug=False):\n",
+    "def dfs_search(start, goal, debug=False):\n",
+    "    agenda = [[start]]\n",
     "    finished = False\n",
     "    while not finished and agenda:\n",
     "        current = agenda[0]\n",
   },
   {
    "cell_type": "code",
-   "execution_count": 20,
+   "execution_count": 19,
    "metadata": {},
    "outputs": [
     {
        "['abbe', 'able', 'ably', 'ally']"
       ]
      },
-     "execution_count": 20,
+     "execution_count": 19,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 21,
+   "execution_count": 20,
    "metadata": {},
    "outputs": [
     {
       "['cart', 'mart', 'tart', 'wart']\n",
       "['cart', 'mart', 'tart', 'tort']\n",
       "['cart', 'mart', 'tart', 'tact']\n",
-      "['cart', 'mart', 'tart', 'taut']\n",
+      "['cart', 'mart', 'tart', 'taut']\n"
+     ]
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
       "['cart', 'mart', 'tart', 'tare']\n",
       "['cart', 'mart', 'tart', 'taro']\n",
       "['cart', 'mart', 'tart', 'tarp']\n",
       "['cart', 'mart', 'wart', 'waft']\n",
       "['cart', 'mart', 'wart', 'wait']\n",
       "['cart', 'mart', 'wart', 'want']\n",
-      "['cart', 'mart', 'wart', 'watt']\n"
-     ]
-    },
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
+      "['cart', 'mart', 'wart', 'watt']\n",
       "['cart', 'mart', 'wart', 'ward']\n",
       "['cart', 'mart', 'wart', 'ware']\n",
       "['cart', 'mart', 'wart', 'warm']\n",
       "['cart', 'wart', 'ware', 'wire']\n",
       "['cart', 'wart', 'ware', 'wore']\n",
       "['cart', 'wart', 'ware', 'wade']\n",
-      "['cart', 'wart', 'ware', 'wage']\n",
+      "['cart', 'wart', 'ware', 'wage']\n"
+     ]
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
       "['cart', 'wart', 'ware', 'wake']\n",
       "['cart', 'wart', 'ware', 'wale']\n",
       "['cart', 'wart', 'ware', 'wane']\n",
        "['cart', 'cant', 'cans', 'vans']"
       ]
      },
-     "execution_count": 21,
+     "execution_count": 20,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 22,
+   "execution_count": 21,
    "metadata": {},
    "outputs": [
     {
        "['cart', 'cant', 'cane', 'vane']"
       ]
      },
-     "execution_count": 22,
+     "execution_count": 21,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 23,
+   "execution_count": 22,
    "metadata": {},
    "outputs": [
     {
        " 'vane']"
       ]
      },
-     "execution_count": 23,
+     "execution_count": 22,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 24,
+   "execution_count": 23,
    "metadata": {
     "collapsed": true
    },
    "outputs": [],
    "source": [
-    "def astar_search(start, target, debug=False):\n",
-    "    return astar([(distance(start, target), [start])], target, debug=debug)"
+    "def astar_search(start, goal, debug=False):\n",
+    "    agenda = [(distance(start, goal), [start])]\n",
+    "    heapq.heapify(agenda)\n",
+    "    finished = False\n",
+    "    while not finished and agenda:\n",
+    "        _, current = heapq.heappop(agenda)\n",
+    "        if debug:\n",
+    "            print(current)\n",
+    "        if current[-1] == goal:\n",
+    "            finished = True\n",
+    "        else:\n",
+    "            successors = extend(current)\n",
+    "            for s in successors:\n",
+    "                heapq.heappush(agenda, (len(current) + distance(s[-1], goal) - 1, s))\n",
+    "    if agenda:\n",
+    "        return current\n",
+    "    else:\n",
+    "        return None        "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 24,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "['cart']\n",
+      "['cart', 'cant']\n",
+      "['cart', 'cant', 'cane']\n",
+      "['cart', 'cant', 'cane', 'vane']\n"
+     ]
+    },
+    {
+     "data": {
+      "text/plain": [
+       "['cart', 'cant', 'cane', 'vane']"
+      ]
+     },
+     "execution_count": 24,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "astar_search('cart', 'vane', debug=True)"
    ]
   },
   {
    },
    "outputs": [],
    "source": [
-    "def astar(agenda, goal, debug=False):\n",
+    "def astar_search_closed(start, goal, debug=False):\n",
+    "    agenda = [(distance(start, goal), [start])]\n",
+    "    heapq.heapify(agenda)\n",
+    "    closed = set()\n",
     "    finished = False\n",
     "    while not finished and agenda:\n",
     "        _, current = heapq.heappop(agenda)\n",
     "        if current[-1] == goal:\n",
     "            finished = True\n",
     "        else:\n",
-    "            successors = extend(current)\n",
+    "            closed.add(current[-1])\n",
+    "            successors = extend(current, closed)\n",
     "            for s in successors:\n",
     "                heapq.heappush(agenda, (len(current) + distance(s[-1], goal) - 1, s))\n",
     "    if agenda:\n",
     }
    ],
    "source": [
-    "astar_search('cart', 'vane', debug=True)"
+    "astar_search_closed('cart', 'vane', debug=True)"
    ]
   },
   {
   },
   {
    "cell_type": "code",
-   "execution_count": 27,
+   "execution_count": 77,
    "metadata": {},
    "outputs": [
     {
        "94"
       ]
      },
-     "execution_count": 27,
+     "execution_count": 77,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 28,
+   "execution_count": 78,
    "metadata": {},
    "outputs": [
     {
        "2204"
       ]
      },
-     "execution_count": 28,
+     "execution_count": 78,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 29,
+   "execution_count": 79,
    "metadata": {},
    "outputs": [
     {
        "1"
       ]
      },
-     "execution_count": 29,
+     "execution_count": 79,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 30,
+   "execution_count": 80,
    "metadata": {
     "scrolled": true
    },
        "Counter({1: 75, 2: 6, 3: 7, 4: 2, 5: 2, 6: 1, 2204: 1})"
       ]
      },
-     "execution_count": 30,
+     "execution_count": 80,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 31,
+   "execution_count": 81,
    "metadata": {},
    "outputs": [
     {
        "[5]"
       ]
      },
-     "execution_count": 31,
+     "execution_count": 81,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 32,
+   "execution_count": 82,
    "metadata": {},
    "outputs": [
     {
        "[{'abbe', 'able', 'ably', 'ally', 'axle'}]"
       ]
      },
-     "execution_count": 32,
+     "execution_count": 82,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 33,
+   "execution_count": 83,
    "metadata": {},
    "outputs": [
     {
        "['buns', 'bunk', 'punk']"
       ]
      },
-     "execution_count": 33,
+     "execution_count": 83,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 34,
-   "metadata": {},
+   "execution_count": 84,
+   "metadata": {
+    "collapsed": true
+   },
    "outputs": [],
    "source": [
     "for a in reachables:\n",
   },
   {
    "cell_type": "code",
-   "execution_count": 35,
-   "metadata": {},
+   "execution_count": 85,
+   "metadata": {
+    "collapsed": true
+   },
    "outputs": [],
    "source": [
     "# longest_chain = []\n",
   },
   {
    "cell_type": "code",
-   "execution_count": 36,
+   "execution_count": 86,
    "metadata": {
     "collapsed": true
    },
   },
   {
    "cell_type": "code",
-   "execution_count": 37,
+   "execution_count": 87,
    "metadata": {},
    "outputs": [
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "['over', 'ever', 'eves', 'eyes', 'dyes', 'does', 'toes', 'tors', 'tars', 'taro', 'tyro']\n",
-      "['aide', 'aids', 'bids', 'bits', 'bats', 'mats']\n",
-      "['miff', 'riff', 'rife', 'rise', 'risk', 'rusk']\n",
-      "['hims', 'hems', 'hens', 'lens', 'lent']\n",
-      "['tipi', 'tips', 'tits', 'tots', 'cots', 'cote']\n",
-      "['apex', 'aped', 'sped', 'sued', 'sues', 'subs', 'tubs', 'tabs', 'tabu']\n",
-      "['wino', 'wind', 'wild', 'gild', 'gilt']\n",
-      "['rasp', 'ramp', 'lamp', 'limp', 'limn']\n",
-      "['paps', 'caps', 'cars', 'card', 'cord', 'lord', 'loud']\n",
-      "['lava', 'lama', 'lamp', 'ramp']\n",
-      "['ways', 'bays', 'boys', 'boos', 'coos', 'coop', 'coup']\n",
-      "['boob', 'boor', 'boar', 'bear', 'beer', 'jeer', 'jeez']\n",
-      "['troy', 'trod', 'prod', 'plod', 'pled', 'pied', 'died', 'diet']\n",
-      "['vest', 'lest', 'less', 'loss', 'lows', 'rows']\n",
-      "['shop', 'shot', 'soot', 'sort', 'sore', 'sire', 'dire', 'dike']\n",
-      "['sing', 'zing']\n",
-      "['beak', 'beat', 'belt', 'welt', 'wilt', 'wily', 'oily', 'only']\n",
-      "['spry', 'spay', 'spas', 'seas', 'secs', 'sics', 'sirs', 'sire', 'tire', 'tiro']\n",
-      "['gosh', 'bosh', 'boss', 'bogs', 'begs', 'bees', 'byes']\n",
-      "['racy', 'race', 'rape', 'raps', 'saps', 'sops']\n"
+      "['late', 'lats', 'lets', 'leas', 'seas', 'seam', 'swam', 'sway']\n",
+      "['leap', 'heap', 'hear', 'dear', 'deer']\n",
+      "['peel', 'peek', 'perk', 'park', 'nark']\n",
+      "['sing', 'sang', 'sand', 'said', 'sail', 'hail', 'haul']\n",
+      "['vats', 'bats', 'bets', 'bees', 'been', 'teen', 'then', 'thin', 'this', 'thus', 'thud']\n",
+      "['sues', 'sees', 'seen', 'sewn', 'hewn']\n",
+      "['rash', 'bash', 'bast', 'bait', 'bail', 'hail', 'hair', 'heir']\n",
+      "['apex', 'aped', 'sped', 'seed', 'deed', 'dead', 'deal', 'veal']\n",
+      "['gulf', 'golf', 'gold', 'bold', 'bond', 'bony', 'tony']\n",
+      "['snag', 'shag', 'shat', 'seat', 'peat', 'pent', 'pint', 'mint']\n",
+      "['rife', 'rime', 'rims', 'rums', 'cums', 'cuss']\n",
+      "['diss', 'kiss', 'kits']\n",
+      "['gyps', 'gaps', 'gads', 'wads', 'wade', 'wide', 'tide']\n",
+      "['bilk', 'bill', 'bell', 'tell', 'teal', 'tear', 'tzar']\n",
+      "['logo', 'loge', 'lode', 'lade', 'jade', 'jape']\n",
+      "['hunt', 'bunt', 'buns', 'nuns', 'nubs']\n",
+      "['glow', 'glop', 'plop', 'prop', 'prep', 'peep', 'keep']\n",
+      "['iamb', 'lamb', 'lams', 'laps', 'lips', 'pips']\n",
+      "['pain', 'lain', 'laid', 'land', 'lend', 'vend', 'veld']\n",
+      "['fake', 'bake', 'bare', 'bars', 'ears', 'errs', 'ergs', 'eggs', 'egos']\n"
      ]
     }
    ],
    "source": [
     "for _ in range(20):\n",
     "    start, goal = random.sample(bigset, 2)\n",
-    "    print(astar_search(start, goal))"
+    "    print(astar_search_closed(start, goal))"
    ]
   },
   {
     "astar_search('wars', 'love')"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": 61,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "CPU times: user 0 ns, sys: 0 ns, total: 0 ns\n",
+      "Wall time: 210 µs\n"
+     ]
+    },
+    {
+     "data": {
+      "text/plain": [
+       "5"
+      ]
+     },
+     "execution_count": 61,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "%time len(astar_search('wars', 'love'))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 62,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "CPU times: user 0 ns, sys: 0 ns, total: 0 ns\n",
+      "Wall time: 252 µs\n"
+     ]
+    },
+    {
+     "data": {
+      "text/plain": [
+       "5"
+      ]
+     },
+     "execution_count": 62,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "%time len(astar_search_closed('wars', 'love'))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 63,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "CPU times: user 24 ms, sys: 0 ns, total: 24 ms\n",
+      "Wall time: 24.2 ms\n"
+     ]
+    },
+    {
+     "data": {
+      "text/plain": [
+       "404"
+      ]
+     },
+     "execution_count": 63,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "%time len(dfs_search('wars', 'love'))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 64,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "CPU times: user 5min 20s, sys: 76 ms, total: 5min 20s\n",
+      "Wall time: 5min 20s\n"
+     ]
+    },
+    {
+     "data": {
+      "text/plain": [
+       "5"
+      ]
+     },
+     "execution_count": 64,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "%time len(bfs_search('wars', 'love'))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 65,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "CPU times: user 1.44 s, sys: 0 ns, total: 1.44 s\n",
+      "Wall time: 1.43 s\n"
+     ]
+    },
+    {
+     "data": {
+      "text/plain": [
+       "5"
+      ]
+     },
+     "execution_count": 65,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "%time len(bfs_search_closed('wars', 'love'))"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": 42,
     "[len(r) for r in reachables if 'star' in r if 'born' in r]"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": 49,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "1 loop, best of 3: 7.9 s per loop\n"
+     ]
+    }
+   ],
+   "source": [
+    "%%timeit\n",
+    "astar_search('bats', 'exit')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 50,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "10 loops, best of 3: 141 ms per loop\n"
+     ]
+    }
+   ],
+   "source": [
+    "%%timeit\n",
+    "astar_search_closed('bats', 'exit')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 51,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "['bats',\n",
+       " 'bans',\n",
+       " 'band',\n",
+       " 'sand',\n",
+       " 'said',\n",
+       " 'skid',\n",
+       " 'skit',\n",
+       " 'smit',\n",
+       " 'emit',\n",
+       " 'exit']"
+      ]
+     },
+     "execution_count": 51,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "astar_search_closed('bats', 'exit')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 88,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "{2: [['heel', 'keel'], ['wane', 'wave'], ['cell', 'sell'], ['cons', 'cobs']],\n",
+       " 3: [['hank', 'haws'], ['bars', 'bets'], ['rats', 'paws'], ['lock', 'hack']],\n",
+       " 4: [['rule', 'sore'], ['wavy', 'rape'], ['peas', 'ping'], ['bond', 'toll']],\n",
+       " 5: [['cope', 'yowl'], ['lose', 'loci'], ['rump', 'dash'], ['four', 'dyes']],\n",
+       " 6: [['boon', 'sell'], ['lots', 'pomp'], ['cola', 'turn'], ['boos', 'laid']],\n",
+       " 7: [['eave', 'inns'], ['meek', 'mere'], ['keys', 'wily'], ['slam', 'yore']],\n",
+       " 8: [['hack', 'flip'], ['crag', 'huge'], ['flux', 'gill'], ['play', 'busy']],\n",
+       " 9: [['lacy', 'whey'], ['wren', 'rook'], ['lire', 'drip'], ['grab', 'lame']],\n",
+       " 10: [['over', 'turn'], ['worn', 'anew'], ['stow', 'elks'], ['ergo', 'rich']],\n",
+       " 11: [['bask', 'idea'], ['gabs', 'thud'], ['idea', 'clod'], ['mark', 'ibis']],\n",
+       " 12: [['umps', 'torn'], ['futz', 'shun'], ['abut', 'face'], ['slug', 'open']],\n",
+       " 13: [['umps', 'skin'], ['chum', 'rats'], ['fury', 'chum'], ['omen', 'zany']],\n",
+       " 14: [['chug', 'gaff'], ['atom', 'fizz']],\n",
+       " 15: [['chug', 'oxen']]}"
+      ]
+     },
+     "execution_count": 88,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "solutions = {}\n",
+    "for _ in range(10000):\n",
+    "    start, goal = random.sample(bigset, 2)\n",
+    "    solution = astar_search_closed(start, goal)\n",
+    "    sl = len(solution)\n",
+    "    if sl not in solutions:\n",
+    "        solutions[sl] = []\n",
+    "    if len(solutions[sl]) < 4:\n",
+    "        solutions[sl].append([start, goal])\n",
+    "        \n",
+    "#     if len(solution) >= 10:\n",
+    "#         solutions += [solution]\n",
+    "        \n",
+    "solutions"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 91,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "solutions = {2: [['heel', 'keel'], ['wane', 'wave'], ['cell', 'sell'], ['cons', 'cobs']],\n",
+    " 3: [['hank', 'haws'], ['bars', 'bets'], ['rats', 'paws'], ['lock', 'hack']],\n",
+    " 4: [['rule', 'sore'], ['wavy', 'rape'], ['peas', 'ping'], ['bond', 'toll']],\n",
+    " 5: [['cope', 'yowl'], ['lose', 'loci'], ['rump', 'dash'], ['four', 'dyes']],\n",
+    " 6: [['boon', 'sell'], ['lots', 'pomp'], ['cola', 'turn'], ['boos', 'laid']],\n",
+    " 7: [['eave', 'inns'], ['meek', 'mere'], ['keys', 'wily'], ['slam', 'yore']],\n",
+    " 8: [['hack', 'flip'], ['crag', 'huge'], ['flux', 'gill'], ['play', 'busy']],\n",
+    " 9: [['lacy', 'whey'], ['wren', 'rook'], ['lire', 'drip'], ['grab', 'lame']],\n",
+    " 10: [['over', 'turn'], ['worn', 'anew'], ['stow', 'elks'], ['ergo', 'rich']],\n",
+    " 11: [['bask', 'idea'], ['gabs', 'thud'], ['idea', 'clod'], ['mark', 'ibis']],\n",
+    " 12: [['umps', 'torn'], ['futz', 'shun'], ['abut', 'face'], ['slug', 'open']],\n",
+    " 13: [['umps', 'skin'], ['chum', 'rats'], ['fury', 'chum'], ['omen', 'zany']],\n",
+    " 14: [['chug', 'gaff'], ['atom', 'fizz'], ['chug', 'jinn'], ['amen', 'flog'],\n",
+    "     ['buzz', 'grog'], ['imps', 'pros']],\n",
+    " 15: [['chug', 'oxen'], ['amen', 'doff']]}"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 54,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[('amen', 'doff', 15), ('chug', 'jinn', 14), ('amen', 'flog', 14)]"
+      ]
+     },
+     "execution_count": 54,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "[(s[0], s[-1], len(s)) for s in solutions if len(s) >= 14]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 55,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "1 loop, best of 3: 360 ms per loop\n"
+     ]
+    }
+   ],
+   "source": [
+    "%%timeit\n",
+    "astar_search_closed('blab', 'amen')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 56,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "CPU times: user 384 ms, sys: 0 ns, total: 384 ms\n",
+      "Wall time: 385 ms\n"
+     ]
+    },
+    {
+     "data": {
+      "text/plain": [
+       "14"
+      ]
+     },
+     "execution_count": 56,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "%time len(astar_search_closed('blab', 'amen'))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 57,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "CPU times: user 124 ms, sys: 0 ns, total: 124 ms\n",
+      "Wall time: 121 ms\n"
+     ]
+    },
+    {
+     "data": {
+      "text/plain": [
+       "15"
+      ]
+     },
+     "execution_count": 57,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "%time len(astar_search_closed('amen', 'doff'))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 58,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "CPU times: user 32 ms, sys: 0 ns, total: 32 ms\n",
+      "Wall time: 32.4 ms\n"
+     ]
+    },
+    {
+     "data": {
+      "text/plain": [
+       "14"
+      ]
+     },
+     "execution_count": 58,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "%time len(astar_search_closed('chug', 'jinn'))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 59,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "CPU times: user 16 ms, sys: 0 ns, total: 16 ms\n",
+      "Wall time: 17.1 ms\n"
+     ]
+    },
+    {
+     "data": {
+      "text/plain": [
+       "14"
+      ]
+     },
+     "execution_count": 59,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "%time len(astar_search_closed('amen', 'flog'))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 73,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "CPU times: user 268 ms, sys: 4 ms, total: 272 ms\n",
+      "Wall time: 272 ms\n"
+     ]
+    },
+    {
+     "data": {
+      "text/plain": [
+       "14"
+      ]
+     },
+     "execution_count": 73,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "%time len(astar_search_closed('buzz', 'grog'))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 74,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "CPU times: user 64 ms, sys: 0 ns, total: 64 ms\n",
+      "Wall time: 64.1 ms\n"
+     ]
+    },
+    {
+     "data": {
+      "text/plain": [
+       "14"
+      ]
+     },
+     "execution_count": 74,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "%time len(astar_search_closed('imps', 'pros'))"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": null,
index e90cacaabbe306e738ac03b11ccfddfc0d247d3e..8a87922bb6b57a44ab864fc9365df8122424c93f 100644 (file)
    },
    "outputs": [],
    "source": [
-    "def extend(chain):\n",
-    "    return [chain + [s] for s in neighbours[chain[-1]]\n",
-    "           if s not in chain]"
+    "def extend(chain):\n",
+    "    return [chain + [s] for s in neighbours[chain[-1]]\n",
+    "           if s not in chain]"
    ]
   },
   {
    },
    "outputs": [],
    "source": [
-    "def extend_raw(chain):\n",
-    "    nbrs = [w for w in adjacents(chain[-1]) if w in words]\n",
+    "def extend(chain, closed=None):\n",
+    "    if closed:\n",
+    "        nbrs = set(neighbours[chain[-1]]) - closed\n",
+    "    else:\n",
+    "        nbrs = neighbours[chain[-1]]\n",
     "    return [chain + [s] for s in nbrs\n",
     "           if s not in chain]"
    ]
    },
    "outputs": [],
    "source": [
-    "def bfs_search(start, target, debug=False):\n",
-    "    return bfs([[start]], target, debug=debug)"
+    "def extend_raw(chain):\n",
+    "    nbrs = [w for w in adjacents(chain[-1]) if w in words]\n",
+    "    return [chain + [s] for s in nbrs\n",
+    "           if s not in chain]"
    ]
   },
   {
    },
    "outputs": [],
    "source": [
-    "def bfs(agenda, goal, debug=False):\n",
+    "def bfs_search(start, goal, debug=False):\n",
+    "    agenda = [[start]]\n",
     "    finished = False\n",
     "    while not finished and agenda:\n",
     "        current = agenda[0]\n",
   },
   {
    "cell_type": "code",
-   "execution_count": 10,
+   "execution_count": 19,
    "metadata": {
     "collapsed": true
    },
    "outputs": [],
    "source": [
-    "def dfs_search(start, target, debug=False):\n",
-    "    return dfs([[start]], target, debug=debug)"
+    "def bfs_search_closed(start, goal, debug=False):\n",
+    "    agenda = [[start]]\n",
+    "    closed = set()\n",
+    "    finished = False\n",
+    "    while not finished and agenda:\n",
+    "        current = agenda[0]\n",
+    "        if debug:\n",
+    "            print(current)\n",
+    "        if current[-1] == goal:\n",
+    "            finished = True\n",
+    "        else:\n",
+    "            closed.add(current[-1])\n",
+    "            successors = extend(current, closed)\n",
+    "            agenda = agenda[1:] + successors\n",
+    "    if agenda:\n",
+    "        return current\n",
+    "    else:\n",
+    "        return None   "
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 11,
+   "execution_count": 10,
    "metadata": {
     "collapsed": true
    },
    "outputs": [],
    "source": [
-    "def dfs(agenda, goal, debug=False):\n",
+    "def dfs_search(start, goal, debug=False):\n",
+    "    agenda = [[start]]\n",
     "    finished = False\n",
     "    while not finished and agenda:\n",
     "        current = agenda[0]\n",
   },
   {
    "cell_type": "code",
-   "execution_count": 12,
+   "execution_count": 11,
    "metadata": {
     "collapsed": true
    },
    "outputs": [],
    "source": [
-    "def astar_search(start, target, debug=False):\n",
-    "    agenda = [(distance(start, target), [start])]\n",
+    "def astar_search(start, goal, debug=False):\n",
+    "    agenda = [(distance(start, goal), [start])]\n",
     "    heapq.heapify(agenda)\n",
-    "    return astar(agenda, target, debug=debug)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 13,
-   "metadata": {
-    "collapsed": true
-   },
-   "outputs": [],
-   "source": [
-    "def astar(agenda, goal, debug=False):\n",
     "    finished = False\n",
     "    while not finished and agenda:\n",
     "        _, current = heapq.heappop(agenda)\n",
   },
   {
    "cell_type": "code",
-   "execution_count": 14,
+   "execution_count": 12,
    "metadata": {
     "collapsed": true
    },
    "outputs": [],
    "source": [
-    "def astar_search_raw(start, target, debug=False):\n",
-    "    agenda = [(distance(start, target), [start])]\n",
+    "# Uses direct lookup of successors, rather than using cached neighbours in the dict\n",
+    "def astar_search_raw(start, goal, debug=False):\n",
+    "    agenda = [(distance(start, goal), [start])]\n",
     "    heapq.heapify(agenda)\n",
-    "    return astar_raw(agenda, target, debug=debug)"
+    "    finished = False\n",
+    "    while not finished and agenda:\n",
+    "        _, current = heapq.heappop(agenda)\n",
+    "        if debug:\n",
+    "            print(current)\n",
+    "        if current[-1] == goal:\n",
+    "            finished = True\n",
+    "        else:\n",
+    "            successors = extend_raw(current) # Difference here\n",
+    "            for s in successors:\n",
+    "                heapq.heappush(agenda, (len(current) + distance(s[-1], goal) - 1, s))\n",
+    "    if agenda:\n",
+    "        return current\n",
+    "    else:\n",
+    "        return None        "
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 15,
+   "execution_count": 13,
    "metadata": {
     "collapsed": true
    },
    "outputs": [],
    "source": [
-    "def astar_raw(agenda, goal, debug=False):\n",
+    "def astar_search_closed(start, goal, debug=False):\n",
+    "    agenda = [(distance(start, goal), [start])]\n",
+    "    heapq.heapify(agenda)\n",
+    "    closed = set()\n",
     "    finished = False\n",
     "    while not finished and agenda:\n",
     "        _, current = heapq.heappop(agenda)\n",
     "        if current[-1] == goal:\n",
     "            finished = True\n",
     "        else:\n",
-    "            successors = extend_raw(current)\n",
+    "            closed.add(current[-1])\n",
+    "            successors = extend(current, closed)\n",
     "            for s in successors:\n",
     "                heapq.heappush(agenda, (len(current) + distance(s[-1], goal) - 1, s))\n",
     "    if agenda:\n",
     "        return current\n",
     "    else:\n",
-    "        return None        "
+    "        return None   "
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 16,
+   "execution_count": 14,
    "metadata": {},
    "outputs": [
     {
        "['vice', 'dice', 'dire', 'dare', 'ware', 'wars']"
       ]
      },
-     "execution_count": 16,
+     "execution_count": 14,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 17,
+   "execution_count": 15,
    "metadata": {},
    "outputs": [
     {
        "['vice', 'dice', 'dire', 'dare', 'ware', 'wars']"
       ]
      },
-     "execution_count": 17,
+     "execution_count": 15,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 18,
+   "execution_count": 16,
    "metadata": {},
    "outputs": [
     {
        "6"
       ]
      },
-     "execution_count": 18,
+     "execution_count": 16,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 19,
+   "execution_count": 17,
    "metadata": {},
    "outputs": [
     {
        "6"
       ]
      },
-     "execution_count": 19,
+     "execution_count": 17,
      "metadata": {},
      "output_type": "execute_result"
     }
     {
      "data": {
       "text/plain": [
-       "793"
+       "6"
       ]
      },
      "execution_count": 20,
     }
    ],
    "source": [
-    "len(dfs_search('vice', 'wars'))"
+    "len(bfs_search_closed('vice', 'wars'))"
    ]
   },
   {
    "cell_type": "code",
    "execution_count": 21,
    "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "793"
+      ]
+     },
+     "execution_count": 21,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "len(dfs_search('vice', 'wars'))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 22,
+   "metadata": {},
    "outputs": [
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "10000 loops, best of 3: 153 µs per loop\n"
+      "10000 loops, best of 3: 158 µs per loop\n"
      ]
     }
    ],
   },
   {
    "cell_type": "code",
-   "execution_count": 22,
+   "execution_count": 23,
    "metadata": {},
    "outputs": [
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "100 loops, best of 3: 15.8 ms per loop\n"
+      "100 loops, best of 3: 15.6 ms per loop\n"
      ]
     }
    ],
   },
   {
    "cell_type": "code",
-   "execution_count": 23,
+   "execution_count": 24,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "10000 loops, best of 3: 168 µs per loop\n"
+     ]
+    }
+   ],
+   "source": [
+    "%%timeit\n",
+    "astar_search_closed('vice', 'wars')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 25,
    "metadata": {},
    "outputs": [
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "1 loop, best of 3: 1min 42s per loop\n"
+      "1 loop, best of 3: 1min 40s per loop\n"
      ]
     }
    ],
   },
   {
    "cell_type": "code",
-   "execution_count": 24,
+   "execution_count": 26,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "1 loop, best of 3: 597 ms per loop\n"
+     ]
+    }
+   ],
+   "source": [
+    "%%timeit\n",
+    "bfs_search_closed('vice', 'wars')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 27,
    "metadata": {},
    "outputs": [
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "10 loops, best of 3: 88 ms per loop\n"
+      "10 loops, best of 3: 85.5 ms per loop\n"
      ]
     }
    ],
   },
   {
    "cell_type": "code",
-   "execution_count": 25,
+   "execution_count": 28,
    "metadata": {
     "collapsed": true
    },
   },
   {
    "cell_type": "code",
-   "execution_count": 26,
+   "execution_count": 29,
    "metadata": {
     "scrolled": true
    },
        " '`bash`, `cash`, `dash`, `gash`, `hash`, `lash`, `mash`, `rasp`, `rush`, `sash`, `wash`')"
       ]
      },
-     "execution_count": 26,
+     "execution_count": 29,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 27,
+   "execution_count": 30,
    "metadata": {
     "scrolled": true
    },
        " '`base`, `bash`, `bask`, `bass`, `bast`, `bath`, `bosh`, `bush`, `case`, `cash`, `cask`, `cast`, `dash`, `dish`, `gash`, `gasp`, `gosh`, `gush`, `hash`, `hasp`, `hath`, `hush`, `lash`, `lass`, `last`, `lath`, `lush`, `mash`, `mask`, `mass`, `mast`, `math`, `mesh`, `mush`, `push`, `ramp`, `rasp`, `ruse`, `rush`, `rusk`, `rust`, `sash`, `sass`, `tush`, `wash`, `wasp`, `wish`')"
       ]
      },
-     "execution_count": 27,
+     "execution_count": 30,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 28,
+   "execution_count": 31,
    "metadata": {
     "scrolled": true
    },
        "180"
       ]
      },
-     "execution_count": 28,
+     "execution_count": 31,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 29,
+   "execution_count": 32,
    "metadata": {
     "scrolled": true
    },
        "2195"
       ]
      },
-     "execution_count": 29,
+     "execution_count": 32,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 30,
+   "execution_count": 33,
    "metadata": {
     "scrolled": true
    },
        "2192"
       ]
      },
-     "execution_count": 30,
+     "execution_count": 33,
      "metadata": {},
      "output_type": "execute_result"
     }
   },
   {
    "cell_type": "code",
-   "execution_count": 31,
+   "execution_count": 34,
    "metadata": {
     "scrolled": true
    },
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "100 loops, best of 3: 5.96 ms per loop\n"
+      "100 loops, best of 3: 5.82 ms per loop\n"
      ]
     }
    ],
   },
   {
    "cell_type": "code",
-   "execution_count": 32,
+   "execution_count": 35,
    "metadata": {
     "scrolled": true
    },