From: Scott Date: Wed, 26 Jan 2022 17:28:53 +0000 (-0800) Subject: Make unscrambler use a cache per instance instead of module and X-Git-Url: https://wannabe.guru.org/gitweb/?a=commitdiff_plain;h=429625f9d90d162e995ea7ee6dc5a6a0c56a777e;hp=2f87b9d503cd98a79042d0070cde4131952cf102;p=python_utils.git Make unscrambler use a cache per instance instead of module and make it have non-static methods. --- diff --git a/unscrambler.py b/unscrambler.py index e53ec2a..6cebacf 100644 --- a/unscrambler.py +++ b/unscrambler.py @@ -85,11 +85,39 @@ letter_sigs = { class Unscrambler(object): - sigs = [] - words = [] + """A class that unscrambles words quickly by computing a signature + (sig) for the word based on its position independent letter + population and then using a pregenerated index to look up known + words the same set of letters. + + Note that each instance of Unscrambler caches its index to speed + up lookups number 2..N; careless reinstantiation will by slower. + + Sigs are designed to cluster similar words near each other so both + lookup methods support a "fuzzy match" argument that can be set to + request similar words that do not match exactly in addition to any + exact matches. + + """ def __init__(self): - pass + # Cached index per instance. + self.sigs = [] + self.words = [] + + if 'unscramble_indexfile' in config.config: + indexfile = config.config['unscramble_indexfile'] + else: + indexfile = "/usr/share/dict/sparse_index" + + with open(indexfile, 'r') as rf: + lines = rf.readlines() + for line in lines: + line = line[:-1] + (fsig, word) = line.split('+') + fsig = int(fsig, 16) + self.sigs.append(fsig) + self.words.append(word) # 52 bits @staticmethod @@ -163,8 +191,9 @@ class Unscrambler(object): indexfile: str = '/usr/share/dict/sparse_index', ) -> None: """Before calling this method, change letter_sigs from the default above - unless you want to populate the same exact files.""" + unless you want to populate the same exact files. + """ words_by_sigs = {} seen = set() with open(dictfile, "r") as f: @@ -185,22 +214,19 @@ class Unscrambler(object): word = words_by_sigs[sig] print(f'0x{sig:x}+{word}', file=f) - @staticmethod - def lookup(word: str, *, include_fuzzy_matches=False) -> Dict[str, bool]: + def lookup(self, word: str, *, include_fuzzy_matches=False) -> Dict[str, bool]: """Looks up a potentially scrambled word optionally including near "fuzzy" matches. - >>> Unscrambler.lookup('eanycleocipd', include_fuzzy_matches=False) + >>> u = Unscrambler() + >>> u.lookup('eanycleocipd', include_fuzzy_matches=False) {'encyclopedia': True} """ sig = Unscrambler.compute_word_sig(word) - return Unscrambler.lookup_by_sig( - sig, include_fuzzy_matches=include_fuzzy_matches - ) + return self.lookup_by_sig(sig, include_fuzzy_matches=include_fuzzy_matches) - @staticmethod - def lookup_by_sig(sig, *, include_fuzzy_matches=False) -> Dict[str, bool]: + def lookup_by_sig(self, sig, *, include_fuzzy_matches=False) -> Dict[str, bool]: """Looks up a word that has already been translated into a signature by a previous call to Unscrambler.compute_word_sig. Optionally returns near "fuzzy" matches. @@ -209,38 +235,23 @@ class Unscrambler(object): >>> sig 18491949645300288339 - >>> Unscrambler.lookup_by_sig(sig, include_fuzzy_matches=True) + >>> u = Unscrambler() + >>> u.lookup_by_sig(sig, include_fuzzy_matches=True) {'pupigerous': False, 'pupigenous': False, 'unpurposing': False, 'superpurgation': False, 'unsupporting': False, 'superseptuaginarian': True, 'purpurogallin': False, 'scuppaug': False, 'purpurigenous': False, 'purpurogenous': False, 'proppage': False} """ - # Cache the index; it doesn't change and this may be called - # more than once. - if len(Unscrambler.sigs) == 0: - if 'unscramble_indexfile' in config.config: - indexfile = config.config['unscramble_indexfile'] - else: - indexfile = "/usr/share/dict/sparse_index" - with open(indexfile, 'r') as rf: - lines = rf.readlines() - for line in lines: - line = line[:-1] - (fsig, word) = line.split('+') - fsig = int(fsig, 16) - Unscrambler.sigs.append(fsig) - Unscrambler.words.append(word) - ret = {} - (exact, location) = list_utils.binary_search(Unscrambler.sigs, sig) + (exact, location) = list_utils.binary_search(self.sigs, sig) start = location - 5 if start < 0: start = 0 end = location + 6 - if end > len(Unscrambler.words): - end = len(Unscrambler.words) + if end > len(self.words): + end = len(self.words) for x in range(start, end): - word = Unscrambler.words[x] - fsig = Unscrambler.sigs[x] + word = self.words[x] + fsig = self.sigs[x] if include_fuzzy_matches is True or (fsig == sig): ret[word] = fsig == sig return ret