2016-04-01 6 views
3

Ich frage mich, ob IntStream und Lambdas irgendwie schnell verwendet werden können (one-line) erstellen Sie ein Array mit einer zufälligen Teilmenge eines vorhandenen Array von Elementen.Schnelle Möglichkeit zum Erstellen einer zufälligen Teilmenge eines Arrays mit IntStream

Zum Beispiel sagen, dass ich einen Pool von Spielern haben:

Player[] allPlayers; 

Und ich möchte eine zufällige Teilmenge dieser Spieler bekommen, die Dimension der erforderlichen Teilmenge gegeben. Traditionell würde ich so etwas wie tun:

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; 

Aber kann dieser Prozess mit Java 8 Funktionen getan werden? Ich nehme an, es würde es verdichten, was ich versuche zu erreichen. Ich bekomme immer noch den Dreh raus mit diesen neuen Java 8 Features, wie IntStream und Lambdas und ich würde nicht wissen, wie man sie für diesen speziellen Fall benutzt.

Antwort

3

In diesem Fall möchten Sie streamSize unterschiedliche Elemente aus einer Eingabe-Array auszuwählen.

können Sie die Random#ints(randomNumberOrigin, randomNumberBound) Methode verwenden:

Gibt einen effektiv unbegrenzten Strom von Pseudo-Zufalls-int-Werten, die jeweils entsprechen, an die angegebenen Herkunft (einschließlich) und gebunden (exklusiv).

Dies gibt einen Stream von zufälligen Ganzzahlen im angegebenen Bereich zurück. Um eindeutige Werte zu gewährleisten, wird distinct() aufgerufen und limit(...) ermöglicht nur die Anzahl der Elemente, die wir wollen, zu behalten.

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

Ich würde jedoch zu beachten, dass, auch wenn dies ein Einzeiler, es ist nicht so effizient wie JB Nizet's solution ist, da diese Zahlen zu erzeugen werden fortgesetzt, bis ein deutlicher gefunden ist.

+2

Obwohl das gut aussieht, finde ich diese Strategie suboptimal. Wenn die Größe beispielsweise 1000 ist und Sie eine Teilmenge von 999 Elementen haben möchten, müssen Sie eine ganze Menge zufälliger Ganzzahlen erzeugen, bis Sie eine solche finden, die noch nicht erzeugt wurde. Collections.shuffle hat dieses Problem nicht. Für eine kleine Untergruppe ist es wahrscheinlich schneller. –

+0

Schön, genau das habe ich gesucht.Ich muss wirklich anfangen, das Potenzial von Lambda zu nutzen. Übrigens, würdest du gerne erklären, wie 'mapToObj' hier funktioniert? – dabadaba

3

Sie könnten folgendes verwenden, das keinen Stream verwendet, kein One-Liner ist, aber bereits mehr kondensiert.

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

Rechts. Meine Frage war, herauszufinden, wie man das mit 'IntStream' und Lambdas macht, aber ich dachte auch nicht an diese Lösung, also danke! – dabadaba

2

Ein effizienter Weg, dies zu tun ist:

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)); 

Dies gilt nicht eine ineffiziente Suche nach nicht verwendeten Indizes verlangen, wenn subsetSize groß ist. Es erfordert keinen Aufruf shuffle auf der gesamten Liste, so ist geeigneter, wenn subsetSize ist klein. Es vermeidet auch den Aufruf ArrayList.remove, der lineare Zeitkomplexität hat.

Dieser Code verwendet keine Stream Operationen, aber ich denke nicht, dass es möglich ist, eine einfache, effiziente Lösung mit Stream zu schreiben, ohne ein Lambda zu verwenden, das den Status ändert (schlechte Vorgehensweise).

+0

Können Sie Ihre letzte Aussage erklären? – dabadaba

+0

@dabadaba Wenn Sie dies in eine Stream-Lösung verwandeln würden, wäre es etwas wie "IntStream.range (0, subsetSize) .forEach (i -> Collections.swap (players, i, ...)") und ist viel schneller als Tunakis Antwort (versuchen Sie diese Antwort mit 'allPlayers.length == subsetSize == 10_000_000', um zu sehen, wie langsam es ist), es ändert den Status von' Spielern' innerhalb des Lambda, das ist nicht wie sie ' Re soll verwendet werden. –

Verwandte Themen