ベクターに一連のオブジェクトがあり、そこからランダムなサブセットを選択します(たとえば、100個のアイテムが戻ってきます。ランダムに5個を選択します)。私の最初の(非常に急いで)パスで、私は非常にシンプルで、おそらく非常に賢い解決策を行いました:
Vector itemsVector = getItems();
Collections.shuffle(itemsVector);
itemsVector.setSize(5);
これには、ニースでシンプルであるという利点がありますが、あまりうまくスケーリングできないと思います。つまり、Collections.shuffle()は少なくともO(n)でなければなりません。
Vector itemsVector = getItems();
Random Rand = new Random(System.currentTimeMillis()); // would make this static to the class
List subsetList = new ArrayList(5);
for (int i = 0; i < 5; i++) {
// be sure to use Vector.remove() or you may get the same item twice
subsetList.add(itemsVector.remove(Rand.nextInt(itemsVector.size())));
}
コレクションからランダムなサブセットを引き出すより良い方法に関する提案はありますか?
Jon Bentleyは、「Programming Pearls」または「More Programming Pearls」でこれについて説明しています。 N of Mの選択プロセスに注意する必要がありますが、表示されているコードは正しく機能していると思います。すべてのアイテムをランダムにシャッフルするのではなく、最初のNポジションだけシャッフルするランダムシャッフルを実行できます。これは、N << Mの場合に便利な節約になります。
Knuthはこれらのアルゴリズムについても説明します。これは第3巻の「並べ替えと検索」だと思いますが、私のセットは家の移動が保留されているため、正式に確認することはできません。
@ジョナサン、
私はこれがあなたが話している解決策であると信じています:
void genknuth(int m, int n)
{ for (int i = 0; i < n; i++)
/* select m of remaining n-i */
if ((bigrand() % (n-i)) < m) {
cout << i << "\n";
m--;
}
}
Jon BentleyによるProgramming Pearlsの127ページにあり、Knuthの実装に基づいています。
編集:129ページでさらに修正を見ました:
void genshuf(int m, int n)
{ int i,j;
int *x = new int[n];
for (i = 0; i < n; i++)
x[i] = i;
for (i = 0; i < m; i++) {
j = randint(i, n-1);
int t = x[i]; x[i] = x[j]; x[j] = t;
}
sort(x, x+m);
for (i = 0; i< m; i++)
cout << x[i] << "\n";
}
これは、「...最初のm配列の要素のみをシャッフルする必要がある...」という考えに基づいています。
これの効率的な実装 数週間前に書きました。 C#にありますが、Javaへの翻訳は簡単です(基本的に同じコード)。プラス面は、それも完全に公平です(既存の回答のいくつかはそうではありません)- テストする方法はここにあります 。
Fisher-YatesシャッフルのDurstenfeld実装に基づいています。
Nのリストからk個の異なる要素を選択しようとする場合、上記のメソッドはO(n)またはO(kn)になります。ベクターから要素を削除すると、 arraycopyを実行して、すべての要素を下にシフトします。
最良の方法を求めているので、入力リストで何を許可するかによって異なります。
例のように入力リストを変更することが許容される場合、k個のランダムな要素をリストの先頭に単純に交換し、次のようにO(k) timeでそれらを返すことができます。
public static <T> List<T> getRandomSubList(List<T> input, int subsetSize)
{
Random r = new Random();
int inputSize = input.size();
for (int i = 0; i < subsetSize; i++)
{
int indexToSwap = i + r.nextInt(inputSize - i);
T temp = input.get(i);
input.set(i, input.get(indexToSwap));
input.set(indexToSwap, temp);
}
return input.subList(0, subsetSize);
}
リストを開始時と同じ状態にする必要がある場合は、交換した位置を追跡し、選択したサブリストをコピーした後、リストを元の状態に戻すことができます。これは、まだO(k)ソリューションです。
ただし、入力リストをまったく変更できず、kがnよりはるかに小さい場合(100から5など)、毎回選択した要素を削除せずに、単に各要素を選択することをお勧めします。複製、それを投げ出し、再選択します。これにより、nがkを支配している場合、O(k)に近いO(kn /(nk)))が得られます(たとえば、kがn/2より小さい場合、 O(k)になります)。
Kがnに支配されておらず、リストを変更できない場合、元のリストをコピーして最初の解決策を使用することもできます。O(n)はOと同じくらい良いからです(k)。
他の人が指摘したように、すべてのサブリストが可能な(そして偏りのない)強いランダム性に依存している場合、Java.util.Random
。見る Java.security.SecureRandom
。
ただし、Randomを使用して要素を選択する2番目のソリューションは適切に思えます。
データの機密性に応じて、何らかのハッシュ法を使用して乱数シードをスクランブルすることをお勧めします。良いケーススタディについては、 オンラインポーカーでのチートの学習方法 を参照してください(ただし、このリンクは2015年12月18日の時点で404です)。別のURL(二重引用符で囲まれた記事タイトルのGoogle検索で検出)には次のものが含まれます。
ベクトルは同期されます。可能であれば、代わりにArrayListを使用してパフォーマンスを改善してください。
これ は、stackoverflowに関する非常によく似た質問です。
そのページからの私のお気に入りの回答を要約するには(ユーザーカイルからの最後の1つ):
ここにいくつかの擬似Pythonがあります-
# Returns a container s with k distinct random numbers from {0, 1, ..., n-1}
def ChooseRandomSubset(n, k):
for i in range(k):
r = UniformRandom(0, n-i) # May be 0, must be < n-i
q = s.FirstIndexSuchThat( s[q] - q > r ) # This is the search.
s.InsertInOrder(q ? r + q : r + len(s)) # Inserts right before q.
return s
時間の複雑さはO(k2)orO(k log k)sのコンテナに検索して挿入できる速さに依存するため。 sが通常のリストの場合、これらの演算の1つは線形であり、k ^ 2を取得します。ただし、バランスの取れたバイナリツリーとしてsを作成する場合は、O(k log k)時間を取得できます。
Set<Integer> s = new HashSet<Integer>()
// add random indexes to s
while(s.size() < 5)
{
s.add(Rand.nextInt(itemsVector.size()))
}
// iterate over s and put the items in the list
for(Integer i : s)
{
out.add(itemsVector.get(i));
}
費用はどれくらいかかりますか?配列を新しいメモリチャンクに書き換える必要がある場合は、以前のO(5n)ではなく、2番目のバージョンでO(n)操作を行ったためです。
Falseに設定されたブール値の配列を作成すると、次のようになります。
for (int i = 0; i < 5; i++){
int r = Rand.nextInt(itemsVector.size());
while (boolArray[r]){
r = Rand.nextInt(itemsVector.size());
}
subsetList.add(itemsVector[r]);
boolArray[r] = true;
}
このアプローチは、サブセットが合計サイズよりも大幅に小さい場合に機能します。これらのサイズが互いに近づくと(つまり、サイズの1/4など)、その乱数ジェネレーターでさらに衝突が発生します。その場合、整数のリストをより大きな配列のサイズにし、その整数のリストをシャッフルし、そこから最初の要素を取り出して、(衝突しない)インデックスを取得します。そうすれば、整数配列の作成にO(n)、シャッフルに別のO(n)のコストがかかりますが、内部whileチェッカーからの衝突はなく、潜在的なO(5n)削除するとコストがかかる場合があります。
私はあなたの初期の実装を個人的に選択します:非常に簡潔です。パフォーマンステストでは、拡張性が示されます。適切に悪用された方法で非常によく似たコードブロックを実装しましたが、十分に拡張されました。特定のコードは、10,000個以上のアイテムを含む配列にも依存していました。
ここに表示されるとは思わない2つの解決策-対応するものは非常に長く、いくつかのリンクが含まれていますが、すべての投稿がN個の要素の集合からK要素を選択する問題に関連するとは思わない。 [「セット」とは、数学用語を指します。つまり、すべての要素が一度出現します。順序は重要ではありません]。
ソル1:
//Assume the set is given as an array:
Object[] set ....;
for(int i=0;i<K; i++){
randomNumber = random() % N;
print set[randomNumber];
//swap the chosen element with the last place
temp = set[randomName];
set[randomName] = set[N-1];
set[N-1] = temp;
//decrease N
N--;
}
これはダニエルが与えた答えに似ていますが、実際には非常に異なっています。 O(k)ランタイムです。
別の解決策は、いくつかの数学を使用することです:配列インデックスをZ_nとして、ランダムに2つの数を選択できるようにします。 「開始点」-その後、シリーズ:a%n、a + x%n、a + 2 * x%n、... a +(k-1)* x%nは、一連の個別の数字です(ただし、 k <= n)。