如何在Python中洗牌磁盘上的文本文件

如何在Python中洗牌磁盘上的文本文件,python,text-files,bigdata,shuffle,Python,Text Files,Bigdata,Shuffle,我正在处理一个大约12*10^6行的文本文件,它存储在我的硬盘上。 该文件的结构是: data|data|data|...|data\n data|data|data|...|data\n data|data|data|...|data\n ... data|data|data|...|data\n 没有标题,也没有唯一标识行的id 因为我想将其用于机器学习,所以我需要确保文本文件中没有可能影响随机学习的顺序 通常我会将这类文件上传到内存中,并在将它们重写到磁盘之前对它们进行洗牌。不幸的是,由

我正在处理一个大约12*10^6行的文本文件,它存储在我的硬盘上。 该文件的结构是:

data|data|data|...|data\n
data|data|data|...|data\n
data|data|data|...|data\n
...
data|data|data|...|data\n
没有标题,也没有唯一标识行的id

因为我想将其用于机器学习,所以我需要确保文本文件中没有可能影响随机学习的顺序


通常我会将这类文件上传到内存中,并在将它们重写到磁盘之前对它们进行洗牌。不幸的是,由于文件的大小,这一次是不可能的,因此我必须直接在磁盘上管理洗牌(假设磁盘空间没有问题)。关于如何使用Python有效地(以尽可能低的复杂度,即写入磁盘)管理这样的任务,您有什么想法吗?

除了其中一个想法之外,其他所有想法都使用O(N)内存,但是如果您使用
数组.array
numpy.ndarray
我们谈论的是N*4字节,这比整个文件小得多。(为了简单起见,我将使用普通列表;如果您需要帮助转换为更紧凑的类型,我也可以展示。)


使用临时数据库和索引列表:

with contextlib.closing(dbm.open('temp.db', 'n')) as db:
    with open(path) as f:
        for i, line in enumerate(f):
            db[str(i)] = line
    linecount = i
    shuffled = random.shuffle(range(linecount))
    with open(path + '.shuffled', 'w') as f:
        for i in shuffled:
            f.write(db[str(i)])
os.remove('temp.db')
这是2N个单行磁盘操作和2N个单dbm密钥磁盘操作,应该是2NlogN个单磁盘操作等效操作,因此总复杂度为O(NlogN)


如果使用关系数据库(如
sqlite3
)而不是dbm,则甚至不需要索引列表,因为您可以这样做:

SELECT * FROM Lines ORDER BY RANDOM()
这具有与上述相同的时间复杂度,并且理论上空间复杂度是O(1)而不是O(N)。实际上,您需要一个RDBMS,它可以从一个100米行集合中一次向您提供一行数据,而无需在两侧存储该100米数据


另一种选择,理论上不使用临时数据库,但实际上,如果恰好有足够的内存使线缓存发挥作用,可能会更快:

with open(path) as f:
    linecount = sum(1 for _ in f)
shuffled = random.shuffle(range(linecount))
with open(path + '.shuffled', 'w') as f:
    for i in shuffled:
        f.write(linecache.getline(path, i))

最后,通过将索引列表的大小加倍,我们可以消除临时磁盘存储。但在实践中,这可能会慢得多,因为您正在进行更多的随机访问读取,而这些驱动器并不擅长

with open(path) as f:
    linestarts = [f.tell() for line in f]
    lineranges = zip(linestarts, linestarts[1:] + [f.tell()])
    shuffled = random.shuffle(lineranges)
    with open(path + '.shuffled', 'w') as f1:
        for start, stop in shuffled:
            f.seek(start)
            f1.write(f.read(stop-start))

除了一个以外,所有这些想法都使用O(N)内存,但如果使用
array.array
numpy.ndarray
我们谈论的是N*4字节,这比整个文件小得多。(为了简单起见,我将使用普通列表;如果您需要帮助转换为更紧凑的类型,我也可以展示。)


使用临时数据库和索引列表:

with contextlib.closing(dbm.open('temp.db', 'n')) as db:
    with open(path) as f:
        for i, line in enumerate(f):
            db[str(i)] = line
    linecount = i
    shuffled = random.shuffle(range(linecount))
    with open(path + '.shuffled', 'w') as f:
        for i in shuffled:
            f.write(db[str(i)])
os.remove('temp.db')
这是2N个单行磁盘操作和2N个单dbm密钥磁盘操作,应该是2NlogN个单磁盘操作等效操作,因此总复杂度为O(NlogN)


如果使用关系数据库(如
sqlite3
)而不是dbm,则甚至不需要索引列表,因为您可以这样做:

SELECT * FROM Lines ORDER BY RANDOM()
这具有与上述相同的时间复杂度,并且理论上空间复杂度是O(1)而不是O(N)。实际上,您需要一个RDBMS,它可以从一个100米行集合中一次向您提供一行数据,而无需在两侧存储该100米数据


另一种选择,理论上不使用临时数据库,但实际上,如果恰好有足够的内存使线缓存发挥作用,可能会更快:

with open(path) as f:
    linecount = sum(1 for _ in f)
shuffled = random.shuffle(range(linecount))
with open(path + '.shuffled', 'w') as f:
    for i in shuffled:
        f.write(linecache.getline(path, i))

最后,通过将索引列表的大小加倍,我们可以消除临时磁盘存储。但在实践中,这可能会慢得多,因为您正在进行更多的随机访问读取,而这些驱动器并不擅长

with open(path) as f:
    linestarts = [f.tell() for line in f]
    lineranges = zip(linestarts, linestarts[1:] + [f.tell()])
    shuffled = random.shuffle(lineranges)
    with open(path + '.shuffled', 'w') as f1:
        for start, stop in shuffled:
            f.seek(start)
            f1.write(f.read(stop-start))

这个问题可以被认为是一个有效的内存页管理问题,以减少交换文件I/O。让缓冲区
buf
成为要存储到输出文件中的连续文件块的列表。让连续的文件块成为固定数量的整行的列表

现在,生成一个随机序列,并重新映射返回的值,以确定块内的块数和行偏移量

此操作留给您一系列的数字
[1..num of chunks]
,可以将其描述为对
[1..num of chunks]
之间的数字页中包含的内存片段的访问序列。对于在线差异(如在真实操作系统中),这个问题没有最优策略,但由于您知道页面引用的实际顺序,因此可以找到一个最优解决方案

这种方法有什么好处?最常使用的页面从HDD中读取的次数最少,这意味着读取数据的I/O操作更少。此外,与内存占用相比,考虑到块大小足够大,可以最大限度地减少页面交换,输出文件的后续行很多时候都是从存储在内存中的同一块(或任何其他块,但尚未交换到驱动器)中获取的,而不是从驱动器中重新读取


也许这不是最简单的解决方案(尽管最佳页面交换算法很容易编写),但这可能是一个有趣的练习,不是吗?

这个问题可以被认为是一个有效的内存页管理问题,以减少交换文件I/O。让您的缓冲区
buf
成为您希望存储到输出文件中的连续文件块的列表。让连续的文件块成为固定数量的整行的列表

现在,生成一个随机序列,并重新映射返回的值,以确定块内的块数和行偏移量

此操作留给您一系列的数字
[1..num of chunks]
,可以将其描述为对
[1..num of chunks]
之间的数字页中包含的内存片段的访问序列。对于在线差异(如在真实操作系统中),这个问题没有最优策略,但由于您知道页面引用的实际顺序,因此可以找到一个最优解决方案

这次批准有什么好处