Answer
一种简单的方法是保留一组迄今为止看到的所有随机值,并在出现重复时重新选择:
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
Notes
这个技术是Python自己的随机抽样()已实施。
该函数使用一个set https://docs.python.org/3/library/stdtypes.html#set-types-set-frozenset跟踪先前的选择,因为搜索集合的时间复杂度为 O(1),而搜索列表的时间复杂度为 O(n)。
计算重复选择的概率相当于著名的生日问题 https://en.wikipedia.org/wiki/Birthday_problem.
给定 2**53 个不同的可能值random(),重复的情况很少见。
平均而言,您预计会出现大约 120,000,000 个样本的重复浮点数。
变体:有限的浮动范围
如果人口仅限于一系列均匀分布的浮点数,那么可以使用随机抽样() https://docs.python.org/2.7/library/random.html#random.sample直接地。唯一的要求是人口必须是Sequence https://docs.python.org/2.7/glossary.html#term-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
下面是从 10.0 到 20.0 的 41 个均匀间隔的浮点数范围中无放回地选择 10 个随机样本的示例。
>>> 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]