9 jupytext_version: 1.14.5
11 display_name: Python 3 (ipykernel)
17 from riddle_definitions import *
19 from typing import Dict, Tuple, List, Set
20 from enum import Enum, auto
26 dictionary_neighbours = {}
28 for line in gzip.open('dictionary_neighbours.txt.gz', 'rt').readlines():
29 words = line.strip().split(',')
30 dictionary_neighbours[words[0]] = words[1:]
32 possible_riddle_clues = list(dictionary_neighbours.keys())
36 len(dictionary_neighbours['sonnet'])
40 def include_exclude_clue(letter: str, limit: int = 3) -> (RiddleClue, RiddleClue):
46 with_letter = random.choice(possible_riddle_clues)
47 has_first = letter in with_letter
49 others = dictionary_neighbours[with_letter][:]
50 random.shuffle(others)
52 while not finished and others:
55 if letter not in other and edit_distance(with_letter, other) <= limit:
60 return (RiddleClue(word=with_letter, valence=RiddleValence.Include),
61 RiddleClue(word=other, valence=RiddleValence.Exclude))
63 a, b = include_exclude_clue('s')
64 a, b, set(a.word) - set(b.word), edit_distance(a.word, b.word)
68 def include_include_clue(letter: str, limit: int = 3) -> (RiddleClue, RiddleClue):
74 with_letter = random.choice(possible_riddle_clues)
75 has_first = letter in with_letter
77 others = dictionary_neighbours[with_letter][:]
78 random.shuffle(others)
80 while not finished and others:
83 if letter in other and edit_distance(with_letter, other) <= limit:
88 return (RiddleClue(word=with_letter, valence=RiddleValence.Include),
89 RiddleClue(word=other, valence=RiddleValence.Include))
91 a, b = include_include_clue('s')
92 a, b, set(a.word) | set(b.word), edit_distance(a.word, b.word)
96 def exclude_exclude_clue(letter: str, limit: int = 3) -> (RiddleClue, RiddleClue):
102 without_letter = random.choice(possible_riddle_clues)
103 has_first = letter not in without_letter
105 others = dictionary_neighbours[without_letter][:]
106 random.shuffle(others)
108 while not finished and others:
111 if letter not in other and edit_distance(without_letter, other) <= limit:
117 return (RiddleClue(word=without_letter, valence=RiddleValence.Exclude),
118 RiddleClue(word=other, valence=RiddleValence.Exclude))
120 a, b = exclude_exclude_clue('s')
121 a, b, set(a.word) | set(b.word), edit_distance(a.word, b.word)
125 def random_clue( letter: str
128 , ee_limit: int = 2) -> (RiddleClue, RiddleClue):
129 clue_type = random.choices(['include_exclude', 'include_include', 'exclude_exclude'],
132 if clue_type == 'include_exclude':
133 return include_exclude_clue(letter, limit=ie_limit)
134 elif clue_type =='include_include':
135 return include_include_clue(letter, limit=ii_limit)
137 return exclude_exclude_clue(letter, limit=ee_limit)
141 def random_riddle( word: str
148 ie_limit=ie_limit, ii_limit=ii_limit, ee_limit=ee_limit)
149 for i, l in enumerate(word)}
153 sample_riddle = random_riddle('teacup')
158 collapse_riddle_clues(sample_riddle)
162 solve_riddle(collapse_riddle_clues(sample_riddle))
166 # write_riddle(sample_riddle)
170 # sample_riddle = random_riddle('sonnet', limit=4)
179 collapse_riddle_clues(sample_riddle)
183 solve_riddle(collapse_riddle_clues(sample_riddle))
187 def valid_random_riddle(word: str) -> Riddle:
190 riddle = random_riddle(word)
191 solns = solve_riddle(collapse_riddle_clues(riddle))
192 finished = (len(solns) == 1)
200 for _ in range(1000):
201 w1, c1 = time.perf_counter(), time.process_time()
202 r = valid_random_riddle(random.choice(possible_riddle_clues))
203 w2, c2 = time.perf_counter(), time.process_time()
205 reports.append({'wall_time': w2 - w1,
207 'riddle_lines': linecount})
209 with open('metrics_lazy.csv', 'w', newline='') as csvfile:
210 fieldnames = list(reports[0].keys())
211 writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
219 def write_include_exclude_line(clue_a: RiddleClue, clue_b: RiddleClue) -> str:
220 line = f"is in {clue_a.word} but not in {clue_b.word}"
225 def write_include_include_line(clue_a: RiddleClue, clue_b: RiddleClue) -> str:
226 if random.randrange(2) == 0:
227 line = f"is in {clue_a.word} and also in {clue_b.word}"
229 line = f"is in both {clue_a.word} and {clue_b.word}"
234 def write_exclude_exclude_line(clue_a: RiddleClue, clue_b: RiddleClue) -> str:
235 line = f"is neither in {clue_a.word} nor in {clue_b.word}"
240 def write_line(a: RiddleClue, b: RiddleClue) -> str:
241 if a.valence == RiddleValence.Include and b.valence == RiddleValence.Include:
242 return write_include_include_line(a, b)
243 elif a.valence == RiddleValence.Include and b.valence == RiddleValence.Exclude:
244 return write_include_exclude_line(a, b)
245 elif a.valence == RiddleValence.Exclude and b.valence == RiddleValence.Exclude:
246 return write_exclude_exclude_line(a, b)
248 return "illegal line"
252 def write_riddle(riddle: Riddle) -> List[str]:
254 for i, (clue_a, clue_b) in sorted(riddle.items()):
255 pos = reverse_ordinals[i]
256 if i == len(riddle) and random.random() <= 0.3:
257 pos = reverse_ordinals[-1]
258 line = write_line(clue_a, clue_b)
259 full_line = f"My {pos} {line}"
260 output.append(full_line)
269 sample_riddle = valid_random_riddle("elephant")
274 write_riddle(sample_riddle)
278 solve_riddle(collapse_riddle_clues(sample_riddle))
282 with open("generated-riddles-lazy.txt", 'w') as file:
288 target = random.choice(possible_riddle_clues)
289 riddle = valid_random_riddle(target)
290 lines = write_riddle(riddle)
291 file.writelines(l + '\n' for l in lines)
292 file.write(f'Target: {target}\n')
297 print('\n'.join(write_riddle(valid_random_riddle("faster"))))
301 len(dictionary_neighbours['sonnet'])
305 ndls = sum(len(ws) for ws in dictionary_neighbours.values())
310 ndls / len(dictionary_neighbours)
314 dn_trimmed = {w : [o for o in dictionary_neighbours[w] if edit_distance(w, o) <= 3]
315 for w in dictionary_neighbours}
319 ndlts = sum(len(ws) for ws in dn_trimmed.values())
324 ndlts / len(dn_trimmed)