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