如何在Python中生成唯一随机浮点数列表

19

我知道生成唯一随机整数列表有简便方法(例如: 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)

有没有更好的方法来做这件事?


2
生成随机整数,进行比例缩放和平移。 - user2357112
1
@PeterWood 同意,但这不是你之前说的话 - 可能有误解。 - miradulo
1
@logc 从连续分布中进行抽样是一个非常著名的问题。请参见维基百科 - miradulo
7
你试图用随机浮点数做什么? - Ry-
1
请注意,“unique”按定义是指与“random”不同的东西。 - Shadur
显示剩余8条评论
8个回答

20

答案

一种简单的方法是保持一个包含所有已见随机值的集合,如果有重复则重新选择:

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

这是一个例子,从10.0到20.0的41个等间距浮点数中选择了十个无重复的随机样本。

>>> 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]

3
这是我会推荐的...集合查找是O(1)...而且随机浮点数很容易。 - Joran Beasley
1
@ViníciusAguiar:查找x in result所需的时间与result的长度成正比。如果将result设置为集合,则不会无序。 - Ry-
2
@RaymondHettinger 同意!我不知道,只是有一种印象,认为99%相信他们需要“唯一随机浮点数”的人实际上并不需要这种“唯一性”,所以我希望他们不要从这个问题/答案中得到错误的想法。从连续分布中进行无替换抽样并不是我曾经遇到过的事情 - 当然,如果你有一个病态小的区间,出于某种原因可能是必要的。 - miradulo
2
@StefanPochmann 要超过2^53,您需要在指数中使用位。这将导致随机浮点数不再等分布。 - Raymond Hettinger
2
我赞同Mitch所说的:我认为从连续分布中采样唯一浮点数的代码从未有过用处。如果您可以接受浮点数彼此任意接近,但不能相同,那么您可能正在做出一些错误的假设。 - Sven Marnach
显示剩余17条评论

9
您可以轻松地使用整数列表生成浮点数:
int_list = random.sample(range(1, 100), 10)
float_list = [x/10 for x in int_list]

请查看关于生成随机浮点数的这个 Stack Overflow 问题

如果你想在 Python 2 中使用它,请添加以下导入:

from __future__ import division

这段代码只能在 Python 3.x 中按预期工作,因为在 Python 2.x 中它会执行整数除法。你可以显式地写成 x / 10. 以使其在两个版本中都能正常工作。 - Graipher
1
@Graipher 谢谢,已添加Python2兼容性。 - Or Duan

5
如果您需要保证唯一性,同时也更高效的话,可以尝试以下方法:
  1. 尝试一次性生成 n 个在 [lo, hi] 范围内的随机浮点数。
  2. 如果唯一的浮点数长度不是 n,则尝试生成所需数量的浮点数
然后继续执行,直到足够为止,而不是通过 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)
<b>153 µs ± 3.71 µs per loop</b> (mean ± std. dev. of 7 runs, 10000 loops each)

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

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

更“平常”的情况(包含更多样本)

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

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

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

1
那么问题是,numpy 在数值计算方面比普通的 Python 更快吗? 而你认为对于一个非 numpy 的问题应该有一个 numpy 的答案? - Raymond Hettinger
@RaymondHettinger 我的观点更多是,如果我们有一种快速的方法(即np.random.uniform)可以一次生成大量随机浮点数,那么尝试一次性生成所有随机数并检查重复项比使用random.uniform逐个生成更快。所以,是的,我不确定在列表推导式中使用random.uniform并更新集合(可能只是少了一些集合检查)会带来多少性能提升,但我认为这对某些人可能很有用? - miradulo
是的。在末尾清除重复项同样有效。 - Raymond Hettinger
1
@RaymondHettinger 我尝试详细阐述我的答案,以使NumPy依赖关系更清晰,并添加了一个Python示例,以说明我之前所说的话。谢谢! - miradulo

4
您可以使用random.uniform(start, stop)。对于双精度浮点数,如果集合较小,您可以相对确定它们是唯一的。如果您想生成大量随机浮点数并且需要避免重复,请在将其添加到列表之前进行检查。
但是,如果您正在寻找特定数字的选择,则这不是解决方案。

1
如果您计划在保留生成的浮点数之前检查其唯一性,我建议使用集合而不是列表来保持O(n),因为每次检查列表会使其变成O(n^2)。 - Niema Moshiri
1
确保唯一性是一个生日问题,因此一旦选择的数量足够大,“相对确定它们是唯一的”就不再可靠。 - Raymond Hettinger
在示例中搜索的浮点数数量很少,因此出现双精度值的风险很小。尽管如此,您是正确的。回答已更新。 - C. Nitschke

1
min_val=-5
max_val=15

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

或者使用uniform。
numpy.random.uniform(min_val,max_val,size=15)

3
这并不保证唯一性。例如,尝试运行 len(set(numpy.random.uniform(1, 1 + 2**-40, size=1000))),你将得到大约887个数字而不是全部的1000个。 - Stefan Pochmann

1

如Python文档所述,Python具有random.random()函数:

import random
random.random()

那么你将会得到一个浮点数,如:0.672807098390448

所以你需要做的就是写一个 for 循环并且输出 random.random():

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

0

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]

0
随机.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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接