Comment puis-je prendre n éléments aléatoires d'un ArrayList<E>
? Idéalement, j'aimerais pouvoir faire des appels successifs à la méthode take()
pour obtenir un autre x éléments, sans remplacement.
Deux manières principales.
Utilisez Random#nextInt(int)
:
List<Foo> list = createItSomehow();
Random random = new Random();
Foo foo = list.get(random.nextInt(list.size()));
Cependant, il n'est pas garanti que des appels n
successifs renvoient des éléments uniques.
Utilisez Collections#shuffle()
:
List<Foo> list = createItSomehow();
Collections.shuffle(list);
Foo foo = list.get(0);
Il vous permet d’obtenir n
éléments uniques par un index incrémenté (en supposant que la liste elle-même contient des éléments uniques).
Au cas où vous vous demanderiez s'il existe une approche Java 8 Stream; non, il n'y en a pas un intégré. Comparator#randomOrder()
n'existe pas (encore?) Dans l'API standard. Vous pouvez essayer quelque chose comme ci-dessous tout en satisfaisant le strict contrat Comparator
(bien que la distribution soit plutôt mauvaise):
List<Foo> list = createItSomehow();
int random = new Random().nextInt();
Foo foo = list.stream().sorted(Comparator.comparingInt(o -> System.identityHashCode(o) ^ random)).findFirst().get();
Mieux vaut utiliser Collections#shuffle()
à la place.
La plupart des solutions proposées jusqu'à présent suggèrent soit un remaniement complet de la liste, soit un choix aléatoire successif en vérifiant l'unicité et en réessayant si nécessaire.
Mais nous pouvons tirer parti de l’algorithme de Durstenfeld (la variante la plus populaire de Fisher-Yates de nos jours).
La solution de Durstenfeld est de déplacer les nombres "frappés" à la fin de la liste en les échangeant avec le dernier numéro non frappé à chaque itération.
En raison de ce qui précède, nous n'avons pas besoin de mélanger toute la liste , mais exécutez la boucle pour autant d'étapes que le nombre d'éléments à renvoyer. L'algorithme garantit que les N derniers éléments à la fin de la liste sont aléatoires à 100% si nous utilisions une fonction aléatoire parfaite.
Parmi les nombreux scénarios du monde réel où nous devons sélectionner un nombre prédéterminé (max) d'éléments aléatoires dans des tableaux/listes, cette méthode optimisée est très utile pour divers jeux de cartes, tels que Texas Poker, où vous connaissez a priori le nombre. de cartes à utiliser par jeu; seul un nombre limité de cartes est généralement requis du jeu.
public static <E> List<E> pickNRandomElements(List<E> list, int n, Random r) {
int length = list.size();
if (length < n) return null;
//We don't need to shuffle the whole list
for (int i = length - 1; i >= length - n; --i)
{
Collections.swap(list, i , r.nextInt(i + 1));
}
return list.subList(length - n, length);
}
public static <E> List<E> pickNRandomElements(List<E> list, int n) {
return pickNRandomElements(list, n, ThreadLocalRandom.current());
}
Si vous voulez choisir successivement n éléments dans la liste et pouvoir le faire sans le remplacer encore et encore, vous avez probablement intérêt à permuter les éléments de manière aléatoire, puis à supprimer des morceaux par blocs de n. Si vous permutez la liste de manière aléatoire, vous garantissez un caractère statistique aléatoire pour chaque bloc sélectionné. La manière la plus simple de le faire serait peut-être d'utiliser Collections.shuffle
.
Simple et clair
// define ArrayList to hold Integer objects
ArrayList<Integer> arrayList = new ArrayList<>();
for (int i = 0; i < maxRange; i++) {
arrayList.add(i + 1);
}
// shuffle list
Collections.shuffle(arrayList);
// adding defined amount of numbers to target list
ArrayList<Integer> targetList = new ArrayList<>();
for (int j = 0; j < amount; j++) {
targetList.add(arrayList.get(j));
}
return targetList;
Une bonne façon de faire est de parcourir la liste, à la énième itération, en calculant la probabilité de choisir ou non le nième élément, qui est essentiellement la fraction du nombre d’éléments que vous devez encore choisir par rapport au nombre d’éléments. disponible dans le reste de la liste. Par exemple:
public static <T> T[] pickSample(T[] population, int nSamplesNeeded, Random r) {
T[] ret = (T[]) Array.newInstance(population.getClass().getComponentType(),
nSamplesNeeded);
int nPicked = 0, i = 0, nLeft = population.length;
while (nSamplesNeeded > 0) {
int Rand = r.nextInt(nLeft);
if (Rand < nSamplesNeeded) {
ret[nPicked++] = population[i];
nSamplesNeeded--;
}
nLeft--;
i++;
}
return ret;
}
(Ce code a été copié d’une page que j’ai écrite il ya quelque temps sur en sélectionnant un échantillon aléatoire dans une liste .)
Utilisez la classe suivante:
import Java.util.Enumeration;
import Java.util.Random;
public class RandomPermuteIterator implements Enumeration<Long> {
int c = 1013904223, a = 1664525;
long seed, N, m, next;
boolean hasNext = true;
public RandomPermuteIterator(long N) throws Exception {
if (N <= 0 || N > Math.pow(2, 62)) throw new Exception("Unsupported size: " + N);
this.N = N;
m = (long) Math.pow(2, Math.ceil(Math.log(N) / Math.log(2)));
next = seed = new Random().nextInt((int) Math.min(N, Integer.MAX_VALUE));
}
public static void main(String[] args) throws Exception {
RandomPermuteIterator r = new RandomPermuteIterator(100);
while (r.hasMoreElements()) System.out.print(r.nextElement() + " ");
}
@Override
public boolean hasMoreElements() {
return hasNext;
}
@Override
public Long nextElement() {
next = (a * next + c) % m;
while (next >= N) next = (a * next + c) % m;
if (next == seed) hasNext = false;
return next;
}
}
Continuez à sélectionner un élément aléatoire et assurez-vous de ne plus choisir le même élément:
public static <E> List<E> selectRandomElements(List<E> list, int amount)
{
// Avoid a deadlock
if (amount >= list.size())
{
return list;
}
List<E> selected = new ArrayList<>();
Random random = new Random();
int listSize = list.size();
// Get a random item until we got the requested amount
while (selected.size() < amount)
{
int randomIndex = random.nextInt(listSize);
E element = list.get(randomIndex);
if (!selected.contains(element))
{
selected.add(element);
}
}
return selected;
}
En théorie, cela pourrait fonctionner sans fin, mais en pratique, tout va bien. Plus la liste originale est proche, plus la durée d'exécution est lente, évidemment, mais ce n'est pas l'intérêt de sélectionner une sous-liste aléatoire, n'est-ce pas?
La classe suivante récupère N éléments d'une liste de tout type. Si vous fournissez une valeur de départ, la liste sera renvoyée à chaque exécution, sinon les éléments de la nouvelle liste changeront à chaque exécution. Vous pouvez vérifier son comportement lors de l'exécution des méthodes principales.
import Java.util.ArrayList;
import Java.util.Arrays;
import Java.util.Collections;
import Java.util.List;
import Java.util.Random;
public class NRandomItem<T> {
private final List<T> initialList;
public NRandomItem(List<T> list) {
this.initialList = list;
}
/**
* Do not provide seed, if you want different items on each run.
*
* @param numberOfItem
* @return
*/
public List<T> retrieve(int numberOfItem) {
int seed = new Random().nextInt();
return retrieve(seed, numberOfItem);
}
/**
* The same seed will always return the same random list.
*
* @param seed,
* the seed of random item generator.
* @param numberOfItem,
* the number of items to be retrieved from the list
* @return the list of random items
*/
public List<T> retrieve(int seed, int numberOfItem) {
Random Rand = new Random(seed);
Collections.shuffle(initialList, Rand);
// Create new list with the number of item size
List<T> newList = new ArrayList<>();
for (int i = 0; i < numberOfItem; i++) {
newList.add(initialList.get(i));
}
return newList;
}
public static void main(String[] args) {
List<String> l1 = Arrays.asList("Foo", "Bar", "Baz", "Qux");
int seedValue = 10;
NRandomItem<String> r1 = new NRandomItem<>(l1);
System.out.println(String.format("%s", r1.retrieve(seedValue, 2)));
}
}
Comme indiqué dans d'autres réponses, Collections.shuffle
n'est pas très efficace lorsque la liste source est volumineuse, à cause de la copie. Voici un one-liner Java 8 qui:
Code:
private static <E> List<E> pickRandom(List<E> list, int n) {
return new Random().ints(n, 0, list.size()).mapToObj(list::get).collect(Collectors.toList());
}
Cependant, pour une liste sans accès aléatoire rapide (comme LinkedList), la complexité serait n*O(list_size)
.