简体   繁体   中英

Efficiently pair random elements of list

I have a list of n elements say: foo = ['a', 'b', 'c', 'd', 'e'] I would like to randomly pair elements of this list to receive for example: bar = [['a', 'c'], ['b', 'e']] where the last element will be discarded if the length of the list is not even.

Right now, I have a brute force function:

def rnd_pair(foo):
    while len(foo) > 1:
         pair_idxs = np.sort(np.random.choice(range(len(foo)), 2, replace=False))
         el_pair = [foo.pop(pair_idxs[0]), foo.pop(pair_idxs[1] - 1)]

         el_pairs.append(el_pair)

This is not really efficient. Also I don't like changing the list in-place. Any ideas on a more efficient way? Thanks in advance.

I would say this is the most pythonic way of handling it:

foo = ['a', 'b', 'c', 'd', 'e', 'f']
random.shuffle(foo)
length = len(foo)//2
result = list(zip(foo[:length], foo[length:]))

Zip will combine the elements at the same index of multiple lists and stop when one list runs out of elements. So you're going to to shuffle the list, and take the first and 2nd half of the list, then combine them element wise until the shorter one runs out of elements.

Edit: you said you were interested in the performance of different ways of handling it. I made a function for each of the unique answers here and timed them:

def a(foo):
    random.shuffle(foo)
    length = len(foo)//2
    return list(zip(foo[:length], foo[length:]))

def b(foo):
    random.shuffle(foo)
    return [[foo[i], foo[i+1]] for i in range(0, len(foo)-(len(foo)%2), 2)]

def c(foo):
    np.random.shuffle(foo)
    return foo[:len(foo)-(len(foo)%2)].reshape(2,-1)

def d(foo):
    result = []
    for i in range(1, len(foo), 2):
        result.append([foo[i-1], foo[i]])
    return result

def e(foo):
    el_pairs = []
    while len(foo) > 1:
         pair_idxs = np.sort(np.random.choice(range(len(foo)), 2, replace=False))
         el_pair = [foo.pop(pair_idxs[0]), foo.pop(pair_idxs[1] - 1)]
         el_pairs.append(el_pair)
    return el_pairs

def f(foo):
    random.shuffle(foo)
    length = len(foo)//2
    return zip(foo[:length], foo[length:])

Zip without list:

%timeit f(foo)
3.96 µs ± 12.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Zip:

%timeit a(foo)
4.36 µs ± 156 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

List comprehension:

%timeit b(foo)
4.38 µs ± 22.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

For loop:

%timeit d(foo)
812 ns ± 5.68 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Original:

%timeit e(foo)
154 ns ± 1.11 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

The numpy answer given didn't run out of box for me.

>>> import random
>>> foo = ['a', 'b', 'c', 'd', 'e']
>>> random.shuffle(foo)
>>> bar = list(zip(foo[::2], foo[1::2]))
>>> bar
[('e', 'd'), ('c', 'a')]

Try this:

from random import shuffle

foo = ['a', 'b', 'c', 'd', 'e']
shuffle(foo)

result = []
for i in range(1, len(foo), 2):
  result.append([foo[i-1], foo[i]])

print(result)
import random
import string

def random_pairs(x):
  random.shuffle(x)
  return zip(*(2 * [iter(x)]))

# Shuffling in place.
array = list(string.lowercase) # a-z
print list(random_pairs(array))

# Not Shuffling in place.
array = list(string.uppercase) # A-Z
pairs = random_pairs(range(len(array)))
print map(lambda pair: map (lambda i: array[i], pair), pairs)

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