Я пытаюсь найти в Python быстрый способ проверить, можно ли сопоставить список терминов со строками размером от 50 до 50 000 символов.
Термин может быть:
Соответствие - это когда слово или фраза существует вокруг границ слов, поэтому:
match(term='apple', string='An apple a day.') # True
match(term='berry pie', string='A delicious berry pie.') # True
match(term='berry pie', string='A delicious blueberry pie.') # False
В настоящее время у меня около 40 терминов, большинство из них - простые слова. Количество терминов со временем будет увеличиваться, но я бы не ожидал, что оно превысит 400.
Меня не интересует, какому термину (ам) соответствует строка или где в строке она соответствует, мне просто нужно значение истина / ложь для совпадения с каждой строкой - гораздо более вероятно, что никакие термины не будут соответствовать строке, так что для 1 из 500, где оно действительно совпадает, я могу сохранить строку для дальнейшей обработки.
Скорость является наиболее важным критерием, и я хотел бы использовать существующий код тех, кто умнее меня, а не пытаться реализовать технический документ. :)
На данный момент самое быстрое решение, которое я придумал, это:
def data():
return [
"The apple is the pomaceous fruit of the apple tree, species Malus domestica in the rose family (Rosaceae).",
"This resulted in early armies adopting the style of hunter-foraging.",
"Beef pie fillings are popular in Australia. Chicken pie fillings are too."
]
def boolean_and(terms):
return '(%s)' % (''.join(['(?=.*\\b%s\\b)' % (term) for term in terms]))
def run():
words_and_phrases = ['apple', 'cherry pie']
booleans = [boolean_and(terms) for terms in [['sweet pie', 'savoury pie', 'meringue'], ['chicken pie', 'beef pie']]]
regex = re.compile(r'(?i)(\b(%s)\b|%s)' % ('|'.join(words_and_phrases), '|'.join(booleans)))
matched_data = list()
for d in data():
if regex.search(d):
matched_data.append(d)
Регулярное выражение завершается так:
(?i)(\b(apple|cherry pie)\b|((?=.*\bsweet pie\b)(?=.*\bsavoury pie\b)(?=.*\bmeringue\b))|((?=.*\bchicken pie\b)(?=.*\bbeef pie\b)))
Таким образом, все термины объединяются вместе, регистр игнорируется, слова / фразы заключаются в \ b для границ слов, логические И используют опережающие попытки, чтобы все термины были сопоставлены, но они не должны совпадать в определенном порядке.
Результат Timeit:
print timeit.Timer('run()', 'from __main__ import run').timeit(number=10000)
1.41534304619
Без опережающих просмотров (т. е. логических И) это действительно быстро, но как только они добавляются, скорость значительно замедляется.
Есть ли у кого-нибудь идеи, как это можно улучшить? Есть ли способ оптимизировать просмотр вперед, а может быть совсем другой подход? Я не думаю, что стемминг сработает, поскольку он имеет тенденцию быть немного жадным к тому, что ему соответствует.