extension: .md
format_name: markdown
format_version: '1.3'
- jupytext_version: 1.14.5
+ jupytext_version: 1.15.0
kernelspec:
display_name: Python 3 (ipykernel)
language: python
# Definitions generally useful for the riddle solver
+While this file is here as a Markdown file, it's intended that Jupytext will save this file as a "percent" Python file, so that it can be imported by other notebooks here.
+
```python
import unicodedata
import re
```
```python
-stop_words = set('my is in within lies and also always you will find the found'.split())
-negative_words = set('but not never neither nor'.split())
+stop_words = set('my is in within lies and also always you will find the found but'.split())
+negative_words = set('not never neither nor'.split())
```
```python
if w.strip().islower()
if w.strip().isalpha()
if len(w.strip()) >= 5
+ if len(w.strip()) <= 12
if w not in stop_words
if w not in negative_words
if w not in ordinals
```python
class RiddleValence(Enum):
+ """Does this part of the riddle include or exclude letters?"""
Include = auto()
Exclude = auto()
@dataclass
class RiddleClue:
+ """A half line of a riddle, like 'is in dreams' or 'not in octet'"""
valence : RiddleValence
word : str
@dataclass
class RiddleElement:
+ """A representation of the constraints that come from a whole line of
+ a riddle"""
valence : RiddleValence
letters : Set[str]
+# A riddle that's been read and parsed.
+# Note that the numbering is one-based, as per the numbers in the riddle text
Riddle = Dict[int, Tuple[RiddleClue, RiddleClue]]
+
+# A riddle that's been processed ready for solving
+# Note that the numbering is one-based, as per the numbers in the riddle text
RiddleElems = Dict[int, RiddleElement]
```
return len(t)
if t == "":
return len(s)
- if s[-1] == t[-1]:
+ if s[0] == t[0]:
cost = 0
else:
cost = 1
res = min(
- [ edit_distance(s[:-1], t)+1
- , edit_distance(s, t[:-1])+1
- , edit_distance(s[:-1], t[:-1]) + cost
+ [ edit_distance(s[1:], t) + 1
+ , edit_distance(s, t[1:]) + 1
+ , edit_distance(s[1:], t[1:]) + cost
])
return res
```
```python
-def collapse_riddle_clues(elems : Dict[int, Tuple[RiddleClue, RiddleClue]]) -> RiddleElems:
+dictionary_neighbours = {
+ w: [o for o in dictionary
+ if edit_distance(w, o) <= 5
+ if not set(w) <= set(o)
+ if not set(o) <= set(w)]
+ for w in dictionary}
+
+dictionary_neighbours = {w: ns
+ for w, ns in dictionary_neighbours.items()
+ if ns}
+```
+
+```python
+def collapse_riddle_clues(elems : Riddle) -> RiddleElems:
+ """Combine the two parts of a riddle line into one element for solving.
+ This takes account of the valence of the two parts."""
def combine_clues(a: RiddleClue, b: RiddleClue) -> RiddleElement:
if a.valence == b.valence:
if a.valence == RiddleValence.Include:
return {i: combine_clues(a, b) for i, (a, b) in elems.items()}
```
-```python
-
-```
-
```python
def matches_element(pos: int, elem: RiddleElement, word: str) -> bool:
+ """Does this element match this position of the the word?
+ Note that positions are one-based, as in the numbering system of the
+ puzzle."""
if len(word) < pos:
return False
if elem.valence == RiddleValence.Include:
```python
def matches_all_elements(riddle: RiddleElems, word: str) -> bool:
+ """Do all the elements of a riddle match the appropriate parts of a word?"""
if -1 in riddle:
last_elem = riddle[-1]
new_riddle = {p: e for p, e in riddle.items() if p != -1}
```python
def solve_riddle(riddle: RiddleElems) -> List[str]:
+ """Find all words that match this riddle"""
return [w for w in dictionary
if len(w) == len(riddle)
if matches_all_elements(riddle, w)]