# Method to return the lowest filename number not being used

I have a Django site where users upload and have the option to delete files. There will be a lot of files that will have the same logical name so I append numbers to them:

foo/bar/file.mp3 foo/bar/file2.mp3...


I needed a method that would return the lowest file number not currently being used. I have a working example here, but it feels wrong and ugly and I don't know if there is an easier way of doing it. If I ran the method on the examples above, it would return "1" so I know I can make the next file foo/bar/file1.mp3.

def get_num(self, files):
"""function to extract the lowest file number to save as, returns string. Takes a list as input"""
nums = []
for f in files:
# Call the unicode function which should return the string path
match = re.findall("([\d]+)\.[\w]+", f.__unicode__())
if match:
nums.append(int(match[0]))
else:
nums.append(0)

for i in range(len(nums) + 1):
if i not in nums:
if i == 0:
return ""
else:
return str(i)

• Could you use a random string generator using 15 digits or something like that? This would give you a unique key with less computational overhead and provided that you give enough digits the odds of duplication are extremely slim. You have a set string size so that's easy enough to parse off on document display and download. (I'm assuming you're already parsing off the number identifier) – Trojan404 Aug 23 '16 at 3:36

When you don't need indexes or ordering, it is generally a good idea to use a set over a list because its membership testing is more efficient. That means, use nums = set() instead of nums = [], and use nums.add(...) instead of nums.append(...). That's all you need to change.

I'm not sure why you are accessing the special method directly. Why not use unicode(f) instead of f.__unicode__()?

Python has its own version of something that is often called a ternary operator. Its format is truth_value if some_condition else false_value. Therefore, you can replace your first if and else blocks with this:

nums.append(int(match[0]) if match else 0)


(It would be nums.add(...) if you decide to use a set.)

and your second if and else blocks with this:

return "" if i == 0 else str(i)


Test file existence, testing numbers with ever larger steps.

In pseudo-code, for powers of two (but three would be nice too, or fibonacci numbers):

find free file number (name)
find file with number (name, 1, 10_000)

""" start: assumed existing, end: assumed not existing """
find file with number (name, start, end)
if start + 1 >= end then end
for i in 1, 2, 4, 8, 16, 32, ... where start + i < end
find where file with number=(start + i) exists
and file with number=(start + next i) >= end or does not exist
then find file with numer (name, start + i, start + next i)


Worst case: $(^2\log n)^2/2$ hence $O((\log n)^2)$