这是一个简单的解决方案,每个样本只通过一次文件。如果您确切知道将从文件中采样的项目数量,那可能是最佳选择。
首先是示例功能。这使用了@NedBatchelder 在对早期答案的评论中链接到的相同算法(尽管那里显示的 Perl 代码只选择了一行,而不是几行)。它从可迭代的行中选择值,并且只需要在任何给定时间将当前选择的行保存在内存中(加上下一个候选行)。ValueError
如果可迭代的值少于请求的样本大小,它会引发 a 。
import random
def random_sample(n, items):
results = []
for i, v in enumerate(items):
r = random.randint(0, i)
if r < n:
if i < n:
results.insert(r, v) # add first n items in random order
else:
results[r] = v # at a decreasing rate, replace random items
if len(results) < n:
raise ValueError("Sample larger than population.")
return results
编辑:在另一个问题中,用户@DzinX 注意到,如果您对大量值进行采样,则insert
在此代码中使用 会使性能变差( )。O(N^2)
他避免这个问题的改进版本在这里。/编辑
现在我们只需要为我们的函数创建一个合适的可迭代项来采样。这是我使用生成器的方法。此代码一次只会打开一个文件,并且一次不需要超过一行内存。可选exclude
参数(如果存在)应该是set
包含在先前运行中选择的行(因此不应再次产生)。
import os
def lines_generator(base_folder, exclude = None):
for dirpath, dirs, files in os.walk(base_folder):
for filename in files:
if filename.endswith(".txt"):
fullPath = os.path.join(dirpath, filename)
with open(fullPath) as f:
for line in f:
cleanLine = line.strip()
if exclude is None or cleanLine not in exclude:
yield cleanLine
现在,我们只需要一个包装函数将这两个部分联系在一起(并管理一组可见的行)。它可以返回单个大小n
的样本或样本列表count
,利用来自随机样本的切片也是随机样本的事实。
_seen = set()
def get_sample(n, count = None):
base_folder = r"C:\Tasks"
if count is None:
sample = random_sample(n, lines_generator(base_folder, _seen))
_seen.update(sample)
return sample
else:
sample = random_sample(count * n, lines_generator(base_folder, _seen))
_seen.update(sample)
return [sample[i * n:(i + 1) * n] for i in range(count)]
以下是它的使用方法:
def main():
s1 = get_sample(10)
print("Sample1:", *s1, sep="\n")
s2, s3 = get_sample(10,2) # get two samples with only one read of the files
print("\nSample2:", *s2, sep="\n")
print("\nSample3:", *s3, sep="\n")
s4 = get_sample(5000) # this will probably raise a ValueError!