В настоящее время я использую модуль 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
Это можно обойти? Идея состоит в том, чтобы избежать повторения скомпилированных регулярных выражений для получения совпадения.
Я попробую и посмотрю, улучшит ли он производительность. Если мы говорим о NFA, я не уверен, можем ли мы имитировать способ захвата групп с помощью re.
@rumpelsepp Я попробовал одно большое регулярное выражение, но столкнулся с несколькими проблемами с python re, поскольку он не поддерживает повторяющиеся имена групп и имеет ограничение на захват групп. В итоге я использовал другой модуль regex, у которого нет этих ограничений. Я не уверен, есть ли какая-то большая разница между тем, как работает re, и тем, как regex работает внутри.
можете ли вы предоставить образцы данных?
Я видел, как Дэвид Бизли проделал трюк, который может быть вам полезен. Он объединил отдельные регулярные выражения, поместив каждое в именованную группу, а затем использовал недокументированную функцию scanner для обработки каждого совпадения отдельно. См. youtube.com/watch?v=D1twn9kLmYg. Единственная проблема заключается в том, что вы также используете именованные группы - возможно, у вас может быть двухэтапный подход: сначала сопоставить общий образец, а затем разбить каждое сопоставление на нужные части. Или иметь отдельное сопоставление групп с именами.
Кстати, вы должны использовать необработанные строки для правильной интерпретации escape-символов? Например. r'^New York(?P<grp1>\d+/\d+): (?P<grp2>.+)$'
Это количество регулярных выражений или размер набора данных, где возникает узкое место в производительности?






Если все ваши шаблоны регулярных выражений следуют одному и тому же формату названия города (не захвачено), за которым следует захваченная серия чисел с разделителями /, двоеточие и пробел, а затем захваченная остальная часть строки, вы можете просто проанализировать их все с тем же шаблоном регулярного выражения:
def find_match(string):
return re.search(r'(?P<grp1>\d+(?:/\d+)*): (?P<grp2>.+)', string)
Это боль, потому что регулярные выражения - это другой шаблон: /
Можете ли вы обновить свой вопрос, добавив в него несколько совершенно разных шаблонов регулярных выражений? Два приведенных вами примера довольно похожи.
Я добавил еще несколько регулярных выражений в свой вопрос выше
Нарушает ли какое-либо из ваших регулярных выражений совместимость с DFA? Не похоже на ваши примеры. Вы можете использовать Обертка Python вокруг реализации DFA C / C++, такой как re2, которая является заменой re. re2 также вернется к использованию re, если регулярное выражение несовместимо с re2синтаксис, поэтому он оптимизирует все возможные случаи и не откажет в несовместимых случаях.
Обратите внимание, что re2делает поддерживает синтаксис захвата (?P<name>regex), но не поддерживает синтаксис обратной ссылки (?P=<name>).
try:
import re2 as re
re.set_fallback_notification(re.FALLBACK_WARNING)
except ImportError:
# latest version was for Python 2.6
else:
import re
Если у вас есть регулярные выражения с обратными ссылками, вы все равно можете использовать re2 с некоторыми особыми соображениями: вам нужно заменить обратные ссылки в своих регулярных выражениях на .*?, и вы можете найти ложные совпадения, которые вы можете отфильтровать с помощью 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) и перебирать совпадения групп в результирующем объекте совпадения, чтобы попытаться сопоставить только соответствующие исходные регулярные выражения. Объект результата совпадения будет иметь то же состояние, что и исходное решение 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
Вы также можете взглянуть на костер, который является лучше поддерживаемой оболочкой для той же библиотеки C++, но не заменяет re. Также есть Python-оболочка для RuRe, который является самым быстрым движком регулярных выражений, о котором я знаю.
Чтобы уточнить мой комментарий: проблема с помещением всего этого в одно большое регулярное выражение заключается в том, что имена групп должны быть уникальными. Однако вы можете обрабатывать свои регулярные выражения следующим образом:
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)
Я читал ваш код внимательнее. Это немного неортодоксально, но имеет смысл. Небольшая проблема заключается в том, что вы аннулировали match.groupdict (), и OP может использовать это в своем коде в другом месте. Я понимаю, что matchdict идентичен. Возможно, вы можете обновить внутреннюю структуру match, чтобы OP не изменял свой код каким-либо образом, чтобы использовать ваше решение. Кажется отдаленной задачей удалить все имена групп, чтобы убедиться, что те, которые вы обертываете, уникальны, но это достаточно просто, если вы можете воссоздать предполагаемое состояние объекта сопоставления.
Понятно. Вам нужно удалить все имена групп, потому что имена групп конфликтуют при объединении регулярных выражений вместе.
Точно! grp1, grp2, year и т. д. использовались в нескольких регулярных выражениях, вызывая проблемы. В качестве альтернативы вы можете добавить префикс к именам групп, а не удалять их, есть много вариантов для улучшения кода.
Предлагаю сделать следующие шаги:
Создайте файл Excel с именем Patterns.csv и добавьте в него два столбца Узоры и Имя, где шаблон - это шаблон регулярного выражения, такой как ^New York(?P<grp1>\d+/\d+): (?P<grp2>.+)$', а имя может быть New York. Это поможет вам сохранить все регулярные выражения на отдельном ресурсе, отличном от вашего кода. Это поможет вам в будущем, если вы захотите добавлять / вычитать / изменять регулярные выражения.
Прочтите этот csv, используя команду ниже:
import pandas as pd
df = pd.read_csv("\\Patterns.csv")
Напишите код для анализа этого CSV, как показано ниже:
pattern = df['pattern'].tolist()
pattern_name = df['name'].tolist()
pattern_dict = dict(zip(pattern_name, pattern))
Напишите шаблон регулярного выражения, чтобы узнать все совпадающие значения:
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 будет диктовкой. разделены ;;, содержащие значения имен шаблонов, которые совпадают, и пустые для того, что не соответствует.
ПОЖАЛУЙСТА, не используйте pandas для чтения в CSV. Это офигенный 25-мегабайтный руль. Просто используйте проклятый import csv. Боже мой, отходы.
@Julian: Прочтите softwarerecs.stackexchange.com/questions/7463/…, чтобы узнать, каков самый быстрый способ чтения csv в python. И здесь OP хочет что-то быстрое и эффективное.
Посмотрите, pandas быстрее анализирует данные из CSV, но это примерно на 0,15 секунды быстрее для набора данных из 20000 строк. Так как это делается только ОДИН РАЗ, я бы сказал, что большая потеря - это огромная библиотека, которую вы втягиваете. IE излишек. Проблемы, с которыми сталкивается OP, не связаны с импортом или даже циклом по списку REGEXES, проблема заключается в сравнении больших наборов данных.
Мы можем согласиться с тем, чтобы не согласиться, но я не думаю, что это правильный вопрос или место для этого
Я бы посмотрел на re.Scanner. Он недокументирован и помечен как экспериментальный, но является хорошим примером использования sre_parse и sre_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)
Вы пробовали вместо этого одно большое регулярное выражение? Вы можете создавать именованные группы захвата и объединять их вместе с
|.