La pregunta tiene muchas interpretaciones válidas. Los comentarios, especialmente el que indica que se necesitan permutaciones de 15 o más elementos (15! = 1307674368000 se está haciendo grande), sugieren que lo que se desea es una muestra aleatoria relativamente pequeña , sin reemplazo, de todo n! = n * (n-1) (n-2) ... * 2 * 1 permutaciones de 1: n. Si esto es cierto, existen soluciones (algo) eficientes.
La siguiente función, rperm
acepta dos argumentos n
(el tamaño de las permutaciones para muestrear) y m
(el número de permutaciones de tamaño n para dibujar). Si m se aproxima o excede n !, la función tomará mucho tiempo y devolverá muchos valores de NA: está destinada a usarse cuando n es relativamente grande (digamos, 8 o más) ym es mucho más pequeño que n !. Funciona almacenando en caché una representación de cadena de las permutaciones encontradas hasta ahora y luego generando nuevas permutaciones (al azar) hasta que se encuentre una nueva. Explota la capacidad asociativa de indexación de listas de R para buscar rápidamente la lista de permutaciones encontradas previamente.
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 naturaleza de replicate
es devolver las permutaciones como vectores de columna ; por ejemplo , lo siguiente reproduce un ejemplo en la pregunta original, transpuesta :
> 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
Los tiempos son excelentes para valores pequeños a moderados de m, hasta aproximadamente 10,000, pero se degradan para problemas más grandes. Por ejemplo, se obtuvo una muestra de m = 10,000 permutaciones de n = 1000 elementos (una matriz de 10 millones de valores) en 10 segundos; una muestra de m = 20,000 permutaciones de n = 20 elementos requirió 11 segundos, a pesar de que la salida (una matriz de 400,000 entradas) fue mucho menor; y la muestra de cálculo de m = 100,000 permutaciones de n = 20 elementos fue abortada después de 260 segundos (no tuve la paciencia para esperar a que se completara). Este problema de escala parece estar relacionado con las ineficiencias de escala en el direccionamiento asociativo de R. Uno puede evitarlo generando muestras en grupos de, digamos, aproximadamente 1000, luego combinando esas muestras en una muestra grande y eliminando duplicados.
Editar
Podemos lograr un rendimiento asintótico casi lineal al dividir el caché en una jerarquía de dos cachés, de modo que R nunca tenga que buscar en una lista grande. Conceptualmente (aunque no según lo implementado), cree una matriz indexada por los primeros elementos de una permutación. Las entradas en esta matriz son listas de todas las permutaciones que comparten esos primeros elementos. Para verificar si se ha visto una permutación, use sus primeros elementos para encontrar su entrada en el caché y luego busque esa permutación dentro de esa entrada. Podemos elegir para equilibrar los tamaños esperados de todas las listas. La implementación real no usa unak k k kkkkkk-fold array, que sería difícil de programar con suficiente generalidad, pero en su lugar usa otra lista.
Estos son algunos tiempos transcurridos en segundos para un rango de tamaños de permutación y números de permutaciones distintas solicitadas:
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
(La aceleración aparentemente anómala de tamaño = 10 a tamaño = 15 se debe a que el primer nivel de la memoria caché es más grande para tamaño = 15, lo que reduce el número promedio de entradas en las listas de segundo nivel, lo que acelera la búsqueda asociativa de R. En algunos costo en RAM, la ejecución podría hacerse más rápido aumentando el tamaño de caché de nivel superior. Simplemente aumentando k.head
en 1 (que multiplica el tamaño de nivel superior por 10) aceleró rperm(100000, size=10)
de 11.77 segundos a 8.72 segundos, por ejemplo. caché 10 veces más grande pero no logró una ganancia apreciable, registrando a 8.51 segundos).
A excepción del caso de 1,000,000 de permutaciones únicas de 10 elementos (una porción sustancial de los 10! = Alrededor de 3.63 millones de permutaciones), prácticamente no se detectaron colisiones. En este caso excepcional, hubo 169.301 colisiones, pero no hubo fallas completas (de hecho, se obtuvo un millón de permutaciones únicas).
Tenga en cuenta que con tamaños de permutación grandes (más de 20 o menos), la posibilidad de obtener dos permutaciones idénticas incluso en una muestra tan grande como 1,000,000,000 es muy pequeña. Por lo tanto, esta solución es aplicable principalmente en situaciones donde (a) un gran número de permutaciones únicas de (B) entre y o así elementos son a generarse pero aún así, (c) sustancialmente menos de todos losSe necesitan permutaciones.n = 15 n !n=5n=15n!
El código de trabajo sigue.
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.