Vishnu
Vishnu

Reputation: 31

How to shuffle a python list such that the occurrence of some elements are preserved?

I start with a list of integers:

A = [ 1, 2, 5, 3, 4, 6, 7, 8 ]

After a shuffle, I would like some elements (say 3, 4 and 5) to preserve their order of occurrence in A while the rest of the elements are free to be randomly shuffled. Something like:

Outcome #1: A = [ 5, 2, 3, 1, 8, 4, 6, 7 ]

-or-

Outcome #2: A = [ 7, 5, 6, 1, 3, 4, 8, 2 ]

-but not-

Outcome #3 (invalid outcome) A = [7, 4, 6, 1, 3, 5, 8, 2]

Appreciate all suggestions!

Upvotes: 2

Views: 97

Answers (3)

ggorlen
ggorlen

Reputation: 57414

Extract the elements you want to maintain relative ordering among, shuffle as normal, then glue the lists back together by randomly picking indexes for the "kept" elements to be inserted. All operations are linear if you use sets for speeding up in operations, which I didn't bother with. Ordering of the keep list matters.

>>> import random
>>> L = [1, 2, 5, 3, 4, 6, 7, 8]
>>> keep = [2, 3, 4]
>>> kept = [L[i] for i in keep][::-1]
>>> unkept = [x for i, x in enumerate(L) if i not in keep]
>>> random.shuffle(unkept)
>>> idxes = random.sample(list(range(len(L))), k=len(keep))
>>> result = [kept.pop() if i in idxes else unkept.pop() for i in range(len(L))]
>>> result
[6, 5, 3, 8, 4, 1, 7, 2]

Random tests:

import random

def shuffle_with_fixed_order(L, keep):
    kept = [L[i] for i in keep][::-1]
    unkept = [x for i, x in enumerate(L) if i not in keep]
    random.shuffle(unkept)
    idxes = random.sample(list(range(len(L))), k=len(keep))
    return [kept.pop() if i in idxes else unkept.pop() for i in range(len(L))]
    
if __name__ == "__main__":
    for _ in range(5000):
        L = list(range(50))
        random.shuffle(L)
        keep = sorted(random.sample(L, k=20))
        shuffled = shuffle_with_fixed_order(L, keep)
        new_locs = [shuffled.index(L[i]) for i in keep]
        assert all(x < y for x, y in zip(new_locs, new_locs[1:]))

Upvotes: 1

Anil Kumar Gupta
Anil Kumar Gupta

Reputation: 179

Probably below will work for you

import random
lst=[1, 2, 5, 3, 4, 6, 7, 8]
s_lst = {5:0,3:0,4:0}

idx_lst = [random.randint(0, len(lst)) for i in range(len(s_lst))]
idx_lst.sort()

for i, s in enumerate(s_lst.keys()):
    s_lst[s] = idx_lst[i]
    lst.remove(s)

random.shuffle(lst)
for k, v in s_lst.items():
    lst.insert(v, k)

print(lst)

Upvotes: 0

Rakesh
Rakesh

Reputation: 82805

This is one approach using random module

Ex:

import random

A = [ 1, 2, 5, 3, 4, 6, 7, 8 ]
result = A[2:5]
del A[2:5]
while A:
    l = len(A)
    result.insert(random.randint(0, l), A.pop(random.randrange(l)))
print(result)

Demo

def rand_shfl(lst):
    result = lst[2:5]
    del lst[2:5]
    while A:
        l = len(A)
        result.insert(random.randint(0, l), A.pop(random.randrange(l)))
    return result

for _ in range(10):
    A = [ 1, 2, 5, 3, 4, 6, 7, 8 ]
    print((rand_shfl(A)))

Output:

[1, 7, 6, 5, 8, 3, 4, 2]
[8, 1, 2, 7, 5, 3, 6, 4]
[8, 6, 7, 2, 1, 5, 3, 4]
[1, 7, 2, 8, 5, 3, 4, 6]
[6, 8, 7, 5, 1, 3, 4, 2]
[7, 2, 5, 6, 1, 3, 4, 8]
[1, 8, 5, 2, 3, 4, 6, 7]
[5, 7, 1, 6, 3, 8, 4, 2]
[1, 5, 7, 3, 2, 4, 8, 6]
[8, 2, 7, 6, 5, 3, 4, 1]

Upvotes: 0

Related Questions