first draft
This commit is contained in:
0
multiplayer_crosswords/__init__.py
Normal file
0
multiplayer_crosswords/__init__.py
Normal file
88
multiplayer_crosswords/crossword.py
Normal file
88
multiplayer_crosswords/crossword.py
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
from enum import Enum
|
||||||
|
from typing import List, Tuple, Optional
|
||||||
|
from .dictionary import Dictionary, Word
|
||||||
|
|
||||||
|
class Orientation(Enum):
|
||||||
|
HORIZONTAL = 1
|
||||||
|
VERTICAL = 2
|
||||||
|
|
||||||
|
class PlacedWord:
|
||||||
|
def __init__(self, word: Word, row: int, col: int, orientation: Orientation):
|
||||||
|
self.word = word
|
||||||
|
self.row = row
|
||||||
|
self.col = col
|
||||||
|
self.orientation = orientation
|
||||||
|
|
||||||
|
class Crossword:
|
||||||
|
def __init__(self, rows: int, cols: int, dictionary: Dictionary):
|
||||||
|
self.rows = rows
|
||||||
|
self.cols = cols
|
||||||
|
self.grid = [['' for _ in range(cols)] for _ in range(rows)]
|
||||||
|
self.dictionary = dictionary
|
||||||
|
self.placed_words: List[PlacedWord] = []
|
||||||
|
|
||||||
|
def _in_bounds(self, row: int, col: int) -> bool:
|
||||||
|
return 0 <= row < self.rows and 0 <= col < self.cols
|
||||||
|
|
||||||
|
def can_place_word(self, word: Word, row: int, col: int, orientation: Orientation) -> bool:
|
||||||
|
length = len(word.word)
|
||||||
|
dr, dc = (0, 1) if orientation == Orientation.HORIZONTAL else (1, 0)
|
||||||
|
|
||||||
|
# Check start block
|
||||||
|
start_r, start_c = row - dr, col - dc
|
||||||
|
if self._in_bounds(start_r, start_c):
|
||||||
|
if self.grid[start_r][start_c] not in ('', '#'):
|
||||||
|
return False
|
||||||
|
if self.grid[start_r][start_c] == '#':
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check end block
|
||||||
|
end_r, end_c = row + dr * length, col + dc * length
|
||||||
|
if self._in_bounds(end_r, end_c):
|
||||||
|
if self.grid[end_r][end_c] not in ('', '#'):
|
||||||
|
return False
|
||||||
|
if self.grid[end_r][end_c] == '#':
|
||||||
|
return False
|
||||||
|
|
||||||
|
for i in range(length):
|
||||||
|
r, c = row + dr * i, col + dc * i
|
||||||
|
if not self._in_bounds(r, c):
|
||||||
|
return False
|
||||||
|
cell = self.grid[r][c]
|
||||||
|
if cell == '#':
|
||||||
|
return False
|
||||||
|
if cell != '' and cell != word.word[i]:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def add_word(self, word: Word, row: int, col: int, orientation: Orientation) -> bool:
|
||||||
|
if not self.can_place_word(word, row, col, orientation):
|
||||||
|
return False
|
||||||
|
|
||||||
|
length = len(word.word)
|
||||||
|
dr, dc = (0, 1) if orientation == Orientation.HORIZONTAL else (1, 0)
|
||||||
|
|
||||||
|
# Place start block
|
||||||
|
start_r, start_c = row - dr, col - dc
|
||||||
|
if self._in_bounds(start_r, start_c):
|
||||||
|
self.grid[start_r][start_c] = '#'
|
||||||
|
|
||||||
|
# Place word
|
||||||
|
for i in range(length):
|
||||||
|
r, c = row + dr * i, col + dc * i
|
||||||
|
self.grid[r][c] = word.word[i]
|
||||||
|
|
||||||
|
# Place end block
|
||||||
|
end_r, end_c = row + dr * length, col + dc * length
|
||||||
|
if self._in_bounds(end_r, end_c):
|
||||||
|
self.grid[end_r][end_c] = '#'
|
||||||
|
|
||||||
|
self.placed_words.append(PlacedWord(word, row, col, orientation))
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '\n'.join(
|
||||||
|
''.join(cell if cell else '.' for cell in row)
|
||||||
|
for row in self.grid
|
||||||
|
)
|
||||||
168
multiplayer_crosswords/dictionary.py
Normal file
168
multiplayer_crosswords/dictionary.py
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
import numpy as np
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
from typing import List, Dict, Any, Optional
|
||||||
|
from numba import njit
|
||||||
|
import time
|
||||||
|
import numpy as np
|
||||||
|
import cProfile
|
||||||
|
import pstats
|
||||||
|
|
||||||
|
|
||||||
|
class Word:
|
||||||
|
|
||||||
|
def __init__(self, word: str, hints: list[str], difficulty: float):
|
||||||
|
self.word = word
|
||||||
|
self.hints = hints
|
||||||
|
self.difficulty = difficulty
|
||||||
|
|
||||||
|
def to_dict(self) -> dict[str, any]:
|
||||||
|
return {
|
||||||
|
"word": self.word,
|
||||||
|
"hints": self.hints,
|
||||||
|
"difficulty": self.difficulty
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_dict(data: dict[str, any]) -> "Word":
|
||||||
|
return Word(
|
||||||
|
word=data["word"],
|
||||||
|
hints=data["hints"],
|
||||||
|
difficulty=data["difficulty"]
|
||||||
|
)
|
||||||
|
|
||||||
|
class Dictionary:
|
||||||
|
def __init__(self):
|
||||||
|
self.words: List[Word] = []
|
||||||
|
self._length_index: Dict[int, List[Word]] = {}
|
||||||
|
self._max_word_length = 0
|
||||||
|
self._pos_index_list = None # Will be built on demand
|
||||||
|
self._index_built = False
|
||||||
|
|
||||||
|
def add_word(self, word: Word):
|
||||||
|
self.words.append(word)
|
||||||
|
l = len(word.word)
|
||||||
|
if l not in self._length_index:
|
||||||
|
self._length_index[l] = []
|
||||||
|
self._length_index[l].append(word)
|
||||||
|
if l > self._max_word_length:
|
||||||
|
self._max_word_length = l
|
||||||
|
self._index_built = False # Invalidate index
|
||||||
|
elif self._index_built:
|
||||||
|
# just add the word to the existing index
|
||||||
|
if self._pos_index_list is not None:
|
||||||
|
for i, c in enumerate(word.word):
|
||||||
|
idx = ord(c) - ord('a')
|
||||||
|
self._pos_index_list[l][i][idx].add(word)
|
||||||
|
|
||||||
|
def _build_pos_index_list(self):
|
||||||
|
# pos_index_list[length][pos][char_index] = set(words)
|
||||||
|
self._pos_index_list = [None] * (self._max_word_length + 1)
|
||||||
|
for length in range(1, self._max_word_length + 1):
|
||||||
|
pos_list = []
|
||||||
|
for pos in range(length):
|
||||||
|
char_list = [set() for _ in range(26)] # a-z
|
||||||
|
pos_list.append(char_list)
|
||||||
|
self._pos_index_list[length] = pos_list
|
||||||
|
for word in self.words:
|
||||||
|
l = len(word.word)
|
||||||
|
for i, c in enumerate(word.word):
|
||||||
|
idx = ord(c) - ord('a')
|
||||||
|
self._pos_index_list[l][i][idx].add(word)
|
||||||
|
self._index_built = True
|
||||||
|
|
||||||
|
def to_dict(self) -> dict[str, any]:
|
||||||
|
return {
|
||||||
|
"words": [w.to_dict() for w in self.words]
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_dict(data: dict[str, any]) -> "Dictionary":
|
||||||
|
d = Dictionary()
|
||||||
|
for word_data in data.get("words", []):
|
||||||
|
d.add_word(Word.from_dict(word_data))
|
||||||
|
return d
|
||||||
|
|
||||||
|
def to_json(self) -> str:
|
||||||
|
import json
|
||||||
|
return json.dumps(self.to_dict(), indent=2)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_json(json_str: str) -> "Dictionary":
|
||||||
|
import json
|
||||||
|
data = json.loads(json_str)
|
||||||
|
return Dictionary.from_dict(data)
|
||||||
|
|
||||||
|
def find_by_pattern(self, pattern: str) -> List[Word]:
|
||||||
|
length = len(pattern)
|
||||||
|
if not self._index_built:
|
||||||
|
self._build_pos_index_list()
|
||||||
|
if length > self._max_word_length or self._pos_index_list[length] is None:
|
||||||
|
return []
|
||||||
|
fixed_positions = [(i, c) for i, c in enumerate(pattern) if c != '*']
|
||||||
|
if not fixed_positions:
|
||||||
|
return [w for w in self._length_index.get(length, [])]
|
||||||
|
sets = []
|
||||||
|
for i, c in fixed_positions:
|
||||||
|
idx = ord(c) - ord('a')
|
||||||
|
sets.append(self._pos_index_list[length][i][idx])
|
||||||
|
if not sets:
|
||||||
|
return []
|
||||||
|
sets.sort(key=len)
|
||||||
|
candidates = set.intersection(*sets)
|
||||||
|
return candidates
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
def generate_dummy_words(n, min_length=4, max_length=8):
|
||||||
|
# Generates n random lowercase words of random length between min_length and max_length
|
||||||
|
rng = np.random.default_rng(42)
|
||||||
|
alphabet = np.array([ord(c) for c in "abcdefghijklmnopqrstuvwxyz"], dtype=np.uint8)
|
||||||
|
words = []
|
||||||
|
lengths = rng.integers(min_length, max_length + 1, size=n)
|
||||||
|
for l in lengths:
|
||||||
|
chars = rng.choice(alphabet, size=l)
|
||||||
|
word = ''.join([chr(c) for c in chars])
|
||||||
|
words.append(word)
|
||||||
|
return words
|
||||||
|
|
||||||
|
N = 500_000
|
||||||
|
MIN_WORD_LENGTH = 4
|
||||||
|
MAX_WORD_LENGTH = 8
|
||||||
|
dummy_words = generate_dummy_words(N, MIN_WORD_LENGTH, MAX_WORD_LENGTH)
|
||||||
|
|
||||||
|
# Fill Dictionary (position-indexed)
|
||||||
|
dict_obj = Dictionary()
|
||||||
|
for w in dummy_words:
|
||||||
|
dict_obj.add_word(Word(w, hints=[], difficulty=1.0))
|
||||||
|
|
||||||
|
# Prepare 1000 patterns (repeat the same 10 patterns 100 times)
|
||||||
|
base_patterns = ["**a*e", "b****", "c***d*", "*e***", "a****z", "****", "*****", "******", "*******", "********"]
|
||||||
|
patterns = base_patterns * 100 # 10 * 100 = 1000
|
||||||
|
|
||||||
|
dict_obj._build_pos_index_list() # Ensure the index is built before testing
|
||||||
|
|
||||||
|
print(f"Testing {len(patterns)} patterns on {N} words with lengths {MIN_WORD_LENGTH}-{MAX_WORD_LENGTH}.")
|
||||||
|
|
||||||
|
# --- Profiling block ---
|
||||||
|
profiler = cProfile.Profile()
|
||||||
|
profiler.enable()
|
||||||
|
|
||||||
|
t0 = time.time()
|
||||||
|
total_matches_dict = 0
|
||||||
|
for pattern in patterns:
|
||||||
|
result1 = dict_obj.find_by_pattern(pattern)
|
||||||
|
total_matches_dict += len(result1)
|
||||||
|
t1 = time.time()
|
||||||
|
|
||||||
|
profiler.disable()
|
||||||
|
print(f"Dictionary (position-indexed) total matches: {total_matches_dict} in {t1-t0:.6f} seconds for 1000 queries.")
|
||||||
|
|
||||||
|
# Print top 20 time-consuming functions
|
||||||
|
stats = pstats.Stats(profiler).sort_stats('cumtime')
|
||||||
|
stats.print_stats(20)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
29
pyproject.toml
Normal file
29
pyproject.toml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
[project]
|
||||||
|
name = "multiplayer-crosswords"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = ""
|
||||||
|
authors = [
|
||||||
|
{name = "Jonas Weinz",email = "jo.we93@gmx.de"}
|
||||||
|
]
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = [
|
||||||
|
"numpy (>=2.2.6,<3.0.0)",
|
||||||
|
"pandas (>=2.2.3,<3.0.0)",
|
||||||
|
"numba (>=0.61.2,<1.0.0)",
|
||||||
|
"bitarray (>=3.4.2,<4.0.0)",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[tool.poetry]
|
||||||
|
|
||||||
|
[tool.poetry.dev-dependencies]
|
||||||
|
pytest = "^7.0"
|
||||||
|
|
||||||
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
jupyterlab = "^4.4.3"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
87
tests/test_crossword.py
Normal file
87
tests/test_crossword.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import pytest
|
||||||
|
from multiplayer_crosswords.crossword import Crossword, Orientation, PlacedWord
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
class DummyWord:
|
||||||
|
def __init__(self, word):
|
||||||
|
self.word = word
|
||||||
|
|
||||||
|
class DummyDictionary:
|
||||||
|
def __init__(self, words):
|
||||||
|
self.words = words
|
||||||
|
|
||||||
|
def make_crossword(rows=5, cols=5):
|
||||||
|
dictionary = DummyDictionary([DummyWord("HELLO"), DummyWord("WORLD")])
|
||||||
|
return Crossword(rows, cols, dictionary)
|
||||||
|
|
||||||
|
def test_can_place_word_empty_grid_horizontal():
|
||||||
|
cw = make_crossword()
|
||||||
|
word = DummyWord("HELLO")
|
||||||
|
assert cw.can_place_word(word, 0, 0, Orientation.HORIZONTAL)
|
||||||
|
assert not cw.can_place_word(word, 0, 1, Orientation.HORIZONTAL) # would go out of bounds
|
||||||
|
|
||||||
|
def test_can_place_word_empty_grid_vertical():
|
||||||
|
cw = make_crossword()
|
||||||
|
word = DummyWord("HELLO")
|
||||||
|
assert cw.can_place_word(word, 0, 0, Orientation.VERTICAL)
|
||||||
|
assert not cw.can_place_word(word, 1, 0, Orientation.VERTICAL) # would go out of bounds
|
||||||
|
|
||||||
|
def test_add_word_and_grid_update():
|
||||||
|
cw = make_crossword()
|
||||||
|
word = DummyWord("HELLO")
|
||||||
|
assert cw.add_word(word, 0, 0, Orientation.HORIZONTAL)
|
||||||
|
# Check grid contents
|
||||||
|
assert cw.grid[0][0] == "H"
|
||||||
|
assert cw.grid[0][1] == "E"
|
||||||
|
assert cw.grid[0][2] == "L"
|
||||||
|
assert cw.grid[0][3] == "L"
|
||||||
|
assert cw.grid[0][4] == "O"
|
||||||
|
# Start and end blocks
|
||||||
|
assert cw.grid[0][0-0][0-1] == "#" if cw._in_bounds(0, -1) else True
|
||||||
|
assert cw.grid[0][5] == "#" if cw._in_bounds(0, 5) else True
|
||||||
|
|
||||||
|
def test_cannot_place_overlapping_different_letter():
|
||||||
|
cw = make_crossword()
|
||||||
|
word1 = DummyWord("HELLO")
|
||||||
|
word2 = DummyWord("WORLD")
|
||||||
|
assert cw.add_word(word1, 0, 0, Orientation.HORIZONTAL)
|
||||||
|
# Try to place "WORLD" vertically overlapping 'E' with 'O'
|
||||||
|
assert not cw.can_place_word(word2, 0, 1, Orientation.VERTICAL)
|
||||||
|
|
||||||
|
def test_can_place_overlapping_same_letter():
|
||||||
|
cw = make_crossword()
|
||||||
|
word1 = DummyWord("HELLO")
|
||||||
|
word2 = DummyWord("EVE")
|
||||||
|
assert cw.add_word(word1, 0, 0, Orientation.HORIZONTAL)
|
||||||
|
# "EVE" vertical, E overlaps at (0,1)
|
||||||
|
word2 = DummyWord("EVE")
|
||||||
|
assert cw.can_place_word(word2, 0, 1, Orientation.VERTICAL)
|
||||||
|
assert cw.add_word(word2, 0, 1, Orientation.VERTICAL)
|
||||||
|
|
||||||
|
def test_blocks_are_placed_correctly():
|
||||||
|
cw = make_crossword()
|
||||||
|
word = DummyWord("HI")
|
||||||
|
assert cw.add_word(word, 2, 2, Orientation.HORIZONTAL)
|
||||||
|
# Start block
|
||||||
|
assert cw.grid[2][1] == "#"
|
||||||
|
# End block
|
||||||
|
assert cw.grid[2][4] == "#"
|
||||||
|
|
||||||
|
def test_str_representation():
|
||||||
|
cw = make_crossword()
|
||||||
|
word = DummyWord("HI")
|
||||||
|
cw.add_word(word, 0, 0, Orientation.HORIZONTAL)
|
||||||
|
s = str(cw)
|
||||||
|
assert "HI" in s
|
||||||
|
assert "." in s
|
||||||
|
|
||||||
|
def test_placed_words_tracking():
|
||||||
|
cw = make_crossword()
|
||||||
|
word = DummyWord("HI")
|
||||||
|
cw.add_word(word, 1, 1, Orientation.VERTICAL)
|
||||||
|
assert len(cw.placed_words) == 1
|
||||||
|
placed = cw.placed_words[0]
|
||||||
|
assert placed.word == word
|
||||||
|
assert placed.row == 1
|
||||||
|
assert placed.col == 1
|
||||||
|
assert placed.orientation == Orientation.VERTICAL
|
||||||
105
tests/test_dictionary.py
Normal file
105
tests/test_dictionary.py
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import pytest
|
||||||
|
from multiplayer_crosswords.dictionary import Word, Dictionary
|
||||||
|
|
||||||
|
def test_add_word_and_to_dict():
|
||||||
|
d = Dictionary()
|
||||||
|
w = Word("apple", ["a red or green fruit"], 0.2)
|
||||||
|
d.add_word(w)
|
||||||
|
result = d.to_dict()
|
||||||
|
assert "words" in result
|
||||||
|
assert len(result["words"]) == 1
|
||||||
|
assert result["words"][0]["word"] == "apple"
|
||||||
|
assert result["words"][0]["hints"] == ["a red or green fruit"]
|
||||||
|
assert result["words"][0]["difficulty"] == 0.2
|
||||||
|
|
||||||
|
def test_from_dict_and_to_dict_roundtrip():
|
||||||
|
data = {
|
||||||
|
"words": [
|
||||||
|
{"word": "banana", "hints": ["a yellow fruit", "something monkeys like to eat"], "difficulty": 0.1},
|
||||||
|
{"word": "car", "hints": ["vehicle", "four wheels"], "difficulty": 0.3}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
d = Dictionary.from_dict(data)
|
||||||
|
assert len(d.words) == 2
|
||||||
|
assert d.words[0].word == "banana"
|
||||||
|
assert d.words[1].hints == ["vehicle", "four wheels"]
|
||||||
|
assert d.words[1].difficulty == 0.3
|
||||||
|
assert d.to_dict() == data
|
||||||
|
|
||||||
|
def test_to_json_and_from_json():
|
||||||
|
d = Dictionary()
|
||||||
|
d.add_word(Word("dog", ["animal", "barks"], 0.15))
|
||||||
|
json_str = d.to_json()
|
||||||
|
d2 = Dictionary.from_json(json_str)
|
||||||
|
assert len(d2.words) == 1
|
||||||
|
assert d2.words[0].word == "dog"
|
||||||
|
assert d2.words[0].hints == ["animal", "barks"]
|
||||||
|
assert d2.words[0].difficulty == 0.15
|
||||||
|
|
||||||
|
def test_empty_dictionary_to_dict_and_json():
|
||||||
|
d = Dictionary()
|
||||||
|
assert d.to_dict() == {"words": []}
|
||||||
|
json_str = d.to_json()
|
||||||
|
assert json_str.strip().startswith("{")
|
||||||
|
d2 = Dictionary.from_json(json_str)
|
||||||
|
assert isinstance(d2, Dictionary)
|
||||||
|
assert d2.words == []
|
||||||
|
|
||||||
|
def test_word_to_dict_and_from_dict():
|
||||||
|
w = Word("cat", ["animal", "meows"], 0.12)
|
||||||
|
d = w.to_dict()
|
||||||
|
assert d == {"word": "cat", "hints": ["animal", "meows"], "difficulty": 0.12}
|
||||||
|
w2 = Word.from_dict(d)
|
||||||
|
assert w2.word == "cat"
|
||||||
|
assert w2.hints == ["animal", "meows"]
|
||||||
|
assert w2.difficulty == 0.12
|
||||||
|
|
||||||
|
def test_find_by_pattern_exact_match():
|
||||||
|
d = Dictionary()
|
||||||
|
d.add_word(Word("apple", ["fruit"], 0.2))
|
||||||
|
d.add_word(Word("apply", ["verb"], 0.3))
|
||||||
|
d.add_word(Word("angle", ["geometry"], 0.4))
|
||||||
|
result = d.find_by_pattern("apple")
|
||||||
|
assert [r.word for r in result] == ["apple"]
|
||||||
|
|
||||||
|
def test_find_by_pattern_with_wildcards():
|
||||||
|
d = Dictionary()
|
||||||
|
d.add_word(Word("apple", ["fruit"], 0.2))
|
||||||
|
d.add_word(Word("apply", ["verb"], 0.3))
|
||||||
|
d.add_word(Word("angle", ["geometry"], 0.4))
|
||||||
|
d.add_word(Word("ample", ["enough"], 0.5))
|
||||||
|
# Pattern: a**le should match apple and angle and ample
|
||||||
|
result = sorted([w.word for w in d.find_by_pattern("a**le")])
|
||||||
|
assert set(result) == {"apple", "angle", "ample"}
|
||||||
|
|
||||||
|
def test_find_by_pattern_all_wildcards():
|
||||||
|
d = Dictionary()
|
||||||
|
d.add_word(Word("apple", ["fruit"], 0.2))
|
||||||
|
d.add_word(Word("apply", ["verb"], 0.3))
|
||||||
|
d.add_word(Word("angle", ["geometry"], 0.4))
|
||||||
|
# Pattern: ***** should match all 5-letter words
|
||||||
|
result = sorted([w.word for w in d.find_by_pattern("*****")])
|
||||||
|
assert set(result) == {"apple", "apply", "angle"}
|
||||||
|
|
||||||
|
def test_find_by_pattern_no_match():
|
||||||
|
d = Dictionary()
|
||||||
|
d.add_word(Word("apple", ["fruit"], 0.2))
|
||||||
|
d.add_word(Word("apply", ["verb"], 0.3))
|
||||||
|
# Pattern: z**** should match nothing
|
||||||
|
result = d.find_by_pattern("z****")
|
||||||
|
assert list(result) == []
|
||||||
|
|
||||||
|
def test_find_by_pattern_duplicate_letters():
|
||||||
|
d = Dictionary()
|
||||||
|
d.add_word(Word("level", ["word"], 0.1))
|
||||||
|
d.add_word(Word("lever", ["tool"], 0.2))
|
||||||
|
# Pattern: l*vel should match "level" only
|
||||||
|
result = d.find_by_pattern("l*vel")
|
||||||
|
assert [r.word for r in result] == ["level"]
|
||||||
|
|
||||||
|
def test_find_by_pattern_length_not_in_index():
|
||||||
|
d = Dictionary()
|
||||||
|
d.add_word(Word("cat", ["animal"], 0.1))
|
||||||
|
# Pattern: four letters, but only 3-letter word in dict
|
||||||
|
result = d.find_by_pattern("****")
|
||||||
|
assert [r.word for r in result] == []
|
||||||
Reference in New Issue
Block a user