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

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

7 ответов


ответ

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

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 случайные.sample () есть.

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

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

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

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

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

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

  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)

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

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

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

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

from __future__ import division

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

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


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)

как указано в документации Python имеет случайный.функция random ():

import random
random.random()

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

поэтому все, что вам нужно сделать, это сделать for loop и распечатать случайным образом.random():

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

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]