Вот простое решение, которое делает только один проход по файлам для каждого образца. Если вы точно знаете, сколько элементов вы будете выбирать из файлов, это, вероятно, оптимально.
Во-первых, это функция выборки. Здесь используется тот же алгоритм, на который @NedBatchelder ссылался в комментарии к более раннему ответу (хотя показанный там код Perl выбрал только одну строку, а не несколько). Он выбирает значения из итерации строк и требует, чтобы в памяти в любой момент времени сохранялись только выбранные в данный момент строки (плюс следующая строка-кандидат). Он поднимает ValueError
, если итерация имеет меньше значений, чем запрошенный размер выборки.
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!
person
Blckknght
schedule
26.08.2012