(注:我误读了“行”而不是“列”,所以下面的代码和解释将处理第一行随机数 1-10、第二行随机数 11-20 等的矩阵,而不是列,但它只是转置完全相同)
此代码应保证唯一性和良好的随机性:
library(gtools)
# helper function
getKthPermWithRep <- function(k,n,r){
k <- k - 1
if(n^r< k){
stop('k is greater than possibile permutations')
}
v <- rep.int(0,r)
index <- length(v)
while ( k != 0 )
{
remainder<- k %% n
k <- k %/% n
v[index] <- remainder
index <- index - 1
}
return(v+1)
}
# get all possible permutations of 10 elements taken 3 at a time
# (singlerowperms = 720)
allperms <- permutations(10,3)
singlerowperms <- nrow(allperms)
# get 20 random and unique bingo cards
cards <- lapply(sample.int(singlerowperms^3,20),FUN=function(k){
perm2use <- getKthPermWithRep(k,singlerowperms,3)
m <- allperms[perm2use,]
m[2,] <- m[2,] + 10
m[3,] <- m[3,] + 20
return(m)
# if you want transpose the result just do:
# return(t(m))
})
说明
(免责声明 tl;dr)
为了保证随机性和唯一性,一种安全的方法是生成所有可能的宾果卡,然后在其中随机选择而不替换。
要生成所有可能的卡片,我们应该:
- 为每行 3 个元素生成所有可能性
- 得到它们的笛卡尔积
使用gtools包的函数permutations可以很容易地获得步骤(1)(参见代码中的对象allPerms)。请注意,我们只需要第一行的排列(即从 1-10 中取出的 3 个元素),因为可以通过分别添加 10 和 20 从第一行轻松获得其他行的排列。
步骤(2)在R中也很容易搞定,但我们先考虑一下会产生多少种可能性。步骤 (1) 每行返回 720 个案例,因此,最后我们将有 720*720*720 = 720^3 = 373248000 可能的宾果卡!
生成所有这些是不切实际的,因为占用的内存会很大,因此我们需要找到一种方法来在这个大范围的可能性中获取 20 个随机元素,而无需将它们实际保存在内存中。
解决方案来自函数getKthPermWithRep,给定索引k,它返回第k个排列,重复从1:n中提取的r元素(请注意,在这种情况下,重复排列对应于笛卡尔积)。
例如
# all permutations with repetition of 2 elements in 1:3 are
permutations(n = 3, r = 2,repeats.allowed = TRUE)
# [,1] [,2]
# [1,] 1 1
# [2,] 1 2
# [3,] 1 3
# [4,] 2 1
# [5,] 2 2
# [6,] 2 3
# [7,] 3 1
# [8,] 3 2
# [9,] 3 3
# using the getKthPermWithRep you can get directly the k-th permutation you want :
getKthPermWithRep(k=4,n=3,r=2)
# [1] 2 1
getKthPermWithRep(k=8,n=3,r=2)
# [1] 3 2
因此,现在我们只需在 1:720^3 范围内选择 20 个随机索引(使用 sample.int 函数),然后对于每个索引,我们使用函数 getKthPermWithRep 从 1:720 中获取 3 个数字的对应排列。
最后,这些数字三元组可以通过将它们用作子集allPerms 的索引转换为实际的卡片行,并得到我们的最终矩阵(当然,在第二行和第三行添加+10 和+20 之后)。
奖金
getKthPermWithRep 的解释
如果您查看上面的示例(以 1:3 重复 2 个元素的排列),然后将所有结果数减去 1,您会得到:
> permutations(n = 3, r = 2,repeats.allowed = T) - 1
[,1] [,2]
[1,] 0 0
[2,] 0 1
[3,] 0 2
[4,] 1 0
[5,] 1 1
[6,] 1 2
[7,] 2 0
[8,] 2 1
[9,] 2 2
如果您将每一行的每个数字视为一个数字,您会注意到这些行 (00, 01, 02...) 是从 0 到 8 的所有数字,以 3 为底表示(是的,3 为n)。因此,当您在 1:n 中通过重复 r 元素来询问第 k 个排列时,您还要求将 k-1 转换为基本 n 并返回增加了 1 的数字。
因此,给定从 10 到 n 的任意数字的算法:
changeBase <- function(num,base){
v <- NULL
while ( num != 0 )
{
remainder = num %% base # assume K > 1
num = num %/% base # integer division
v <- c(remainder,v)
}
if(is.null(v)){
return(0)
}
return(v)
}
您可以轻松获取getKthPermWithRep函数。