子文件夹中的Python随机行

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)

现在,我们只需要一个包装函数将这两个部分绑在一起(并管理一组看到的行).它可以返回单个样本大小ncount样本列表,利用随机样本中的切片也是随机样本的事实.

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


Mar*_*ers 4

为了在所有这些文件中获得正确的随机分布,您需要将它们视为一大组行并随机选择 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 的答案。:-)

  • 实际上,您不需要知道总共有多少行,就可以随机选择 10 行。您可以通过降低您保留的每一行的概率来随机选择一行:http://www.perlmonks.org/?node_id=1910。对于 N 行,您保留一个 N 列表,并且对于每个新行,减少保留它的概率:http://www.perlmonks.org/?node_id=1910(对所有 Perl 感到抱歉)。 (2认同)