Nano Hash - криптовалюты, майнинг, программирование

Python, самый быстрый способ перебирать регулярные выражения, но останавливаться при первом совпадении

У меня есть функция, которая возвращает True, если строка соответствует хотя бы одному регулярному выражению в списке, и False в противном случае. Функция вызывается достаточно часто, поэтому производительность является проблемой.

При запуске через cProfile функция тратит около 65% своего времени на сопоставление и 35% своего времени на итерацию по списку.

Я бы подумал, что будет способ использовать map() или что-то в этом роде, но я не могу придумать, как остановить итерацию после того, как он найдет совпадение.

Есть ли способ сделать функцию быстрее, но при этом вернуть ее после нахождения первого совпадения?

def matches_pattern(str, patterns):
    for pattern in patterns:
        if pattern.match(str):
            return True
    return False

Ответы:


1

Первое, что приходит на ум, — это перенести цикл на сторону C с помощью выражения генератора:

def matches_pattern(s, patterns):
    return any(p.match(s) for p in patterns)

Возможно, вам даже не нужна отдельная функция для этого.

Еще одна вещь, которую вы должны попробовать, — создать одно составное регулярное выражение с использованием оператора чередования |, чтобы у движка была возможность оптимизировать его для вас. Вы также можете создать регулярное выражение динамически из списка шаблонов строк, если это необходимо:

def matches_pattern(s, patterns):
    return re.match('|'.join('(?:%s)' % p for p in patterns), s)

Конечно, вам нужно, чтобы ваши регулярные выражения были в строковой форме, чтобы это работало. Просто профилируйте оба из них и проверьте, какой из них быстрее :)

Вы также можете ознакомиться с общим советом по отладке регулярных выражений в Python. Это также может помочь найти возможности для оптимизации.

ОБНОВЛЕНИЕ: мне стало любопытно, и я написал небольшой тест:

import timeit

setup = """
import re
patterns = [".*abc", "123.*", "ab.*", "foo.*bar", "11010.*", "1[^o]*"]*10
strings = ["asdabc", "123awd2", "abasdae23", "fooasdabar", "111", "11010100101", "xxxx", "eeeeee", "dddddddddddddd", "ffffff"]*10
compiled_patterns = list(map(re.compile, patterns))

def matches_pattern(str, patterns):
    for pattern in patterns:
        if pattern.match(str):
            return True
    return False

def test0():
    for s in strings:
        matches_pattern(s, compiled_patterns)

def test1():
    for s in strings:
        any(p.match(s) for p in compiled_patterns)

def test2():
    for s in strings:
        re.match('|'.join('(?:%s)' % p for p in patterns), s)

def test3():
    r = re.compile('|'.join('(?:%s)' % p for p in patterns))
    for s in strings:
        r.match(s)
"""

import sys
print(timeit.timeit("test0()", setup=setup, number=1000))
print(timeit.timeit("test1()", setup=setup, number=1000))
print(timeit.timeit("test2()", setup=setup, number=1000))
print(timeit.timeit("test3()", setup=setup, number=1000))

Вывод на моей машине:

1.4120500087738037
1.662621021270752
4.729579925537109
0.1489570140838623

Так что any не кажется быстрее, чем ваш первоначальный подход. Динамическое создание регулярного выражения также не очень быстрое. Но если вам удастся создать регулярное выражение заранее и использовать его несколько раз, это может привести к повышению производительности. Вы также можете адаптировать этот тест для тестирования некоторых других вариантов :)

14.05.2012
  • '|'.join(patterns) небезопасно. Regex — сложный язык. Вы хотите сначала обернуть каждый термин в (?:...), чтобы быть в безопасности. 15.05.2012
  • @Karl: Совершенно верно, спасибо, что указали на это. Это все еще не совсем безопасно, хотя, я думаю, если есть обратные ссылки. Это надо рассматривать в каждом конкретном случае. 15.05.2012
  • Огромное спасибо. Я попробую создать регулярное выражение заранее. Также я не могу поверить, что забыл о any. 15.05.2012

  • 2

    Самый быстрый способ сделать это — объединить все регулярные выражения в одно с "|" между ними, а затем выполнить один вызов сопоставления регулярных выражений. Кроме того, вы захотите скомпилировать его один раз, чтобы избежать повторной компиляции регулярных выражений.

    Например:

    def matches_pattern(s, pats):
        pat = "|".join("(%s)" % p for p in pats)
        return bool(re.match(pat, s))
    

    Это для pats в виде строк, а не скомпилированных шаблонов. Если вы действительно только скомпилировали регулярные выражения, то:

    def matches_pattern(s, pats):
        pat = "|".join("(%s)" % p.pattern for p in pats)
        return bool(re.match(pat, s))
    
    14.05.2012
  • Возможно, вы сможете дополнительно оптимизировать их, проанализировав их на наличие перекрытий и уменьшив общее количество шаблонов. 15.05.2012

  • 3

    В дополнение к превосходным ответам выше, убедитесь, что вы сравнили вывод re.match с None:

    >>> timeit('None is None')
    0.03676295280456543
    >>> timeit('bool(None)')
    0.1125330924987793
    >>> timeit('re.match("a","abc") is None', 'import re')
    1.0200879573822021
    >>> timeit('bool(re.match("a","abc"))', 'import re')
    1.134294033050537
    
    17.04.2013

    4

    Это не совсем то, о чем спрашивал ОП, но это хорошо сработало для меня как альтернатива длительному итеративному сопоставлению.

    Вот несколько примеров данных и кода:

    import random
    import time
    mylonglist = [ ''.join([ random.choice("ABCDE") for i in range(50)]) for j in range(3000) ]
    
    # check uniqueness
    print "uniqueness:"
    print len(mylonglist) == len(set(mylonglist))
    
    # subsample 1000
    subsamp = [ mylonglist[x] for x in random.sample(xrange(3000),1000) ]
    # join long string for matching
    string = " ".join(subsamp)
    
    # test function 1
    def by_string_match(string, mylonglist):
        counter = 0
        t1 = time.time()
        for i in mylonglist:
            if i in string:
                counter += 1
        t2 = time.time()
        print "It took {} seconds to find {} items".format(t2-t1,counter)
    
    # test function 2
    def by_iterative_match(subsamp, mylonglist):
        counter = 0
        t1 = time.time()
        for i in mylonglist:
            if any([ i in s for s in subsamp ]):
                counter += 1
        t2 = time.time()
        print "It took {} seconds to find {} items".format(t2-t1,counter)
    
    # test 1:
    print "string match:"
    by_string_match(string, mylonglist)
    # test 2:
    print "iterative match:"
    by_iterative_match(subsamp, mylonglist)
    
    14.08.2018
    Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..