Python: совпадение регулярных выражений между границами файловых блоков

Огромный текстовый файл данных

Я прочитал огромный файл кусками, используя python. Затем я применяю регулярное выражение к этому фрагменту. На основе тега идентификатора я хочу извлечь соответствующее значение. Из-за размера фрагмента данные отсутствуют на границах фрагмента.

Требования:

  • Файл должен быть прочитан кусками.
  • Размеры блоков должны быть меньше или равны 1 ГиБ.


Пример кода Python

identifier_pattern = re.compile(r'Identifier: (.*?)\n')
with open('huge_file', 'r') as f:
    data_chunk = f.read(1024*1024*1024)
    m = re.findall(identifier_pattern, data_chunk)


Примеры фрагментов данных

Хорошо: количество тегов соответствует количеству значений

Идентификатор: value
Идентификатор: value
Идентификатор: value
Идентификатор: value


Из-за размера фрагмента возникают различные проблемы с границами, перечисленные ниже. Третий идентификатор возвращает неполное значение "v" вместо "value". Следующий фрагмент содержит «alue». Это приводит к отсутствию данных после синтаксического анализа.

Плохо: значение идентификатора не заполнено

Идентификатор: value
Идентификатор: value
Идентификатор: v


Как вы решаете подобные проблемы с границами блоков?


person JodyK    schedule 27.05.2017    source источник
comment
Возможно, вы найдете ответ здесь: поток синтаксического анализа регулярных выражений Python   -  person Chiheb Nexus    schedule 27.05.2017
comment
Также здесь: регулярное выражение в потоке вместо строки?   -  person Chiheb Nexus    schedule 27.05.2017
comment
Поскольку ваш шаблон появляется на границе строки, возможно, вы могли бы просто читать строку за раз и сопоставлять строку вместо фрагмента.   -  person Himanshu    schedule 27.05.2017
comment
Основана ли строка файла?   -  person Pedro Lobito    schedule 27.05.2017
comment
@PedroLobito: нет, к сожалению, файл не является строковым.   -  person JodyK    schedule 27.05.2017


Ответы (5)


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

import re
matches = []
identifier_pattern = re.compile(r'Identifier: (.*?)$')
with open('huge_file') as f:
    for line in f:
        matches += re.findall(identifier_pattern, line)

print("matches", matches)
person Jack    schedule 27.05.2017
comment
Хорошее решение с низким объемом памяти. Файл не является строковым, как предполагает представленный пример. Я не указал требование однозначно. Мне пришлось явно указать, что файл нужно читать кусками. Каким-то образом я должен найти решение на границе фрагмента, избегая при этом случайного двойного счета. - person JodyK; 27.05.2017

Вы можете контролировать формирование чанка и сделать его близким к 1024 * 1024 * 1024, в этом случае вы избежите пропущенных частей:

import re


identifier_pattern = re.compile(r'Identifier: (.*?)\n')
counter = 1024 * 1024 * 1024
data_chunk = ''
with open('huge_file', 'r') as f:
    for line in f:
        data_chunk = '{}{}'.format(data_chunk, line)
        if len(data_chunk) > counter:
            m = re.findall(identifier_pattern, data_chunk)
            print m.group()
            data_chunk = ''
    # Analyse last chunk of data
    m = re.findall(identifier_pattern, data_chunk)
    print m.group()

В качестве альтернативы вы можете пройти два раза по одному и тому же файлу с другой начальной точкой read (первый раз с: 0, второй раз с максимальной длиной совпадающей строки, собранной во время первой итерации), сохранить результаты в виде словарей, где key=[start position of matched string in file], эта позиция будет одинаковой для каждой итерации, поэтому слияние результатов не должно быть проблемой, однако я думаю, что было бы точнее выполнить слияние по начальной позиции и длине совпадающей строки.

Удачи!

person Andriy Ivaneyko    schedule 27.05.2017
comment
Это очень умный подход, наиболее близкий к тому, что я хочу. Я не думал об этом так. Однако чтение на основе строк создаст новую проблему для фрагментов с множественной обработкой. Вот почему я бы предпочел метод f.read() и передачу фрагментов отдельным процессам. Построчная синхронизация будет очень затратной межпроцессной операцией. - person JodyK; 27.05.2017
comment
@JodyK спасибо за ваш комментарий, вы правы, я обновил ответ альтернативным подходом - person Andriy Ivaneyko; 27.05.2017

Если файл построчный, объект file является ленивым генератором строк, он будет загружать файл в память построчно ( кусками), исходя из этого, вы можете использовать:

import re
matches = []
for line in open('huge_file'):
    matches += re.findall("Identifier:\s(.*?)$", line)
person Pedro Lobito    schedule 27.05.2017
comment
Это действительно отличное решение для строковых файлов. Есть ли также решение, в котором файл не является строковым и где вы «должны» читать куски? - person JodyK; 27.05.2017

У меня есть решение, очень похожее на ответ Джека:

#!/usr/bin/env python3

import re

identifier_pattern = re.compile(r'Identifier: (.*)$')

m = []
with open('huge_file', 'r') as f:
    for line in f:
        m.extend(identifier_pattern.findall(line))

Вы можете использовать другую часть API регулярных выражений, чтобы получить тот же результат:

#!/usr/bin/env python3

import re

identifier_pattern = re.compile(r'Identifier: (.*)$')

m = []
with open('huge_file', 'r') as f:
    for line in f:
        pattern_found = identifier_pattern.search(line)
        if pattern_found:
            value_found = pattern_found.group(0)
            m.append(value_found)

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

#!/usr/bin/env python3

import re

identifier_pattern = re.compile(r'Identifier: (.*)$')

with open('huge_file', 'r') as f:
    patterns_found = (identifier.search(line) for line in f)
    m = [pattern_found.group(0) 
         for pattern_found in patterns_found if pattern_found]
person EvensF    schedule 27.05.2017
comment
Я согласен, что это хорошие решения для линейных файлов. Предполагая, что у нас есть строгое условие, при котором мы «должны» читать файл по частям: есть ли возможное решение, чтобы обойти проблему с границей части? - person JodyK; 27.05.2017
comment
Эти примеры были основаны на вашем примере. Но можете ли вы для каждой итерации сохранить последние несколько символов из предыдущего фрагмента, где мог появиться шаблон? - person EvensF; 27.05.2017
comment
Я не ясно выразился в требовании к чанку. Ваше предложение близко к подходу Андрея. Я думаю, это самый близкий способ решить эту проблему. Я боюсь, что невозможно сделать что-то вроде предпросмотра в последующем фрагменте или ретроспективного просмотра в предыдущем фрагменте. Построчные подходы лишают преимуществ многопроцессорной обработки, которые можно было бы получить с большими фрагментами. - person JodyK; 27.05.2017

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

Предположим, что длина результата равна 3, сохраните последние 2 символа последнего фрагмента, а затем добавьте его в новый фрагмент для сопоставления.

Псевдокод:

regex  pattern
string boundary
int    match_result_len

for chunk in chunks:
    match(boundary + chunk, pattern)
    boundary = chunk[-(match_result_len - 1):]
person dotslashlu    schedule 24.07.2018