[JAVA] Extrahieren Sie effizient mehrere Elemente zufällig und ohne Duplizierung aus der Liste

Einführung

(Wie in [Kommentar] ausgeführt (https://qiita.com/sdkei/items/43d2902908efcfca7f25#comment-b5adc7209cd15444da9e), gibt es Raum für Effizienzverbesserungen.)

Kommentar von Hokuhoku no Imo Dies ist die Methode, an die ich gedacht habe.

In diesem Abschnitt wird beschrieben, wie Sie mehrere Elemente ohne Duplizierung nach dem Zufallsprinzip effizient aus einer Liste extrahieren können. Die "Liste" hier ist eine zufällig zugängliche Liste variabler Länge [^ zufällig zugängliche Liste variabler Länge].

[^ Zufällig zugängliche Liste variabler Länge]: java.util.ArrayList für Java, Array für JavaScript, std: vector für C ++

Die Erklärung wird in Java gegeben, aber der endgültige Code, der für die Wiederverwendung funktionalisiert wurde, wird in Java, JavaScript und Kotlin bereitgestellt.

Angenommen, im Java-Codebeispiel wird am Anfang der Datei die folgende "import" -Anweisung deklariert:

Java


import java.util.*;

Elemente nach dem Zufallsprinzip aus der Liste abrufen

Nur einer

Es ist einfach, nur ein Element zufällig aus der Liste auszuwählen.

Java


List<Character> list = Arrays.asList('A', 'B', 'C', 'D'); // A, B, C,Liste mit 4 Elementen von D.

int index = new Random().nextInt(list.size()); //Zufällig ausgewählte Ganzzahl größer oder gleich 0 und kleiner als 4
Character result = list.get(index); //Zufällig ausgewählte Elemente

System.out.println(result); //Ausgabebeispiel> B

alles

Das Abrufen aller Elemente in zufälliger Reihenfolge ist auch dann einfach, wenn die Standardbibliothek über eine solche Funktion verfügt.

Java


List<Character> list = Arrays.asList('A', 'B', 'C', 'D'); // A, B, C,Liste mit 4 Elementen von D.

List<Character> shuffled = new ArrayList<>(list); //Eine Kopie der Liste, die dann zufällig sortiert wird
Collections.shuffle(shuffled); //Sortieren Sie die Elemente nach dem Zufallsprinzip.

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

Mehrfach, nicht alle

Wie wäre es also damit, mehrere Elemente, nicht alle Elemente, in zufälliger Reihenfolge abzurufen?

Java (Hinweis: Ineffizient)


List<Character> list = Arrays.asList('A', 'B', 'C', 'D'); // A, B, C,Liste mit 4 Elementen von D.

List<Character> shuffled = new ArrayList<>(list); //Eine Kopie der Liste, die dann zufällig sortiert wird
Collections.shuffle(shuffled); //Sortieren Sie die Elemente nach dem Zufallsprinzip.
List result = shuffled.subList(0, 2); //Neue Liste mit zwei Elementen von vorne gemischt

System.out.println(result); //Ausgabebeispiel> [C, B]

Nein, das ist einfach zu schreiben, Es gibt viel Abfall, weil alle Elemente gemischt werden. Wenn Sie 10.000 Elemente mischen, wenn Sie 100 Elemente aus einer Liste von 10.000 Elementen abrufen, werden 9900 Elemente verschwendet.

Was machst du dann?

Vermeiden Sie unnötiges Mischen

Wählen Sie zufällig nur ein Element aus, entfernen Sie das ausgewählte Element aus der Liste usw.

Java (Hinweis: Ineffizient)


List<Character> list = Arrays.asList('A', 'B', 'C', 'D'); // A, B, C,Liste mit 4 Elementen von D.

List<Character> result = new ArrayList<>(); //Eine Liste mit zufällig ausgewählten Elementen
List<Character> remaining = new ArrayList<>(list); //Liste der verbleibenden Elemente
Random random = new Random(); //Zufallsgenerator
for (int i = 0; i < 2; i++) { //Wiederholen Sie zweimal.
    int remainingCount = remaining.size(); //Anzahl der verbleibenden Elemente
    int index = random.nextInt(remainingCount); //Zufällig ausgewählter Index

    Character element = remaining.remove(index); //Extrahiert zufällig ausgewählte Elemente.
    result.add(element); //Zufällig ausgewählte Elemente hinzufügen Fügen Sie am Ende der Liste zufällig ausgewählte Elemente hinzu.
}

System.out.println(result); //Ausgabebeispiel> [B, D]

Es gibt kein nutzloses Mischen. Dies ist jedoch auch ineffizient.

Zufällig zugängliche Listen verwenden intern Arrays, um Elemente zu speichern Wenn Sie ein Element in der Mitte löschen, wird der Vorgang des Füllens der Elemente danach ausgeführt.

|A|B|C|D| | | | |
↓ Löschen B.
|A|C|D| | | | | |

Je früher das zu löschende Element ist, desto länger dauert die Verarbeitung.

Was soll ich dann tun?

Vermeiden Sie das Löschen von Elementen in der Mitte

Es ist langsam, weil es die Elemente in der Mitte löscht. Löschen Sie nur das Ende. Ersetzen wir die Elemente in der Mitte, ohne sie zu löschen.

Beim Löschen B.


|A|B|C|D| | | | |
↓ Löschen Sie das D am Ende und
|A|B|C| | | | | |
↓ Ersetzen Sie B durch D.
|A|D|C| | | | | |

Der Code sieht folgendermaßen aus:

Java


List<Character> list = Arrays.asList('A', 'B', 'C', 'D'); // A, B, C,Liste mit 4 Elementen von D.

List<Character> result = new ArrayList<>(); //Eine Liste mit zufällig ausgewählten Elementen

List<Character> remaining = new ArrayList<>(list); //Liste der verbleibenden Elemente
Random random = new Random(); //Zufallsgenerator
for (int i = 0; i < 2; i++) { //Wiederholen Sie zweimal.
    int remainingCount = remaining.size(); //Anzahl der verbleibenden Elemente
    int index = random.nextInt(remainingCount); //Zufällig ausgewählter Index

    Character element = remaining.get(index); //Zufällig ausgewählte Elemente
    result.add(element); //Zufällig ausgewählte Elemente hinzufügen Fügen Sie am Ende der Liste zufällig ausgewählte Elemente hinzu.

    int lastIndex = remainingCount - 1; //Index am Ende der Liste der verbleibenden Elemente
    Character lastElement = remaining.remove(lastIndex); //Entfernen Sie das Ende aus der Liste der verbleibenden Elemente.
    if (index < lastIndex) { //Wenn das zufällig ausgewählte Element nicht das Ende ist ...
        remaining.set(index, lastElement); //Ersetzen Sie es durch das letzte Element.
    }
}

System.out.println(result); //Ausgabebeispiel> [D, A]

Messung

Lassen Sie uns den Unterschied in der Verarbeitungsgeschwindigkeit leicht messen. Vergleichen Sie diejenige, die alle Elemente mischt, um die erste Teilliste zu erhalten, mit der letzten, die am wenigsten verschwenderisch ist.

Wenn Sie die Anzahl der Elemente in der Liste auf 100.000 festlegen und zufällig 50.000 daraus erwerben, Ungefähr 590 ms für alle Elementmischungen Wenn es keinen Abfall gibt, sind es ungefähr 370 ms.

Code für Mess- und Ausgabebeispiel

Java


public class TakeAtRandom {
    public static void main(String... args) {
        //Listengröße
        final int LIST_SIZE = 10_000_000;
        //Anzahl der Elemente, die aus der Liste abgerufen werden sollen
        final int N = 5_000_000;
        //Anzahl der Wiederholungen
        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("Mische alle Elemente: %,d ms", finishedAt - startAt));
            }
            {
                final long startAt = System.currentTimeMillis();
                takeAtRandom(list, N, random);
                final long finishedAt = System.currentTimeMillis();
                System.out.print(String.format("Keine Verschwendung: %,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); //Eine Liste mit zufällig ausgewählten Elementen

        final List<T> remaining = new ArrayList<>(list); //Liste der verbleibenden Elemente
        for (int i = 0; i < n; i++) { //N-mal wiederholen.
            final int remainingCount = remaining.size(); //Anzahl der verbleibenden Elemente
            final int index = random.nextInt(remainingCount); //Zufällig ausgewählter Index

            final T element = remaining.get(index); //Zufällig ausgewählte Elemente
            taken.add(element); //Fügen Sie zufällig ausgewählte Elemente am Ende der Liste der zufällig ausgewählten Elemente hinzu.

            final int lastIndex = remainingCount - 1; //Index am Ende der Liste der verbleibenden Elemente
            final T lastElement = remaining.remove(lastIndex); //Entfernen Sie das Ende aus der Liste der verbleibenden Elemente.
            if (index < lastIndex) { //Wenn das zufällig ausgewählte Element nicht das Ende ist ...
                remaining.set(index, lastElement); //Ersetzen Sie es durch das letzte Element.
            }
        }

        return taken;
    }
}

Ausgabebeispiel


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

Funktionalisierung

Ich habe es zu einer Funktion gemacht, damit es wiederverwendet werden kann.

Java

Java


import java.util.*;

public class TakeAtRandom {
    /**
     *Ruft die angegebene Anzahl von Elementen zufällig aus der Liste ab, ohne sie zu duplizieren.
     *
     * @Parameterliste Extrahieren Sie Elemente aus dieser Liste. Diese Liste ändert sich nicht.
     * @param n Anzahl der abzurufenden Elemente.
     * @param <T>Elementtyp.
     * @return Eine Liste mit den abgerufenen Elementen.
     */
    public static <T> List<T> takeAtRandom(Collection<T> list, int n) {
        return takeAtRandom(list, n, new Random());
    }

    /**
     *Ruft die angegebene Anzahl von Elementen zufällig aus der Liste ab, ohne sie zu duplizieren.
     * 
     * @Parameterliste Extrahieren Sie Elemente aus dieser Liste. Diese Liste ändert sich nicht.
     * @param n Anzahl der abzurufenden Elemente.
     * @param random Zufallsgenerator zu verwenden.
     * @param <T>Elementtyp.
     * @return Eine Liste mit den abgerufenen Elementen.
     */
    public static <T> List<T> takeAtRandom(Collection<T> list, int n, Random random) {
        if (list == null) {
            throw new IllegalArgumentException("Der Wert der Argumentliste ist null.");
        }
        if (n > list.size()) {
            throw new IllegalArgumentException(
                    String.format("Wert des Arguments n%d ist die Größe der Argumentliste%Größer als d.",
                            n, list.size()));
        }
        if (random == null) {
            throw new IllegalArgumentException("Der Wert des Arguments random ist null.");
        }

        final List<T> taken = new ArrayList<>(n); //Eine Liste mit zufällig ausgewählten Elementen

        final List<T> remaining = new ArrayList<>(list); //Liste der verbleibenden Elemente
        for (int i = 0; i < n; i++) { //N-mal wiederholen.
            final int remainingCount = remaining.size(); //Anzahl der verbleibenden Elemente
            final int index = random.nextInt(remainingCount); //Zufällig ausgewählter Index

            final T element = remaining.get(index); //Zufällig ausgewählte Elemente
            taken.add(element); //Fügen Sie zufällig ausgewählte Elemente am Ende der Liste der zufällig ausgewählten Elemente hinzu.

            final int lastIndex = remainingCount - 1; //Index am Ende der Liste der verbleibenden Elemente
            final T lastElement = remaining.remove(lastIndex); //Entfernen Sie das Ende aus der Liste der verbleibenden Elemente.
            if (index < lastIndex) { //Wenn das zufällig ausgewählte Element nicht das Ende ist ...
                remaining.set(index, lastElement); //Ersetzen Sie es durch das letzte Element.
            }
        }

        return taken;
    }
}

JavaScript

JavaScript


/**
 *Ruft die angegebene Anzahl von Elementen nach dem Zufallsprinzip ohne Duplizierung aus dem Array ab.
 *
 * @param {Array}Array Extrahiert Elemente aus diesem Array. Dieses Array bleibt unverändert.
 * @param {number}n Anzahl der abzurufenden Elemente.
 * @return {Array}Ein Array mit den abgerufenen Elementen.
 */
function takeAtRandom(array, n) {
  if (array == null) {
    throw new Error("Der Wert des Argumentarrays ist null oder undefiniert.");
  }
  if (n > array.length) {
    throw new Error(`Wert des Arguments n${n}Ist die Größe des Argumentarrays${array.length}Ist größer.`);
  }
  
  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;
}

Generatorversion

JavaScript


/**
 *Gibt einen Generator zurück, der Elemente ohne Duplizierung zufällig aus einem Array abruft.
 *
 * @param {Array}Array Extrahiert Elemente aus diesem Array. Dieses Array bleibt unverändert.
 */
function* takeAtRandom(array) {
  if (array == null) {
    throw new Error("Der Wert des Argumentarrays ist null oder undefiniert.");
  }
  
  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

/**
 *Ruft die angegebene Anzahl von Elementen zufällig aus der Liste ab, ohne sie zu duplizieren.
 *
 * @Empfänger Extrahiert ein Element aus dieser Liste.
 * @param n Anzahl der abzurufenden Elemente.
 * @param random Zufallsgenerator zu verwenden.
 * @param <T>Elementtyp.
 * @return Eine Liste mit den abgerufenen Elementen.
 */
fun <T> Collection<T>.takeAtRandom(n: Int, random: Random = Random): List<T> {
    require(n <= size) { "Wert des Arguments n$n ist die Größe des Empfängers${size}Ist größer." }

    val taken = mutableListOf<T>() //Eine Liste mit zufällig ausgewählten Elementen

    val remaining = toMutableList() //Liste der verbleibenden Elemente
    //N-mal wiederholen.
    repeat(n) {
        val remainingCount = remaining.size //Anzahl der verbleibenden Elemente
        val index = random.nextInt(remainingCount) //Zufällig ausgewählter Index

        taken += remaining[index] //Zufällig ausgewählte Elemente

        val lastIndex = remainingCount - 1 //Index am Ende der Liste der verbleibenden Elemente
        val lastElement = remaining.removeAt(lastIndex) //Entfernen Sie das Ende aus der Liste der verbleibenden Elemente.
        if (index < lastIndex) { //Wenn das zufällig ausgewählte Element nicht das Ende ist ...
            remaining[index] = lastElement //Ersetzen Sie es durch das letzte Element.
        }
    }

    return taken
}

Sequenzversion

Kotlin


import kotlin.random.Random

/**
 *Gibt eine Sequenz zurück, die die angegebene Anzahl von Elementen ohne Duplizierung zufällig aus der Liste abruft.
 *
 * @Empfänger Extrahiert ein Element aus dieser Liste.
 * @param random Zufallsgenerator zu verwenden.
 * @param <T>Elementtyp.
 * @Rückgabesequenz.
 */
fun <T> Collection<T>.toRandomSequence(random: Random = Random): Sequence<T> = sequence {
    val remaining = toMutableList() //Liste der verbleibenden Elemente
    while (true) {
        val remainingCount = remaining.size //Anzahl der verbleibenden Elemente
            .takeIf { it > 0 }
            ?: break
        val index = random.nextInt(remainingCount) //Zufällig ausgewählter Index

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

        val lastIndex = remainingCount - 1 //Index am Ende der Liste der verbleibenden Elemente
        val lastElement = remaining.removeAt(lastIndex) //Entfernen Sie das Ende aus der Liste der verbleibenden Elemente.
        if (index < lastIndex) { //Wenn das zufällig ausgewählte Element nicht das Ende ist ...
            remaining[index] = lastElement //Ersetzen Sie es durch das letzte Element.
        }
    }
}

/das ist alles

Recommended Posts

Extrahieren Sie effizient mehrere Elemente zufällig und ohne Duplizierung aus der Liste
ArrayList und die Rolle der Schnittstelle aus List
Extrahieren Sie ein bestimmtes Element aus der Liste der Objekte
[Java] Löschen Sie die Elemente von List
Extrahieren Sie die erforderlichen Informationen aus pom.xml
Wählen Sie die erste nicht leere aus mehreren Optional aus und rufen Sie ihre Methode auf
[Java] Generieren Sie mithilfe der Stream-API eine verengte Liste aus mehreren Listen