質問には多くの有効な解釈があります。コメント(特に15以上の要素の順列が必要であるというコメントが必要です(15!= 1307674368000が大きくなっています))-必要なのは、すべてのnの置換なしの比較的小さなランダムサンプルであることを示唆しています!= n *(n-1)(n-2) ... * 2 * 1 1:nの順列。これが本当なら、(ある程度)効率的なソリューションが存在します。
次の関数はrperm
、2つの引数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順列(1,000万値の行列)のサンプルが10秒で取得されました。出力(400,000エントリの行列)ははるかに小さくても、n = 20要素のm = 20,000置換のサンプルには11秒必要でした。また、m = 100,000の順列の計算、n = 20の要素の計算は、260秒後に中止されました(完了を待つ忍耐力がありませんでした)。このスケーリングの問題は、Rの連想アドレッシングにおけるスケーリングの非効率性に関連しているようです。たとえば、1000程度のグループでサンプルを生成し、それらのサンプルを結合して大きなサンプルを作成し、重複を削除することで、この問題を回避できます。
編集する
Rが大きなリストを検索する必要がないように、キャッシュを2つのキャッシュの階層に分割することで、ほぼ線形の漸近的なパフォーマンスを実現できます。概念的には(実装されているわけではありませんが)、順列の最初の要素によってインデックスが付けられた配列を作成します。この配列のエントリは、最初の要素を共有するすべての順列のリストです。順列が表示されたかどうかを確認するには、最初の要素を使用してキャッシュ内のエントリを見つけ、そのエントリ内でその順列を検索します。すべてのリストの予想されるサイズのバランスを取るためにを選択できます。実際の実装では使用しませんk k k kkkkkk-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
(size = 10からsize = 15への明らかに異常なスピードアップは、キャッシュの最初のレベルがsize = 15の方が大きく、第2レベルのリストのエントリの平均数を減らし、それによってRの連想検索を高速化するためです。 RAMのコストを節約するには、上位レベルのキャッシュサイズを増やすことで実行を高速化できます。たとえば、k.head
1を増やすだけで(上位レベルのサイズに10を掛ける)rperm(100000, size=10)
、11.77秒から8.72秒にスピードアップします。キャッシュは10倍大きくなりますが、8.51秒で計時するほどの向上はありません。
10個の要素の1,000,000個の一意の置換(10個すべてのかなりの部分!=約363万個のそのような置換)の場合を除いて、実際には衝突は検出されませんでした。この例外的なケースでは、169,301回の衝突がありましたが、完全な失敗はありませんでした(実際に100万の一意の順列が取得されました)。
順列サイズが大きい(20を超えるなど)場合、1,000,000,000ものサンプルでも2つの同じ順列を取得する可能性は非常に小さいことに注意してください。したがって、このソリューションは主に(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.