Source code for fuzzy_search.search.context_searcher

"""Context-aware fuzzy phrase searcher.

Defines :class:`FuzzyContextSearcher`, which extends
:class:`~fuzzy_search.search.phrase_searcher.FuzzyPhraseSearcher` to attach a
window of surrounding text to each match, and to search within that context
window for additional phrase matches.
"""
from typing import List, Union

from fuzzy_search.match.phrase_match import PhraseMatch, PhraseMatchInContext
from fuzzy_search.search.phrase_searcher import FuzzyPhraseSearcher


[docs] class FuzzyContextSearcher(FuzzyPhraseSearcher): """Fuzzy phrase searcher that attaches surrounding text context to each match. Extends FuzzyPhraseSearcher's matching with a configurable prefix/suffix context window around each match, and supports re-searching that context window for further matches. Attributes ---------- context_size : int default size (in characters) of the prefix and suffix context window added around each match. """ def __init__(self, config: Union[dict, None] = None): super().__init__(config) self.context_size = 100 if config is not None: self.configure_context(config)
[docs] def configure_context(self, config: dict) -> None: """Configure the context searcher. :param config: a dictionary with configuration parameters to override the defaults :type config: dict """ super().configure(config) if "context_size" in config: self.context_size = config["context_size"]
[docs] def add_match_context(self, match: PhraseMatch, text: Union[str, dict], context_size: Union[None, int] = None, prefix_size: Union[None, int] = None, suffix_size: Union[None, int] = None) -> PhraseMatchInContext: """Add context to a given match and its corresponding text document. :param match: a phrase match object :type match: PhraseMatch :param text: the text that the match was taken from :type text: Union[str, dict] :param context_size: the size of the pre- and suffix window :type context_size: int :param prefix_size: size of the prefix context :type prefix_size: Union[None, int] :param suffix_size: size of the suffix context :type suffix_size: Union[None, int] :return: the phrase match object with context :rtype: PhraseMatchInContext """ if context_size is None: context_size = self.context_size prefix_size = prefix_size if prefix_size is not None else context_size suffix_size = suffix_size if suffix_size is not None else context_size return PhraseMatchInContext(match, text, prefix_size=prefix_size, suffix_size=suffix_size)
[docs] def find_matches(self, text: Union[str, dict], use_word_boundaries: Union[None, bool] = None, allow_overlapping_matches: bool = True, include_variants: bool = None, filter_distractors: bool = None, prefix_size: Union[None, int] = None, suffix_size: Union[None, int] = None, skip_exact_matching: bool = None) -> List[PhraseMatchInContext]: """Find fuzzy matches for registered phrases and add context around match string. This extends the find_matches function of the FuzzyPhraseSearcher by adding local context to each match. :param text: the text (string or dictionary with 'text' property) to find fuzzy matching phrases in. :type text: Union[str, Dict[str, str]] :param use_word_boundaries: use word boundaries in determining match boundaries :type use_word_boundaries: bool :param allow_overlapping_matches: boolean flag for whether to allow matches to overlap in their text ranges :type allow_overlapping_matches: bool :param include_variants: boolean flag for whether to include phrase variants for finding matches :type include_variants: bool :param filter_distractors: boolean flag for whether to remove phrase matches that better match distractors :type filter_distractors: bool :param prefix_size: the size of the prefix context window :type prefix_size: Union[None, int] :param suffix_size: the size of the suffix context window :type suffix_size: Union[None, int] :param skip_exact_matching: boolean flag whether to skip the exact matching step :type skip_exact_matching: Union[None, bool] :return: a list of phrases matches with text surrounding the match string :rtype: PhraseMatchInContext """ matches = super().find_matches(text, use_word_boundaries=use_word_boundaries, allow_overlapping_matches=allow_overlapping_matches, include_variants=include_variants, filter_distractors=filter_distractors, skip_exact_matching=skip_exact_matching) return [self.add_match_context(match, text, prefix_size=prefix_size, suffix_size=suffix_size) for match in matches]
[docs] def find_matches_in_context(self, match_in_context: PhraseMatchInContext, use_word_boundaries: Union[None, bool] = None, include_variants: Union[None, bool] = None, filter_distractors: Union[None, bool] = None) -> List[PhraseMatch]: """Use a MatchInContext object to find other phrases in the context of that match. :param match_in_context: a match phrase with context from the text that the match was taken from :type match_in_context: PhraseMatchInContext :param use_word_boundaries: boolean whether to adjust match strings to word boundaries :type use_word_boundaries: bool :param include_variants: boolean whether to include variants of phrases in matching :type include_variants: bool :param filter_distractors: boolean whether to remove matches that are closer to distractors :type filter_distractors: bool :return: a list of match objects :rtype: List[PhraseMatch] """ # override searcher configuration if a boolean is passed use_word_boundaries = self.use_word_boundaries if use_word_boundaries is None else use_word_boundaries include_variants = self.include_variants if include_variants is None else include_variants filter_distractors = self.filter_distractors if filter_distractors is None else filter_distractors # search the context of the original match context_matches = self.find_matches(match_in_context.context, use_word_boundaries=use_word_boundaries, include_variants=include_variants, filter_distractors=filter_distractors) # recalculate the match offset with respect to the original text for match in context_matches: match.offset += match_in_context.context_start match.end += match_in_context.context_start return context_matches
if __name__ == "__main__": sample_text = "A'nthony van der Truyn en Adriaen Bosman, Makelaers tot Rotterdam, prefenteren," + \ "uyt de Hint te verkopen etfn curieufc Party opreckw ?al somfl'e Schalyen of Leyen."