繁体   English   中英

Python正则表达式到NFA

[英]Python Regular Expressions to NFA

我目前正在使用python的re模块来搜索和捕获组。 我列出了正则表达式,我必须编译并匹配导致性能问题的大型数据集

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+)$'
    .
    .
    .
    .
]

注意 :正则表达式不会相似

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

有没有解决的办法? 我们的想法是避免通过编译的正则表达式进行迭代以获得匹配。

你的任何正则表达式是否会破坏DFA兼容性? 在你的例子中看起来不像。 您可以使用围绕C / C ++ DFA实现的Python包装器 ,例如re2 ,它可以替代re 如果正则表达式与re2 语法不兼容, re2也将回退到使用re ,因此它将优化所有可能的情况,并且在不兼容的情况下不会失败。

请注意, re2 确实支持(?P<name>regex)捕获语法,但它支持(?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

如果你有backrefs的regexs,你仍然可以使用re2 ,但有一些特殊注意事项:你需要用.*?替换你的regexp中的backrefs .*? ,你可能会发现可以用re过滤掉的错误匹配。 在现实世界数据中,错误匹配可能不常见。

这是一个说明性的例子:

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

如果这还不够快,你可以采用各种技术来养活DFA命中re为他们进行处理,而不是在文件或存储器中存储他们,交给他们关闭,一旦他们都在收集。

您还可以将所有正则表达式组合成一个交替组的大DFA正则表达式(r1)|(r2)|(r3)| ... |(rN) (r1)|(r2)|(r3)| ... |(rN)并在生成的匹配对象上迭代您的组匹配,以尝试仅匹配相应的原始正则表达式。 匹配结果对象将具有与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

您还可以查看pyre ,它是相同C ++库的更好维护包装器,但不是替代re替代品。 还有一个用于RuRePython包装器 ,它是我所知道的最快的正则表达式引擎。

详细说明我的意见:把它放在一个大的正则表达式中的问题是组名必须是唯一的。 但是,您可以按如下方式处理正则表达式:

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)

我建议做以下步骤:

  1. 创建一个名为Patterns.csv的excel,其中有两列: PatternsName ,其中pattern是正则表达式,如^New York(?P<grp1>\\d+/\\d+): (?P<grp2>.+)$'而名字可以是New York 这将帮助您在除代码之外的单独资源中维护所有正则表达式。 如果您想添加/减去/修改正则表达式,它将在未来帮助您。

  2. 使用以下命令读取该csv:

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

  3. 编写代码来解析这个csv,如下所示:

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

  4. 编写模式正则表达式以找出匹配的所有值:

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

你的NLU_Dict将是一个词典。 分开;; 包含匹配的模式名称的值,以及不匹配的模式名称的空白。

我会看看re.Scanner 它没有文档并标记为实验,但是使用sre_parsesre_compile通过解析,合并然后编译来构建正则表达式的一个很好的例子。 如果您不关心组名,并且只想捕获组,这应该可行。 请注意,此代码没有错误检查。

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)

如果所有正则表达式模式都遵循相同的城市名称格式(未捕获),然后是捕获的一系列/界数字,冒号和空格,然后是捕获的其余字符串,您只需将它们全部解析具有相同的正则表达式模式:

def find_match(string):
    return re.search(r'(?P<grp1>\d+(?:/\d+)*): (?P<grp2>.+)', string)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM