Як перепрограмувати R без повторень перестановок?


12

У R, якщо я set.seed (), а потім використовую функцію вибірки для рандомізації списку, чи можу я гарантувати, що я не буду генерувати ту саму перестановку?

тобто ...

set.seed(25)
limit <- 3
myindex <- seq(0,limit)
for (x in seq(1,factorial(limit))) {
    permutations <- sample(myindex)
    print(permutations)
}

Це виробляє

[1] 1 2 0 3
[1] 0 2 1 3
[1] 0 3 2 1
[1] 3 1 2 0
[1] 2 3 0 1
[1] 0 1 3 2

чи всі надруковані перестановки будуть унікальними перестановками? Або є якийсь шанс, виходячи з способу здійснення цього, я можу отримати кілька повторів?

Я хочу, щоб це можна було зробити без повторів, гарантовано. Як би я це зробив?

(Я також хочу уникати використання такої функції, як permn (), яка має дуже механістичний метод для генерації всіх перестановок --- це не виглядає випадково.)

Крім того, sidenote --- здається, що ця проблема є O ((n!)!), Якщо я не помиляюся.


За замовчуванням аргумент "замінити" на "зразок" встановлений на FALSE.
окрам

Спасибі окрам, але це працює в рамках певного зразка. Таким чином, це гарантує, що 0,1,2 та 3 не повторяться протягом жеребкування (значить, я не можу зробити 0,1,2,2), але я не знаю, чи гарантує це другий зразок, Я не можу знову зробити ту саму послідовність 0123. Це те, що мені цікаво втілення в життя, чи впливає встановлення насіння на це повторення.
Mittenchops

Так, це я нарешті зрозумів, читаючи відповіді ;-)
ocram

1
Якщо вона limitперевищує 12, ви, ймовірно, втрачаєте оперативну пам'ять, коли R намагатиметься виділити місце для seq(1,factorial(limit)). (На 12! Потрібно близько 2 ГБ, тож на 13!
Знадобиться

2
Існує швидке, компактне та елегантне рішення для генерації випадкових послідовностей усіх перестановок 1: n за умови, що ви можете зручно зберігати n! цілих чисел у діапазоні 0: (n!). Він поєднує в собі таблицю інверсії перестановки з факторним базовим поданням чисел.
whuber

Відповіді:


9

Питання має багато вагомих тлумачень. Зауваження - особливо той, що вказує на перестановки з 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 kkkkkk-складний масив, який важко запрограмувати в достатній загальності, але натомість використовує інший список.

Ось кілька минулих разів у секундах для діапазону розмірів перестановки та кількості різних запитуваних запитів:

 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=15n!

Наступний робочий код.

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.

Це близько, але я помічаю, що я отримую деякі помилки, наприклад, 1, 2 і 4, але я думаю, що я бачу, що ви маєте на увазі, і повинні мати можливість працювати з цим. Дякую! > rperm(6,3) $failures [1] 9 $sample [,1] [,2] [,3] [1,] 3 1 3 [2,] 2 2 1 [3,] 1 3 2 [4,] 1 2 2 [5,] 3 3 1 [6,] 2 1 3
Mittenchops

4

Використовуючи uniqueправильний спосіб, слід зробити трюк:

set.seed(2)
limit <- 3
myindex <- seq(0,limit)

endDim<-factorial(limit)
permutations<-sample(myindex)

while(is.null(dim(unique(permutations))) || dim(unique(permutations))[1]!=endDim) {
    permutations <- rbind(permutations,sample(myindex))
}
# Resulting permutations:
unique(permutations)

# Compare to
set.seed(2)
permutations<-sample(myindex)
for(i in 1:endDim)
{
permutations<-rbind(permutations,sample(myindex))
}
permutations
# which contains the same permutation twice

Вибачте за неправильне пояснення коду. Зараз я трохи поспішаю, але я рада відповісти на будь-які запитання, які ви отримаєте пізніше. Крім того, я не маю поняття про швидкість вищевказаного коду ...
MånsT

1
Я функціоналізував те, що ви мені дали так: `myperm <- функція (ліміт) {myindex <- seq (0, ліміт) endDim <-факторний (ліміт) перестановки <-sample (myindex) while (is.null (dim (унікальний) (перестановки))) || dim (унікальний (перестановки)) [1]! = endDim) {перестановки <- rbind (перестановки, зразок (myindex))} return (унікальний (перестановки))} 'Це працює, але поки я можна зробити ліміт = 6, ліміт = 7 змушує мій комп'ютер перегріватися. = Я думаю, що все-таки має бути спосіб
підгрупувати

@Mittenchops, чому ти кажеш, що нам потрібно використовувати унікальний для перекомпонування в R, не повторюючи перестановки? Дякую.
Френк

2

Я трохи побіжно зупинюсь на вашому першому запитанні, і я припускаю, що якщо ви маєте справу з відносно короткими векторами, ви можете просто генерувати всі перестановки, використовуючи permnїх, і довільно замовляти такі, що використовують sample:

x <- combinat:::permn(1:3)
> x[sample(factorial(3),factorial(3),replace = FALSE)]
[[1]]
[1] 1 2 3

[[2]]
[1] 3 2 1

[[3]]
[1] 3 1 2

[[4]]
[1] 2 1 3

[[5]]
[1] 2 3 1

[[6]]
[1] 1 3 2

Мені це подобається ЛІТ, і я впевнений, що це правильне мислення. Але моя проблема змушує мене використовувати послідовність до 10. Пермн () був значно повільнішим між факторіальним (7) і факторіальним (8), тому я думаю, що 9 і 10 будуть надзвичайно величезними.
Mittenchops

@Mittenchops Щоправда, але все ж можливо, що вам насправді потрібно обчислити їх лише один раз, правда? Збережіть їх у файл, а потім завантажте їх, коли вам знадобляться, та "зразок" із попередньо визначеного списку. Таким чином, ви можете зробити повільний розрахунок permn(10)або що-небудь просто один раз.
Жоран

Правильно, але якщо я зберігаю всі перестановки десь, навіть це руйнується навколо факторіалу (15) --- просто занадто багато місця для зберігання. Ось чому мені цікаво, чи встановлення насіння дозволить мені колективно відбирати перестановки --- а якщо ні, чи існує алгоритм для цього.
Mittenchops

@Mittenchops Налаштування насіння не вплине на продуктивність, воно просто гарантує однаковий початок кожного разу, коли ви телефонуєте в PRNG.
Роман Луштрик

1
@Mitten Дивіться довідку щодо set.seed: в ньому описано, як зберегти стан RNG та відновити його пізніше.
whuber
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.