Выбор случайного файла из каталога (с большим количеством файлов) в Python

У меня есть каталог с большим количеством файлов (~ 1 мил). Мне нужно выбрать случайный файл из этого каталога. Поскольку существует так много файлов, os.listdir, естественно, заканчивается вечностью.

Есть ли способ обойти эту проблему? Может быть, каким-то образом узнать количество файлов в каталоге (без его перечисления) и выбрать n-й файл, где n генерируется случайным образом?

Файлы в каталоге называются случайным образом.

Ответ 1

Увы, я не думаю, что есть решение вашей проблемы. Во-первых, я не знаю портативного API, который вернет вам количество записей в каталоге (без перечисления их в первую очередь). Во-вторых, я не думаю, что API возвратит вам запись каталога по номеру, а не по имени.

Таким образом, в целом, программа должна будет перечислять записи каталога O (n), чтобы получить один случайный. Тривиальный подход к определению количества записей, а затем выбор одного из них потребует достаточного количества ОЗУ для хранения полного списка (os.listdir()) или потребуется перечислить второй раз в каталоге, чтобы найти случайный (n) элемент - общие операции n+n/2 в среднем.

Есть немного лучший подход - но только слегка - см. случайный выбор-строк-из файлов. Короче говоря, есть способ выбрать случайный элемент из списка/итератора с неизвестной длиной, одновременно читая один элемент и гарантируя, что любой предмет может быть выбран с равной вероятностью. Но это не поможет с os.listdir(), потому что он уже возвращает list в памяти, которая уже содержит все записи 1M +, поэтому вы можете также задать вопрос о len()...

Ответ 2

Я не уверен, что это возможно. Даже на уровне VFS или файловой системы нет гарантии, что счетчик записей в каталогах даже поддерживается. Например, многие файловые системы просто записывают объединенный размер байта структур записи каталога, содержащихся в заданной директории.

Оценка может быть сделана, если записи в каталогах являются структурами фиксированного размера, но это редко встречается сейчас (рассмотрим LFN для FAT32). Даже если данная файловая система предоставила счетчик записей, не требуя повторения в каталоге, или если VFS кэширует запись длины каталогов, это определенно будет операционной системой, файловой системой и конкретным ядром.

Ответ 3

У меня аналогичная потребность в OP.

Я думаю, что я улажу метод префикса: вы сохраняете в TXT файле список всех файлов, тогда вы можете просто умело искать случайную строку в своем листинге (даже не загружая ее в память), и все готово!

Конечно, вам все равно нужно обновить кеш и, что еще важнее, определить , когда вам нужно обновить кеш, но в зависимости от ваших потребностей это может быть легко (сразу после определенного действия, или когда что-то изменилось и т.д.).

Код для умного чтения случайной строки из файла в Python Джонатана Купфермана:

http://www.regexprn.com/2008/11/read-random-line-in-large-file-in.html

Ответ 4

Возможно, вы сможете запустить это:

http://mail.python.org/pipermail/python-list/2009-July/1213182.html

И это, вероятно, лучшее возможное решение вашей проблемы, но только там, где n невелик - если n идет большим, то os.listdir, вероятно, так же хорош для вашей цели.

Я охотился и не нашел другого способа открыть файл в каталоге. Если бы у меня было больше времени, я был бы склонен немного поиграть и сгенерировать свои файлы ~ 1mil.


Я просто подумал о другом способе сделать это: Предполагая, что файлы постоянны - вы не получаете больше или меньше - вы можете сохранить список имен файлов в базе данных sqlite. Тогда было бы относительно просто запросить базу данных для имени случайным ROWID. Я не знаю, будет ли у вас по-прежнему долгое время искать правильный файл, но по крайней мере получение имени файла должно занимать небольшую сумму.

Конечно, если файлы в каталоге называются случайным образом, вы можете переименовать файлы (?) и поместить их в структуру каталогов, как предлагает AdamK.

Ответ 5

попробуйте это, (здесь очень быстро с файлами 50K...)

import glob
import random

list = glob.glob("*/*.*")
print list[random.randrange(0,list.__len__())]