4 "cell_type": "markdown",
9 "\"Word chain\" puzzles are where you transform one word into another, by changing one letter at a time, with all the intermediate steps being valid words. \n",
11 "For instance, you can transform 'rash' to 'jags' like this:\n",
21 "(the capital letter is the one changed in each step).\n",
25 "Given this [list of words](words4.txt), what is the minimum number of steps to go from `vice` to `wars`?"
53 "output_type": "execute_result"
57 "words = [w.strip() for w in open('words4.txt').readlines()]\n",
69 "def adjacents(word):\n",
70 " return [word[0:i] + l + word[i+1:]\n",
71 " for i in range(len(word))\n",
72 " for l in string.ascii_lowercase\n",
84 "neighbours = {w: [n for n in adjacents(w) if n in words]\n",
96 "def distance(w1, w2):\n",
97 " return sum(1 for i in range(len(w1))\n",
103 "execution_count": 6,
109 "def extend(chain):\n",
110 " return [chain + [s] for s in neighbours[chain[-1]]\n",
111 " if s not in chain]"
116 "execution_count": 7,
122 "def bfs_search(start, target, debug=False):\n",
123 " return bfs([[start]], target, debug=debug)"
128 "execution_count": 8,
134 "def bfs(agenda, goal, debug=False):\n",
135 " finished = False\n",
136 " while not finished and agenda:\n",
137 " current = agenda[0]\n",
140 " if current[-1] == goal:\n",
141 " finished = True\n",
143 " successors = extend(current)\n",
144 " agenda = agenda[1:] + successors\n",
153 "execution_count": 9,
159 "def dfs_search(start, target, debug=False):\n",
160 " return dfs([[start]], target, debug=debug)"
165 "execution_count": 10,
171 "def dfs(agenda, goal, debug=False):\n",
172 " finished = False\n",
173 " while not finished and agenda:\n",
174 " current = agenda[0]\n",
177 " if current[-1] == goal:\n",
178 " finished = True\n",
180 " successors = extend(current)\n",
181 " agenda = successors + agenda[1:]\n",
190 "execution_count": 57,
196 "def astar_search(start, target, debug=False):\n",
197 " agenda = [(distance(start, target), [start])]\n",
198 " heapq.heapify(agenda)\n",
199 " return astar(agenda, target, debug=debug)"
204 "execution_count": 55,
210 "def astar(agenda, goal, debug=False):\n",
211 " finished = False\n",
212 " while not finished and agenda:\n",
213 " _, current = heapq.heappop(agenda)\n",
216 " if current[-1] == goal:\n",
217 " finished = True\n",
219 " successors = extend(current)\n",
220 " for s in successors:\n",
221 " heapq.heappush(agenda, (len(current) + distance(s[-1], goal) - 1, s))\n",
230 "execution_count": 58,
236 "['vice', 'dice', 'dire', 'dare', 'ware', 'wars']"
239 "execution_count": 58,
241 "output_type": "execute_result"
245 "astar_search('vice', 'wars')"
250 "execution_count": 60,
259 "execution_count": 60,
261 "output_type": "execute_result"
265 "len(astar_search('vice', 'wars'))"
270 "execution_count": 15,
279 "execution_count": 15,
281 "output_type": "execute_result"
285 "len(bfs_search('vice', 'wars'))"
290 "execution_count": 16,
299 "execution_count": 16,
301 "output_type": "execute_result"
305 "len(dfs_search('vice', 'wars'))"
310 "execution_count": 17,
315 "output_type": "stream",
317 "10000 loops, best of 3: 154 µs per loop\n"
323 "astar_search('vice', 'wars')"
328 "execution_count": 18,
333 "output_type": "stream",
335 "1 loop, best of 3: 1min 40s per loop\n"
341 "bfs_search('vice', 'wars')"
346 "execution_count": 19,
351 "output_type": "stream",
353 "10 loops, best of 3: 86.3 ms per loop\n"
359 "dfs_search('vice', 'wars')"
363 "cell_type": "markdown",
368 "The example shows that `jags` is reachable in four steps from `rash`. There are 11 words one step away from `rash`: \n",
369 "`bash`, `cash`, `dash`, `gash`, `hash`, `lash`, `mash`, `rasp`, `rush`, `sash`, and `wash`. \n",
371 "There are 47 words reachable in one or two steps from `rash`. They are `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`, and `wish`.\n",
373 "There are 180 words reachable in up to three steps from `rash`.\n",
375 "How many words are reachable in up to ten steps from `vice`?"
380 "execution_count": 37,
386 "def reachable_in(word, n, trim_extras=False):\n",
387 " reachable = set()\n",
388 " boundary = set([word])\n",
389 " for i in range(n):\n",
391 " for w in boundary:\n",
392 " extras.update(neighbours[w])\n",
393 " if trim_extras:\n",
394 " extras.difference_update(reachable)\n",
395 " reachable.update(boundary)\n",
396 " boundary = extras.copy()\n",
397 " return reachable.union(extras).difference(set([word]))"
402 "execution_count": 38,
411 " '`bash`, `cash`, `dash`, `gash`, `hash`, `lash`, `mash`, `rasp`, `rush`, `sash`, `wash`')"
414 "execution_count": 38,
416 "output_type": "execute_result"
420 "len(reachable_in('rash', 1)), ', '.join(sorted('`{}`'.format(r) for r in reachable_in('rash', 1)))"
425 "execution_count": 39,
434 " '`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`')"
437 "execution_count": 39,
439 "output_type": "execute_result"
443 "len(reachable_in('rash', 2)), ', '.join(sorted('`{}`'.format(r) for r in reachable_in('rash', 2)))"
448 "execution_count": 40,
459 "execution_count": 40,
461 "output_type": "execute_result"
465 "len(reachable_in('rash', 3))"
470 "execution_count": 48,
481 "execution_count": 48,
483 "output_type": "execute_result"
487 "len(reachable_in('rash', 10))"
492 "execution_count": 47,
503 "execution_count": 47,
505 "output_type": "execute_result"
509 "len(reachable_in('vice', 10))"
514 "execution_count": 46,
521 "output_type": "stream",
523 "100 loops, best of 3: 5.97 ms per loop\n"
529 "len(reachable_in('rash', 10))"
534 "execution_count": 44,
541 "output_type": "stream",
543 "100 loops, best of 3: 3.1 ms per loop\n"
549 "len(reachable_in('rash', 10, trim_extras=True))"
554 "execution_count": null,
564 "display_name": "Python 3",
565 "language": "python",
573 "file_extension": ".py",
574 "mimetype": "text/x-python",
576 "nbconvert_exporter": "python",
577 "pygments_lexer": "ipython3",