|
|
@ -13,7 +13,7 @@ class FuzzyFilteredMap: |
|
|
|
def __init__(self, filter_function=None, matcher=None, additive_only_filter=True): |
|
|
|
def __init__(self, filter_function=None, matcher=None, additive_only_filter=True): |
|
|
|
self.filter = filter_function or (lambda n: True) |
|
|
|
self.filter = filter_function or (lambda n: True) |
|
|
|
self.matcher = matcher or FuzzyMatcher() |
|
|
|
self.matcher = matcher or FuzzyMatcher() |
|
|
|
self._values = {} |
|
|
|
self._map = {} |
|
|
|
self.length_cutoff = 0 |
|
|
|
self.length_cutoff = 0 |
|
|
|
self.logger = logging.getLogger(__name__) |
|
|
|
self.logger = logging.getLogger(__name__) |
|
|
|
self._stale = True |
|
|
|
self._stale = True |
|
|
@ -22,43 +22,40 @@ class FuzzyFilteredMap: |
|
|
|
@property |
|
|
|
@property |
|
|
|
def filtered_items(self): |
|
|
|
def filtered_items(self): |
|
|
|
if not self.additive_only_filter: |
|
|
|
if not self.additive_only_filter: |
|
|
|
return [item for item in self._values.items() if self.filter(item[1])] |
|
|
|
return [(k, v) for k, v in self._map.items() if self.filter(v)] |
|
|
|
if self._needs_update: |
|
|
|
if self._needs_update: |
|
|
|
self._update_items() |
|
|
|
self._update_items() |
|
|
|
return self._filtered_items |
|
|
|
return self._filtered_items |
|
|
|
|
|
|
|
|
|
|
|
@property |
|
|
|
@property |
|
|
|
def _needs_update(self): |
|
|
|
def _needs_update(self): |
|
|
|
return self._stale or any(self.filter(item[1]) for item in self._filtered_out_items) |
|
|
|
return self._stale or any(self.filter(v) for k, v in self._filtered_out_items) |
|
|
|
|
|
|
|
|
|
|
|
def _update_items(self): |
|
|
|
def _update_items(self): |
|
|
|
self._filtered_items = [item for item in self._values.items() if self.filter(item[1])] |
|
|
|
self._filtered_items = [(k, v) for k, v in self._map.items() if self.filter(v)] |
|
|
|
self._filtered_out_items = [item for item in self._values.items() if not self.filter(item[1])] |
|
|
|
self._filtered_out_items = [(k, v) for k, v in self._map.items() if not self.filter(v)] |
|
|
|
self._stale = False |
|
|
|
self._stale = False |
|
|
|
|
|
|
|
|
|
|
|
def values(self): |
|
|
|
def values(self): |
|
|
|
return FuzzyDictValuesView(self) |
|
|
|
return FuzzyDictValuesView(self) |
|
|
|
|
|
|
|
|
|
|
|
def has_exact(self, key): |
|
|
|
def has_exact(self, key): |
|
|
|
return romanize(key) in self._values |
|
|
|
return romanize(key) in self._map |
|
|
|
|
|
|
|
|
|
|
|
def __delitem__(self, key): |
|
|
|
def __delitem__(self, key): |
|
|
|
k = romanize(key) |
|
|
|
k = romanize(key) |
|
|
|
self._values.__delitem__(k) |
|
|
|
del self._map[k] |
|
|
|
self._stale = True |
|
|
|
self._stale = True |
|
|
|
|
|
|
|
|
|
|
|
def __setitem__(self, key, value): |
|
|
|
def __setitem__(self, key, value): |
|
|
|
key = romanize(key) |
|
|
|
key = romanize(key) |
|
|
|
self._values[key] = value |
|
|
|
self._map[key] = value |
|
|
|
new_cutoff = math.ceil(len(key) * 1.1) |
|
|
|
new_cutoff = math.ceil(len(key) * 1.1) |
|
|
|
if new_cutoff > self.length_cutoff: |
|
|
|
if new_cutoff > self.length_cutoff: |
|
|
|
self.length_cutoff = new_cutoff |
|
|
|
self.length_cutoff = new_cutoff |
|
|
|
self.matcher.set_max_length(new_cutoff) |
|
|
|
self.matcher.set_max_length(new_cutoff) |
|
|
|
self._stale = True |
|
|
|
self._stale = True |
|
|
|
|
|
|
|
|
|
|
|
def alias(self, key, value): |
|
|
|
|
|
|
|
self[key] = _Alias(value) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __getitem__(self, key): |
|
|
|
def __getitem__(self, key): |
|
|
|
start_time = timeit.default_timer() |
|
|
|
start_time = timeit.default_timer() |
|
|
|
key = romanize(key) |
|
|
|
key = romanize(key) |
|
|
@ -67,12 +64,10 @@ class FuzzyFilteredMap: |
|
|
|
return None |
|
|
|
return None |
|
|
|
try: |
|
|
|
try: |
|
|
|
matcher = self.matcher |
|
|
|
matcher = self.matcher |
|
|
|
result = min((score, item) for score, item in |
|
|
|
result = min(((score, v) for score, v in |
|
|
|
((matcher.score(key, item[0]), item) for item in self.filtered_items) |
|
|
|
((matcher.score(key, k), v) for k, v in self.filtered_items) |
|
|
|
if score <= 0)[1][1] |
|
|
|
if score <= 0), key=lambda v: v[0])[1] |
|
|
|
self.logger.info(f'Found key "{key}" in time {timeit.default_timer() - start_time}.') |
|
|
|
self.logger.info(f'Found key "{key}" in time {timeit.default_timer() - start_time}.') |
|
|
|
if isinstance(result, _Alias): |
|
|
|
|
|
|
|
return result.value |
|
|
|
|
|
|
|
return result |
|
|
|
return result |
|
|
|
except ValueError: |
|
|
|
except ValueError: |
|
|
|
self.logger.info(f'Found no results for key "{key}" in time {timeit.default_timer() - start_time}.') |
|
|
|
self.logger.info(f'Found no results for key "{key}" in time {timeit.default_timer() - start_time}.') |
|
|
@ -84,35 +79,34 @@ class FuzzyFilteredMap: |
|
|
|
self.logger.debug(f'Rejected key "{key}" due to length.') |
|
|
|
self.logger.debug(f'Rejected key "{key}" due to length.') |
|
|
|
return [] |
|
|
|
return [] |
|
|
|
key = romanize(key) |
|
|
|
key = romanize(key) |
|
|
|
values = [item[1].value if isinstance(item[1], _Alias) else item[1] for score, item in |
|
|
|
values = [v for score, v in |
|
|
|
sorted( |
|
|
|
sorted(((self.matcher.score(key, k), v) for k, v in self.filtered_items), key=lambda v: v[0]) |
|
|
|
(self.matcher.score(key, item[0]), item) for item in self.filtered_items) |
|
|
|
|
|
|
|
if score <= 0] |
|
|
|
if score <= 0] |
|
|
|
seen = set() |
|
|
|
seen_ids = set() |
|
|
|
unique = [] |
|
|
|
unique = [] |
|
|
|
for value in values: |
|
|
|
for value in values: |
|
|
|
if id(value) in seen: |
|
|
|
if id(value) in seen_ids: |
|
|
|
continue |
|
|
|
continue |
|
|
|
unique.append(value) |
|
|
|
unique.append(value) |
|
|
|
seen.add(id(value)) |
|
|
|
seen_ids.add(id(value)) |
|
|
|
self.logger.info(f'Searched key "{key}" in time {timeit.default_timer() - start_time}.') |
|
|
|
self.logger.info(f'Searched key "{key}" in time {timeit.default_timer() - start_time}.') |
|
|
|
return unique |
|
|
|
return unique |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _Alias: |
|
|
|
|
|
|
|
def __init__(self, value): |
|
|
|
|
|
|
|
self.value = value |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FuzzyDictValuesView: |
|
|
|
class FuzzyDictValuesView: |
|
|
|
def __init__(self, map: FuzzyFilteredMap): |
|
|
|
def __init__(self, source: FuzzyFilteredMap): |
|
|
|
self._map = map |
|
|
|
self._map = source |
|
|
|
|
|
|
|
|
|
|
|
def __contains__(self, item): |
|
|
|
def __contains__(self, item): |
|
|
|
return item in self._map._values.values() and self._map.filter(item) |
|
|
|
return item in self._map._map.values() and self._map.filter(item) |
|
|
|
|
|
|
|
|
|
|
|
def __iter__(self): |
|
|
|
def __iter__(self): |
|
|
|
yield from (v for _, v in self._map.filtered_items if not isinstance(v, _Alias)) |
|
|
|
seen_ids = set() |
|
|
|
|
|
|
|
for _, value in self._map.filtered_items: |
|
|
|
|
|
|
|
value_id = id(value) |
|
|
|
|
|
|
|
if value_id not in seen_ids: |
|
|
|
|
|
|
|
seen_ids.add(value_id) |
|
|
|
|
|
|
|
yield value |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass |
|
|
|
@dataclass |
|
|
|