【问题标题】:Python: regex match across file chunk boundariesPython:跨文件块边界的正则表达式匹配
【发布时间】:2017-10-27 23:22:21
【问题描述】:

巨大的纯文本数据文件

我使用 python 分块读取了一个巨大的文件。然后我对该块应用正则表达式。基于标识符标签,我想提取相应的值。由于块大小,块边界处缺少数据。

要求:

  • 文件必须分块读取。
  • 块大小必须小于 或等于 1 GiB。


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)


块数据示例

好:标签数等于值数

标识符:值
标识符:值
标识符:值
标识符:值


由于块大小,您会遇到如下所列的不同边界问题。第三个标识符返回一个不完整的值,“v”而不是“value”。下一个块包含“alue”。这会导致解析后数据丢失。

错误:标识符值不完整

标识符:值
标识符:值
标识符:v


你如何解决这样的块边界问题?

【问题讨论】:

标签: python regex boundary


【解决方案1】:

假设这是您的确切问题,您可能只需调整您的正则表达式并逐行读取(这不会将完整文件加载到内存中):

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)

【讨论】:

  • 良好的低内存占用解决方案。该文件不是基于行的,正如所提供的示例所暗示的那样。我没有明确指定要求。我必须明确指定文件必须以块的形式读取。在某种程度上,我必须在块边界处找到解决方案,同时避免意外的重复计数。
【解决方案2】:

您可以控制块形成并使其接近 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,第二次从第一次迭代期间收集的匹配字符串的最大长度开始),将结果存储为字典,其中 @987654323 @,每次迭代的位置都是相同的,所以合并结果应该不是问题,但是我认为按开始位置和匹配字符串的长度进行合并会更准确。

祝你好运!

【讨论】:

  • 这是一个非常聪明的方法,最接近我想要的。我没有这样想过。然而,基于行的读取将对多处理块构成新的挑战。这就是为什么我更喜欢 f.read() 方法并将块提供给单独的进程。逐行同步将是非常昂贵的进程间操作。
  • @JodyK 感谢您的评论,您是对的,我已经用另一种方法更新了答案
【解决方案3】:

如果文件是基于行的file 对象是行的惰性生成器,它将文件逐行加载到内存中(分块),基于此,您可以使用:

import re
matches = []
for line in open('huge_file'):
    matches += re.findall("Identifier:\s(.*?)$", line)

【讨论】:

  • 这对于基于行的文件来说确实是一个很好的解决方案。是否还有一种解决方案,文件不是基于行的,您“必须”在哪里读取块?
【解决方案4】:

我有一个与杰克的回答非常相似的解决方案:

#!/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)

我们可以使用generator expressionlist comprehension 进行简化

#!/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]

【讨论】:

  • 我同意这些对于基于行的文件来说是很好的解决方案。假设我们有一个严格的条件,即我们“必须”以块的形式读取文件:是否有可能的解决方案来解决块边界问题?
  • 这些示例基于您的示例。但是对于每次迭代,您能否保留上一个可能出现模式的块中的最后几个字符?
  • 我对块的要求不是很清楚。你的提议接近安德烈的方法。我想这是解决这个问题的最接近的方法。恐怕不可能在随后的块中进行某种前瞻或在前一个块中进行后瞻。逐行方法消除了大块的多处理优势。
【解决方案5】:

如果知道匹配结果字符串的长度,我认为最简单的方法是在边界附近缓存最后一个块的字节。

假设结果长度为3,保留最后一个chunk的最后2个字符,然后添加到新的chunk中进行匹配。

伪代码:

regex  pattern
string boundary
int    match_result_len

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

【讨论】:

    猜你喜欢
    • 2015-11-16
    • 2022-07-06
    • 1970-01-01
    • 2013-02-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多