我正在处理一个大约 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.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-key 磁盘操作,应该是 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))
关于python - 如何在 Python 中随机播放磁盘上的文本文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19304279/