简体   繁体   中英

python tuples shuffle on first element keep second ordering

Is it possible to shuffle a list of python tuples whilst keeping the ordering of the second element.

Input:

[(1,1), (1,2), (1,3), (2,2), (2,3), (3,1), (3,2), (3,3)]

Example Output:

[(3,1), (1,1), (2,2), (3,2), (2, 3), (1,2), (1,3), (3,3)]

Essentially, I would like to randomize the first element keeping the ordering of the second element. As an example:

np.random.seed(41)
x =  [(1,1), (1,2), (1,3), (2,2), (2,3), (3,1), (3,2), (3,3)]
np.random.shuffle(x)

Would output

[(3, 2), (3, 1), (1, 2), (3, 3), (1, 3), (2, 3), (2, 2), (1, 1)]

With no ordering on the second element and not what is desired.

You could use random:

import random
l = [(1,1), (1,2), (1,3), (2,2), (2,3), (3,1), (3,2), (3,3)]
print(sorted(l, key=lambda x: (x[1], random.random())))

If the order of the 2nd elements is arbitrary (ie not always increasing) a sort will not be sufficient. One way to do it would be to keep track of the 2nd element order in a dictionary and shuffle the (repeating) 1st elements, then sequentially map the second element to the result using the dictionary. An iterator can be stored in the dictionary to allow using the next() function for the 2nd part of the tuples.

T = [(1,1), (1,2), (1,3), (2,2), (2,3), (3,1), (3,2), (3,3)]

from random import sample

groups = {g:iter([t for t in T if t[0]==g]) for g in dict(T)}
result = [next(groups[v0]) for v0,_ in sample(T,len(T))]

print(result)

[(3, 1), (3, 2), (1, 1), (3, 3), (2, 2), (1, 2), (1, 3), (2, 3)]

How it works is by first building a dictionary of the tuples grouped by their fist element and preserving the original order in the associated list.

{ 
  1: [(1,1), (1,2), (1,3)],
  2: [(2,2), (2,3)],
  3: [(3,1), (3,2), (3,3)]
}

Then the whole list of tuples is shuffled (using the sample() function).

[(3, 3), (3, 2), (1, 2), (3, 1), (2, 2), (1, 3), (1, 2), (2, 3)]

This does not preserve the order of the second element so we replace each tuple in the shuffled list with the one corresponding to its 1st element in the dictionary, matching the original order. In the end, shuffling the tuples only aims to shuffle their 1st element given that the 2nd element in the shuffled order is ignored.

   [(3, 3), (3, 2), (1, 2), (3, 1), (2, 2), (1, 3), (1, 2), (2, 3)] # shuffled
     :  –    :  –    :  –    :  –    :  –    :  –    :  –    :  –
     :       :       :       :       :       :       :       :  
1: [ :       :      (1, 1),  :       :      (1, 2), (1, 3)]  :      # 
2: [ :       :          |    :      (2, 2),     |       |   (2, 3)] # groups
3: [(3, 1), (3, 2),     |   (3, 3)]     |       |       |       |   #
        |       |       |       |       |       |       |       |
        v       v       v       v       v       v       v       v
   [(3, 1), (3, 2), (1, 1), (3, 3), (2, 2), (1, 2), (1, 3), (2, 3)] # result

The iter() function is merely a trick to process the mapping inside a list comprehension. You could do the same thing using groups[v0].pop(0) to consume the grouped lists in the original order (although that would likely be less efficient)

Special thanks to Kelly Bundy for helping debug and optimize the solution

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM