Питання має багато вагомих тлумачень. Зауваження - особливо той, що вказує на перестановки з 15 і більше елементів (15! = 1307674368000 стає все більшим) - дозволяє припустити, що те, що потрібно, є порівняно невеликим випадковим зразком, без заміни, усіх п! = n * (n-1) (n-2) ... * 2 * 1 перестановки 1: n. Якщо це правда, існують (дещо) ефективні рішення.
Наступна функція, rperm
приймає два аргументи n
(розмір перестановок для вибірки) та m
(кількість перестановок розміром n для малювання). Якщо m наближається до n або перевищує 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 секунд була отримана вибірка m = 10000 перестановок n = 1000 елементів (матриця значень 10 мільйонів); зразок m = 20 000 перестановок з n = 20 елементів вимагав 11 секунд, навіть якщо вихід (матриця з 400 000 записів) був значно меншим; і зразок обчислень m = 100 000 перестановок з n = 20 елементів був перерваний через 260 секунд (я не мав терпіння чекати завершення). Ця проблема масштабування, як видається, пов'язана зі зменшенням неефективності асоціативної адреси R. Можна обійти його, генеруючи зразки в групах, скажімо, 1000 або близько того, потім комбінуючи ці зразки у великий зразок та видаляючи дублікати.
Редагувати
Ми можемо досягти майже лінійної асимптотичної продуктивності , розбивши кеш на ієрархію двох кешів, так що R ніколи не доводиться шукати великий список. Концептуально (хоча і не реалізовано) створіть масив, індексований першими елементами перестановки. Записи в цьому масиві - це списки всіх перестановок, які спільно використовують ці перші елементи. Щоб перевірити, чи переглянуто перестановку, використовуйте її перші елементи, щоб знайти її запис у кеші, а потім шукайте цю перестановку в межах цього запису. Ми можемо вибрати щоб збалансувати очікувані розміри всіх списків. Фактична реалізація не використовуєk k k kккккк-складний масив, який важко запрограмувати в достатній загальності, але натомість використовує інший список.
Ось кілька минулих разів у секундах для діапазону розмірів перестановки та кількості різних запитуваних запитів:
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
(Очевидно аномальне прискорення з розміру = 10 до розміру = 15 відбувається тому, що кеш-пам'ять першого рівня більша за розмір = 15, зменшуючи середню кількість записів у списках другого рівня, тим самим прискорюючи асоціативний пошук R. У деяких вартість оперативної пам’яті, виконання може бути зроблено швидше, збільшивши розмір кешу верхнього рівня. Просто збільшення k.head
на 1 (що помножує розмір верхнього рівня на 10) збільшується, наприклад, rperm(100000, size=10)
від 11,77 секунд до 8,72 секунди. Зробити верхній рівень кеш в 10 разів більший, але не досягнуто помітного посилення, тактирується за 8,51 секунди.)
За винятком випадків 1 000 000 унікальних перестановок з 10 елементів (значна частина всіх 10! = Приблизно 3,63 мільйона таких перестановок), практично жодного зіткнення не було виявлено. У цьому винятковому випадку було зіткнення 169 301, але повних збоїв не було (насправді отримано мільйон унікальних перестановок).
Зауважте, що при великих розмірах перестановки (більше 20 або більше) шанс отримати дві однакові перестановки навіть у вибірці великою, ніж 1 000 000 000, зникає мало. Таким чином, це рішення застосовне насамперед у ситуаціях, коли (a) створюється велика кількість унікальних перестановок (b) між і або близько таких елементів, але навіть так, (c) істотно менше, ніж усінеобхідні перестановки.n = 15 n !n = 5n = 15н !
Наступний робочий код.
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.