<gh_stars>1-10
"""Map phrases to preferred phrases."""

import logging
from collections import namedtuple


PhraseMapping = namedtuple("PhraseMapping", "preferred_phrase phrase_length")
SortableMatch = namedtuple(
        "SortableMatch",
        "start descending_phrase_length end preferred_phrase")


class PhraseMapper:

    """Rewrites a string, mapping phrases to preferred phrases."""

    logger = logging.getLogger("PhraseMapper")

    def __init__(self, automaton, phrase_tuples):
        """Initialize with an Aho-Corasick automaton and a list of tuples
        containing a phrase and its preferred phrase."""
        self.automaton = automaton
        self.phrase_tuples = phrase_tuples

    def map_phrases(self, text):
        """Return a list of phrases preferred to those found in the text."""
        normalized_text = normalize_text(text)
        matches = self.automaton.iter(normalized_text)
        sortable_matches = [
                match_to_sortable_match(match)
                for match in matches
                if is_valid_match(match, normalized_text)]
        sortable_matches.sort()
        longest_matches = longest_non_overlapping_matches(sortable_matches)
        return [match.preferred_phrase for match in longest_matches]

    def setup(self):
        """Setup and return the phrase mapper."""
        for phrase, preferred_phrase in self.phrase_tuples:
            normalized_phrase = normalize_text(phrase)
            phrase_mapping = PhraseMapping(
                    preferred_phrase,
                    len(normalized_phrase))
            self.automaton.add_word(normalized_phrase, phrase_mapping)
        self.automaton.make_automaton()
        return self


def normalize_text(text):
    """Normalize a text by converting it to lower case and removing
    excess white space."""
    return " ".join(text.casefold().split())


def is_valid_match(match, text):
    """True unless the match starts or ends in the middle of a word."""
    end, phrase_mapping = match
    start = end - phrase_mapping.phrase_length + 1
    return (
            end + 1 == len(text)
            or (not text[end + 1].isalnum())
            or (not text[end].isalnum())
        ) and (
            start == 0
            or (not text[start].isalnum())
            or (not text[start - 1].isalnum())
        )


def match_to_sortable_match(match):
    """Return a sortable match given a match tuple.  Matches will be sorted by
    start position and decending by phrase length."""
    end, phrase_mapping = match
    return SortableMatch(
        start=end - phrase_mapping.phrase_length,
        end=end,
        descending_phrase_length=-phrase_mapping.phrase_length,
        preferred_phrase=phrase_mapping.preferred_phrase)


def longest_non_overlapping_matches(sorted_matches):
    """Return a generator of the longest non-overlapping matches from a sorted
    list of matches."""
    start = -1
    for match in sorted_matches:
        if match.start < start:
            continue
        yield match
        start = match.end


# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 autoindent

"""Map phrases to preferred phrases."""

import logging
from collections import namedtuple


PhraseMapping = namedtuple("PhraseMapping", "preferred_phrase phrase_length")
SortableMatch = namedtuple(
        "SortableMatch",
        "start descending_phrase_length end preferred_phrase")


class PhraseMapper:

    """Rewrites a string, mapping phrases to preferred phrases."""

    logger = logging.getLogger("PhraseMapper")

    def __init__(self, automaton, phrase_tuples):
        """Initialize with an Aho-Corasick automaton and a list of tuples
        containing a phrase and its preferred phrase."""
        self.automaton = automaton
        self.phrase_tuples = phrase_tuples

    def map_phrases(self, text):
        """Return a list of phrases preferred to those found in the text."""
        normalized_text = normalize_text(text)
        matches = self.automaton.iter(normalized_text)
        sortable_matches = [
                match_to_sortable_match(match)
                for match in matches
                if is_valid_match(match, normalized_text)]
        sortable_matches.sort()
        longest_matches = longest_non_overlapping_matches(sortable_matches)
        return [match.preferred_phrase for match in longest_matches]

    def setup(self):
        """Setup and return the phrase mapper."""
        for phrase, preferred_phrase in self.phrase_tuples:
            normalized_phrase = normalize_text(phrase)
            phrase_mapping = PhraseMapping(
                    preferred_phrase,
                    len(normalized_phrase))
            self.automaton.add_word(normalized_phrase, phrase_mapping)
        self.automaton.make_automaton()
        return self


def normalize_text(text):
    """Normalize a text by converting it to lower case and removing
    excess white space."""
    return " ".join(text.casefold().split())


def is_valid_match(match, text):
    """True unless the match starts or ends in the middle of a word."""
    end, phrase_mapping = match
    start = end - phrase_mapping.phrase_length + 1
    return (
            end + 1 == len(text)
            or (not text[end + 1].isalnum())
            or (not text[end].isalnum())
        ) and (
            start == 0
            or (not text[start].isalnum())
            or (not text[start - 1].isalnum())
        )


def match_to_sortable_match(match):
    """Return a sortable match given a match tuple.  Matches will be sorted by
    start position and decending by phrase length."""
    end, phrase_mapping = match
    return SortableMatch(
        start=end - phrase_mapping.phrase_length,
        end=end,
        descending_phrase_length=-phrase_mapping.phrase_length,
        preferred_phrase=phrase_mapping.preferred_phrase)


def longest_non_overlapping_matches(sorted_matches):
    """Return a generator of the longest non-overlapping matches from a sorted
    list of matches."""
    start = -1
    for match in sorted_matches:
        if match.start < start:
            continue
        yield match
        start = match.end


# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 autoindent
Map phrases to preferred phrases. Rewrites a string, mapping phrases to preferred phrases. Initialize with an Aho-Corasick automaton and a list of tuples         containing a phrase and its preferred phrase. Return a list of phrases preferred to those found in the text. Setup and return the phrase mapper. Normalize a text by converting it to lower case and removing     excess white space. True unless the match starts or ends in the middle of a word. Return a sortable match given a match tuple.  Matches will be sorted by     start position and decending by phrase length. Return a generator of the longest non-overlapping matches from a sorted     list of matches. # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 autoindent