I know the most popular permutation algorithms (thanks to wonderful question/answer on SO, and other related sites, such as Wikipedia, etc), but I recently wanted to see if I could get the Nth permutation without exhausting the whole permutation space.
Factorial comes to mind, so I ended up looking at posts such as this one that implements the unrank and rank algorithm, as well as many, many other ones. (Here as mentioned, I take into account other sites as "post")
I stumbled upon this ActiveState recipe which seems like it fit what I wanted to do, but it doesn't support doing the reverse (using the result of the function and reusing the index to get back the original sequence/order).
I also found a similar and related answer on SO: https://stackoverflow.com/a/38166666/12349101
But the same problem as above.
I tried and made different versions of the unrank/rank implementation(s) above, but they require that the sorted sequence be passed as well as the index given by the rank function. If a random (even within the range of the total permutation count) is given, it won't work (most of the time I tried at least).
I don't know how to implement this and I don't think I saw anyone on SO doing this yet. Is there any existing algorithm or way to do this/approach this?
To make things clearer:
Here is the Activestate recipe I posted above (at least the one posted in the comment):
from functools import reduce
def NPerms (seq):
"computes the factorial of the length of "
return reduce(lambda x, y: x * y, range (1, len (seq) 1), 1)
def PermN (seq, index):
"Returns the th permutation of (in proper order)"
seqc = list (seq [:])
result = []
fact = NPerms (seq)
index %= fact
while seqc:
fact = fact // len (seqc)
choice, index = index // fact, index % fact
result = [seqc.pop (choice)]
return result
As mentioned, this handles doing part of what I mentioned in the title, but I don't know how to get back the original sequence/order using both the result of that function the same index used.
Say I use the above on a string such as hello world
inside a list:
print(PermN(list("hello world"), 20))
This output: ['h', 'e', 'l', 'l', 'o', ' ', 'w', 'd', 'r', 'o', 'l']
Now to see if this can go back to the original using the same index result of the above:
print(PermN(['h', 'e', 'l', 'l', 'o', ' ', 'w', 'd', 'r', 'o', 'l'], 20))
Output: ['h', 'e', 'l', 'l', 'o', ' ', 'w', 'l', 'r', 'd', 'o']
CodePudding user response:
I think this does what you want, and has the benefit that it doesn't matter what the algorithm behind PermN
is:
def NmreP(seq,index):
# Copied from PermN
seqc = list (seq [:])
result = []
fact = NPerms (seq)
index %= fact
seq2 = list(range(len(seq))) # Make a list of seq indices
fwd = PermN(seq2,index) # Arrange them as PermN would
result = [0]*len(seqc) # Make an array to fill with results
for i,j in enumerate(fwd): # For each position, find the element in seqc in the position this started from
result[j] = seqc[i]
return result