Einen zufälligen Wert aus einer Aufzählung auswählen?

161

Wenn ich eine Aufzählung wie diese habe:

public enum Letter {
    A,
    B,
    C,
    //...
}

Was ist der beste Weg, um eine zufällig auszuwählen? Es muss nicht kugelsicher in Produktionsqualität sein, aber eine ziemlich gleichmäßige Verteilung wäre schön.

Ich könnte so etwas tun

private Letter randomLetter() {
    int pick = new Random().nextInt(Letter.values().length);
    return Letter.values()[pick];
}

Aber gibt es einen besseren Weg? Ich denke, das ist etwas, das schon einmal gelöst wurde.

Nick Heiner
quelle
Was ist Ihrer Meinung nach falsch an Ihrer Lösung? Es sieht ziemlich gut für mich aus.
Präsident James K. Polk
1
@GregS - Das Problem ist, dass bei jedem Aufruf von Letter.values()eine neue Kopie des internen LetterWertearrays erstellt werden muss.
Stephen C

Antworten:

143

Das einzige, was ich vorschlagen würde, ist das Zwischenspeichern des Ergebnisses, values()da jeder Aufruf ein Array kopiert. Erstellen Sie auch nicht Randomjedes Mal ein. Behalte einen. Davon abgesehen ist das, was Sie tun, in Ordnung. So:

public enum Letter {
  A,
  B,
  C,
  //...

  private static final List<Letter> VALUES =
    Collections.unmodifiableList(Arrays.asList(values()));
  private static final int SIZE = VALUES.size();
  private static final Random RANDOM = new Random();

  public static Letter randomLetter()  {
    return VALUES.get(RANDOM.nextInt(SIZE));
  }
}
Cletus
quelle
8
Wenn Sie es für nützlich halten, können Sie hierfür eine Dienstprogrammklasse erstellen. So etwas wie RandomEnum <T erweitert Enum> mit einem Konstruktor, der die Klasse <T> zum Erstellen der Liste empfängt.
Helios
15
Ich sehe wirklich keinen Sinn darin, das values()Array in eine nicht modifizierbare Liste umzuwandeln . Das VALUESObjekt ist bereits durch Deklaration gekapselt private. Es wäre einfacher UND effizienter, es zu machen private static final Letter[] VALUES = ....
Stephen C
4
Arrays in Java sind veränderbar. Wenn Sie also ein Array-Feld haben und es in einer öffentlichen Methode zurückgeben, kann der Aufrufer es ändern und das private Feld so ändern, dass Sie das Array defensiv kopieren müssen. Wenn Sie diese Methode häufig aufrufen, kann dies ein Problem sein. Fügen Sie sie stattdessen in eine unveränderliche Liste ein, um unnötiges defensives Kopieren zu vermeiden.
Cletus
1
@cletus: Enum.values ​​() gibt bei jedem Aufruf ein neues Array zurück, sodass es nicht umbrochen werden muss, bevor es an anderen Stellen übergeben / verwendet wird.
Chii
5
private static final Letter [] VALUES ... ist in Ordnung. Es ist privat, also unveränderlich. Sie benötigen nur die öffentliche randomLetter () -Methode, die offensichtlich einen einzelnen Wert zurückgibt. Stephen C hat recht.
Helios
125

Eine einzige Methode ist alles, was Sie für alle Ihre zufälligen Aufzählungen benötigen:

    public static <T extends Enum<?>> T randomEnum(Class<T> clazz){
        int x = random.nextInt(clazz.getEnumConstants().length);
        return clazz.getEnumConstants()[x];
    }

Welche Sie verwenden werden:

randomEnum(MyEnum.class);

Ich bevorzuge es auch, SecureRandom zu verwenden als:

private static final SecureRandom random = new SecureRandom();
Eldelshell
quelle
1
Genau das, wonach ich gesucht habe. Ich mochte die akzeptierte Antwort und das ließ mich mit Boilerplate-Code zurück, als ich aus meiner zweiten Aufzählung zufällig auswählen musste. Außerdem kann man SecureRandom manchmal leicht vergessen. Vielen Dank.
Siamaster
Sie haben meine Gedanken gelesen, genau das, wonach ich gesucht habe, um sie in meine Testklasse für Generatoren für zufällige Entitäten aufzunehmen. Vielen Dank für die Hilfe
Roque Sosa
43

Die Vorschläge von Cletus und Helios kombinieren ,

import java.util.Random;

public class EnumTest {

    private enum Season { WINTER, SPRING, SUMMER, FALL }

    private static final RandomEnum<Season> r =
        new RandomEnum<Season>(Season.class);

    public static void main(String[] args) {
        System.out.println(r.random());
    }

    private static class RandomEnum<E extends Enum<E>> {

        private static final Random RND = new Random();
        private final E[] values;

        public RandomEnum(Class<E> token) {
            values = token.getEnumConstants();
        }

        public E random() {
            return values[RND.nextInt(values.length)];
        }
    }
}

Edit: Ups, ich habe den begrenzten Typparameter vergessen <E extends Enum<E>>.

Müllgott
quelle
1
Siehe auch Klassenliterale als Laufzeit-Token .
Trashgod
1
Sehr alte Antwort, die ich kenne, aber sollte das nicht sein E extends Enum<E>?
Lino - Abstimmung sagen nicht Danke
1
@Lino: Aus Gründen der Übersichtlichkeit bearbeitet; Ich denke nicht, dass es für die korrekte Typinferenz des gebundenen Parameters erforderlich ist , aber ich würde eine Korrektur begrüßen. Beachten Sie auch, dass new RandomEnum<>(Season.class)seit Java 7 erlaubt ist.
trashgod
Diese einzelne RandomEnumKlasse wäre als Mikrobibliothek gut geeignet, wenn Sie sie bündeln und in der Zentrale veröffentlichen möchten.
Greg Chabala
34

Einzelne Zeile

return Letter.values()[new Random().nextInt(Letter.values().length)];
Mohamed Taher Alrefaie
quelle
10

Stimmen Sie mit Stphen C & Helios überein. Der bessere Weg, um zufällige Elemente aus Enum abzurufen, ist:

public enum Letter {
  A,
  B,
  C,
  //...

  private static final Letter[] VALUES = values();
  private static final int SIZE = VALUES.length;
  private static final Random RANDOM = new Random();

  public static Letter getRandomLetter()  {
    return VALUES[RANDOM.nextInt(SIZE)];
  }
}
Deepti
quelle
7
Letter lettre = Letter.values()[(int)(Math.random()*Letter.values().length)];
anonym
quelle
5

Dies ist wahrscheinlich der prägnanteste Weg, um Ihr Ziel zu erreichen. Sie müssen nur anrufen Letter.getRandom()und erhalten einen zufälligen Aufzählungsbrief.

public enum Letter {
    A,
    B,
    C,
    //...

    public static Letter getRandom() {
        return values()[(int) (Math.random() * values().length)];
    }
}
Adilli Adil
quelle
4

Es ist wahrscheinlich am einfachsten, eine Funktion zu haben, um einen zufälligen Wert aus einem Array auszuwählen. Dies ist allgemeiner und einfach aufzurufen.

<T> T randomValue(T[] values) {
    return values[mRandom.nextInt(values.length)];
}

Rufen Sie so an:

MyEnum value = randomValue(MyEnum.values());
Joseph Thomson
quelle
4

Hier eine Version, die Shuffle und Streams verwendet

List<Direction> letters = Arrays.asList(Direction.values());
Collections.shuffle(letters);
return letters.stream().findFirst().get();
Major Seitan
quelle
4

Einfache Kotlin-Lösung

MyEnum.values().random()

random()ist eine Standarderweiterungsfunktion, die in Base Kotlin für das CollectionObjekt enthalten ist. Kotlin-Dokumentationslink

Wenn Sie es mit einer Erweiterungsfunktion vereinfachen möchten, versuchen Sie Folgendes:

inline fun <reified T : Enum<T>> random(): T = enumValues<T>().random()

// Then call
random<MyEnum>()

Um es in Ihrer Enum-Klasse statisch zu machen. Stellen Sie sicher, dass Sie my.package.randomin Ihre Aufzählungsdatei importieren

MyEnum.randomValue()

// Add this to your enum class
companion object {
    fun randomValue(): MyEnum {
        return random()
    }
}

Wenn Sie dies von einer Instanz der Aufzählung aus tun müssen, versuchen Sie diese Erweiterung

inline fun <reified T : Enum<T>> T.random() = enumValues<T>().random()

// Then call
MyEnum.VALUE.random() // or myEnumVal.random() 
Gibolt
quelle
3

Wenn Sie dies zum Testen tun, können Sie Quickcheck verwenden ( dies ist ein Java-Port, an dem ich gearbeitet habe ).

import static net.java.quickcheck.generator.PrimitiveGeneratorSamples.*;

TimeUnit anyEnumValue = anyEnumValue(TimeUnit.class); //one value

Es unterstützt alle primitiven Typen, Typzusammensetzungen, Sammlungen, verschiedene Verteilungsfunktionen, Grenzen usw. Es unterstützt Läufer, die mehrere Werte ausführen:

import static net.java.quickcheck.generator.PrimitiveGeneratorsIterables.*;

for(TimeUnit timeUnit : someEnumValues(TimeUnit.class)){
    //..test multiple values
}

Der Vorteil von Quickcheck besteht darin, dass Sie Tests basierend auf einer Spezifikation definieren können, bei der einfaches TDD mit Szenarien arbeitet.

Thomas Jung
quelle
sieht faszinierend aus. Ich muss es versuchen.
Nick Heiner
Sie können mir eine E-Mail senden, wenn etwas nicht funktioniert. Sie sollten die Version 0.5b verwenden.
Thomas Jung
2

Es ist einfacher, eine Zufallsfunktion für die Aufzählung zu implementieren.

public enum Via {
    A, B;

public static Via viaAleatoria(){
    Via[] vias = Via.values();
    Random generator = new Random();
    return vias[generator.nextInt(vias.length)];
    }
}

und dann rufst du es aus der Klasse an, die du so brauchst

public class Guardia{
private Via viaActiva;

public Guardia(){
    viaActiva = Via.viaAleatoria();
}
Folea
quelle
2

Ich würde dies verwenden:

private static Random random = new Random();

public Object getRandomFromEnum(Class<? extends Enum<?>> clazz) {
    return clazz.values()[random.nextInt(clazz.values().length)];
}
Konstantin Pawlow
quelle
1

Ich denke, dass diese Single-Line-Return-Methode effizient genug ist, um in einem so einfachen Job verwendet zu werden:

public enum Day {
    SUNDAY,
    MONDAY,
    THURSDAY,
    WEDNESDAY,
    TUESDAY,
    FRIDAY;

    public static Day getRandom() {
        return values()[(int) (Math.random() * values().length)];
    }

    public static void main(String[] args) {
        System.out.println(Day.getRandom());
    }
}
Muhammad Zidan
quelle