2

I need to generate a randomly-paired permutation for an ordered array of integers (indexes). For example, for N=10 the input array looks like:

[0 1 2 3 4 5 6 7 8 9]

A valid randomly-paired permutation would be:

[7 8 6 5 9 3 2 0 1 4]

Notice that the indexes are randomly-paired, i.e.: if element 0 (input array) is paired with 7 (0th position in the permuted array), then element 7 (input array) is paired with 0 (7th position in the permuted array), etc. No element in the input array can be paired with itself, i.e. this is not a valid permutation:

[7 1 6 5 9 3 2 0 8 4]

because the elements 1 and 8 are in their "index" positions. This is related to derangement permutation, a topic already covered a few times in SO:

I'm not certain how (if) I could use any of the answers listed there to my benefit. The code below does what I need, but it becomes very slow for large N values. I need to perform this operation thousands of times for N ~ 1500, meaning this method is of no use.

I'm positive there must be a more clever way to do this.

N = 10
idxs = np.arange(N)

# Generate a shuffled array
input_idxs = idxs.copy()
np.random.shuffle(input_idxs)

# Empty array to store the final randomly paired indexes
shuffled_idxs = np.zeros(N, dtype=int)

used_idxs = []
for i in idxs:
    if i not in used_idxs:
        for j in input_idxs:
            if j not in used_idxs:
                if j != i:
                    shuffled_idxs[i] = j
                    shuffled_idxs[j] = i
                    used_idxs.append(i)
                    used_idxs.append(j)
                    break
Gabriel
  • 40,504
  • 73
  • 230
  • 404
  • Just to make sure if I understand this correctly: Your goal is to randomly swap elements in your list with the requirements that every number is only swapped once, correct? – Ofi91 Dec 18 '20 at 18:28
  • I guess you could think of it that way. The main point is that the random swaps must be done in pairs. – Gabriel Dec 18 '20 at 18:33

1 Answers1

4

The pairing of swaps would imply that N is always an even number. If that is the case, then you can take a sample of half the indexes and pair them with a shuffled list of the remaining indexes:

import random
def randomPairs(N):
    result = [None] * N
    A  = random.sample(range(N),N//2)
    B  = random.sample(list(set(range(N))-set(A)),N//2)
    for a,b in zip(A,B):
        result[a] = b
        result[b] = a
    return result

for _ in range(10):
    print(randomPairs(6))

[4, 3, 5, 1, 0, 2]
[5, 4, 3, 2, 1, 0]
[5, 3, 4, 1, 2, 0]
[2, 3, 0, 1, 5, 4]
[3, 2, 1, 0, 5, 4]
[2, 3, 0, 1, 5, 4]
[4, 2, 1, 5, 0, 3]
[3, 4, 5, 0, 1, 2]
[2, 3, 0, 1, 5, 4]
[1, 0, 3, 2, 5, 4]

Performance

# 1000 shuffles of 1500 elements in pairs
def test1():
    for _ in range(1000):
        randomPairs(1500)
        
from timeit import timeit
count = 1

t = timeit(test1,number=count)
print(t) # 1.12 seconds

[EDIT] a slightly faster solution would be to shuffle all the indexes and pair every other position with the next one in the shuffled list:

import random
def randomPairs(N):
    result = [None] * N
    A  = random.sample(range(N),N)
    for a,b in zip(A[::2],A[1::2]):
        result[a] = b
        result[b] = a
    return result
Alain T.
  • 40,517
  • 4
  • 31
  • 51