Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Computing the nth 6 character permutation of an alphabet

I have been researching for days trying to figure out a solution to this problem. I would be happy to pay someone for consulting time to solve this if need be.

I am currently using Python itertools to generate 6 character permutations of a 32 character alphabet. Via the following command:

gen = itertools.permutations('ABCDEFGHJKLMNPQRSTUVWXYZ23456789',6) 

From the documentation, this function produces "r-length tuples, all possible orderings, no repeated elements".

You can use the library to grab a slice of the resulting permutations via the following command (this example grabs the first 10 permutations, 0-10:

gen2 = itertools.islice(gen,0,10)

When iterating over the result gen2, I get exactly what I want:

('A', 'B', 'C', 'D', 'E', 'F')
('A', 'B', 'C', 'D', 'E', 'G')
('A', 'B', 'C', 'D', 'E', 'H')
('A', 'B', 'C', 'D', 'E', 'J')
('A', 'B', 'C', 'D', 'E', 'K')
('A', 'B', 'C', 'D', 'E', 'L')
('A', 'B', 'C', 'D', 'E', 'M')
('A', 'B', 'C', 'D', 'E', 'N')
('A', 'B', 'C', 'D', 'E', 'P')
('A', 'B', 'C', 'D', 'E', 'Q')

This is great, but my real desire is to be able to pick any arbitrary permutation and grab it from the permutation list (without having to store all possible permutation values). If my calculations are correct, when generating 6 character sequences of the alphabet listed above there are 652,458,240 possible combinations. So I would like to be able to do something like grab the 10,353,345th permutation. The problem is that if you use the islice function above to grab this permutation it has to iterate over the entire set of permutations up to 10,353,345th element before returning it to you. As you can imagine, this is very inefficient and takes a long time to return.

My question is, what is the algorithm to achieve the computation desired? I've done quite a bit of research on factorial decomposition and base n conversions, but have been unable to find anything explaining how to achieve something close to what I want or an algorithm that I can modify to achieve this result.

Any help would be greatly appreciated!

like image 821
Jordan Avatar asked Nov 01 '22 04:11

Jordan


1 Answers

What you are looking is called unrank in combinatorial algorithm. Consider the list of the element of a set S in a fixed order, unrank_S(i) returns the i-th element of the list without computing the list. So your S here is Perm(n, k) : the list of all k-permutation of a set of size n. As you know the size of this set is n!/k!. One way to do that is to use Factoradic numbers

Here is an unrank algorithm in python:

def factorial(n):
    if n == 0: return 1
    return n*factorial(n-1)

def unrank(S, k, i):
    S = list(S)   # make a copy to avoid destroying the list
    n = len(S)
    nb = factorial(n) // factorial(n-k)
    if i >= nb:
        raise IndexError
    res = []
    while k > 0:
        nb = nb // n
        pos = i // nb   # the factoradic digits
        i = i % nb      # the remaining digits
        res.append(S[pos])
        del S[pos]
        k = k-1
        n = n-1
    return res

Then

[unrank(range(5), 2, i) for i in range(20)]
 [[0, 1], [0, 2], [0, 3], [0, 4], [1, 0], [1, 2], [1, 3], [1, 4], [2, 0], [2, 1], [2, 3], [2, 4], [3, 0], [3, 1], [3, 2], [3, 4], [4, 0], [4, 1], [4, 2], [4, 3]]

and

unrank(list('ABCDEFGHJKLMNPQRSTUVWXYZ23456789'),6, 128347238)\
['G', 'L', 'E', 'H', 'T', 'R']

Of course you might want to compute factorial using a better method or even to cache it in a pre-computed array to avoid recomputing it.

like image 90
hivert Avatar answered Nov 05 '22 03:11

hivert