繁体   English   中英

如何在Python中随机播放磁盘上的文本文件

[英]How to shuffle a text file on disk in Python

我正在使用存储在我的硬盘上的大约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.arraynumpy.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,它可以一次从100M行集中为您提供一行,而无需在那一侧存储该100M。


不使用临时数据库的另一种选择-理论上是O(N ** 2),但实际上,如果碰巧有足够的内存来使行缓存有用,则可能会更快:

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))

这是基于我上面的评论的建议。 它依赖于压缩后的行仍然能够装入内存。 否则,将需要其他解决方案。

import zlib
from random import shuffle

def heavy_shuffle(filename_in, filename_out):
    with open(filename_in, 'r') as f:
        zlines = [zlib.compress(line, 9) for line in f]
    shuffle(zlines)
    with open(filename_out, 'w') as f:
        for zline in zlines:
            f.write(zlib.decompress(zline) + '\n')

我的经验是zlib速度很快,而bz2提供了更好的压缩效果,因此您可能需要比较一下。

另外,如果您可以避免分块(例如,n行)在一起,则这样做可能会提高压缩率。


我想知道有用压缩的可能性,所以这是一个IPython实验。 我不知道您的数据是什么样子,所以我只将浮点数(作为字符串)四舍五入到3个位置并与管道串在一起:

最佳情况(例如,许多行都具有相同的数字):

In [38]: data = '0.000|'*200

In [39]: len(data)
Out[39]: 1200

In [40]: zdata = zlib.compress(data, 9)

In [41]: print 'zlib compression ratio: ',1.-1.*len(zdata)/len(data)
zlib compression ratio:  0.98

In [42]: bz2data = bz2.compress(data, 9)

In [43]: print 'bz2 compression ratio: ',1.-1.*len(bz2data)/len(data)
bz2 compression ratio:  0.959166666667

不出所料,最佳情况确实很好,压缩率> 95%。

最坏的情况(随机数据):

In [44]: randdata = '|'.join(['{:.3f}'.format(x) for x in np.random.randn(200)])

In [45]: zdata = zlib.compress(randdata, 9)

In [46]: print 'zlib compression ratio: ',1.-1.*len(zdata)/len(data)
zlib compression ratio:  0.5525

In [47]: bz2data = bz2.compress(randdata, 9)

In [48]: print 'bz2 compression ratio: ',1.-1.*len(bz2data)/len(data)
bz2 compression ratio:  0.5975

令人惊讶的是,最坏的情况还不是太糟糕的〜60%的压缩率,但是如果您只有8 GB的内存(15 GB的60%是9 GB),则可能会出现问题。

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

现在,生成一个随机序列,然后将返回的值重新映射为该块内适当的块号和行偏移量。

此操作为您提供了一个数字序列[1..num of chunks] ,可以将其描述为对[1..num of chunks]之间的数字页中包含的内存碎片的访问序列。 对于在线变体(例如在实际OS中),没有针对此问题的最佳策略,但是由于您知道页面引用的实际顺序,因此可以在此处找到最佳解决方案。

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

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

假设磁盘空间对您来说不是问题,那么我将创建多个文件来保存数据。

import random
import os

PMSize = 100 #Lesser value means using more primary memory
shuffler = lambda x: open(x, 'w')
shufflers = [shuffler('file'+str(x)) for x in range(PMSize)]

with open('filename') as file:
    for line in file:
        i = random.randint(0, len(shufflers)-1)
        shufflers[i].write(line)

with open('filename', 'w') as file:
    for file in shufflers:
        newfile.write(file.read())

for file in shufflers:
    os.remove(file)

您的内存复杂度将由PMSize控制。 时间复杂度将约为O(N + PMSize)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM