La question a de nombreuses interprétations valables. Les commentaires - en particulier celui indiquant que des permutations de 15 éléments ou plus sont nécessaires (15! = 1307674368000 grossit) - suggèrent que ce qu'il faut, c'est un échantillon aléatoire relativement petit , sans remplacement, de tous les n! = n * (n-1) (n-2) ... * 2 * 1 permutations de 1: n. Si cela est vrai, il existe des solutions (quelque peu) efficaces.
La fonction suivante,, rperm
accepte deux arguments n
(la taille des permutations à échantillonner) et m
(le nombre de permutations de taille n à dessiner). Si m s'approche ou dépasse n !, la fonction prendra du temps et renverra de nombreuses valeurs NA: elle est destinée à être utilisée lorsque n est relativement grand (disons, 8 ou plus) et m est beaucoup plus petit que n !. Il fonctionne en mettant en cache une représentation sous forme de chaîne des permutations trouvées jusqu'à présent, puis en générant de nouvelles permutations (au hasard) jusqu'à ce qu'une nouvelle soit trouvée. Il exploite la capacité d'indexation de liste associative de R pour rechercher rapidement la liste des permutations précédemment trouvées.
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.
La nature de replicate
est de renvoyer les permutations sous forme de vecteurs de colonne ; Par exemple , ce qui suit reproduit un exemple dans la question d'origine, transposé :
> 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
Les délais sont excellents pour les valeurs de m petites à modérées, jusqu'à environ 10 000, mais se dégradent pour les problèmes plus importants. Par exemple, un échantillon de m = 10 000 permutations de n = 1 000 éléments (une matrice de 10 millions de valeurs) a été obtenu en 10 secondes; un échantillon de m = 20 000 permutations de n = 20 éléments a nécessité 11 secondes, même si la sortie (une matrice de 400 000 entrées) était beaucoup plus petite; et le calcul de l'échantillon de m = 100 000 permutations de n = 20 éléments a été abandonné après 260 secondes (je n'ai pas eu la patience d'attendre la fin). Ce problème de mise à l'échelle semble être lié à des inefficacités de mise à l'échelle dans l'adressage associatif de R. On peut contourner ce problème en générant des échantillons en groupes de, disons, 1000 environ, puis en combinant ces échantillons en un grand échantillon et en supprimant les doublons.
Éditer
Nous pouvons obtenir des performances asymptotiques presque linéaires en divisant le cache en une hiérarchie de deux caches, de sorte que R n'a jamais à parcourir une grande liste. Conceptuellement (mais pas tel qu'implémenté), créez un tableau indexé par les premiers éléments d'une permutation. Les entrées de ce tableau sont des listes de toutes les permutations partageant ces premiers éléments. Pour vérifier si une permutation a été vue, utilisez ses premiers éléments pour trouver son entrée dans le cache, puis recherchez cette permutation dans cette entrée. Nous pouvons choisir pour équilibrer les tailles attendues de toutes les listes. L'implémentation réelle n'utilise pas dek k k kkkkkk-fold array, qui serait difficile à programmer en généralité suffisante, mais utilise à la place une autre liste.
Voici quelques temps écoulés en secondes pour une plage de tailles de permutation et de nombres de permutations distinctes demandées:
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
(L'accélération apparemment anormale de size = 10 à size = 15 est due au fait que le premier niveau du cache est plus grand pour size = 15, ce qui réduit le nombre moyen d'entrées dans les listes de second niveau, accélérant ainsi la recherche associative de R.. coût en RAM, l'exécution pourrait être accélérée en augmentant la taille du cache de niveau supérieur. Une simple augmentation k.head
de 1 (qui multiplie la taille de niveau supérieur par 10) passait rperm(100000, size=10)
de 11,77 secondes à 8,72 secondes, par exemple. Faire le niveau supérieur cache 10 fois plus grand mais sans gain appréciable, cadencé à 8,51 secondes.)
À l'exception du cas de 1 000 000 de permutations uniques de 10 éléments (une partie substantielle des 10! = Environ 3,63 millions de ces permutations), pratiquement aucune collision n'a été détectée. Dans ce cas exceptionnel, il y a eu 169 301 collisions, mais aucune défaillance complète (un million de permutations uniques ont en fait été obtenues).
Notez qu'avec de grandes tailles de permutation (supérieures à 20 environ), la chance d'obtenir deux permutations identiques, même dans un échantillon de 1 000 000 000, est extrêmement faible. Ainsi, cette solution est applicable principalement dans les situations où (a) un grand nombre de permutations uniques de (b) entre et environ éléments doivent être générés mais même ainsi, (c) sensiblement moins que tous lesdes permutations sont nécessaires.n = 15 n !n = 5n=15n!
Le code de travail suit.
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.