4 "cell_type": "markdown",
8 "# Honeycomb letter puzzle\n",
9 "Solving the puzzle as posted on [FiveThirtyEight](https://fivethirtyeight.com/features/can-you-solve-the-vexing-vexillology/), also solved by [David Robinson](http://varianceexplained.org/r/honeycomb-puzzle/).\n"
20 "import collections\n",
21 "from dataclasses import dataclass\n",
32 "def only_letters(text):\n",
33 " return ''.join([c.lower() for c in text if c in string.ascii_letters])"
50 "output_type": "execute_result"
54 "only_letters('Hell!o')"
71 "output_type": "execute_result"
75 "with open('/usr/share/dict/british-english') as f:\n",
76 " words = [line.rstrip() for line in f]\n",
82 "execution_count": 79,
92 "execution_count": 79,
94 "output_type": "execute_result"
98 "with open('enable1.txt') as f:\n",
99 " words = [line.rstrip() for line in f]\n",
105 "execution_count": 80,
110 "words = set(only_letters(w) \n",
111 " for w in words \n",
112 " if len(only_letters(w)) >= 4\n",
113 " if len(frozenset(only_letters(w))) <= 7\n",
119 "execution_count": 81,
129 "execution_count": 81,
131 "output_type": "execute_result"
140 "execution_count": 82,
150 "execution_count": 82,
152 "output_type": "execute_result"
156 "word_sets = collections.defaultdict(set)\n",
158 " letters = frozenset(w)\n",
159 " word_sets[letters].add(w)\n",
165 "execution_count": 86,
172 "{'elephant', 'naphthalene', 'pentathlete'}"
175 "execution_count": 86,
177 "output_type": "execute_result"
181 "word_sets[frozenset('elephant')]"
186 "execution_count": 87,
191 "@dataclass(frozen=True)\n",
192 "class Honeycomb():\n",
194 " letters: frozenset\n",
196 " def __repr__(self):\n",
197 " return f'{self.mandatory}|{\"\".join(sorted(self.letters - set(self.mandatory)))}'"
202 "execution_count": 88,
212 "execution_count": 88,
214 "output_type": "execute_result"
218 "honeycomb = Honeycomb(mandatory='g', letters=frozenset('apxmelg'))\n",
224 "execution_count": 89,
229 "def present(honeycomb, target):\n",
230 " return (honeycomb.mandatory in target) and target.issubset(honeycomb.letters)"
235 "execution_count": 90,
245 "execution_count": 90,
247 "output_type": "execute_result"
251 "present_sets = [s for s in word_sets if present(honeycomb, s)]\n",
257 "execution_count": 91,
264 "[frozenset({'a', 'e', 'g', 'p'}),\n",
265 " frozenset({'a', 'e', 'g', 'm'}),\n",
266 " frozenset({'a', 'g', 'm'}),\n",
267 " frozenset({'a', 'e', 'g', 'l', 'p'}),\n",
268 " frozenset({'a', 'e', 'g', 'l'}),\n",
269 " frozenset({'a', 'e', 'g'}),\n",
270 " frozenset({'a', 'g', 'l'}),\n",
271 " frozenset({'a', 'g', 'l', 'm'}),\n",
272 " frozenset({'a', 'e', 'g', 'l', 'm'}),\n",
273 " frozenset({'a', 'g'}),\n",
274 " frozenset({'a', 'g', 'l', 'x'}),\n",
275 " frozenset({'e', 'g', 'l'}),\n",
276 " frozenset({'a', 'g', 'l', 'p'}),\n",
277 " frozenset({'a', 'g', 'm', 'p'})]"
280 "execution_count": 91,
282 "output_type": "execute_result"
291 "execution_count": 92,
297 "output_type": "stream",
299 "{'peag', 'agapae', 'peage', 'gape', 'agape', 'page'}\n",
300 "{'game', 'gemmae', 'mage', 'gemma'}\n",
301 "{'magma', 'agma', 'agama', 'gamma', 'gama'}\n",
302 "{'pelage', 'plage'}\n",
303 "{'eagle', 'algae', 'galeae', 'aglee', 'allege', 'legal', 'galea', 'gale', 'gaggle', 'egal'}\n",
304 "{'gage', 'agee'}\n",
305 "{'gala', 'gall', 'alga', 'algal'}\n",
307 "{'agleam', 'gleam'}\n",
310 "{'glee', 'gelee', 'gleg'}\n",
317 "for s in present_sets:\n",
318 " print(word_sets[s])"
323 "execution_count": 93,
328 "def score(present_set):\n",
330 " for w in word_sets[present_set]:\n",
331 " if len(w) == 4:\n",
334 " score += len(w)\n",
335 " if len(present_set) == 7:\n",
336 " score += len(word_sets[present_set]) * 7\n",
342 "execution_count": 94,
352 "execution_count": 94,
354 "output_type": "execute_result"
358 "sum(score(present_set) for present_set in present_sets)"
363 "execution_count": 95,
373 "execution_count": 95,
375 "output_type": "execute_result"
379 "set('megaplex') in words"
384 "execution_count": 96,
389 "scored_sets = {s: score(s) for s in word_sets}"
394 "execution_count": 97,
400 "output_type": "stream",
402 "frozenset({'e', 'a', 'p', 'g'}) {'peag', 'agapae', 'peage', 'gape', 'agape', 'page'} 19 19\n",
403 "frozenset({'m', 'a', 'e', 'g'}) {'game', 'gemmae', 'mage', 'gemma'} 13 13\n",
404 "frozenset({'m', 'a', 'g'}) {'magma', 'agma', 'agama', 'gamma', 'gama'} 17 17\n",
405 "frozenset({'p', 'l', 'a', 'g', 'e'}) {'pelage', 'plage'} 11 11\n",
406 "frozenset({'a', 'l', 'e', 'g'}) {'eagle', 'algae', 'galeae', 'aglee', 'allege', 'legal', 'galea', 'gale', 'gaggle', 'egal'} 45 45\n",
407 "frozenset({'a', 'e', 'g'}) {'gage', 'agee'} 2 2\n",
408 "frozenset({'a', 'l', 'g'}) {'gala', 'gall', 'alga', 'algal'} 8 8\n",
409 "frozenset({'m', 'a', 'l', 'g'}) {'amalgam'} 7 7\n",
410 "frozenset({'m', 'a', 'l', 'g', 'e'}) {'agleam', 'gleam'} 11 11\n",
411 "frozenset({'a', 'g'}) {'gaga'} 1 1\n",
412 "frozenset({'a', 'l', 'x', 'g'}) {'galax'} 5 5\n",
413 "frozenset({'l', 'e', 'g'}) {'glee', 'gelee', 'gleg'} 7 7\n",
414 "frozenset({'l', 'a', 'p', 'g'}) {'plagal'} 6 6\n",
415 "frozenset({'m', 'a', 'p', 'g'}) {'gamp'} 1 1\n"
420 "for s in present_sets:\n",
421 " print(s, word_sets[s], score(s), scored_sets[s])"
426 "execution_count": 98,
431 "def score_honeycomb(honeycomb):\n",
432 " return sum(sc for letters, sc in scored_sets.items() \n",
433 " if honeycomb.mandatory in letters\n",
434 " if letters.issubset(honeycomb.letters)\n",
435 "# if present(honeycomb, letters)\n",
441 "execution_count": 99,
451 "execution_count": 99,
453 "output_type": "execute_result"
457 "score_honeycomb(honeycomb)"
462 "execution_count": 100,
469 "# for mand in 'abcde':\n",
470 "# remaining = set('abcde') - set(mand)\n",
471 "# for others in itertools.combinations(remaining, r=3):\n",
472 "# hcs.append(Honeycomb(mandatory=mand, letters=frozenset(others) | frozenset(mand)))\n",
479 "execution_count": 101,
489 "execution_count": 102,
494 "# honeycombs = []\n",
496 "# for mand in string.ascii_lowercase:\n",
497 "# remaining = set(string.ascii_lowercase) - set(mand)\n",
498 "# for others in itertools.combinations(remaining, r=6):\n",
499 "# honeycombs.append(Honeycomb(mandatory=mand, letters=frozenset(others) | frozenset(mand)))\n",
501 "# print(len(honeycombs))"
506 "execution_count": 103,
511 "# honeycombs = []\n",
513 "# candidate_letters = set(string.ascii_lowercase)\n",
514 "# candidate_letters.remove('s')\n",
515 "# candidate_letters = frozenset(candidate_letters)\n",
517 "# for mand in candidate_letters:\n",
518 "# remaining = candidate_letters - set(mand)\n",
519 "# for others in itertools.combinations(remaining, r=6):\n",
520 "# honeycombs.append(Honeycomb(mandatory=mand, letters=frozenset(others) | frozenset(mand)))\n",
522 "# print(len(honeycombs))"
527 "execution_count": 104,
532 "# [(h, score_honeycomb(h)) for h in honeycombs[:5]]"
537 "execution_count": 105,
543 "# max(honeycombs, key=score_honeycomb)"
548 "execution_count": 106,
558 "execution_count": 106,
560 "output_type": "execute_result"
564 "pangram_sets = [s for s in word_sets.keys() if len(s) == 7 if 's' not in s]\n",
570 "execution_count": 107,
576 "output_type": "stream",
583 "ps_honeycombs = []\n",
585 "for ps in pangram_sets:\n",
586 " for mand in ps:\n",
587 " ps_honeycombs.append(Honeycomb(mandatory=mand, letters=ps))\n",
589 "print(len(ps_honeycombs))"
594 "execution_count": 108,
600 "# max(ps_honeycombs, key=score_honeycomb)"
605 "execution_count": 109,
610 "## 1 a,e,g,i,n,r,t r 3898\n",
611 "## 2 a,e,g,i,n,r,t n 3782\n",
612 "## 3 a,e,g,i,n,r,t e 3769"
617 "execution_count": 110,
627 "execution_count": 110,
629 "output_type": "execute_result"
633 "score_honeycomb(Honeycomb('r', frozenset('aeginrt')))"
638 "execution_count": 111,
648 "execution_count": 111,
650 "output_type": "execute_result"
654 "score_honeycomb(Honeycomb('n', frozenset('aeginrtn')))"
659 "execution_count": 112,
669 "execution_count": 112,
671 "output_type": "execute_result"
675 "score_honeycomb(Honeycomb('e', frozenset('aeginrte')))"
680 "execution_count": 113,
690 "execution_count": 113,
692 "output_type": "execute_result"
696 "partitioned_scored_sets = {l: {s: scored_sets[s] for s in scored_sets if l in s} \n",
697 " for l in string.ascii_lowercase}\n",
698 "len(partitioned_scored_sets)"
703 "execution_count": 114,
708 "def partitioned_score_honeycomb(honeycomb):\n",
709 " return sum(sc for letters, sc in partitioned_scored_sets[honeycomb.mandatory].items() \n",
710 " if letters.issubset(honeycomb.letters)\n",
716 "execution_count": 115,
722 "output_type": "stream",
730 "print(max(ps_honeycombs, key=partitioned_score_honeycomb))"
735 "execution_count": 74,
745 "execution_count": 74,
747 "output_type": "execute_result"
751 "# partitioned_score_honeycomb(Honeycomb('a', 'abc'))"
756 "execution_count": 76,
757 "id": "77d74a74-55e9-498b-abdf-b02c1f7f01a3",
766 "execution_count": 76,
768 "output_type": "execute_result"
772 "# max(len(partitioned_scored_sets[k]) for k in partitioned_scored_sets)"
777 "execution_count": 77,
778 "id": "35b58dec-7138-4fdb-86cc-80f3eb6a555a",
787 "execution_count": 77,
789 "output_type": "execute_result"
798 "execution_count": null,
799 "id": "7e4173b4-26a9-4198-b572-d57db321fe94",
807 "formats": "ipynb,auto:light"
810 "display_name": "Python 3 (ipykernel)",
811 "language": "python",
819 "file_extension": ".py",
820 "mimetype": "text/x-python",
822 "nbconvert_exporter": "python",
823 "pygments_lexer": "ipython3",