Getting Nth permutation of a sequence and getting back original using its index and modified sequence

Question:

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']

Asked By: Nordine Lotfi

||

Answers:

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
Answered By: Scott Hunter
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.