이 질문에는 많은 유효한 해석이 있습니다. 주석-특히 15 개 이상의 요소의 순열을 나타내는 주석이 필요합니다 (15! = 1307674368000이 커짐)-원하는 것은 대체하지 않고 모든 n 의 비교적 작은 무작위 샘플 이라는 것을 제안합니다 ! = n * (n-1) (n-2) ... * 2 * 1 1 : n의 순열입니다. 이것이 사실이라면, 효율적인 솔루션이 존재합니다.
다음 함수 rperm
는 두 개의 인수 n
(샘플링 할 순열의 크기)와 m
(그리기 위해 크기 n의 순열 수)를 허용합니다. m이 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까지 우수하지만 더 큰 문제에서는 저하됩니다. 예를 들어, n = 1000 요소의 m = 10,000 순열의 샘플 (천만 값의 행렬)이 10 초 내에 얻어졌다. 출력 (400,000 개 항목의 행렬)이 훨씬 작더라도 m = 20,000 개의 순열 n = 20 개 요소의 샘플은 11 초가 필요했습니다. 260 초 후에 m = 100,000 순열의 n = 20 요소의 계산 샘플이 중단되었습니다 (완료를 기다리는 인내심이 없었습니다). 이 스케일링 문제는 R의 연관 어드레싱에서의 스케일링 비효율 성과 관련이있는 것으로 보인다. 1000 개 정도의 그룹으로 샘플을 생성 한 다음이 샘플을 큰 샘플로 결합하고 복제본을 제거하여이 문제를 해결할 수 있습니다.
편집하다
캐시를 두 개의 캐시 계층으로 나누면 거의 선형적인 점근 적 성능 을 달성 할 수 있으므로 R은 큰 목록을 검색 할 필요가 없습니다. 개념적으로 (구현되지는 않았지만) 순열 의 첫 번째 요소에 의해 인덱스 된 배열을 만듭니다 . 이 배열의 항목은 첫 요소를 공유하는 모든 순열의 목록입니다 . 순열이 있는지 확인하려면 첫 번째 요소를 사용 하여 캐시에서 해당 항목을 찾은 다음 해당 항목에서 해당 순열을 검색하십시오. 를 선택 하여 모든 목록의 예상 크기의 균형을 맞출 수 있습니다 . 실제 구현은 사용하지 않습니다k k k k케이케이케이케이케이-fold 배열은 충분한 일반성으로 프로그램하기는 어렵지만 대신 다른 목록을 사용합니다.
다음은 다양한 순열 크기 및 요청 된 고유 순열 수에 대한 몇 초의 경과 시간입니다.
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 로의 비정상적인 속도 향상은 size = 15에 대해 캐시의 첫 번째 수준이 더 커서 두 번째 수준 목록의 평균 항목 수를 줄여서 R의 연관 검색 속도를 높이기 때문입니다. RAM 비용이 증가하면 상위 캐시 크기를 늘림으로써 실행 속도를 높일 수 있습니다. 예 k.head
를 들어, 1을 늘리면 (예 : 상위 크기에 10을 곱한) rperm(100000, size=10)
11.77 초에서 8.72 초로 증가했습니다. 캐시는 10 배 더 크지 만 8.51 초의 클럭으로 눈에 띄는 이득을 얻지 못했습니다.)
10 개 요소의 1,000,000 개의 고유 순열 (10!의 실질적인 부분 = 약 3,630 만 순열)을 제외하고는 실제로 충돌이 감지되지 않았습니다. 이 예외적 인 경우에는 169,301 건의 충돌이 있었지만 완전한 실패는 없었습니다 (실제로 백만개의 순열이 얻어졌습니다).
큰 순열 크기 (20 이상)에서는 1,000,000,000의 표본에서도 두 개의 동일한 순열을 얻을 가능성이 거의 없습니다. 따라서이 솔루션은 주로 (a) ( ) 에서 사이의 많은 고유 순열이 발생하는 상황에서 적용 할 수 있지만 (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.