2016-04-01 12 views
3

IntStreamとlambdasを使って素早く(1行で)既存の要素配列のランダムな部分集合を含む配列を作成できるかどうかは疑問です。例えばIntreamを使用して配列のランダムなサブセットを作成する簡単な方法

は、私は選手たちのプール持っていると言う:

Player[] allPlayers; 

を私は必要なサブセットの寸法を与えられ、それらのプレーヤーのランダムなサブセットを取得したいです。伝統的に私は次のようなことをします:

List<Player> players = new ArrayList<Player>(Arrays.asList(allPlayers)); 

int subsetSize = 8; 
Player[] subset = new Player[subsetSize]; 
for (int i = 0; i < subsetSize; i++) { 
    int randIndex = new Random().nextInt(players.size()); 
    subset[i] = players[randIndex]; 

    players.remove(randIndex); 
} 

return subset; 

しかし、このプロセスはJava 8の機能で実行できますか?私はこれをより凝縮させると思いますが、それは私が達成しようとしているものです。私はまだIntStreamとlambdasのようなこれらの新しいJava 8機能のハングアップを取得しており、この特定のケースでそれらを使用する方法はわかりません。

答えて

3

、あなたは、入力配列からstreamSize異なる要素を選択します。

あなたはRandom#ints(randomNumberOrigin, randomNumberBound)メソッドを使用することができます

はそれぞれ、(排他的)(包括的)および結合した特定の起源に準拠し、擬似ランダムint型の値を効果的に無限のストリームを返します。

これは、指定された範囲内のランダムな整数のストリームを返します。明確な値を保証するために、distinct()が呼び出され、limit(...)は、必要な要素の数だけを保持することを可能にします。

Random random = new Random(); 
Player[] subset = random.ints(0, allPlayers.length) 
         .distinct() 
         .limit(subsetSize) 
         .mapToObj(i -> allPlayers[i]) 
         .toArray(Player[]::new); 

私は、これはワンライナーであっても、これは明確なものが見つかるまでの整数を生成し続けているので、それはJB Nizet's solutionほど効率的ではない、ということ、しかし注意します。これを行うには

+2

これはうまく見えますが、私はこの戦略が最適ではないと感じます。たとえば、サイズが1000の場合、999要素のサブセットが必要な場合は、まだ生成されていないものを見つけるまで、非常に多くの乱数を生成する必要があります。 Collections.shuffleにはこの問題はありません。小さなサブセットの場合は、おそらくもっと速いでしょう。 –

+0

美しい、これは私が探していたものです。私は本当にラムダの可能性を使用し始める必要があります。 Btw、ここで 'mapToObj'がどのように動作するのか気になりますか? – dabadaba

3

ストリームを使用しない以下を使用できます.1ライナーではなく、すでに凝縮されています。この場合

List<Player> copy = new ArrayList<>(Arrays.asList(allPlayers)); 
Collections.shuffle(copy); 
return copy.subList(0, subsetSize).toArray(new Player[0]); 
+0

右。私の質問は 'IntStream'とlambdasでこれをどうやって行うのかを調べることを目標にしていましたが、私はこの解決策も考えていませんでした。 – dabadaba

2

効率的な方法は次のとおりです。

List<String> players = Arrays.asList("a", "b", "c", "d", "e"); 
int subsetSize = 3; 
for (int i = 0; i < subsetSize; i++) 
    Collections.swap(players, i, ThreadLocalRandom.current().nextInt(i, players.size())); 
System.out.println(players.subList(0, subsetSize)); 

subsetSizeが大きい場合、これは、未使用のインデックスのために非効率的な検索を必要としません。リスト全体にshuffleを呼び出す必要はないので、subsetSizeが小さい場合はより適切です。また、線形時間の複雑さを有するArrayList.removeを呼び出すことも回避する。

このコードではStream操作を使用していませんが、状態を変更するラムダ(悪い習慣)を使用せずに、Streamを使用して簡単で効率的なソリューションを作成することはできません。

+0

あなたの最後の声明を説明できますか? – dabadaba

+0

@dabadabaこれをストリームソリューションに変換すると、 'IntStream.range(0、subsetSize).forEach(i-> Collections.swap(players、i、...)'のようなものになります。そしてTunakiの答えよりもはるかに速いです( 'allPlayers.length == subsetSize == 10_000_000'で答えてみてください)、ラムダ内の' players'の状態を変更します。使用することを意図しています。 –

関連する問題