Как сгенерировать список уникальных случайных чисел в Python

Я знаю, что есть простые способы генерации списков уникальных случайных целых чисел (например, random.sample(range(1, 100), 10)).

Интересно, есть ли лучший способ генерации списка уникальных случайных чисел, кроме написания функции, которая действует как диапазон, но принимает такие поплавки:

import random

def float_range(start, stop, step):
    vals = []
    i = 0
    current_val = start
    while current_val < stop:
        vals.append(current_val)
        i += 1
        current_val = start + i * step
    return vals

unique_floats = random.sample(float_range(0, 2, 0.2), 3)

Есть ли лучший способ сделать это?

Ответ 1

Ответ

Одним из простых способов является сохранение набора всех случайных значений, видимых до сих пор, и повторного выбора, если есть повторение:

import random

def sample_floats(low, high, k=1):
    """ Return a k-length list of unique random floats
        in the range of low <= x <= high
    """
    result = []
    seen = set()
    for i in range(k):
        x = random.uniform(low, high)
        while x in seen:
            x = random.uniform(low, high)
        seen.add(x)
        result.append(x)
    return result

Примечания

  • Этот метод реализуется как собственный метод Python random.sample().

  • Функция использует set для отслеживания предыдущих выборов, поскольку поиск набора - это O (1) при поиске списка O (n).

  • Вычисление вероятности повторного выбора эквивалентно знаменитой проблеме с днем ​​рождения.

  • Учитывая 2 ** 53 различных возможных значения из random(), дубликаты нечасты. В среднем вы можете ожидать дублирования поплавка около 120 000 000 образцов.

Вариант: Ограниченный диапазон поплавка

Если население ограничено только диапазоном равномерно расположенных поплавков, то можно напрямую использовать random.sample(). Единственное требование состоит в том, чтобы население было Sequence:

from __future__ import division
from collections import Sequence

class FRange(Sequence):
    """ Lazily evaluated floating point range of evenly spaced floats
        (inclusive at both ends)

        >>> list(FRange(low=10, high=20, num_points=5))
        [10.0, 12.5, 15.0, 17.5, 20.0]

    """
    def __init__(self, low, high, num_points):
        self.low = low
        self.high = high
        self.num_points = num_points

    def __len__(self):
        return self.num_points

    def __getitem__(self, index):
        if index < 0:
            index += len(self)
        if index < 0 or index >= len(self):
            raise IndexError('Out of range')
        p = index / (self.num_points - 1)
        return self.low * (1.0 - p) + self.high * p

Ниже приведен пример выбора десяти случайных выборок без замены из диапазона из 41 равномерно расположенных поплавков с 10.0 до 20.0.

>>> import random
>>> random.sample(FRange(low=10.0, high=20.0, num_points=41), k=10)
[13.25, 12.0, 15.25, 18.5, 19.75, 12.25, 15.75, 18.75, 13.0, 17.75]

Ответ 2

Вы можете легко использовать свой список целых чисел для создания float:

int_list = random.sample(range(1, 100), 10)
float_list = [x/10 for x in int_list]

Откажитесь от этого вопроса о переполнении Stack   о создании случайных поплавков.

Если вы хотите, чтобы он работал с python2, добавьте этот импорт:

from __future__ import division

Ответ 3

Если вам нужно гарантировать уникальность, это может быть более эффективным для

  • Попробуйте создать n случайные поплавки в [lo, hi] сразу.
  • Если длина уникальных поплавков не равна n, попробуйте и сгенерируйте, однако по-прежнему нужно много поплавков.

и продолжайте соответственно до тех пор, пока у вас не будет достаточно, в отличие от генерации их 1 на 1 в цикле уровня Python, проверяющих набор.

Если вы можете позволить NumPy делать это с помощью np.random.uniform, это может быть огромным ускорением.

import numpy as np

def gen_uniq_floats(lo, hi, n):
    out = np.empty(n)
    needed = n
    while needed != 0:
        arr = np.random.uniform(lo, hi, needed)
        uniqs = np.setdiff1d(np.unique(arr), out[:n-needed])
        out[n-needed: n-needed+uniqs.size] = uniqs
        needed -= uniqs.size
    np.random.shuffle(out)
    return out.tolist()

Если вы не можете использовать NumPy, он по-прежнему может быть более эффективным, в зависимости от ваших данных, чтобы применить ту же концепцию проверки дубликатов, поддерживая набор.

def no_depend_gen_uniq_floats(lo, hi, n):
    seen = set()
    needed = n
    while needed != 0:
        uniqs = {random.uniform(lo, hi) for _ in range(needed)}
        seen.update(uniqs)
        needed -= len(uniqs)
    return list(seen)

Грубая оценка

Экстремальный вырожденный случай

# Mitch NumPy solution
%timeit gen_uniq_floats(0, 2**-50, 1000)
153 µs ± 3.71 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

# Mitch Python-only solution
%timeit no_depend_gen_uniq_floats(0, 2**-50, 1000)
495 µs ± 43.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

# Raymond Hettinger solution (single number generation)
%timeit sample_floats(0, 2**-50, 1000)
618 µs ± 13 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Более "нормальный" случай (с большим образцом)

# Mitch NumPy solution
%timeit gen_uniq_floats(0, 1, 10**5)
15.6 ms ± 1.12 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)

# Mitch Python-only solution
%timeit no_depend_gen_uniq_floats(0, 1, 10**5)
65.7 ms ± 2.31 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

# Raymond Hettinger solution (single number generation)
%timeit sample_floats(0, 1, 10**5)
78.8 ms ± 4.22 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Ответ 4

Вы можете просто использовать random.uniform(start, stop). С плавающей точкой с двойной точностью вы можете быть уверены, что они уникальны, если ваш набор невелик. Если вы хотите сгенерировать большое количество случайных поплавков и вам нужно избегать того, что у вас есть номер дважды, проверьте, прежде чем добавлять их в список.

Однако, если вы ищете выбор конкретных номеров, это не решение.

Ответ 5

min_val=-5
max_val=15

numpy.random.random_sample(15)*(max_val-min_val) + min_val

или используйте единый

numpy.random.uniform(min_val,max_val,size=15)

Ответ 6

Как указано в документации Python имеет функцию random.random():

import random
random.random()

Затем вы получите float val как: 0.672807098390448

Итак, все, что вам нужно сделать, это сделать цикл for и распечатать random.random():

>>> for i in range(10):
print(random.random())

Ответ 7

more_itertools имеет общий numeric_range, который обрабатывает как целые числа, так и float.

import random

import more_itertools as mit

random.sample(list(mit.numeric_range(0, 2, 0.2)), 3)
# [0.8, 1.0, 0.4]

random.sample(list(mit.numeric_range(10.0, 20.0, 0.25)), 10)
# [17.25, 12.0, 19.75, 14.25, 15.25, 12.75, 14.5, 15.75, 13.5, 18.25]