4 Commits
Author SHA1 Message Date
cblanken 612482587d Bump package version 2024-06-25 22:15:02 -04:00
cblanken 4587e7bc70 Optimize boggle tree operations
- Cache WordNode and WordTree properties with frequent lookups
- Reduce `len()` calls
- Remove unnecessary debug logs
2024-06-25 22:07:22 -04:00
cblanken 9b7b05f79d Fix min python version 2024-06-14 11:09:06 -04:00
cblanken 72f3d20401 Bump patch version 2024-06-14 10:36:21 -04:00
2 changed files with 34 additions and 56 deletions
+25 -37
View File
@@ -1,4 +1,5 @@
"""Boggler Utils"""
from __future__ import annotations
from pathlib import Path
from multiprocessing import Pool
@@ -95,22 +96,22 @@ class BoggleBoard:
body += header
return f"{header}{body}"
@property
@functools.cached_property
def height(self) -> int:
"""Getter for height property"""
return self.__height
@property
@functools.cached_property
def width(self) -> int:
"""Getter for width property"""
return self.__width
@property
@functools.cached_property
def max_word_len(self) -> int:
"""Getter for maximum word length property"""
return self.__max_word_len
@property
@functools.cached_property
def board(self) -> dict[tuple[int, int], BoardCell]:
"""Getter for board property"""
return self.__board
@@ -158,11 +159,16 @@ class WordNode:
self.__parent = parent
self.__board_pos = board_pos
@property
@functools.cached_property
def letters(self) -> str:
"""Getter for letter property"""
return self.__letters
@functools.cached_property
def letters_count(self) -> int:
"""Getter for letters count"""
return len(self.__letters)
@property
def is_word(self) -> bool:
"""Getter for is_word property"""
@@ -172,7 +178,7 @@ class WordNode:
def is_word(self, value):
self.__is_word = value
@property
@functools.cached_property
def children(self) -> dict[str, WordNode]:
"""Getter for children property"""
return self.__children
@@ -221,7 +227,7 @@ class WordTree:
self,
alphabet: list[str],
root: WordNode,
words: list[str] = None,
words: list[str] | None = None,
max_word_len=16,
) -> None:
self.__alphabet = alphabet
@@ -240,27 +246,27 @@ class WordTree:
for word in words:
self.__insert_word(word)
@property
@functools.cached_property
def alphabet(self) -> list:
"""Getter for alphabet property"""
return self.__alphabet
@property
def wordlist(self) -> list[str]:
def wordlist(self) -> list[str] | None:
"""Getter for wordlist property"""
return self.__wordlist
@property
@functools.cached_property
def root(self) -> WordNode:
"""Getter for root property"""
return self.__root
@property
@functools.cached_property
def max_word_len(self) -> int:
"""Getter for max_word_len property"""
return self.__max_word_len
@property
@functools.cached_property
def tree(self) -> dict[str, WordNode]:
"""Getter for tree property"""
return self.__tree
@@ -289,7 +295,6 @@ class WordTree:
board_pos=None,
):
"""Create WordNode for `letters` and into WordTree under `parent`"""
log.debug("inserting node: %s", letters)
node = WordNode(letters, is_word, parent, children, board_pos)
parent.add_child_node(node)
@@ -298,41 +303,31 @@ class WordTree:
otherwise returns False"""
# Insert root node
prefix = word[0 : len(self.root.letters)] # prefix = first letter block
prefix = word[0 : self.root.letters_count] # prefix = first letter block
if word is None or prefix != self.root.letters:
return False
curr_node = self.tree[prefix]
word_len = len(word)
i = len(prefix)
i_max = min(self.max_word_len, len(word))
i_max = min(self.max_word_len, word_len)
while i < i_max:
letters = word[i]
log.debug(
"1 letter seq: %s %s %s", word, letters, letters in curr_node.children
)
if letters not in self.alphabet:
# Check two letter sequences like ("Qu", "Th", etc.) at current index
letters = word[i : i + 2]
log.debug(
"2 letter seq: %s %s %s",
word,
letters,
letters in curr_node.children,
)
if letters not in self.alphabet:
return False
# Insert node
if letters not in curr_node.children:
self.insert_node(letters, curr_node)
log.debug("3 letter seq: %s %s", word, letters)
curr_node = curr_node.children[letters]
i += len(letters)
# Mark the last node as a word
curr_node.is_word = len(word) <= self.max_word_len
curr_node.is_word = word_len <= self.max_word_len
return True
def search(self, word: str, curr_node=None) -> WordNode:
@@ -373,7 +368,7 @@ class WordTree:
"""
if word_len > self.max_word_len or word_len >= board.max_word_len:
log.debug(f"MAX WORD LENGTH REACHED! len = {word_len}")
# MAX WORD LENGTH REACHED!
subtree.active_node = subtree.active_node.parent
self.active_node = self.active_node.parent
return subtree
@@ -382,22 +377,15 @@ class WordTree:
if self.active_node.is_word and len(self.active_node.children) == 0:
word_path = subtree.active_node.path[::-1]
subtree.word_paths.append((subtree.active_node.get_word(board), word_path))
log.debug(
"1: WORD FOUND: %s %s",
"".join([board.board[x].letters for x in word_path]),
word_path,
)
# Word found
self.active_node = self.active_node.parent
subtree.active_node = subtree.active_node.parent
return subtree
elif self.active_node.is_word:
# Word found
word_path = subtree.active_node.path[::-1]
subtree.word_paths.append((subtree.active_node.get_word(board), word_path))
log.debug(
"2: WORD FOUND: %s %s",
"".join([board.board[x].letters for x in word_path]),
word_path,
)
# Branch for each adjacent board cell
for cell in board_cell.adjacent_cells:
+9 -19
View File
@@ -1,32 +1,22 @@
[project]
name = "boggler"
version = "2.0.1"
authors = [
{ name="Cameron Blankenbuehler", email="cameron.blankenbuehler@gmail.com" },
]
description = "Utilities for solving the Boggle word game."
readme="README.md"
license = { file="LICENSE" }
requires-python = ">=3.7"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: POSIX :: Linux",
]
[project.urls]
"Homepage" = "https://github.com/cblanken/boggler"
"Bug Tracker" = "https://github.com/cblanken/boggler/issues"
[tool.poetry]
name = "boggler"
version = "2.0.1"
version = "2.0.4"
description = "Utilities for solving the Boggle word game."
authors = ["Cameron Blankenbuehler <cameron.blankenbuehler@protonmail.com>"]
authors = ["Cameron Blankenbuehler <cameron.blankenbuehler@gmail.com>"]
license = "LICENSE"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: POSIX :: Linux",
]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.11"
python = "^3.8"
rich = "^13.4.1"
[tool.poetry.scripts]