--- /dev/null
+# ---
+# jupyter:
+# jupytext:
+# formats: ipynb,py:light
+# text_representation:
+# extension: .py
+# format_name: light
+# format_version: '1.5'
+# jupytext_version: 1.11.1
+# kernelspec:
+# display_name: Python 3 (ipykernel)
+# language: python
+# name: python3
+# ---
+
+# # Honeycomb letter puzzle
+# 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/).
+#
+
+import string
+import collections
+from dataclasses import dataclass
+import itertools
+import ctypes
+import cProfile
+
+with open('enable1.txt') as f:
+ words = [line.strip() for line in f]
+
+words = set(w for w in words
+ if len(w) >= 4
+ if len(frozenset(w)) <= 7
+ if 's' not in w)
+
+word_sets = collections.defaultdict(set)
+for w in words:
+ letters = frozenset(w)
+ word_sets[letters].add(w)
+
+
+def encode(letters):
+ encoded = 0
+ for l in string.ascii_lowercase:
+ encoded <<= 1
+ if l in letters:
+ encoded |= 1
+ return encoded
+
+
+def decode(letter_bits):
+ letters = set()
+ for l in string.ascii_lowercase:
+ e = encode(l)
+ if (e & letter_bits):
+ letters.add(l)
+ return letters
+
+
+def subset(this, that):
+ return this & that == this
+
+
+@dataclass(frozen=True, init=False)
+class Honeycomb():
+ mandatory: int
+ letters: int
+
+ def __init__(self, mandatory, letters):
+ object.__setattr__(self, 'mandatory', encode(mandatory))
+ object.__setattr__(self, 'letters', encode(letters))
+
+ def __repr__(self):
+ return f'{"".join(decode(self.mandatory))}|{"".join(sorted(decode(self.letters) - set(decode(self.mandatory))))}'
+
+
+def present(target, honeycomb):
+ return (subset(honeycomb.mandatory, target)
+ and subset(target, honeycomb.letters))
+
+
+def score(present_set):
+ score = 0
+ for w in word_sets[present_set]:
+ if len(w) == 4:
+ score += 1
+ else:
+ score += len(w)
+ if len(present_set) == 7:
+ score += len(word_sets[present_set]) * 7
+ return score
+
+
+scored_sets = {s: score(s) for s in word_sets}
+encoded_scored_sets = {encode(s): scored_sets[s] for s in scored_sets}
+
+
+def raw_score_honeycomb(honeycomb):
+ return sum(sc for letters, sc in scored_sets.items()
+ # if honeycomb.mandatory in letters
+ # if letters.issubset(honeycomb.letters)
+ if present(letters, honeycomb)
+ )
+
+
+def score_honeycomb(honeycomb):
+ return sum(sc for letters, sc in encoded_scored_sets.items()
+ # if honeycomb.mandatory in letters
+ # if letters.issubset(honeycomb.letters)
+ if present(letters, honeycomb)
+ )
+
+
+pangram_sets = [s for s in word_sets.keys() if len(s) == 7 if 's' not in s]
+
+with open('pangaram_words.txt', 'w') as f:
+ for s in pangram_sets:
+ for w in word_sets[s]:
+ f.write(f'{w}\n')
+
+# +
+# ps_honeycombs = []
+
+# for ps in pangram_sets:
+# for mand in ps:
+# ps_honeycombs.append(Honeycomb(mandatory=mand, letters=ps))
+
+ps_honeycombs = [Honeycomb(mandatory=mand, letters=ps)
+ for ps in pangram_sets
+ for mand in ps]
+# -
+
+partitioned_scored_sets = {encode(l): {s: encoded_scored_sets[s]
+ for s in encoded_scored_sets
+ if subset(encode(l), s)}
+ for l in string.ascii_lowercase}
+
+
+def partitioned_score_honeycomb(honeycomb):
+ return sum(sc for letters, sc in partitioned_scored_sets[honeycomb.mandatory].items()
+ if subset(letters, honeycomb.letters)
+ )
+
+
+# +
+# # %%timeit
+best_honyecomb = max(ps_honeycombs, key=partitioned_score_honeycomb)
+print(best_honyecomb, partitioned_score_honeycomb(best_honyecomb))
+# -
+
+# # %%timeit
+# best_honyecomb = max(ps_honeycombs, key=score_honeycomb)
+# print(best_honyecomb, score_honeycomb(best_honyecomb))
+
+# +
+# cProfile.run('max(ps_honeycombs, key=score_honeycomb)')
+
+
+# +
+# cProfile.run('max(ps_honeycombs, key=partitioned_score_honeycomb)')
+# -
+
+