Поиск соответствия строки шаблону
в какой-то момент в моем приложении мне нужно сопоставить некоторые строки с шаблоном. Предположим, что некоторые из примеров строк выглядят следующим образом:
- Привет, Джон.
- какой прекрасный день сегодня!
6 ответов
моей первой мыслью было бы, чтобы двигатель regexp взял на себя все хлопоты по обработке этого. Они обычно оптимизированы для обработки большого количества текста, поэтому это не должно быть такой большой проблемой производительности. Это грубая сила, но представление, кажется, в порядке. И вы можете разделить вход на части и иметь несколько процессов, обрабатывающих их. Вот мое умеренно протестированное решение (в Python).
import random
import string
import re
def create_random_sentence():
nwords = random.randint(4, 10)
sentence = []
for i in range(nwords):
sentence.append("".join(random.choice(string.lowercase) for x in range(random.randint(3,10))))
ret = " ".join(sentence)
print ret
return ret
patterns = [ r"Hi there, [a-zA-Z]+.",
r"What a lovely day today!",
r"Lovely sunset today, [a-zA-Z]+, isn't it?",
r"Will you be meeting [a-zA-Z]+ today, [a-zA-Z]+\?"]
for i in range(95):
patterns.append(create_random_sentence())
monster_pattern = "|".join("(%s)"%x for x in patterns)
print monster_pattern
print "--------------"
monster_regexp = re.compile(monster_pattern)
inputs = ["Hi there, John.",
"What a lovely day today!",
"Lovely sunset today, John, isn't it?",
"Will you be meeting Linda today, John?",
"Goobledigoock"]*2000
for i in inputs:
ret = monster_regexp.search(i)
if ret:
print ".",
else:
print "x",
Я создал сотню шаблонов. Это максимальный предел библиотека регулярных выражений python. 4 из них-ваши фактические примеры, а остальные-случайные предложения, чтобы немного подчеркнуть производительность.
затем я объединил их в одно регулярное выражение со 100 группами. (group1)|(group2)|(group3)|...
. Я предполагаю, что вам придется дезинфицировать входные данные для вещей, которые могут иметь значения в регулярных выражениях (например,?
etc.). Это monster_regexp
.
тестирование одной строки против этого проверяет его на 100 шаблонов за один выстрел. Есть методы, которые fetch вне точная группа, которая была сопоставлена. Я тестирую 10000 строк, 80% из которых должны соответствовать и 10%, которые не будут. Это короткие cirtcuits так что если есть успех, это будет сравнительно быстро. Сбои должны будут проходить через все регулярное выражение, поэтому оно будет медленнее. Вы можете заказать вещи на основе частоты ввода, чтобы получить дополнительную производительность.
Я запустил это на своей машине, и это мое время.
python /tmp/scratch.py 0.13s user 0.00s system 97% cpu 0.136 total
что не так уж плохо.
однако для запуска шаблона против такого большого регулярного выражения и сбоя потребуется больше времени, поэтому я изменил входные данные, чтобы иметь много случайно сгенерированных строк, которые не будут совпадать, а затем попытался. 10000 строк, ни одна из которых не соответствует monster_regexp, и я получил это.
python /tmp/scratch.py 3.76s user 0.01s system 99% cpu 3.779 total
Я рассматриваю это как проблему разбора. Идея заключается в том, что функция parser принимает строку и определяет, является ли она допустимой или нет.
строка действительна, если вы можете find
среди данной модели. Это означает, что вам нужен индекс всех моделей. Индекс должен быть полнотекстовым. Также он должен соответствовать согласно положению слова. например. оно должно короткое замыкание если первое слово входного сигнала не найдено среди первого слова картин. Он должен позаботиться из any
матч ie %s
в шаблоне.
одним из решений является размещение шаблонов в базе данных in memory (например. redis) и сделать на нем полнотекстовый индекс. (это не будет соответствовать позиции слова), но вы должны быть в состоянии сузить до правильного шаблона, разделив ввод на слова и поиск. Поиск будет очень быстрым, потому что у вас есть небольшая база данных в памяти. Также обратите внимание, что вы ищете ближайший матч. Одно или несколько слов не совпадут. Этот наибольшее количество совпадений-это шаблон, который вы хотите.
еще лучшим решением является создание собственного индекса в формате словаря. Вот пример индекса для четырех шаблонов, которые вы дали в качестве объекта JavaScript.
{
"Hi": { "there": {"%s": null}},
"What: {"a": {"lovely": {"day": {"today": null}}}},
"Lovely": {"sunset": {"today": {"%s": {"isnt": {"it": null}}}}},
"Will": {"you": {"be": {"meeting": {"%s": {"today": {"%s": null}}}}}}
}
этот индекс рекурсивно убывает в соответствии со словом postion. Поэтому ищите первое слово, если найдено, ищите следующее внутри объекта, возвращаемого первым и так далее. Одни и те же слова на данном уровне будут иметь только один ключ. Вы также должны матч any
случае. Это должно быть ослепительно быстро в памяти.
аналогичное решение Noufal, но возвращает подходящий шаблон или нет.
import re
patterns = [
"Hi there, %s.",
"What a lovely day today!",
"Lovely sunset today, %s, isn't it",
"Will you be meeting %s today, %s?"
]
def make_re_pattern(pattern):
# characters like . ? etc. have special meaning in regular expressions.
# Escape the string to avoid interpretting them as differently.
# The re.escape function escapes even %, so replacing that with XXX to avoid that.
p = re.escape(pattern.replace("%s", "XXX"))
return p.replace("XXX", "\w+")
# Join all the pattens into a single regular expression.
# Each pattern is enclosed in () to remember the match.
# This will help us to find the matched pattern.
rx = re.compile("|".join("(" + make_re_pattern(p) + ")" for p in patterns))
def match(s):
"""Given an input strings, returns the matched pattern or None."""
m = rx.match(s)
if m:
# Find the index of the matching group.
index = (i for i, group in enumerate(m.groups()) if group is not None).next()
return patterns[index]
# Testing with couple of patterns
print match("Hi there, John.")
print match("Will you be meeting Linda today, John?")
решение на Python. JS должен быть похожим.
>>> re2.compile('^ABC(.*)E$').search('ABCDE') == None
False
>>> re2.compile('^ABC(.*)E$').search('ABCDDDDDDE') == None
False
>>> re2.compile('^ABC(.*)E$').search('ABX') == None
True
>>>
трюк состоит в том, чтобы использовать ^ и$, чтобы связать ваш шаблон и сделать его "шаблоном". Использовать.( *) или.( + ) или то, что вы хотите "искать".
основным узким местом для вас, имхо, будет итерация по списку этих шаблонов. Поиск регулярных выражений является вычислительно дорогостоящим.
Если вы хотите результат" соответствует ли какой-либо шаблон", создайте массивное или основанное регулярное выражение и позвольте вашему движку regex обрабатывать "ОР" для тебя.
кроме того, если у вас есть только шаблоны префиксов, проверьте структуру данных TRIE.
Это может быть задание для sscanf, есть реализация в js:http://phpjs.org/functions/sscanf/; копируемая функция такова:http://php.net/manual/en/function.sscanf.php.
вы должны иметь возможность использовать его, не сильно меняя подготовленные строки, но у меня есть сомнения относительно выступлений.
проблема мне не ясна. Вы хотите взять шаблоны и построить из них regexes? Большинство движков регулярных выражений имеют опцию "quoted string". (\Q \E). Так что ты можешь взять веревочку и сделать это. ^\QHi там,\E (?:.*) \\В. Е$ это будут регексы, которые точно соответствуют строке, которую вы хотите вне ваших переменных.
Если вы хотите использовать одно регулярное выражение для соответствия только одному шаблону, вы можете поместить их в сгруппированные шаблоны, чтобы узнать, какой из них соответствует, но это не даст вам каждый совпадение, только первое.
Если вы используете правильный парсер (я использовал PEG.js), это может быть более доступным для обслуживания. Так что это еще один вариант, если вы думаете, что можете застрять в regex hell