Как создать список уникальных случайных чисел в 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)

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

8 ответов

Решение

Ответ

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

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().

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

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

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

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

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

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]

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

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

Проверьте этот вопрос переполнения стека о генерации случайных чисел с плавающей точкой.

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

from __future__ import division

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

  1. Попробуй и сгенерируй n случайные плавает в [lo, hi] однажды.
  2. Если длина уникальных чисел не 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's 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's 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's 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's 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's 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's 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)

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

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

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

import random
random.random()

Тогда вы получите float val как: 0.672807098390448

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

>>> for i in range(10):
print(random.random())
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)

random.uniform генерирует значения с плавающей запятой

      import random

def get_random(low,high,length):
  lst = []
  while len(lst) < length:
    lst.append(random.uniform(low,high))
    lst = list(set(lst))
  return lst

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

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]
Другие вопросы по тегам