I'm working on a python module to randomly choose a wallpaper for my desktop background among thousands of pictures in my photo library.
I though I'd make a general function:
Return the name of a random file in a directory tree with top as root.
Since I'm dealing with a very large directory tree, listing all possible files would be very time consuming (edit: see comments on C. Harley answer). So I went for heuristic method, making sure all files are covered and adding an upper bound in case there are no files with given extension. There are occasionally false negatives, which is not a too big deal, but most importantly, it runs nearly instantly.
Algorithm is simple:
- Get a list of directories, including
- Pick a random directory from that list.
- If the picked directory is
.(either randomly, or only available option), then retrieve a list of files in that directory.
- If the list of files is empty, go back to 1.
- Return a random file from that list.
Note that this algorithm can go many levels deep into sub-directories.
So far I'm happy with performance and returned values. It is able to quickly pick up a file in a very large tree.
However, I'm after general comments in terms of performance and also code style.
What I'm not particularly proud of in my code is the use of
next(os.walk(top)) to get the list of sub-directories in the current directory.
There are also false negatives, when asking for a file extension with not many occurrences in a tree with many directories. However, I don't want to increase the limit too much to avoid waiting when really there are not such files in the tree.
# -*- coding: utf-8 -*- """ Created on Wed Aug 29 14:05:27 2018 @author: raf """ import os import sys import glob import random def get_random_files(ext, top): '''Return the name of a random file within a top path. Works recursivelly in subdirectories. Note that a full list of files could be got with glob(top + '/**/*.' + ext, recursive=True) However that would be extremely slow for large directories. ''' _top = top ct, limit = 0, 50000 while True: if ct > limit: return 'No file found after %d iterations.' % limit ct += 1 try: dirs = next(os.walk(top)) except StopIteration: # access denied and other exceptions top = _top continue i = random.randint(0, len(dirs)) if i == 0: # use . files = glob.glob(top + '/*.' + ext) if not files: top = _top continue i = random.randint(0, len(files)-1) return files[i] top += '/' + dirs[i-1] if __name__ == '__main__': if len(sys.argv) > 1: print(get_random_files(sys.argv, os.getcwd())) else: print(get_random_files('*', os.getcwd()))