这个问题有许多有效的解释。注释(尤其是需要15个或更多元素的排列的注释(15!= 1307674368000越来越大))建议,建议所需要的是相对较小的随机样本,而不替换所有n!个样本。= n *(n-1)(n-2) ... * 2 * 1 1:n的排列。如果是这样,则存在(某种程度上)有效的解决方案。
以下函数rperm
接受两个参数n
(要采样m
的排列的大小)和(要绘制的大小为n的排列的数目)。如果m接近或超过n !,则该函数将花费很长时间并返回许多NA值:该函数适用于n相对较大(例如8或更大)且m比n!小得多的情况。它的工作方式是缓存到目前为止找到的排列的字符串表示形式,然后(随机)生成新的排列,直到找到一个新的排列。它利用R的关联列表索引功能来快速搜索先前找到的排列列表。
rperm <- function(m, size=2) { # Obtain m unique permutations of 1:size
# Function to obtain a new permutation.
newperm <- function() {
count <- 0 # Protects against infinite loops
repeat {
# Generate a permutation and check against previous ones.
p <- sample(1:size)
hash.p <- paste(p, collapse="")
if (is.null(cache[[hash.p]])) break
# Prepare to try again.
count <- count+1
if (count > 1000) { # 1000 is arbitrary; adjust to taste
p <- NA # NA indicates a new permutation wasn't found
hash.p <- ""
break
}
}
cache[[hash.p]] <<- TRUE # Update the list of permutations found
p # Return this (new) permutation
}
# Obtain m unique permutations.
cache <- list()
replicate(m, newperm())
} # Returns a `size` by `m` matrix; each column is a permutation of 1:size.
本质上replicate
是将排列返回为列向量;例如,以下是原始问题转置后的示例:
> set.seed(17)
> rperm(6, size=4)
[,1] [,2] [,3] [,4] [,5] [,6]
[1,] 1 2 4 4 3 4
[2,] 3 4 1 3 1 2
[3,] 4 1 3 2 2 3
[4,] 2 3 2 1 4 1
时序对于m的小到中等值是极好的,最高可达10,000,但是对于较大的问题会降低时序。例如,在10秒内获得了n = 1000个元素的m = 10,000个排列的样本(一千万个值的矩阵);即使输出(400,000个条目的矩阵)小得多,一个m = 20,000个n = 20个元素的排列的样本也需要11秒;260秒后,中止了m = 100,000个n = 20个元素的排列的计算样本(我没有耐心等待完成)。此缩放问题似乎与R的关联寻址的缩放效率低下有关。一个人可以解决这个问题,方法是生成大约1000个左右的样本组,然后将这些样本合并成一个大样本并删除重复项。
编辑
通过将缓存分成两个缓存的层次结构,我们可以实现接近线性渐近的性能,因此R不必搜索大列表。从概念上(尽管未实现),创建一个由排列的前元素索引的数组。该数组中的条目是共享那些共享前元素的所有排列的列表。要检查是否已看到排列,请使用其前元素在缓存中找到其条目,然后在该条目中搜索该排列。我们可以选择来平衡所有列表的预期大小。实际的实现不使用ķ ķ ķ ķkkkkk-fold数组,很难以足够的通用性进行编程,而是使用另一个列表。
这是一系列排列大小和请求的不同排列数的经过时间(以秒为单位):
Number Size=10 Size=15 Size=1000 size=10000 size=100000
10 0.00 0.00 0.02 0.08 1.03
100 0.01 0.01 0.07 0.64 8.36
1000 0.08 0.09 0.68 6.38
10000 0.83 0.87 7.04 65.74
100000 11.77 10.51 69.33
1000000 195.5 125.5
(显然,从size = 10到size = 15的异常加速是因为对于size = 15,缓存的第一级较大,从而减少了第二级列表中条目的平均数量,从而加快了R的关联搜索。在某些情况下例如,由于增加了RAM的开销,可以通过增加上层缓存的大小来加快执行速度,例如,仅增加k.head
1(将上层大小乘以10)就可以rperm(100000, size=10)
从11.77秒加快到8.72秒。快10倍,却没有获得可观的增益,时钟为8.51秒。)
除了10个元素的1,000,000个唯一排列的情况(所有10!的很大一部分=约363万个此类排列)之外,几乎没有检测到碰撞。在这种例外情况下,发生了169,301次碰撞,但没有完全失败(实际上获得了一百万个唯一排列)。
注意,对于较大的排列大小(大于20左右),即使在高达1,000,000,000的样本中获得两个相同排列的机会也很小。因此,该解决方案主要适用于以下情况:(a)将会生成(b)在和之间的大量唯一置换,因此,即使如此,(c)基本上少于所有元素需要排列。n = 15 n !n=5n=15n!
工作代码如下。
rperm <- function(m, size=2) { # Obtain m unique permutations of 1:size
max.failures <- 10
# Function to index into the upper-level cache.
prefix <- function(p, k) { # p is a permutation, k is the prefix size
sum((p[1:k] - 1) * (size ^ ((1:k)-1))) + 1
} # Returns a value from 1 through size^k
# Function to obtain a new permutation.
newperm <- function() {
# References cache, k.head, and failures in parent context.
# Modifies cache and failures.
count <- 0 # Protects against infinite loops
repeat {
# Generate a permutation and check against previous ones.
p <- sample(1:size)
k <- prefix(p, k.head)
ip <- cache[[k]]
hash.p <- paste(tail(p,-k.head), collapse="")
if (is.null(ip[[hash.p]])) break
# Prepare to try again.
n.failures <<- n.failures + 1
count <- count+1
if (count > max.failures) {
p <- NA # NA indicates a new permutation wasn't found
hash.p <- ""
break
}
}
if (count <= max.failures) {
ip[[hash.p]] <- TRUE # Update the list of permutations found
cache[[k]] <<- ip
}
p # Return this (new) permutation
}
# Initialize the cache.
k.head <- min(size-1, max(1, floor(log(m / log(m)) / log(size))))
cache <- as.list(1:(size^k.head))
for (i in 1:(size^k.head)) cache[[i]] <- list()
# Count failures (for benchmarking and error checking).
n.failures <- 0
# Obtain (up to) m unique permutations.
s <- replicate(m, newperm())
s[is.na(s)] <- NULL
list(failures=n.failures, sample=matrix(unlist(s), ncol=size))
} # Returns an m by size matrix; each row is a permutation of 1:size.