X-Git-Url: https://wannabe.guru.org/gitweb/?a=blobdiff_plain;f=unscrambler.py;h=9df82f61a516b78541feddf864ca49f9ce31668b;hb=6e8e3458d9641394b8f061d55ba569a72e6a8493;hp=05f810a225ccf88b5e0232669179170fc6978b33;hpb=8811fb88b36f76360ce8fc20a67931863e59f004;p=python_utils.git diff --git a/unscrambler.py b/unscrambler.py index 05f810a..9df82f6 100644 --- a/unscrambler.py +++ b/unscrambler.py @@ -1,17 +1,18 @@ #!/usr/bin/env python3 import logging -from typing import Dict, Mapping +from typing import Dict, Mapping, Optional import config import decorator_utils +import file_utils import list_utils cfg = config.add_commandline_args( - f'Unscramble! ({__file__})', 'A fast word unscrambler.' + f'Unscrambler base library ({__file__})', 'A fast word unscrambler.' ) cfg.add_argument( - "--unscramble_indexfile", + "--unscrambler_default_indexfile", help="Path to a file of signature -> word index.", metavar="FILENAME", default="/usr/share/dict/sparse_index", @@ -100,31 +101,34 @@ class Unscrambler(object): """ - def __init__(self): + def __init__(self, indexfile: Optional[str] = None): # 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: + filename = self.get_indexfile(indexfile) + with open(filename, '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) + isig = int(fsig, 16) + self.sigs.append(isig) self.words.append(word) + def get_indexfile(self, indexfile: Optional[str]) -> str: + if indexfile is None: + if 'unscrambler_default_indexfile' in config.config: + indexfile = config.config['unscramble_indexfile'] + else: + indexfile = "/usr/share/dict/sparse_index" + else: + assert file_utils.file_is_readable(indexfile), f"Can't read {indexfile}" + return indexfile + # 52 bits @staticmethod - def _compute_word_fingerprint( - word: str, - population: Mapping[str, int] - ) -> int: + def _compute_word_fingerprint(word: str, population: Mapping[str, int]) -> int: fp = 0 for pair in sorted(population.items(), key=lambda x: x[1], reverse=True): letter = pair[0] @@ -140,9 +144,9 @@ class Unscrambler(object): # 32 bits @staticmethod def _compute_word_letter_sig( - letter_sigs: Mapping[str, int], - word: str, - population: Mapping[str, int], + letter_sigs: Mapping[str, int], + word: str, + population: Mapping[str, int], ) -> int: sig = 0 for pair in sorted(population.items(), key=lambda x: x[1], reverse=True): @@ -201,7 +205,7 @@ class Unscrambler(object): unless you want to populate the same exact files. """ - words_by_sigs = {} + words_by_sigs: Dict[int, str] = {} seen = set() with open(dictfile, "r") as f: for word in f: @@ -221,29 +225,19 @@ class Unscrambler(object): word = words_by_sigs[sig] print(f'0x{sig:x}+{word}', file=f) - def lookup( - self, - word: str, - *, - include_fuzzy_matches: bool = False - ) -> Dict[str, bool]: + def lookup(self, word: str, *, window_size: int = 5) -> Dict[str, bool]: """Looks up a potentially scrambled word optionally including near "fuzzy" matches. >>> u = Unscrambler() - >>> u.lookup('eanycleocipd', include_fuzzy_matches=False) + >>> u.lookup('eanycleocipd', window_size=0) {'encyclopedia': True} """ sig = Unscrambler.compute_word_sig(word) - return self.lookup_by_sig(sig, include_fuzzy_matches=include_fuzzy_matches) - - def lookup_by_sig( - self, - sig: int, - *, - include_fuzzy_matches:bool = False - ) -> Dict[str, bool]: + return self.lookup_by_sig(sig, window_size=window_size) + + def lookup_by_sig(self, sig: int, *, window_size: int = 5) -> 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. @@ -253,23 +247,23 @@ class Unscrambler(object): 18491949645300288339 >>> u = Unscrambler() - >>> u.lookup_by_sig(sig, include_fuzzy_matches=True) + >>> u.lookup_by_sig(sig) {'pupigerous': False, 'pupigenous': False, 'unpurposing': False, 'superpurgation': False, 'unsupporting': False, 'superseptuaginarian': True, 'purpurogallin': False, 'scuppaug': False, 'purpurigenous': False, 'purpurogenous': False, 'proppage': False} """ ret = {} (exact, location) = list_utils.binary_search(self.sigs, sig) - start = location - 5 + start = location - window_size if start < 0: start = 0 - end = location + 6 + end = location + 1 + window_size if end > len(self.words): end = len(self.words) for x in range(start, end): word = self.words[x] fsig = self.sigs[x] - if include_fuzzy_matches is True or (fsig == sig): + if window_size > 0 or (fsig == sig): ret[word] = fsig == sig return ret