I wrote the following little piece of Python code to take in some integer, and return a permutation that has that rank in the lexicographical ordering of permutations. While this works, I'm worried that the number of calls to pop() and append() might be slowing this down. This function is part of a larger program, which will call FromRank() millions of times, and any small efficiency may make a large impact down the road.
Note: The same topic for Java is here!
Input:
There are four arguments: rank, level, ordered, and perm. rank is the rank given. level is the number of uncertain digits of the permutation minus one. Hence, if the size of a permutation is 8 digits, then level is initially 7. ordered is initially list(range(size)) and is always sorted. perm keeps track of the permutation, and is initially an empty list.
Code:
def FromRank(rank,level,ordered,perm):
fact = math.factorial(level)
section = rank // fact
if len(ordered) == 1:
perm.extend(ordered)
return perm
elif rank % fact == 0:
ordered.sort()
nxt = ordered.pop(section - 1)
perm.append(nxt)
ordered.sort(reverse = True)
perm.extend(ordered)
return perm
elif rank < fact:
ordered.sort()
first = ordered.pop(0)
perm.append(first)
return FromRank(rank,level - 1,ordered,perm)
else:
ordered.sort()
nxt = ordered.pop(section)
perm.append(nxt)
rank = rank - (rank // fact) * fact
return FromRank(rank,level - 1,ordered,perm)
Time Complexity Example:
While the goal of this function is to work on a single rank, I ran the following code to see how runtime would ramp up with an increasing number of permutations to handle. And, although this function would never be running on permutations larger than 20, I included a section giving the runtimes for a single permutation of increasing size.
I ran this to see how the function would respond to volume
for j in range(2,11):
start_time = timeit.default_timer()
for i in range(1,math.factorial(j)+1):
x = FromRank(i,j-1,list(range(j)),[])))
elapsed_time = timeit.default_timer() - start_time
print("Permutations of size {} took {} seconds."\
.format(j,elapsed_time))
and got the following times:
Permutations of size 2 took 0.000249750356557 seconds.
Permutations of size 3 took 0.00016293644837 seconds.
Permutations of size 4 took 0.000660726542604 seconds.
Permutations of size 5 took 0.00366414564209 seconds.
Permutations of size 6 took 0.0185743274595 seconds.
Permutations of size 7 took 0.160154982071 seconds.
Permutations of size 8 took 1.48163329891 seconds.
Permutations of size 9 took 15.2436537109 seconds.
Permutations of size 10 took 177.243403105 seconds.
I then ran the following to see how the function would respond to large permutations:
for size in [10,50,100,500,1000]:
start_time = timeit.default_timer()
x = FromRank(size ** 2, size - 1, list(range(size)), [])
elapsed_time = timeit.default_timer() - start_time
print(elapsed_time)
and got the following times:
0.000118032702744
0.00074668514128
0.00260142366255
0.110989229516
0.684973282271
permutationsfunction in theitertoolsmodule? \$\endgroup\$itertools.permutations()generates permutations, and I am more interested in finding a specific permutation based on its order. Unless I'm not seeing what you're saying? \$\endgroup\$itertools.permutations, and the itertools function was much faster. Maybe I don't really understand what your code does. I added aprint xto your doubleforloop that tests your code, it seemed like it generates permutations too. Maybe you could add docstrings, doctests, or other information to your code so that it is easy to understand expected inputs and outputs? \$\endgroup\$