[JAVA] Extraire efficacement plusieurs éléments de la liste de manière aléatoire et sans duplication

introduction

(Comme indiqué dans Comment, il est possible d'améliorer l'efficacité.)

Commentaire de Hokuhoku no Imo C'est la méthode à laquelle j'ai pensé.

Cette section décrit comment extraire efficacement plusieurs éléments d'une liste au hasard sans duplication. La "liste" ici est une liste de longueur variable accessible au hasard [^ liste de longueur variable accessible de manière aléatoire].

[^ Liste de longueur variable accessible aléatoirement]: java.util.ArrayList pour Java, ʻArray pour JavaScript, std: vector` pour C ++

L'explication est donnée en Java, mais le code final qui a été fonctionnalisé pour la réutilisation est fourni en Java, JavaScript et Kotlin.

Dans l'exemple de code Java, supposons que l'instruction ʻimport` suivante soit déclarée au début du fichier.

Java


import java.util.*;

Récupérer aléatoirement des éléments de la liste

Seulement un

Il est facile de choisir au hasard un seul élément de la liste.

Java


List<Character> list = Arrays.asList('A', 'B', 'C', 'D'); // A, B, C,Liste avec 4 éléments de D

int index = new Random().nextInt(list.size()); //Entier sélectionné au hasard supérieur ou égal à 0 et inférieur à 4
Character result = list.get(index); //Éléments sélectionnés au hasard

System.out.println(result); //Exemple de sortie> B

tout

La récupération de tous les éléments dans un ordre aléatoire est également facile si la bibliothèque standard a une telle fonction.

Java


List<Character> list = Arrays.asList('A', 'B', 'C', 'D'); // A, B, C,Liste avec 4 éléments de D

List<Character> shuffled = new ArrayList<>(list); //Une copie de la liste qui est ensuite triée aléatoirement
Collections.shuffle(shuffled); //Triez au hasard les éléments de shuffled.

System.out.println(shuffled); //Exemple de sortie> [C, A, D, B]

Multiple, pas tout

Alors, que diriez-vous de récupérer plusieurs éléments, pas tous les éléments, dans un ordre aléatoire?

Java (Remarque: Inefficace)


List<Character> list = Arrays.asList('A', 'B', 'C', 'D'); // A, B, C,Liste avec 4 éléments de D

List<Character> shuffled = new ArrayList<>(list); //Une copie de la liste qui est ensuite triée aléatoirement
Collections.shuffle(shuffled); //Triez au hasard les éléments de shuffled.
List result = shuffled.subList(0, 2); //Nouvelle liste avec deux éléments du début de la lecture aléatoire

System.out.println(result); //Exemple de sortie> [C, B]

Non, c'est facile d'écrire ça, Il y a beaucoup de gaspillage car tous les éléments sont mélangés. Si vous mélangez 10 000 éléments lorsque vous récupérez 100 éléments d'une liste de 10 000 éléments, 9900 éléments seront perdus.

Alors tu fais quoi?

Évitez les mélanges inutiles

Sélectionnez au hasard un seul élément, supprimez l'élément sélectionné de la liste, etc.

Java (Remarque: Inefficace)


List<Character> list = Arrays.asList('A', 'B', 'C', 'D'); // A, B, C,Liste avec 4 éléments de D

List<Character> result = new ArrayList<>(); //Une liste avec des éléments sélectionnés au hasard
List<Character> remaining = new ArrayList<>(list); //Liste des éléments restants
Random random = new Random(); //Générateur aléatoire
for (int i = 0; i < 2; i++) { //Répétez deux fois.
    int remainingCount = remaining.size(); //Nombre d'éléments restants
    int index = random.nextInt(remainingCount); //Index sélectionné au hasard

    Character element = remaining.remove(index); //Extrait des éléments sélectionnés au hasard.
    result.add(element); //Avoir des éléments sélectionnés au hasard Ajoutez des éléments sélectionnés au hasard à la fin de la liste.
}

System.out.println(result); //Exemple de sortie> [B, D]

Il n'y a pas de mélange inutile. Cependant, cela est également inefficace.

Les listes accessibles au hasard utilisent des tableaux en interne pour contenir des éléments Si vous supprimez un élément au milieu, le processus de bourrage des éléments après cela sera effectué.

|A|B|C|D| | | | |
↓ Supprimer B
|A|C|D| | | | | |

Par conséquent, plus l'élément à supprimer est précoce, plus le traitement prend du temps.

Alors que dois-je faire?

Évitez de supprimer des éléments au milieu

C'est lent car cela supprime les éléments au milieu. Supprimez uniquement la fin. Remplaçons les éléments du milieu sans les supprimer.

Lors de la suppression de B


|A|B|C|D| | | | |
↓ Supprimez le D à la fin et
|A|B|C| | | | | |
↓ Remplacez B par D.
|A|D|C| | | | | |

Le code ressemble à ceci:

Java


List<Character> list = Arrays.asList('A', 'B', 'C', 'D'); // A, B, C,Liste avec 4 éléments de D

List<Character> result = new ArrayList<>(); //Une liste avec des éléments sélectionnés au hasard

List<Character> remaining = new ArrayList<>(list); //Liste des éléments restants
Random random = new Random(); //Générateur aléatoire
for (int i = 0; i < 2; i++) { //Répétez deux fois.
    int remainingCount = remaining.size(); //Nombre d'éléments restants
    int index = random.nextInt(remainingCount); //Index sélectionné au hasard

    Character element = remaining.get(index); //Éléments sélectionnés au hasard
    result.add(element); //Avoir des éléments sélectionnés au hasard Ajoutez des éléments sélectionnés au hasard à la fin de la liste.

    int lastIndex = remainingCount - 1; //Index à la fin de la liste des éléments restants
    Character lastElement = remaining.remove(lastIndex); //Supprimez la fin de la liste des éléments restants.
    if (index < lastIndex) { //Si l'élément sélectionné au hasard n'est pas la fin ...
        remaining.set(index, lastElement); //Remplacez-le par le dernier élément.
    }
}

System.out.println(result); //Exemple de sortie> [D, A]

la mesure

Mesurons facilement la différence de vitesse de traitement. Comparez celui qui mélange tous les éléments pour obtenir la première liste partielle avec le dernier qui est le moins coûteux.

Si vous définissez le nombre d'éléments de la liste sur 100 000 et que vous en acquérez au hasard 50 000, Environ 590 ms pour la lecture aléatoire de tous les éléments S'il n'y a pas de déchets, ce sera environ 370 ms.

Code utilisé pour la mesure et l'exemple de sortie

Java


public class TakeAtRandom {
    public static void main(String... args) {
        //Taille de la liste
        final int LIST_SIZE = 10_000_000;
        //Nombre d'éléments à extraire de la liste
        final int N = 5_000_000;
        //Nombre de répétitions
        final int REPETITION = 100;

        final List<Integer> list = new ArrayList<>(LIST_SIZE);
        for (int i = 0; i < LIST_SIZE; i++) {
            list.add(i);
        }
        final Random random = new Random();

        for (int i = 0; i < REPETITION; i++) {
            {
                final long startAt = System.currentTimeMillis();
                takeFromShuffled(list, N, random);
                final long finishedAt = System.currentTimeMillis();
                System.out.print(String.format("Mélanger tous les éléments: %,d ms", finishedAt - startAt));
            }
            {
                final long startAt = System.currentTimeMillis();
                takeAtRandom(list, N, random);
                final long finishedAt = System.currentTimeMillis();
                System.out.print(String.format("Pas de déchets: %,d ms", finishedAt - startAt));
            }
            System.out.println();
        }
    }

    public static <T> List<T> takeFromShuffled(List<T> list, int n, Random random) {
        final List<T> workList = new ArrayList<>(list);
        Collections.shuffle(workList);
        return workList.subList(0, n);
    }

    public static <T> List<T> takeAtRandom(Collection<T> list, int n, Random random) {
        final List<T> taken = new ArrayList<>(n); //Une liste avec des éléments sélectionnés au hasard

        final List<T> remaining = new ArrayList<>(list); //Liste des éléments restants
        for (int i = 0; i < n; i++) { //Répétez n fois.
            final int remainingCount = remaining.size(); //Nombre d'éléments restants
            final int index = random.nextInt(remainingCount); //Index sélectionné au hasard

            final T element = remaining.get(index); //Éléments sélectionnés au hasard
            taken.add(element); //Ajoutez des éléments sélectionnés au hasard à la fin de la liste des éléments sélectionnés au hasard.

            final int lastIndex = remainingCount - 1; //Index à la fin de la liste des éléments restants
            final T lastElement = remaining.remove(lastIndex); //Supprimez la fin de la liste des éléments restants.
            if (index < lastIndex) { //Si l'élément sélectionné au hasard n'est pas la fin ...
                remaining.set(index, lastElement); //Remplacez-le par le dernier élément.
            }
        }

        return taken;
    }
}

Exemple de sortie


Mélanger tous les éléments:608 ms pas de gaspillage: 705 ms
Mélanger tous les éléments:659 ms pas de gaspillage: 1,985 ms
Mélanger tous les éléments:585 ms pas de gaspillage: 372 ms
Mélanger tous les éléments:598 ms pas de gaspillage: 378 ms
Mélanger tous les éléments:598 ms pas de gaspillage: 397 ms
Mélanger tous les éléments:586 ms pas de gaspillage: 451 ms
Mélanger tous les éléments:583 ms pas de gaspillage: 381 ms
Mélanger tous les éléments:584 ms pas de gaspillage: 391 ms
Mélanger tous les éléments:580 ms pas de gaspillage: 383 ms
Mélanger tous les éléments:578 ms pas de gaspillage: 383 ms
Mélanger tous les éléments:584 ms pas de gaspillage: 377 ms
Mélanger tous les éléments:585 ms pas de gaspillage: 378 ms
Mélanger tous les éléments:598 ms pas de gaspillage: 381 ms
Mélanger tous les éléments:591 ms pas de gaspillage: 359 ms
Mélanger tous les éléments:605 ms pas de gaspillage: 354 ms
Mélanger tous les éléments:590 ms pas de gaspillage: 373 ms
Mélanger tous les éléments:584 ms pas de gaspillage: 379 ms
Mélanger tous les éléments:586 ms pas de gaspillage: 359 ms
Mélanger tous les éléments:603 ms pas de gaspillage: 357 ms
Mélanger tous les éléments:589 ms pas de gaspillage: 376 ms
Mélanger tous les éléments:579 ms pas de gaspillage: 385 ms
Mélanger tous les éléments:581 ms pas de gaspillage: 352 ms
Mélanger tous les éléments:610 ms pas de gaspillage: 350 ms
Mélanger tous les éléments:587 ms pas de gaspillage: 372 ms
Mélanger tous les éléments:603 ms pas de gaspillage: 381 ms
Mélanger tous les éléments:595 ms pas de gaspillage: 353 ms
Mélanger tous les éléments:606 ms pas de gaspillage: 348 ms
Mélanger tous les éléments:579 ms pas de gaspillage: 376 ms
Mélanger tous les éléments:576 ms pas de gaspillage: 390 ms
Mélanger tous les éléments:582 ms pas de gaspillage: 359 ms
Mélanger tous les éléments:602 ms pas de gaspillage: 357 ms
Mélanger tous les éléments:583 ms pas de gaspillage: 375 ms
Mélanger tous les éléments:585 ms pas de gaspillage: 379 ms
Mélanger tous les éléments:586 ms pas de gaspillage: 353 ms
Mélanger tous les éléments:603 ms pas de gaspillage: 350 ms
Mélanger tous les éléments:590 ms pas de gaspillage: 370 ms
Mélanger tous les éléments:587 ms pas de gaspillage: 376 ms
Mélanger tous les éléments:586 ms pas de gaspillage: 351 ms
Mélanger tous les éléments:600 ms pas de gaspillage: 359 ms
Mélanger tous les éléments:585 ms pas de gaspillage: 379 ms
Mélanger tous les éléments:579 ms pas de gaspillage: 382 ms
Mélanger tous les éléments:580 ms pas de gaspillage: 352 ms
Mélanger tous les éléments:605 ms pas de gaspillage: 349 ms
Mélanger tous les éléments:588 ms pas de gaspillage: 372 ms
Mélanger tous les éléments:586 ms pas de gaspillage: 375 ms
Mélanger tous les éléments:584 ms pas de gaspillage: 351 ms
Mélanger tous les éléments:598 ms pas de gaspillage: 358 ms
Mélanger tous les éléments:579 ms pas de gaspillage: 376 ms
Mélanger tous les éléments:579 ms pas de gaspillage: 381 ms
Mélanger tous les éléments:578 ms pas de gaspillage: 357 ms
Mélanger tous les éléments:601 ms pas de gaspillage: 348 ms
Mélanger tous les éléments:593 ms pas de gaspillage: 371 ms
Mélanger tous les éléments:585 ms pas de gaspillage: 376 ms
Mélanger tous les éléments:584 ms pas de gaspillage: 352 ms
Mélanger tous les éléments:602 ms pas de gaspillage: 349 ms
Mélanger tous les éléments:586 ms pas de gaspillage: 372 ms
Mélanger tous les éléments:586 ms pas de gaspillage: 376 ms
Mélanger tous les éléments:578 ms pas de gaspillage: 359 ms
Mélanger tous les éléments:598 ms pas de gaspillage: 357 ms
Mélanger tous les éléments:583 ms pas de gaspillage: 379 ms
Mélanger tous les éléments:578 ms pas de gaspillage: 382 ms
Mélanger tous les éléments:592 ms pas de gaspillage: 356 ms
Mélanger tous les éléments:605 ms pas de gaspillage: 350 ms
Mélanger tous les éléments:587 ms pas de gaspillage: 374 ms
Mélanger tous les éléments:586 ms pas de gaspillage: 377 ms
Mélanger tous les éléments:586 ms pas de gaspillage: 353 ms
Mélanger tous les éléments:600 ms pas de gaspillage: 357 ms
Mélanger tous les éléments:581 ms pas de gaspillage: 380 ms
Mélanger tous les éléments:580 ms pas de gaspillage: 380 ms
Mélanger tous les éléments:580 ms pas de gaspillage: 352 ms
Mélanger tous les éléments:605 ms pas de gaspillage: 349 ms
Mélanger tous les éléments:592 ms pas de gaspillage: 373 ms
Mélanger tous les éléments:585 ms pas de gaspillage: 374 ms
Mélanger tous les éléments:585 ms pas de gaspillage: 352 ms
Mélanger tous les éléments:598 ms pas de gaspillage: 357 ms
Mélanger tous les éléments:580 ms pas de gaspillage: 377 ms
Mélanger tous les éléments:582 ms pas de gaspillage: 383 ms
Mélanger tous les éléments:578 ms pas de gaspillage: 362 ms
Mélanger tous les éléments:611 ms pas de gaspillage: 358 ms
Mélanger tous les éléments:581 ms pas de gaspillage: 375 ms
Mélanger tous les éléments:594 ms pas de gaspillage: 377 ms
Mélanger tous les éléments:587 ms pas de gaspillage: 362 ms
Mélanger tous les éléments:603 ms pas de gaspillage: 350 ms
Mélanger tous les éléments:587 ms pas de gaspillage: 373 ms
Mélanger tous les éléments:585 ms pas de gaspillage: 376 ms
Mélanger tous les éléments:586 ms pas de gaspillage: 353 ms
Mélanger tous les éléments:601 ms pas de gaspillage: 358 ms
Mélanger tous les éléments:593 ms pas de gaspillage: 378 ms
Mélanger tous les éléments:580 ms pas de gaspillage: 384 ms
Mélanger tous les éléments:577 ms pas de gaspillage: 351 ms
Mélanger tous les éléments:606 ms pas de gaspillage: 349 ms
Mélanger tous les éléments:589 ms pas de gaspillage: 380 ms
Mélanger tous les éléments:590 ms pas de gaspillage: 352 ms
Mélanger tous les éléments:605 ms pas de gaspillage: 348 ms
Mélanger tous les éléments:590 ms pas de gaspillage: 375 ms
Mélanger tous les éléments:581 ms pas de gaspillage: 359 ms
Mélanger tous les éléments:599 ms pas de gaspillage: 357 ms
Mélanger tous les éléments:581 ms pas de gaspillage: 381 ms
Mélanger tous les éléments:579 ms pas de gaspillage: 353 ms
Mélanger tous les éléments:607 ms pas de gaspillage: 349 ms

Fonctionnalisation

J'en ai fait une fonction pour qu'elle puisse être réutilisée.

Java

Java


import java.util.*;

public class TakeAtRandom {
    /**
     *Récupère de manière aléatoire le nombre spécifié d'éléments de la liste sans duplication.
     *
     * @param list Extraire les éléments de cette liste. Cette liste ne change pas.
     * @param n Nombre d'éléments à récupérer.
     * @param <T>Type d'élément.
     * @return Une liste avec les éléments récupérés.
     */
    public static <T> List<T> takeAtRandom(Collection<T> list, int n) {
        return takeAtRandom(list, n, new Random());
    }

    /**
     *Récupère de manière aléatoire le nombre spécifié d'éléments de la liste sans duplication.
     * 
     * @param list Extraire les éléments de cette liste. Cette liste ne change pas.
     * @param n Nombre d'éléments à récupérer.
     * @param générateur aléatoire aléatoire à utiliser.
     * @param <T>Type d'élément.
     * @return Une liste avec les éléments récupérés.
     */
    public static <T> List<T> takeAtRandom(Collection<T> list, int n, Random random) {
        if (list == null) {
            throw new IllegalArgumentException("La valeur de la liste d'arguments est nulle.");
        }
        if (n > list.size()) {
            throw new IllegalArgumentException(
                    String.format("Valeur de l'argument n%d est la taille de la liste d'arguments%Supérieur à d.",
                            n, list.size()));
        }
        if (random == null) {
            throw new IllegalArgumentException("La valeur de l'argument random est nulle.");
        }

        final List<T> taken = new ArrayList<>(n); //Une liste avec des éléments sélectionnés au hasard

        final List<T> remaining = new ArrayList<>(list); //Liste des éléments restants
        for (int i = 0; i < n; i++) { //Répétez n fois.
            final int remainingCount = remaining.size(); //Nombre d'éléments restants
            final int index = random.nextInt(remainingCount); //Index sélectionné au hasard

            final T element = remaining.get(index); //Éléments sélectionnés au hasard
            taken.add(element); //Ajoutez des éléments sélectionnés au hasard à la fin de la liste des éléments sélectionnés au hasard.

            final int lastIndex = remainingCount - 1; //Index à la fin de la liste des éléments restants
            final T lastElement = remaining.remove(lastIndex); //Supprimez la fin de la liste des éléments restants.
            if (index < lastIndex) { //Si l'élément sélectionné au hasard n'est pas la fin ...
                remaining.set(index, lastElement); //Remplacez-le par le dernier élément.
            }
        }

        return taken;
    }
}

JavaScript

JavaScript


/**
 *Récupérez au hasard le nombre spécifié d'éléments du tableau sans duplication.
 *
 * @param {Array}array Extrait des éléments de ce tableau. Ce tableau est inchangé.
 * @param {number}n Nombre d'éléments à récupérer.
 * @return {Array}Un tableau avec les éléments récupérés.
 */
function takeAtRandom(array, n) {
  if (array == null) {
    throw new Error("La valeur du tableau d'arguments est nulle ou non définie.");
  }
  if (n > array.length) {
    throw new Error(`Valeur de l'argument n${n}Est la taille du tableau d'arguments${array.length}Est plus grand.`);
  }
  
  const taken = [];
  
  const remaining = array.concat();
  for (let i = 0; i < n; i++) {
    const remainingCount = remaining.length;
    const index = Math.floor(Math.random() * remainingCount);
    
    const element = remaining[index];
    taken.push(element);
    
    const lastIndex = remainingCount - 1;
    const lastElement = remaining.pop(lastIndex);
    if (index < lastIndex) {
      remaining[index] = lastElement;
    }
  }
  
  return taken;
}

Version du générateur

JavaScript


/**
 *Renvoie un générateur qui récupère aléatoirement des éléments d'un tableau sans duplication.
 *
 * @param {Array}array Extrait des éléments de ce tableau. Ce tableau est inchangé.
 */
function* takeAtRandom(array) {
  if (array == null) {
    throw new Error("La valeur du tableau d'arguments est nulle ou non définie.");
  }
  
  const remaining = array.concat();
  while(true) {
    const remainingCount = remaining.length;
    if (remainingCount === 0) {
      break;
    }
    
    const index = Math.floor(Math.random() * remainingCount);
    yield remaining[index];
    
    const lastIndex = remainingCount - 1;
    const lastElement = remaining.pop();
    if (index < lastIndex) {
      remaining[index] = lastElement;
    }
  }
}

Kotlin

Kotlin


import kotlin.random.Random

/**
 *Récupère de manière aléatoire le nombre spécifié d'éléments de la liste sans duplication.
 *
 * @receiver Extrait un élément de cette liste.
 * @param n Nombre d'éléments à récupérer.
 * @param générateur aléatoire aléatoire à utiliser.
 * @param <T>Type d'élément.
 * @return Une liste avec les éléments récupérés.
 */
fun <T> Collection<T>.takeAtRandom(n: Int, random: Random = Random): List<T> {
    require(n <= size) { "Valeur de l'argument n$n est la taille du récepteur${size}Est plus grand." }

    val taken = mutableListOf<T>() //Une liste avec des éléments sélectionnés au hasard

    val remaining = toMutableList() //Liste des éléments restants
    //Répétez n fois.
    repeat(n) {
        val remainingCount = remaining.size //Nombre d'éléments restants
        val index = random.nextInt(remainingCount) //Index sélectionné au hasard

        taken += remaining[index] //Éléments sélectionnés au hasard

        val lastIndex = remainingCount - 1 //Index à la fin de la liste des éléments restants
        val lastElement = remaining.removeAt(lastIndex) //Supprimez la fin de la liste des éléments restants.
        if (index < lastIndex) { //Si l'élément sélectionné au hasard n'est pas la fin ...
            remaining[index] = lastElement //Remplacez-le par le dernier élément.
        }
    }

    return taken
}

Version de séquence

Kotlin


import kotlin.random.Random

/**
 *Renvoie une séquence qui récupère de manière aléatoire le nombre spécifié d'éléments de la liste sans duplication.
 *
 * @receiver Extrait un élément de cette liste.
 * @param générateur aléatoire aléatoire à utiliser.
 * @param <T>Type d'élément.
 * @séquence de retour.
 */
fun <T> Collection<T>.toRandomSequence(random: Random = Random): Sequence<T> = sequence {
    val remaining = toMutableList() //Liste des éléments restants
    while (true) {
        val remainingCount = remaining.size //Nombre d'éléments restants
            .takeIf { it > 0 }
            ?: break
        val index = random.nextInt(remainingCount) //Index sélectionné au hasard

        remaining[index].also {
            yield(it)
        }

        val lastIndex = remainingCount - 1 //Index à la fin de la liste des éléments restants
        val lastElement = remaining.removeAt(lastIndex) //Supprimez la fin de la liste des éléments restants.
        if (index < lastIndex) { //Si l'élément sélectionné au hasard n'est pas la fin ...
            remaining[index] = lastElement //Remplacez-le par le dernier élément.
        }
    }
}

/c'est tout

Recommended Posts

Extraire efficacement plusieurs éléments de la liste de manière aléatoire et sans duplication
ArrayList et le rôle de l'interface vu depuis List
Extraire un élément spécifique de la liste des objets
[Java] Supprimer les éléments de la liste
Extraire les informations requises de pom.xml
Sélectionnez le premier non vide parmi plusieurs facultatifs et appelez sa méthode
[Java] Générez une liste restreinte à partir de plusieurs listes à l'aide de l'API Stream