Применение регулярок к большому тексту

Есть .csv файл с регулярками, а также несколько больших текстов. Мне нужно посчитать число вхождений в текст нужных слов по этим регуляркам.

      for strings in file:
          temp = reg_file.drop(np.where(reg_file['section'] != "Основные элементы")[0])
          for index, row in temp.iterrows():
              t1 = t1 + len(re.findall(row.loc['regex'], strings))
          temp = reg_file.drop(np.where(reg_file['section'] != "Элементы2")[0])
          for index, row in temp.iterrows():
              t2 = t2 + len(re.findall(row.loc['regex'], strings))

Т.е. беру пандами из файла нужный комплект регулярок (всего их 4) и считаю количество вхождений через findall в каждой строке текста на каждый regexp. Учитывая, что и регэкспов много (около 10 каждого типа), если это работало на мелком тексте, то для большого время обработки неприемлемо большое. Как можно это выполнить быстрее?


Ответы (1 шт):

Автор решения: Stanislav Volodarskiy

Не очень много данных приведено в вопросе, кое-что пришлось додумывать.

  1. В глаза бросается повторяющаяся выборка из датафрейма (я думаю что это датафрейм). Повторять эту выборку многократно не нужно, её можно вынести за пределы цикла.

  2. re.findall компилирует регулярные выражения снова и снова. Тоже можно вынести из цикла.

Функция get_regexes извлекает из датафрейма регулярные выражения и компилирует их. Функция count_regexes применяет выражения к строкам:

def get_regexes(reg_file, section):
    return tuple(map(re.compile, reg_file[reg_file['section'] == section]['regex']))

def count_regexes(str_file, reg_file):
    regexes1 = get_regexes(reg_file, 'Основные элементы')
    regexes2 = get_regexes(reg_file, 'Элементы2')
    t1 = 0
    t2 = 0
    for s in str_file:
        t1 += sum(len(e.findall(s)) for e in regexes1)
        t2 += sum(len(e.findall(s)) for e in regexes2)
    return t1, t2

Ещё одна возможная оптимизация состоит в объединении различных регулярных выражений в одно. Не уверен что будет выигрыш, но можно попробовать. Но должен предупредить что может измениться результат если различные регулярные выражения могут находить перекрывающиеся фрагменты в тексте:

# поиск по отдельным фрагментам найдёт два вхождения:
re.findall('ab', 'aba') # одно
re.findall('ba', 'aba') # ещё одно

# поиск по общему выражению найдёт только одно вхождение:
re.findall('ab|ba', 'aba') # только одно

Если это не проблема, то ниже вариант с общим регулярным выражением. get_compound_regex собирает регулярки в единое выражение и компилирует его. count_regexes теперь содержит на один цикл меньше:

def get_compound_regex(reg_file, section):
    return re.compile('|'.join(
        '(?:{})'.format(e) for e in reg_file[reg_file['section'] == section]['regex']
    ))

def count_regexes(str_file, reg_file):
    e1 = get_compound_regex(reg_file, 'Основные элементы')
    e2 = get_compound_regex(reg_file, 'Элементы2')
    t1 = 0
    t2 = 0
    for s in str_file:
        t1 += len(e1.findall(s))
        t2 += len(e2.findall(s))
    return t1, t2

Если тексты помещаются в память, то можно убрать цикл по строкам. В примере добавлен код, который собирает все строки из str_file в единый текст, к которому затем применяются регулярные выражения:

def count_regexes(str_file, reg_file):
    text = '\n'.join(str_file)
    e1 = get_compound_regex(reg_file, 'Основные элементы')
    e2 = get_compound_regex(reg_file, 'Элементы2')
    t1 = len(e1.findall(text))
    t2 = len(e2.findall(text))
    return t1, t2
→ Ссылка