La domanda ha molte interpretazioni valide. I commenti - in particolare quello che indica le permutazioni di 15 o più elementi (15! = 1307674368000 sta diventando grande) - suggeriscono che ciò che si desidera è un campione casuale relativamente piccolo , senza sostituzione, di tutti n! = n * (n-1) (n-2) ... * 2 * 1 permutazioni di 1: n. Se questo è vero, esistono soluzioni (in qualche modo) efficienti.
La seguente funzione rperm
accetta due argomenti n
(la dimensione delle permutazioni da campionare) e m
(il numero di permutazioni della dimensione n da disegnare). Se m si avvicina o supera n !, la funzione richiederà molto tempo e restituirà molti valori NA: è destinata all'uso quando n è relativamente grande (diciamo 8 o più) e m è molto più piccolo di n !. Funziona memorizzando nella cache una rappresentazione in forma di stringa delle permutazioni trovate finora e quindi generando nuove permutazioni (in modo casuale) fino a quando non ne viene trovata una nuova. Sfrutta la capacità associativa di indicizzazione di elenchi di R per cercare rapidamente l'elenco di permutazioni trovate in precedenza.
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 natura di replicate
è di restituire le permutazioni come vettori di colonna ; ad esempio , quanto segue riproduce un esempio nella domanda originale, trasposta :
> 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
I tempi sono eccellenti per valori da piccoli a moderati di m, fino a circa 10.000, ma si degradano per problemi più grandi. Ad esempio, un campione di m = 10.000 permutazioni di n = 1000 elementi (una matrice di 10 milioni di valori) è stato ottenuto in 10 secondi; un campione di m = 20.000 permutazioni di n = 20 elementi ha richiesto 11 secondi, anche se l'output (una matrice di 400.000 voci) era molto più piccolo; e il campione di calcolo di m = 100.000 permutazioni di n = 20 elementi è stato interrotto dopo 260 secondi (non ho avuto la pazienza di attendere il completamento). Questo problema di ridimensionamento sembra essere correlato alle inefficienze di ridimensionamento nell'indirizzamento associativo di R. Si può aggirare il problema generando campioni in gruppi, per esempio, circa 1000, quindi combinando quei campioni in un campione di grandi dimensioni e rimuovendo i duplicati.
modificare
Siamo in grado di ottenere prestazioni asintotiche quasi lineari suddividendo la cache in una gerarchia di due cache, in modo che R non debba mai cercare in un ampio elenco. Concettualmente (sebbene non come implementato), creare un array indicizzato dai primi elementi di una permutazione. Le voci in questo array sono elenchi di tutte le permutazioni che condividono quei primi elementi. Per verificare se è stata vista una permutazione, usa i suoi primi elementi per trovare la sua voce nella cache e quindi cercare quella permutazione all'interno di quella voce. Possiamo scegliere per bilanciare le dimensioni previste di tutti gli elenchi. L'implementazione effettiva non utilizza unkkkkk-fold array, che sarebbe difficile da programmare in una generalità sufficiente, ma utilizza invece un altro elenco.
Ecco alcuni tempi trascorsi in secondi per un intervallo di dimensioni di permutazione e numeri di permutazioni distinte richieste:
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'accelerazione apparentemente anomala da size = 10 a size = 15 è perché il primo livello della cache è più grande per size = 15, riducendo il numero medio di voci negli elenchi di secondo livello, accelerando così la ricerca associativa di R. costo in RAM, l'esecuzione potrebbe essere resa più veloce aumentando la dimensione della cache di livello superiore. Aumentando solo k.head
di 1 (che moltiplica la dimensione di livello superiore per 10) rperm(100000, size=10)
, ad esempio, si è accelerato da 11,77 secondi a 8,72 secondi. cache 10 volte più grande ma non ottenuto alcun guadagno apprezzabile, con un clock di 8,51 secondi.)
Tranne il caso di 1.000.000 di permutazioni uniche di 10 elementi (una parte sostanziale di tutti i 10! = Circa 3,63 milioni di tali permutazioni), praticamente non sono mai state rilevate collisioni. In questo caso eccezionale, ci furono 169.301 collisioni, ma nessun completo fallimento (furono infatti ottenute un milione di permutazioni uniche).
Si noti che con grandi dimensioni di permutazione (maggiori di circa 20), la possibilità di ottenere due permutazioni identiche anche in un campione grande come 1.000.000.000 è evanescente. Pertanto, questa soluzione è applicabile principalmente in situazioni in cui (a) devono essere generati un gran numero di permutazioni uniche di (b) tra e ma anche così, (c) sostanzialmente meno di tutte lesono necessarie permutazioni.n=5n=15n!
Segue il codice di lavoro.
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.