Какой самый быстрый способ в Python определить, соответствует ли строка каким-либо условиям в список слов, фраз, логических И?

Я пытаюсь найти в Python быстрый способ проверить, можно ли сопоставить список терминов со строками размером от 50 до 50 000 символов.

Термин может быть:

  • A слово, например. 'яблоко'
  • Фраза, например. 'вишневый пирог'
  • Логическое И для слов и фраз, например. 'сладкий пирог И соленый пирог И безе'

Соответствие - это когда слово или фраза существует вокруг границ слов, поэтому:

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

Без опережающих просмотров (т. е. логических И) это действительно быстро, но как только они добавляются, скорость значительно замедляется.

Есть ли у кого-нибудь идеи, как это можно улучшить? Есть ли способ оптимизировать просмотр вперед, а может быть совсем другой подход? Я не думаю, что стемминг сработает, поскольку он имеет тенденцию быть немного жадным к тому, что ему соответствует.

5
задан jscs 25 March 2011 в 02:17
поделиться