我有一个包含大约20亿行文本的文件(~200gig)。我想生成一个包含相同文本行的新文件,但是按行随机洗牌。我无法将所有数据保存在内存中。有没有一种好的方法在python /命令行中执行此操作需要一段合理的时间(几天)?
我以为我可以触摸50个空文件。流过20亿行文件,并将每行随机分配到50个空文件中的一个。然后cat 50个文件。这种方法会有任何重大的系统性偏见吗?
答案 0 :(得分:7)
如果你可以为这个程序保留16 GB的内存,我编写了一个名为sample
的程序,通过读取它们的字节偏移,改变偏移量,然后通过搜索来打印输出来对文件的行进行混洗。文件到洗牌补偿。它为每个64位偏移使用8个字节,因此对于20亿行输入使用16个字节。
它不会很快,但在具有足够内存的系统上,sample
将会混乱大到足以导致GNU shuf
失败的文件。此外,它使用mmap例程来尝试最小化第二次通过文件的I / O开销。它还有一些其他选择;有关详细信息,请参阅--help
。
默认情况下,此程序将在不进行替换的情况下进行采样,并通过单行进行随机播放。如果您想要替换,或者您的输入是FASTA,FASTQ或其他多行格式,您可以添加一些选项来调整采样的方式。 (或者您可以应用替代方法,我将其链接到下面的Perl要点,但sample
解决了这些问题。)
如果你的FASTA序列在每两行上,也就是说,它们在一行上的序列标题和下一行的序列数据之间交替,你仍然可以使用sample
和内存的一半进行随机播放,因为你只是拖延了一半的补偿。请参阅--lines-per-offset
选项;例如,你可以指定2
来改变线对。
对于FASTQ文件,它们的记录每四行分割一次。您可以指定--lines-per-offset=4
来使用洗牌单行文件所需的内存的四分之一来随机播放FASTQ文件。
或者,我有一个用Perl编写的gist here,它将对FASTA文件中的序列进行采样,而不考虑序列中的行数。请注意,这与对整个文件进行混洗并不完全相同,但您可以将此作为起点,因为它会收集偏移量。您可以删除第47行,对排序的索引进行排序,然后使用文件搜索操作来读取文件,直接使用混洗索引列表。
同样,它不会很快,因为你正在按顺序跳过一个非常大的文件,但是存储偏移比存储整行要便宜得多,并且添加mmap例程可以帮助实现基本上是一系列随机访问操作。如果您正在使用FASTA,那么存储的偏移量会更少,因此您的内存使用量(除了任何相对无关紧要的容器和程序开销)应该最多为8 GB - 并且可能更少,具体取决于其结构。
答案 1 :(得分:5)
怎么样:
import mmap
from random import shuffle
def find_lines(data):
for i, char in enumerate(data):
if char == '\n':
yield i
def shuffle_file(in_file, out_file):
with open(in_file) as f:
data = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
start = 0
lines = []
for end in find_lines(data):
lines.append((start, end))
start = end + 1
shuffle(lines)
with open(out_file, 'w') as out:
for start, end in lines:
out.write(data[start:end+1])
if __name__ == "__main__":
shuffle_file('data', 'result')
此解决方案应该只存储文件中行的所有文件偏移量,每行2个字,加上容器开销。
答案 2 :(得分:4)
您可以查看我的HugeFileProcessor工具。它类似于@ Alex-Reynolds的sample
,但应该明显更快,因为没有寻求。
以下是关于改组实施的详细信息。它需要指定 batchSize - 写入输出时保留在RAM中的行数。越多越好(除非你没有RAM),因为总的洗牌时间是(sourceFile中的行数)/ batchSize *(完全读取sourceFile的时间)。请注意,程序随机播放整个文件,而不是按批次。
算法如下。
计算 sourceFile 中的行数。这可以通过逐行读取整个文件来完成。 (参见一些比较here。)这也可以衡量一次读取整个文件需要多长时间。因此,我们可以估计完成一次shuffle需要多少次,因为它需要 Ceil(linesCount / batchSize)完整的文件读取。
我们现在知道 linesCount 的总数,我们可以创建 linesCount 大小的索引数组并使用Fisher–Yates对其进行随机播放(称为代码中的orderArray 。这将给我们一个订单,我们希望在洗牌文件中包含行。请注意,这是整个文件的全局顺序,而不是每批或块或其他内容。
现在是实际的代码。我们需要按照我们刚刚计算的顺序从 sourceFile 获取所有行,但是我们无法在内存中读取整个文件。所以我们只是拆分任务。
为什么会有效?
因为我们所做的只是从头到尾阅读源文件。没有寻求前进/后退,这就是硬盘驱动器所喜欢的。文件根据内部HDD缓冲区,FS块,CPU cahce等以块的形式读取,所有内容都按顺序读取。
有些数字
在我的机器上(Core i5,16GB RAM,Win8.1,HDD Toshiba DT01ACA200 2TB,NTFS)我可以使用 batchSize <在大约5小时内将132 GB(84 000 000行)的文件随机播放/ em>为3 500 000. batchSize 为2 000 000,耗时约8小时。读取速度约为每秒118000行。
答案 3 :(得分:1)
我认为在你的情况下最简单的是做一个递归的shuffle&amp; split - shuffle - merge。
您定义了两个数字:要分割一个文件的文件数:N
(典型地介于32和256之间),以及您可以直接在内存中随机播放的大小M
(通常大约为128) MO)。然后你有伪代码:
def big_shuffle(file):
if size_of(file) < M :
memory_shuffle(file)
else:
create N files
for line in file:
write_randomly_to_one_of_the_N_files
for sub_file in (N_files):
big_shuffle(file)
merge_the_N_files_one_line_each
当每个子文件被洗牌时,你应该没有偏见。
它将远远低于Alex Reynolds解决方案(因为有很多磁盘io),但你唯一的限制是磁盘空间。
答案 4 :(得分:0)
您可以创建一个给出置换的迭代器。您将读入的文件偏移给它的数量。因为迭代器提供了排列,所以您永远不会读取相同的数据两次。
一组N个元素的所有置换都可以通过换位生成,换位是置换第0个和第ith个元素(假定从0开始索引)并保留所有其他元素的置换。因此,您可以通过组合一些随机选择的换位来进行随机排列。这是用Python编写的示例:
import random
class Transposer:
def __init__(self,i):
"""
(Indexes start at 0)
Swap 0th index and ith index, otherwise identity mapping.
"""
self.i = i
def map(self,x):
if x == 0:
return self.i
if x == self.i:
return 0
return x
class RandomPermuter:
def __init__(self,n_gens,n):
"""
Picks n_gens integers in [0,n) to make transposers that, when composed,
form a permutation of a set of n elements. Of course if there are an even number of drawn
integers that are equal, they cancel each other out. We could keep
drawing numbers until we have n_gens unique numbers... but we don't for
this demo.
"""
gen_is = [random.randint(0,n-1) for _ in range(n_gens)]
self.trans = [Transposer(g) for g in gen_is]
def map(self,x):
for t in self.trans:
x = t.map(x)
return x
rp = RandomPermuter(10,10)
# Use these numbers to seek into a file
print(*[rp.map(x) for x in range(10)])
答案 5 :(得分:0)
我必须解决上述问题,以便重新整理庞大的文本文件。这样,脚本会将项目放置在缓冲区中。此外,在打开原始文件和写入新文件之间没有任何对象,这意味着该脚本不会使用太多RAM。您还可以节省对文件的一次迭代,而不是对文件/对象进行多次迭代。一旦制作了这些较小的随机文件,重新组合这些文件就很简单。只需将每个文件读入新文件即可。 Python代码:
import random
import io
from tqdm import tqdm
file_in = "file\\to\\randomize"
file_out = "base\\path\\to\\place\\randomized\\files\\"
files_out = []
NUM_OF_FILES = 1_000
for i in range(NUM_OF_FILES):
f_ = file_out + str(i)
files_out.append(io.open(f_, 'w', encoding='utf-8'))
with io.open(file_in, 'r', encoding='utf-8') as source:
for f in tqdm(source):
files_out[random.randint(0, NUM_OF_FILES - 1)].write(f)
for i in range(NUM_OF_FILES):
files_out[i].close()
for i in range(NUM_OF_FILES):
f_ = file_out + str(i)
data = []
with io.open(f_, 'r', encoding='utf-8') as file:
data = [(random.random(), line) for line in tqdm(file)]
data.sort()
with io.open(f_, 'w', encoding='utf-8') as file:
for _, line in tqdm(data):
file.write(line)
答案 6 :(得分:0)
似乎与 How can I shuffle a very large list stored in a file in Python?
相同的问题如果你会使用java或者愿意翻译一些代码,我建议使用https://tracinsy.ewi.tudelft.nl/pubtrac/Utilities/wiki/utilities中的ImmutableList解决方案。如果您的原始文件具有随机访问权限(以便您可以获得项目 N),那么您甚至不需要创建第二个混洗文件。