编辑:
作为 cmets 中的Paul Hankin,我最初的测试只检查每个元素落入每个位置的概率,而不是所有排列的可能性相同,这是一个更强的要求。下面的 sn-p 计算了每个排列的频率,这是我们应该关注的:
import math
import random
def knuth_shuffle(ar):
num = len(ar)
for i in range(num):
index = random.randint(0, i)
ar[i], ar[index] = ar[index], ar[i]
return ar
# This function computes a unique index for a given permutation
# Adapted from https://www.jaapsch.net/puzzles/compindx.htm#perm
def permutation_index(permutation):
n = len(permutation)
t = 0
for i in range(n):
t = t * (n - i)
for j in range(i + 1, n):
if permutation[i] > permutation[j]:
t += 1
return t
N = 6 # Test list size
T = 1000 # Trials / number of permutations
random.seed(100)
n_perm = math.factorial(N)
trials = T * n_perm
ar = list(range(N))
freq = [0] * n_perm
for _ in range(trials):
ar_shuffle = ar.copy()
knuth_shuffle(ar_shuffle)
freq[permutation_index(ar_shuffle)] += 1
如果 shuffle 是统一的,则生成的 freq 向量的值应根据 T * N! 试验和成功概率 1 / (N!) 的二项分布进行分布。这是上一个示例的分布估计图(使用Seaborn 完成),其中频率值应该在 1000 左右:
我认为这看起来不错,但同样,对于定量结果,您需要更深入的统计分析,例如 Pearson's chi-squared test,正如 David Eisenstat 所建议的那样。
原始答案:
我将在这里提出一些基本的想法,但我没有最强大的统计背景,所以有人可能想补充或纠正任何错误的地方。
您可以制作一个包含每个值的频率矩阵,以进行多次试验:
def knuth_shuffle(ar):
num = len(ar)
for i in range(num):
index = random.randint(0, i)
ar[i], ar[index] = ar[index], ar[i]
return ar
N = 100 # Test list size
T = 10000 # Number of trials
ar = list(range(N))
freq = [[0] * N for _ in range(N)]
for _ in range(T):
ar_shuffle = ar.copy()
kunth_shuffle(ar_shuffle)
for i, j in enumerate(ar_shuffle):
freq[i][j] += 1
一旦您可以做到这一点,您可以采取多种方法。一个简单的想法是,如果洗牌是统一的,freq / T 应该趋向于1 / N,因为T 趋向于无穷大。因此,您可以只使用“非常大”的值 T 并看到这些值“足够接近”。或者检查freq / T - 1 / N的标准差是否“足够小”。
这些“足够接近”和“足够小”虽然不是很可靠的概念。扎根分析需要更多的统计工具。我认为您需要test the hipothesis,每个频率值都是从binomial distribution 中采样的,T 试验1 / N 成功概率。正如我所说,没有完整解释的背景,这可能不是它的地方,但如果你真的需要一个彻底的分析,你可以阅读这个主题。