|
11 | 11 | abc |
12 | 12 | ) |
13 | 13 | from typing import ( |
14 | | - Any, Callable, Dict, Generator, List, Mapping, MutableMapping, Optional, Sequence, Set, Tuple, Union |
| 14 | + Any, Callable, Collection, Dict, Generator, List, Mapping, MutableMapping, Optional, Sequence, Set, Tuple, Union |
15 | 15 | ) |
16 | 16 |
|
17 | 17 | import os |
@@ -163,6 +163,25 @@ def scan( |
163 | 163 | for suffix, found in self.scan( current=child, depth=max(0, depth-1), predicate=predicate ): |
164 | 164 | yield prefix + char + suffix, found |
165 | 165 |
|
| 166 | + def options( |
| 167 | + self, |
| 168 | + prefix: str = '', |
| 169 | + current: Optional[TrieNode] = None, |
| 170 | + ) -> Generator[Tuple[bool, Set[str]], str, None]: |
| 171 | + """With each symbol provided, yields the next available symbol options. |
| 172 | +
|
| 173 | + Doesn't advance unless a truthy symbol is provided via <generator>send(symbol). |
| 174 | +
|
| 175 | + Completes when the provided symbol doesn't match one of the available options. |
| 176 | + """ |
| 177 | + last: str = '' |
| 178 | + *_, (terminal, _, current) = self.find(prefix, current=current) |
| 179 | + while current is not None: |
| 180 | + terminal = current.value is not current.EMPTY |
| 181 | + symbol: str = yield (terminal, set(current.children)) |
| 182 | + if symbol: |
| 183 | + current = current.children.get(symbol) |
| 184 | + |
166 | 185 | def dump_lines( |
167 | 186 | self, |
168 | 187 | current: Optional[TrieNode] = None, |
@@ -343,6 +362,9 @@ def unique( current ): |
343 | 362 | # Only abbreviations (not terminal words) that led to a unique terminal word |
344 | 363 | yield abbrev |
345 | 364 |
|
| 365 | + def options(self, *args, **kwargs): |
| 366 | + return self._trie.options(*args, **kwargs) |
| 367 | + |
346 | 368 | def __str__(self): |
347 | 369 | return str(self._trie) |
348 | 370 |
|
@@ -663,6 +685,46 @@ def find_language( |
663 | 685 |
|
664 | 686 | return language_indices[candidate], candidate |
665 | 687 |
|
| 688 | + @classmethod |
| 689 | + def collect( |
| 690 | + cls, |
| 691 | + languages: Optional[Collection[str]] = None, |
| 692 | + wordlist_path: Optional[Dict[str, Union[str, List[str]]]] = None, |
| 693 | + ) -> Generator[Tuple[Set[str], bool, Set[str]], str, None]: |
| 694 | + """A generator taking input symbols, and producing a sequence of sets of possible next |
| 695 | + characters in all remaining languages. |
| 696 | +
|
| 697 | + With each symbol provided, yields the remaining candidate languages, whether the symbol |
| 698 | + indicated a terminal word in some language, and the available next symbols in all remaining |
| 699 | + languages. |
| 700 | +
|
| 701 | + """ |
| 702 | + candidates: Dict[str, WordIndices] = dict( |
| 703 | + (candidate, words_indices) |
| 704 | + for candidate, _, words_indices in cls.wordlist_indices( wordlist_path=wordlist_path ) |
| 705 | + if languages is None or candidate in languages |
| 706 | + ) |
| 707 | + |
| 708 | + word: str = '' |
| 709 | + updaters = { |
| 710 | + candidate: words_indices.options() |
| 711 | + for candidate, words_indices in candidates.items() |
| 712 | + } |
| 713 | + |
| 714 | + symbol = None |
| 715 | + complete = set() |
| 716 | + while complete < set(updaters): |
| 717 | + terminal = False |
| 718 | + possible = set() |
| 719 | + for candidate, updater in updaters.items(): |
| 720 | + try: |
| 721 | + done, available = updater.send(symbol) |
| 722 | + except StopIteration: |
| 723 | + complete.add( candidate ) |
| 724 | + terminal |= done |
| 725 | + possible |= available |
| 726 | + symbol = yield (set(updaters) - complete, terminal, possible) |
| 727 | + |
666 | 728 | @classmethod |
667 | 729 | def is_valid(cls, mnemonic: Union[str, List[str]], language: Optional[str] = None, **kwargs) -> bool: |
668 | 730 | """Checks if the given mnemonic is valid. |
|
0 commit comments