Как создать список уникальных случайных чисел в 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
Если вам нужно гарантировать уникальность, это может быть более эффективным
- Попробуй и сгенерируй
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'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]