web-dev-qa-db-fra.com

Expressions régulières Python vers NFA

J'utilise actuellement le module re de python pour rechercher et capturer des groupes . J'ai une liste d'expressions régulières que je dois compiler et faire correspondre à un grand ensemble de données qui pose des problèmes de performances.

Example:

REGEXES = [
    '^New York(?P<grp1>\d+/\d+): (?P<grp2>.+)$',
    '^Ohio (?P<grp1>\d+/\d+/\d+): (?P<grp2>.+)$',
    '(?P<year>\d{4}-\d{1,2}-\d{1,2})$',
    '^(?P<year>\d{1,2}/\d{1,2}/\d{2,4})$',
    '^(?P<title>.+?)[- ]+E(?P<epi>\d+)$'
    .
    .
    .
    .
]

Note: Les expressions rationnelles ne seront pas similaires

COMPILED_REGEXES = [re.compile(r, flags=re.I) for r in REGEXES]

def find_match(string):
    for regex in COMPILED_REGEXES:
        match = regex.search(string)
        if not match:
            continue
        return match

Y a-t-il un moyen de contourner ceci? L'idée est d'éviter les itérations dans les expressions rationnelles compilées pour obtenir une correspondance.

8
Abhi

Est-ce que l'un de vos regexps rompt la compatibilité DFA? Cela ne ressemble pas à cela dans vos exemples. Vous pouvez utiliser un wrapper Python autour d'une implémentation DFA C/C++ telle que re2 , qui remplace avantageusement re. re2 utilisera également re si l'expression régulière est incompatible avec le re2syntaxe , afin d'optimiser tous les cas possibles et de ne pas échouer dans les cas incompatibles.

Notez que re2ne supporte la syntaxe de capture (?P<name>regex), mais ne ne prend pas en charge le (?P=<name>) backref sytnax.

try:
    import re2 as re
    re.set_fallback_notification(re.FALLBACK_WARNING)
except ImportError:
    # latest version was for Python 2.6
else:
    import re

Si vous avez des expressions rationnelles avec des backrefs, vous pouvez toujours utiliser re2 avec quelques considérations spéciales: vous devrez remplacer les backrefs dans vos regexps par .*? et vous pourrez trouver de fausses correspondances que vous pourrez filtrer avec re. Dans les données du monde réel, les faux appariements seront probablement rares.

Voici un exemple illustratif:

import re
try:
    import re2
    re2.set_fallback_notification(re2.FALLBACK_WARNING)
except ImportError:
    # latest version was for Python 2.6

REGEXES = [
    '^New York(?P<grp1>\d+/\d+): (?P<grp2>.+)$',
    '^Ohio (?P<grp1>\d+/\d+/\d+): (?P<grp2>.+)$',
    '(?P<year>\d{4}-\d{1,2}-\d{1,2})$',
    '^(?P<year>\d{1,2}/\d{1,2}/\d{2,4})$',
    '^(?P<title>.+?)[- ]+E(?P<epi>\d+)$',
]

COMPILED_REGEXES = [re.compile(r, flags=re.I) for r in REGEXES]
# replace all backrefs with .*? for re2 compatibility
# is there other unsupported syntax in REGEXES?
COMPILED_REGEXES_DFA = [re2.compile(re2.sub(r'\\d|\\g\\d|\\g\<\d+\>|\\g\<\w+\>', '.*?', r), flags=re2.I) for r in REGEXES]

def find_match(string):
    for regex, regex_dfa in Zip(COMPILED_REGEXES, COMPILED_REGEXES_DFA):
        match_dfa = regex_dfa.search(string)
        if not match_dfa:
            continue
        match = regex.search(string)
        # most likely branch comes first for better branch prediction
        if match:
            return match

Si cela n’est pas assez rapide, vous pouvez utiliser diverses techniques pour alimenter les hits DFA en re au fur et à mesure de leur traitement, au lieu de les stocker dans un fichier ou en mémoire et de les remettre une fois qu’ils ont été collectés.

Vous pouvez également combiner toutes vos expressions rationnelles en une seule grande expression rationnelle DFA de groupes alternatifs (r1)|(r2)|(r3)| ... |(rN) et parcourir les correspondances de groupe sur l'objet de correspondance obtenu pour essayer de ne faire correspondre que les expressions rationnelles correspondantes. L'objet résultat de la correspondance aura le même état que la solution d'origine de OP.

# rename group names in regexeps to avoid name collisions
REGEXES_PREFIXED = [re2.sub(r'\(\?P\<(\w+)\>', r'(P<re{}_\1>'.format(idx), r) for idx, r in enumerate(REGEXES)]
# wrap and fold regexps (?P<hit0>pattern)| ... |(?P<hitN>pattern)
REGEX_BIG = ''
for idx, r in enumerate(REGEXES_PREFIXED):
    REGEX_BIG += '(?P<hit{}>{})|'.format(idx, r)
else:
    REGEX_BIG = REGEX_BIG[0:-1]
regex_dfa_big = re2.compile(REGEX_BIG, flags = re2.I)

def find_match(string):
    match_dfa = regex_dfa_big.search(string)
    if match_dfa:
        # only interested in hit# match groups
        hits = [n for n, _ in match_dfa.groupdict().iteritems() if re2.match(r'hit\d+', n)]
        # check for false positives
        for idx in [int(h.replace('hit', '')) for h in hits]
            match = COMPILED_REGEXES[idx].search(string)
            if match:
                return match

Vous pouvez aussi regarder pyre } _ qui est un wrapper mieux maintenu pour la même bibliothèque C++, mais pas une substitution pour re. Il y a aussi un Python Wrapper pour RuRe , qui est le moteur de regex le plus rapide que je connaisse.

5
okovko

Pour élaborer mon commentaire: le problème avec tout mettre dans une grande expression rationnelle est que les noms de groupe doivent être uniques. Cependant, vous pouvez traiter vos expressions rationnelles comme suit:

import re

REGEXES = [
    r'^New York(?P<grp1>\d+/\d+): (?P<grp2>.+)$',
    r'^Ohio (?P<grp1>\d+/\d+/\d+): (?P<grp2>.+)$',
    r'(?P<year>\d{4}-\d{1,2}-\d{1,2})$',
    r'^(?P<year>\d{1,2}/\d{1,2}/\d{2,4})$',
    r'^(?P<title>.+?)[- ]+E(?P<epi>\d+)$']

# Find the names of groups in the regexps
groupnames = {'RE_%s'%i:re.findall(r'\(\?P<([^>]+)>', r) for i, r in enumerate(REGEXES)}

# Convert the named groups into unnamed ones
re_list_cleaned = [re.sub(r'\?P<([^>]+)>', '', r) for r in REGEXES]

# Wrap each regexp in a named group
token_re_list = ['(?P<RE_%s>%s)'%(i, r) for i, r in enumerate(re_list_cleaned)]

# Put them all together
mighty_re = re.compile('|'.join(token_re_list), re.MULTILINE)

# Use the regexp to process a big file
with open('bigfile.txt') as f:
    txt = f.read()
for match in mighty_re.finditer(txt):
    # Now find out which regexp made the match and put the matched data in a dictionary
    re_name = match.lastgroup
    groups = [g for g in match.groups() if g is not None]
    gn = groupnames[re_name]
    matchdict = dict(Zip(gn, groups[1:]))
    print ('Found:', re_name, matchdict)
5
EvertW

Je suggère de faire les étapes suivantes:

  1. Créez un fichier Excel appelé Patterns.csv contenant deux colonnes: Patterns & Name, où motif est le motif d'expression régulière tel que ^New York(?P<grp1>\d+/\d+): (?P<grp2>.+)$' et nom peut être New York. Cela vous aidera à conserver toutes les expressions rationnelles dans une ressource distincte autre que votre code. Cela vous aidera à l’avenir si vous souhaitez ajouter/soustraire/modifier des expressions rationnelles.

  2. Lisez que csv en utilisant la commande ci-dessous:

    import pandas as pd
    df = pd.read_csv("\\Patterns.csv")

  3. Écrivez le code pour analyser ce csv comme ci-dessous:

    pattern = df['pattern'].tolist() pattern_name = df['name'].tolist() pattern_dict = dict(Zip(pattern_name, pattern))

  4. Ecrivez un motif regex pour trouver toutes les valeurs qui correspondent:

import collections sep = " ;; " NLU_Dict=collections.defaultdict() for pn, p in pattern_dict.items(): val = sep.join([sep.join(filter(lambda x: len(str(x).strip()) >0, map(str, v))) for in re.findall(p, text, re.I)]) NLU_Dict[pn] = val

Votre NLU_Dict sera un dictée. séparés par ;; contenant les valeurs des noms de modèle qui correspondent et vides pour ce qui ne correspond pas.

2
Rahul Agarwal

Je regarderais re.Scanner. C'est non documenté et marqué comme expérimental, mais c'est un bon exemple d'utilisation de sre_parse et sre_compile pour construire une expression régulière en analysant, en fusionnant, puis en compilant. Si vous ne vous souciez pas des noms de groupe et que vous ne voulez capturer que des groupes, cela devrait fonctionner. Remarquez, ce code n'a pas d'erreur de vérification.

import re
import sre_parse
import sre_compile


def compile_multiple(subpatterns, flags=0):
    """
    Return a compiled regex from an iterable collection of
    pattern strings so that it matches any of the patterns
    in the collection.
    """
    from sre_constants import BRANCH, SUBPATTERN
    if isinstance(flags, re.RegexFlag):
        flags = flags.value
    pattern = sre_parse.Pattern()
    pattern.flags = flags
    parsed_subpatterns = []
    for subpattern in subpatterns:
        gid = pattern.opengroup()
        parsed_subpattern = sre_parse.parse(subpattern, flags)
        parsed_subpatterns.append(sre_parse.SubPattern(pattern, [
            (SUBPATTERN, (gid, 0, 0, sre_parse.parse(subpattern, flags))),
        ]))
        pattern.closegroup(gid, parsed_subpatterns[-1])
    combined_pattern = sre_parse.SubPattern(pattern, [(BRANCH, (None, parsed_subpatterns))])
    return sre_compile.compile(combined_pattern)
0
bitsplit