排序案例的单行解决方案
这里有一个简单的一行程序,它以相等的可能性产生所有的可能性:[9*i + x for i, x in enumerate(sorted(random.sample(range(13), 4)))]
一些示例输出:
^{pr2}$
输出总是按排序顺序生成的;如果这不是您想要的,您可以很容易地向结果中添加一个shuffle(或参见下面的一般解决方案)。在
说明:如果[a, b, c, d]是一个满足您要求的有序列表,那么[a, b-9, c-18, d-27]是{}中长度为4的有序样本,反之亦然。因此,您只需从range(13)生成样本,对它们进行排序,然后重新添加9的必要倍数,以获得至少相距10的值。在
一般非排序解
这是一个不需要对随机样本进行排序的通用解决方案。相反,我们计算样本元素的秩,并使用这些元素来计算必要的偏移量。在import random
def ranks(sample):
"""
Return the ranks of each element in an integer sample.
"""
indices = sorted(range(len(sample)), key=lambda i: sample[i])
return sorted(indices, key=lambda i: indices[i])
def sample_with_minimum_distance(n=40, k=4, d=10):
"""
Sample of k elements from range(n), with a minimum distance d.
"""
sample = random.sample(range(n-(k-1)*(d-1)), k)
return [s + (d-1)*r for s, r in zip(sample, ranks(sample))]
以及一些示例输出:>>> sample_with_minimum_distance()
[17, 27, 3, 38]
>>> sample_with_minimum_distance()
[27, 38, 10, 0]
>>> sample_with_minimum_distance()
[36, 13, 1, 24]
>>> sample_with_minimum_distance()
[1, 25, 15, 39]
>>> sample_with_minimum_distance()
[26, 12, 1, 38]
“廉价伎俩”解决方案
如果原始问题中的各种常量是固定不变的(总体range(40),样本长度为4,最小距离为10),那么有一个明显的廉价窍门:只有715可能的不同排序样本,所以只要预先创建一个包含所有这些样本的列表,然后每次需要生成样本,使用random.choice从预先创建的列表中选择一个。在
对于这一代人来说,我们要么采用一种效率极低但显然正确的暴力解决方案:>>> import itertools
>>> all_samples = [ # inefficient brute-force solution
... sample for sample in itertools.product(range(40), repeat=4)
... if all(x - y >= 10 for x, y in zip(sample[1:], sample))
... ]
>>> len(all_samples)
715
这仍然足够快,在我的机器上只需要几秒钟。或者,我们可以使用上面提到的同一个双射来做一些更精细和直接的事情。在>>> all_samples = [
... [9*i + s for i, s in enumerate(sample)]
... for sample in itertools.combinations(range(13), 4)
... ]
>>> len(all_samples)
715
不管怎样,我们只生成一次样本列表,然后在每次需要时使用random.choice来选取一个:>>> random.choice(all_samples)
(1, 11, 21, 38)
>>> random.choice(all_samples)
(0, 10, 23, 33)
当然,这个解决方案不能很好地伸缩:对于最小距离为5的range(100)中的7个样本,可能有超过20亿个不同的排序样本。在
均匀性证明
我在前面说过,一个线性函数以相等的可能性产生所有的可能性(当然,假设一个完美的随机数来源,但是Python的Mersenne Twister足够好,我们不太可能在下面的测试中检测到由核心生成器产生的统计异常)。这就是这种一致性的证明。在
首先,为了方便起见,我们将把一行程序包装在函数中。我们还将其更改为返回tuple,而不是list,因为下一步我们需要一些散列的东西。在>>> def sorted_sample():
... return tuple(9*i + x for i, x in
... enumerate(sorted(random.sample(range(13), 4))))
现在,我们生成1000万个样本(这需要几分钟),并计算每个样本发生的频率:>>> from collections import Counter
>>> samples = Counter(sorted_sample() for _ in range(10**7))
一些快速检查:>>> len(samples)
715
>>> 10**7 / 715
13986.013986013986
>>> samples[0, 10, 20, 30]
14329
>>> samples[0, 11, 22, 33]
13995
>>> min(samples.values())
13624
>>> max(samples.values())
14329
我们收集了715个不同的组合,一点数学知识告诉我们,这正是我们期望的数字(13选4),因此在均匀分布的情况下,我们预计每个组合大约发生10**7 / 715次,或大约14000次。我们上面检查的两个组合都在14000左右,最小和最大计数也出现了,但并不奇怪,有一些随机变化。在
这种随机变化在可接受的范围内吗?{15}我们可以用cd15来做测试。我们的零假设是我们从中提取的总体是一致的:即我们的代码以相等的似然生成每个可能的样本。在>>> from scipy.stats import chisquare
>>> chisquare(list(samples.values()))
Power_divergenceResult(statistic=724.682234, pvalue=0.3825060783237031)
我们得到的p值小于0.01,因此我们无法拒绝无效假设:即,我们没有非均匀性的证据。在