首页 > 解决方案 > 在 Python (SageMath 9.0) 中 - 1B 行上的文本文件 - 从特定行读取的最佳方式

问题描述

我在 Windows 10 操作系统上运行 SageMath 9.0

我在这个网站上阅读了几个类似的问题(和答案)。主要是从第 7 行读取的这一篇,以及关于优化的这一篇。但是我有一些具体的问题:我需要了解如何从特定的(可能很远的)行以最佳方式读取,以及我是否应该逐行读取,或者在我的情况下是否按块读取可能“更佳”。

我有一个 12Go 文本文件,由大约 10 亿行小行组成,全部由 ASCII 可打印字符组成。每行都有固定数量的字符。以下是实际的前 5 行:

J??????????
J???????C??
J???????E??
J??????_A??
J???????F??
...

对于上下文,此文件是 11 个顶点上的所有非同构图的列表,使用 graph6 格式编码。该文件由Brendan McKay计算并其网页上提供。

我需要检查每个图表的某些属性。我可以使用发电机for G in graphs(11),但这可能会很长(至少在我的笔记本电脑上几天)。我想在文件中使用完整的数据库,以便能够从某个点停止并重新开始。

我当前的代码从头开始逐行读取文件,并在读取每一行后进行一些计算:

with open(filename,'r') as file:
    while True: 
        # Get next line from file 
        line = file.readline() 

        # if line is empty, end of file is reached 
        if not line: 
            print("End of Database Reached")
            break  
        
        G = Graph()
        from_graph6(G,line.strip())

        run_some_code(G)

为了能够停止代码,或者在崩溃的情况下保存进度,我在想:

所以我的新代码是

 from itertools import islice
 start_line = load('foo')
 count = start_line 
 save_every_n_lines = 1000000


 with open(filename,'r') as file:
     for line in islice(file, start_line, None):
         G = Graph()
         from_graph6(G,line.strip())

         run_some_code(G)
         count +=1

         if (count % save_every_n_lines )==0:
             save(count,'foo')

该代码确实有效,但我想了解是否可以对其进行优化。我不喜欢我在循环中的if陈述。for

每行都有固定数量的字符。所以“跳跃”可能是可行的。

标签: pythonoptimizationsage

解决方案


假设每一行的大小相同,您可以使用内存映射文件按索引读取它,而无需使用 seek 和 tell。内存映射文件模拟 a bytearray,您可以从数组中获取记录大小的切片以获得所需的数据。如果要暂停处理,只需将当前记录索引保存在数组中,以后可以使用该索引重新启动。

这个例子是在 linux 上的——在 windows 上打开 mmap 有点不同——但是在设置之后,访问应该是一样的。

import os
import mmap

# I think this is the record plus newline
LINE_SZ = 12
RECORD_SZ = LINE_SZ - 1 

# generate test file
testdata = "testdata.txt"
with open(testdata, 'wb') as f:
    for i in range(100):
        f.write("R{: 10}\n".format(i).encode('ascii'))

f = open(testdata, 'rb')
data = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)

# the i-th record is
i = 20
record = data[i*LINE_SZ:i*LINE_SZ+RECORD_SZ] 
print("record 20", record)

# you can stick it in a function. this is a bit slower, but encapsulated
def get_record(mmapped_file, index):
    return mmapped_file[i*LINE_SZ:i*LINE_SZ+RECORD_SZ]

print("get record 20", get_record(data, 11))

# to enumerate
def enum_records(mmapped_file, start, stop=None, step=1):
    if stop is None:
        stop = mmapped_file.size()/LINE_SZ
    for pos in range(start*LINE_SZ, stop*LINE_SZ, step*LINE_SZ):
        yield mmapped_file[pos:pos+RECORD_SZ]

print("enum 6 to 8", [record for record in enum_records(data,6,9)])

del data
f.close()

推荐阅读