Как мне составить список случайных уникальных кортежей? - PullRequest
1 голос
/ 11 марта 2020

Я просмотрел несколько ответов, похожих на этот вопрос, и у всех, кажется, есть хорошие ответы от oneliner, которые, однако, имеют дело только с фактом создания уникального списка путем удаления дубликатов. Мне нужно, чтобы в списке было ровно 5.

Единственный код, который я мог придумать, таков:

from random import *

tuples = []

while len(tuples) < 5:
    rand = (randint(0, 6), randint(0,6))
    if rand not in tuples:
        tuples.append(rand)

Мне кажется, что есть более простой способ, но я не могу понять это из. Я попытался поиграть с sample () из случайного числа:

sample((randint(0,6), randint(0,6)), 5)

Но это дает мне ошибку "Sample больше, чем совокупность или отрицателен".

Ответы [ 2 ]

3 голосов
/ 11 марта 2020

Один быстрый способ - использовать itertools.product для генерации всех возможностей кортежей, прежде чем использовать sample, чтобы выбрать 5 из них:

from itertools import product
from random import sample
sample(list(product(range(7), repeat=2)), k=5)
2 голосов
/ 11 марта 2020

Для такого небольшого набора входов просто сгенерируйте все возможные выходы и sample их:

 import itertools
 import random

 size = 6
 random.sample(list(itertools.product(range(size+1), repeat=2)), 5)

Вы указываете, что границы (size) могут быть параметром, хотя и если границы могут быть даже немного больше, это может быть проблемой (вы будете генерировать size ** 2 tuple s, чтобы выбрать 5 из них, и использование памяти может выйти из-под контроля). Если это проблема, поскольку вам нужна только пара целых чисел, есть дешевый трюк: выберите one случайное целое число, которое кодирует оба получаемых целых числа, а затем декодируйте его. Например:

size = 6
raw_sample = random.sample(range((size + 1) ** 2), 5)
decoded_sample = [divmod(x, size+1) for x in raw_sample)]

Поскольку range не требует дополнительных затрат (использование памяти не зависит от длины), вы можете выбрать из него ровно пять значений, а служебные расходы будут пропорциональны выбранным пяти, а не 49 возможных результатов. Затем вы вычисляете частное и остаток на основе диапазона одного значения (от 0 до size включительно в данном случае, поэтому size + 1 возможных значений), и это позволяет получить максимальные и минимальные результаты дешево.

Различия в производительности резко; сравнение:

def unique_random_pairs_by_product(size):
    return random.sample(list(itertools.product(range(size+1), repeat=2)), 5)

с:

def unique_random_pairs_optimized(size):
    val_range = size + 1
    return [divmod(x, val_range) for x in random.sample(range(val_range * val_range), 5)]

версия optimized занимает примерно на 15% меньше времени даже для аргумента 6 (~ 4,65 мкс для product, ~ 3,95 мкс для optimized). Но при size из 6 вы вообще не видите коэффициент масштабирования. Для size=100, optimized увеличивается только до ~ 4,35 мкс (время немного увеличивается, потому что более крупный range с большей вероятностью должен будет выделять новые int с вместо использования небольшого кеша int), в то время как product скачок до 387 мкс, разница почти в 100 раз. А для size=1000 время для product увеличивается до 63,8 мс, а optimized остается ~ 4,35 мкс; разница в 10 000 раз во время выполнения (и еще более высокий коэффициент использования памяти). Если size становится больше этого значения, решение на основе product быстро достигнет точки, когда задержка даже из одной выборки будет заметна для человека; optimized решение будет продолжать работать с идентичной производительностью (по модулю невероятно крошечные различия в стоимости divmod).

...