As a result, this last step takes time proportional to idx_r, where idx_r is the index of the derangement, a number between 0 and factorial(k), where k is the number of elements which are deranged by the returned permutation.
from sympy import subfactorial from math import comb from itertools import count, accumulate, pairwise, permutations from more_itertools import nth_combination, nth def number_of_derangements_for_permutation_at_index(n, idx): #n = len(seq) for k, (low_acc, high_acc) in enumerate(pairwise(accumulate((comb(n,k) * subfactorial(k) for k in count(2)), initial=1)), start=2): if low_acc <= idx < high_acc: return k, low_acc def is_derangement(seq, perm): return all(i != j for i,j in zip(seq, perm)) def lift_permutation(seq, deranged, permutation): result = list(seq) for i,j in zip(deranged, permutation): result[i] = seq[j] return result # THIS FUNCTION NOT EFFICIENT def nth_derangement(seq, idx): return nth((p for p in permutations(seq) if is_derangement(seq, p)), idx) def nth_permutation(seq, idx): if idx == 0: return list(seq) n = len(seq) k, acc = number_of_derangements_for_permutation_at_index(n, idx) idx_q, idx_r = divmod(idx - acc, subfactorial(k)) deranged = nth_combination(range(n), k, idx_q) derangement = nth((p for p in permutations(deranged) if is_derangementnth_derangement(deranged, p)), idx_r) # TODO: FIND EFFICIENT VERSION OF THIS LINE return lift_permutation(seq, deranged, derangement)