1

Is it possible to find all the permutations of a list (n=27) with the limitation that elements x0 to x7 can only be in any position as long as it is in index 0 to 7 of the permutation?

keys = [x0, x1, x2, x3, x4, x5, x6, x7 ... x26]
[x1, x2, x3, x4, x5, x6, x7, x0 ... x26] #is okay
[x0, x1, x2, x3, x4, x5, x6, x8, x7 ... x26] #is NOT okay

I need it to be 'resume-able' from the nth permutation, as there are going to be a lot of permutations I can't test them all in one run. It will probably have to be a generator (of some sort) so I can test each permutation as it is generated otherwise it will eat up the memory in no time.

Any pointers are much appreciated.

Solutions I've considered:

permitted = [x0, x1, x2, x3, x4, x5, x6, x7]

for p in itertools.permutations(keys):
   if p[0] not in permitted:
      continue
   if p[1] not in permitted:
      continue
   ...
   # if it passes all the limitations, test this permutation
   test(p)

The problem with this is that I can't generate all the permutations and test them in one uninterpreted run.

The other approach I tried from this answer here:

from math import factorial

def ith_permutation(i, seq, r=None):
    li = list(seq)
    length = len(li)

    if r is None:
        r = length
    res = []
    current_factorial = factorial(length) // factorial(length - r)

    if current_factorial <= i:
        raise ValueError('out of range')

    for x in range(length, length-r, -1):
        current_factorial //= x
        div, mod = divmod(i, current_factorial)
        i = mod
        res.append(li[div])
        del(li[div])

    return res


for i in range(0, factorial(len(keys))-1):
   p = ith_permutation(i, keys)
   test(p)

This is the same as above in principle but again I'd have to go through 1.08e+28 permutations! Which is impossible.

Hal
  • 27
  • 4

2 Answers2

0

Note that you are looking for a permutation of x0,...,x7 followed by a permutation of x8,...,x26. So, a double loop will do ya.

Igor Rivin
  • 4,632
  • 2
  • 23
  • 35
0

First you have to write a function that will give you the nth permutation of elements in a list. Then you can combine the permutations of the 0..7 sublist with the permutations of 8...26 sublist.

A function to get the nth permutation can be defined using a variable base composed of factorials. For example, the first elements of an N size list will be at 0*base, 1*base, 2*base, ... So you can determine the index of the first element by computing the value of the base (N-1)! and dividing the position by that base. The remainder of that division is the position of the second element in the N-1 remaining elements. You can go through this process recursively until you get to the last element.

For example:

from math import factorial

def nthPermute(A,n):
    if not A: return tuple()        
    i,j = divmod(n,factorial(len(A)-1))
    return (A[i],)+nthPermute(A[:i]+A[i+1:],j)

output:

for i in range(24):
    print(i,nthPermute("ABCD",i))

0  ('A', 'B', 'C', 'D')
1  ('A', 'B', 'D', 'C')
2  ('A', 'C', 'B', 'D')
3  ('A', 'C', 'D', 'B')
4  ('A', 'D', 'B', 'C')
5  ('A', 'D', 'C', 'B')
6  ('B', 'A', 'C', 'D')
7  ('B', 'A', 'D', 'C')
8  ('B', 'C', 'A', 'D')
9  ('B', 'C', 'D', 'A')
10 ('B', 'D', 'A', 'C')
11 ('B', 'D', 'C', 'A')
12 ('C', 'A', 'B', 'D')
13 ('C', 'A', 'D', 'B')
14 ('C', 'B', 'A', 'D')
15 ('C', 'B', 'D', 'A')
16 ('C', 'D', 'A', 'B')
17 ('C', 'D', 'B', 'A')
18 ('D', 'A', 'B', 'C')
19 ('D', 'A', 'C', 'B')
20 ('D', 'B', 'A', 'C')
21 ('D', 'B', 'C', 'A')
22 ('D', 'C', 'A', 'B')
23 ('D', 'C', 'B', 'A')

The order of the permutations follows the order of the elements in the list. If your list is sorted, you will be able to use a binary search algorithm to find the index of a given permutation:

def indexOfPermute(A,P):
    lo,hi = 0,factorial(len(A))-1
    while lo<=hi:
        mid = (lo+hi)//2
        p = nthPermute(A,mid)
        if   p<P: lo = mid+1
        elif p>P: hi = mid-1
        else: return mid

i = indexOfPermute("ABCD",tuple('BCAD'))
print(i)
# 8

Applying the same principle to your two part permutations, you can create a function to get the nth value of your constrained permutations of 27 elements.

def nthPerm_8_19(A,n):
    i,j = divmod(n,factorial(19))
    return nthPermute(A[:8],i)+nthPermute(A[8:],j)

output:

A = "12345678ABCDEFGHIJKLMNOPQRS"
for g in range(0,factorial(19)*7,factorial(19)):
    for i in range(g,g+4):
        print(i,"".join(nthPerm_8_19(A,i)))

0                  12345678ABCDEFGHIJKLMNOPQRS
1                  12345678ABCDEFGHIJKLMNOPQSR
2                  12345678ABCDEFGHIJKLMNOPRQS
3                  12345678ABCDEFGHIJKLMNOPRSQ
121645100408832000 12345687ABCDEFGHIJKLMNOPQRS
121645100408832001 12345687ABCDEFGHIJKLMNOPQSR
121645100408832002 12345687ABCDEFGHIJKLMNOPRQS
121645100408832003 12345687ABCDEFGHIJKLMNOPRSQ
243290200817664000 12345768ABCDEFGHIJKLMNOPQRS
243290200817664001 12345768ABCDEFGHIJKLMNOPQSR
243290200817664002 12345768ABCDEFGHIJKLMNOPRQS
243290200817664003 12345768ABCDEFGHIJKLMNOPRSQ
364935301226496000 12345786ABCDEFGHIJKLMNOPQRS
364935301226496001 12345786ABCDEFGHIJKLMNOPQSR
364935301226496002 12345786ABCDEFGHIJKLMNOPRQS
364935301226496003 12345786ABCDEFGHIJKLMNOPRSQ
486580401635328000 12345867ABCDEFGHIJKLMNOPQRS
486580401635328001 12345867ABCDEFGHIJKLMNOPQSR
486580401635328002 12345867ABCDEFGHIJKLMNOPRQS
486580401635328003 12345867ABCDEFGHIJKLMNOPRSQ
608225502044160000 12345876ABCDEFGHIJKLMNOPQRS
608225502044160001 12345876ABCDEFGHIJKLMNOPQSR
608225502044160002 12345876ABCDEFGHIJKLMNOPRQS
608225502044160003 12345876ABCDEFGHIJKLMNOPRSQ
729870602452992000 12346578ABCDEFGHIJKLMNOPQRS
729870602452992001 12346578ABCDEFGHIJKLMNOPQSR
729870602452992002 12346578ABCDEFGHIJKLMNOPRQS
729870602452992003 12346578ABCDEFGHIJKLMNOPRSQ

With this you can use the nthPerm_8_19() function as if you had a super long list containing all the 4,904,730,448,484,106,240,000 permutations of your elements.

To implement a "resumable" process, you only need to record the position in the virtual permutation list and continue from there upon resumption. You could also use the position to "shard" computations for parallel processing.

The indexing scheme would also allow you to "skip" a chunk of permutations. For example, if you get to a point where you want to skip permutations up to the next value at position 11, you could update your index by adding the modulo complement of base (26-11)! :

 i    = 851515702861824002       
 s    = "".join(nthPerm_8_19(A,i))  # '12346587ABCDEFGHIJKLMNOPRQS'[11] = 'D'

 base = factorial(26-11)
 i   += base - i % base
 s    = "".join(nthPerm_8_19(A,i))  # '12346587ABCEDFGHIJKLMNOPQRS'[11] = 'E' 

[EDIT]

further break down (responding to comment):

def nthPerm_8_10_9(A,n):
    i,j = divmod(n,factorial(10)*factorial(9))
    j,k = divmod(j,factorial(9))
    return nthPermute(A[:8],i) + nthPermute(A[8:18],j) + nthPermute(A[18:],k)

This could be generalized directly into the nthPermute() function like this:

def nthPermute(A,n,chunks=None):
    if not A: return tuple()
    if chunks is None:
        if n>=factorial(len(A)): return None
        i,j = divmod(n,factorial(len(A)-1))
        return (A[i],)+nthPermute(A[:i]+A[i+1:],j)
    result = tuple()
    for size in reversed(chunks):
        base   = factorial(size)
        n,i    = divmod(n,base)
        A,a    = A[:-size],A[-size:]
        result = nthPermute(a,i) + result
    return result if n==0 else None

and also in the reverse function to get the index of a permutation (if elements are sorted within chunks):

def indexOfPermute(A,P,chunks=None):
    lo,hi = 0,1
    for c in chunks or [len(A)]: hi *= factorial(c)
    hi -= 1
    while lo<=hi:
        mid = (lo+hi)//2
        p = nthPermute(A,mid,chunks)
        if   p<P: lo = mid+1
        elif p>P: hi = mid-1
        else: return mid

which would allow you to play with the chunking as you like:

P = nthPermute(A,121645100408832000,[8,19])
print("".join(P),indexOfPermute(A,P,[8,19]))

# 12345687ABCDEFGHIJKLMNOPQRS 121645100408832000


P = nthPermute(A,26547069911040000,[8,10,9])
print("".join(P),indexOfPermute(A,P,[8,10,9]))
# 51234678ABCDEFGHIJKLMNOPQRS 26547069911040000


P = nthPermute(A,67722117120000,[6,6,9,6])
print("".join(P),indexOfPermute(A,P,[6,6,9,6]))
# 41235678ABCDEFGHIJKLMNOPQRS 67722117120000
Alain T.
  • 40,517
  • 4
  • 31
  • 51
  • This is very very helpful Alain. As the total number of permutations is still very large, how would the nthPerm_8_19 func change so that it's the first 8 elements followed by the next 10 and finally the final 9? – Hal May 02 '20 at 03:31