use*_*596 6 python random-sample python-3.x
我在多个子文件夹中的.txt文件中有很多任务.我试图从这些文件夹,它们包含的文件以及文件中的文本行中随机选取总共10个任务.应删除或标记选定的行,以便在下次执行时不会选择它.这可能是一个太宽泛的问题,但我很欣赏任何意见或方向.
这是我到目前为止的代码:
#!/usr/bin/python
import random
with open('C:\\Tasks\\file.txt') as f:
lines = random.sample(f.readlines(),10)
print(lines)
Run Code Online (Sandbox Code Playgroud)
Blc*_*ght 14
这是一个简单的解决方案,只需一次通过每个样本的文件.如果您确切知道将从文件中采样的项目数量,则可能是最佳选择.
首先是示例函数.这使用了@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
Run Code Online (Sandbox Code Playgroud)
编辑:在另一个问题中,用户@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
Run Code Online (Sandbox Code Playgroud)
现在,我们只需要一个包装函数将这两个部分绑在一起(并管理一组看到的行).它可以返回单个样本大小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)]
Run Code Online (Sandbox Code Playgroud)
以下是它的使用方法:
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!
Run Code Online (Sandbox Code Playgroud)
为了在所有这些文件中获得正确的随机分布,您需要将它们视为一大组行并随机选择 10 个。换句话说,您必须至少读取所有这些文件一次才能至少弄清楚您有多少行。
然而,您不需要将所有行都保存在内存中。您必须分两个阶段执行此操作:对文件进行索引以计算每个文件中的行数,然后选择要从这些文件中读取的 10 个随机行。
第一次索引:
import os
root_path = r'C:\Tasks\\'
total_lines = 0
file_indices = dict()
# Based on /sf/ask/59154091/, bufcount function
def linecount(filename, buf_size=1024*1024):
with open(filename) as f:
return sum(buf.count('\n') for buf in iter(lambda: f.read(buf_size), ''))
for dirpath, dirnames, filenames in os.walk(root_path):
for filename in filenames:
if not filename.endswith('.txt'):
continue
path = os.path.join(dirpath, filename)
file_indices[total_lines] = path
total_lines += linecount(path)
offsets = list(file_indices.keys())
offsets.sort()
Run Code Online (Sandbox Code Playgroud)
现在我们有了偏移量的映射,指向文件名和总行数。现在我们选择十个随机索引,并从您的文件中读取这些索引:
import random
import bisect
tasks = list(range(total_lines))
task_indices = random.sample(tasks, 10)
for index in task_indices:
# find the closest file index
file_index = offsets[bisect.bisect(offsets, index) - 1]
path = file_indices[file_index]
curr_line = file_index
with open(path) as f:
while curr_line <= index:
task = f.readline()
curr_line += 1
print(task)
tasks.remove(index)
Run Code Online (Sandbox Code Playgroud)
请注意,您只需要索引一次;您可以将结果存储在某处,并且仅在文件更新时更新它。
另请注意,您的任务现在已“存储”在tasks列表中;这些是文件中行的索引,当打印所选任务时,我从该变量中删除索引。下次运行random.sample()选择时,之前选择的任务将不再可供下次选择。如果您的文件发生更改,则需要更新此结构,因为必须重新计算索引。这file_indices将帮助您完成该任务,但这超出了本答案的范围。:-)
如果您只需要一个10 项样本,请使用Blckknght 的解决方案,因为它只会遍历文件一次,而我的解决方案需要 10 个额外的文件打开。如果您需要多个样本,此解决方案仅需要每次需要样本时额外打开 10 个文件,它不会再次扫描所有文件。如果你的文件少于 10 个,仍然使用 Blckknght 的答案。:-)
| 归档时间: |
|
| 查看次数: |
1814 次 |
| 最近记录: |