+---
+jupyter:
+ jupytext:
+ formats: ipynb,md
+ text_representation:
+ extension: .md
+ format_name: markdown
+ format_version: '1.3'
+ jupytext_version: 1.14.5
+ kernelspec:
+ display_name: Python 3 (ipykernel)
+ language: python
+ name: python3
+---
+
+```python
+from riddle_definitions import *
+
+from typing import Dict, Tuple, List, Set
+from enum import Enum, auto
+import random
+import gzip
+```
+
+```python
+dictionary_neighbours = {}
+
+for line in gzip.open('dictionary_neighbours.txt.gz', 'rt').readlines():
+ words = line.strip().split(',')
+ dictionary_neighbours[words[0]] = words[1:]
+
+possible_riddle_clues = list(dictionary_neighbours.keys())
+```
+
+```python
+len(dictionary_neighbours['sonnet'])
+```
+
+```python
+def include_exclude_clue(letter: str, limit: int = 3) -> (RiddleClue, RiddleClue):
+ finished = False
+ while not finished:
+
+ has_first = False
+ while not has_first:
+ with_letter = random.choice(possible_riddle_clues)
+ has_first = letter in with_letter
+
+ others = dictionary_neighbours[with_letter][:]
+ random.shuffle(others)
+
+ while not finished and others:
+ other = others[0]
+
+ if letter not in other and edit_distance(with_letter, other) <= limit:
+ finished = True
+ else:
+ others = others[1:]
+
+ return (RiddleClue(word=with_letter, valence=RiddleValence.Include),
+ RiddleClue(word=other, valence=RiddleValence.Exclude))
+
+a, b = include_exclude_clue('s')
+a, b, set(a.word) - set(b.word), edit_distance(a.word, b.word)
+```
+
+```python
+def include_include_clue(letter: str, limit: int = 3) -> (RiddleClue, RiddleClue):
+ finished = False
+ while not finished:
+
+ has_first = False
+ while not has_first:
+ with_letter = random.choice(possible_riddle_clues)
+ has_first = letter in with_letter
+
+ others = dictionary_neighbours[with_letter][:]
+ random.shuffle(others)
+
+ while not finished and others:
+ other = others[0]
+
+ if letter in other and edit_distance(with_letter, other) <= limit:
+ finished = True
+ else:
+ others = others[1:]
+
+ return (RiddleClue(word=with_letter, valence=RiddleValence.Include),
+ RiddleClue(word=other, valence=RiddleValence.Include))
+
+a, b = include_include_clue('s')
+a, b, set(a.word) | set(b.word), edit_distance(a.word, b.word)
+```
+
+```python
+def exclude_exclude_clue(letter: str, limit: int = 3) -> (RiddleClue, RiddleClue):
+ finished = False
+ while not finished:
+
+ has_first = False
+ while not has_first:
+ without_letter = random.choice(possible_riddle_clues)
+ has_first = letter not in without_letter
+
+ others = dictionary_neighbours[without_letter][:]
+ random.shuffle(others)
+
+ while not finished and others:
+ other = others[0]
+
+ if letter not in other and edit_distance(without_letter, other) <= limit:
+ finished = True
+ else:
+ others = others[1:]
+
+
+ return (RiddleClue(word=without_letter, valence=RiddleValence.Exclude),
+ RiddleClue(word=other, valence=RiddleValence.Exclude))
+
+a, b = exclude_exclude_clue('s')
+a, b, set(a.word) | set(b.word), edit_distance(a.word, b.word)
+```
+
+```python
+def random_clue( letter: str
+ , ie_limit: int = 3
+ , ii_limit: int = 2
+ , ee_limit: int = 2) -> (RiddleClue, RiddleClue):
+ clue_type = random.choices(['include_exclude', 'include_include', 'exclude_exclude'],
+ weights=[7, 2, 1],
+ k=1)[0]
+ if clue_type == 'include_exclude':
+ return include_exclude_clue(letter, limit=ie_limit)
+ elif clue_type =='include_include':
+ return include_include_clue(letter, limit=ii_limit)
+ else:
+ return exclude_exclude_clue(letter, limit=ee_limit)
+```
+
+```python
+def random_riddle( word: str
+ , ie_limit: int = 3
+ , ii_limit: int = 2
+ , ee_limit: int = 2
+ ) -> Riddle:
+ return {i+1 :
+ random_clue(l,
+ ie_limit=ie_limit, ii_limit=ii_limit, ee_limit=ee_limit)
+ for i, l in enumerate(word)}
+```
+
+```python
+sample_riddle = random_riddle('teacup')
+sample_riddle
+```
+
+```python
+collapse_riddle_clues(sample_riddle)
+```
+
+```python
+solve_riddle(collapse_riddle_clues(sample_riddle))
+```
+
+```python
+# write_riddle(sample_riddle)
+```
+
+```python
+# sample_riddle = random_riddle('sonnet', limit=4)
+# sample_riddle
+```
+
+```python
+sample_riddle
+```
+
+```python
+collapse_riddle_clues(sample_riddle)
+```
+
+```python
+solve_riddle(collapse_riddle_clues(sample_riddle))
+```
+
+```python
+def valid_random_riddle(word: str) -> Riddle:
+ finished = False
+ while not finished:
+ riddle = random_riddle(word)
+ solns = solve_riddle(collapse_riddle_clues(riddle))
+ finished = (len(solns) == 1)
+ return riddle
+```
+
+```python
+import time
+import csv
+reports = []
+for _ in range(1000):
+ w1, c1 = time.perf_counter(), time.process_time()
+ r = valid_random_riddle(random.choice(possible_riddle_clues))
+ w2, c2 = time.perf_counter(), time.process_time()
+ linecount = len(r)
+ reports.append({'wall_time': w2 - w1,
+ 'cpu_time': c2 - c1,
+ 'riddle_lines': linecount})
+
+with open('metrics_lazy.csv', 'w', newline='') as csvfile:
+ fieldnames = list(reports[0].keys())
+ writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
+
+ writer.writeheader()
+ for r in reports:
+ writer.writerow(r)
+```
+
+```python
+def write_include_exclude_line(clue_a: RiddleClue, clue_b: RiddleClue) -> str:
+ line = f"is in {clue_a.word} but not in {clue_b.word}"
+ return line
+```
+
+```python
+def write_include_include_line(clue_a: RiddleClue, clue_b: RiddleClue) -> str:
+ if random.randrange(2) == 0:
+ line = f"is in {clue_a.word} and also in {clue_b.word}"
+ else:
+ line = f"is in both {clue_a.word} and {clue_b.word}"
+ return line
+```
+
+```python
+def write_exclude_exclude_line(clue_a: RiddleClue, clue_b: RiddleClue) -> str:
+ line = f"is neither in {clue_a.word} nor in {clue_b.word}"
+ return line
+```
+
+```python
+def write_line(a: RiddleClue, b: RiddleClue) -> str:
+ if a.valence == RiddleValence.Include and b.valence == RiddleValence.Include:
+ return write_include_include_line(a, b)
+ elif a.valence == RiddleValence.Include and b.valence == RiddleValence.Exclude:
+ return write_include_exclude_line(a, b)
+ elif a.valence == RiddleValence.Exclude and b.valence == RiddleValence.Exclude:
+ return write_exclude_exclude_line(a, b)
+ else:
+ return "illegal line"
+```
+
+```python
+def write_riddle(riddle: Riddle) -> List[str]:
+ output = []
+ for i, (clue_a, clue_b) in sorted(riddle.items()):
+ pos = reverse_ordinals[i]
+ if i == len(riddle) and random.random() <= 0.3:
+ pos = reverse_ordinals[-1]
+ line = write_line(clue_a, clue_b)
+ full_line = f"My {pos} {line}"
+ output.append(full_line)
+ return output
+```
+
+```python
+
+```
+
+```python
+sample_riddle = valid_random_riddle("elephant")
+sample_riddle
+```
+
+```python
+write_riddle(sample_riddle)
+```
+
+```python
+solve_riddle(collapse_riddle_clues(sample_riddle))
+```
+
+```python
+with open("generated-riddles-lazy.txt", 'w') as file:
+ between = False
+ for _ in range(10):
+ if between:
+ file.write('\n')
+ between = True
+ target = random.choice(possible_riddle_clues)
+ riddle = valid_random_riddle(target)
+ lines = write_riddle(riddle)
+ file.writelines(l + '\n' for l in lines)
+ file.write(f'Target: {target}\n')
+
+```
+
+```python
+print('\n'.join(write_riddle(valid_random_riddle("faster"))))
+```
+
+```python
+len(dictionary_neighbours['sonnet'])
+```
+
+```python
+ndls = sum(len(ws) for ws in dictionary_neighbours.values())
+ndls
+```
+
+```python
+ndls / len(dictionary_neighbours)
+```
+
+```python
+dn_trimmed = {w : [o for o in dictionary_neighbours[w] if edit_distance(w, o) <= 3]
+ for w in dictionary_neighbours}
+```
+
+```python
+ndlts = sum(len(ws) for ws in dn_trimmed.values())
+ndlts
+```
+
+```python
+ndlts / len(dn_trimmed)
+```
+
+```python
+148 / 940
+```
+
+```python
+1/7
+```
+
+```python
+1/6
+```
+
+```python
+
+```