Choisissez une valeur aléatoire dans une énumération?

162

Si j'ai une énumération comme celle-ci:

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

Quelle est la meilleure façon d'en choisir un au hasard? Il n'a pas besoin d'être à l'épreuve des balles de qualité de production, mais une distribution assez uniforme serait bien.

Je pourrais faire quelque chose comme ça

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

Mais y a-t-il une meilleure façon? J'ai l'impression que c'est quelque chose qui a déjà été résolu.

Nick Heiner
la source
qu'est-ce qui ne va pas avec votre solution? Cela me semble plutôt bien.
President James K. Polk
1
@GregS - le problème est que chaque appel à Letter.values()doit créer une nouvelle copie du Lettertableau de valeurs interne .
Stephen C

Réponses:

144

La seule chose que je suggérerais est de mettre en cache le résultat, values()car chaque appel copie un tableau. De plus, ne créez pas de fichier à Randomchaque fois. Gardez-en un. A part ça, ce que vous faites est bien. Alors:

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
la source
8
Si vous le jugez utile, vous pouvez créer une classe utilitaire pour ce faire. Quelque chose comme RandomEnum <T étend Enum> avec un constructeur recevant la classe <T> pour créer la liste.
helios
15
Je ne vois vraiment pas l'intérêt de convertir le values()tableau en une liste non modifiable. L' VALUESobjet est déjà encapsulé en raison de sa déclaration private. Ce serait plus simple ET plus efficace de le faire private static final Letter[] VALUES = ....
Stephen C
4
Les tableaux en Java sont modifiables, donc si vous avez un champ de tableau et le renvoyez dans une méthode publique, l'appelant peut le modifier et il modifie le fichier privé, vous devez donc copier le tableau de manière défensive. Si vous appelez cette méthode plusieurs fois, cela peut être un problème, vous la mettez donc dans une liste immuable à la place pour éviter une copie défensive inutile.
cletus
1
@cletus: Enum.values ​​() renverra un nouveau tableau à chaque invocation, il n'est donc pas nécessaire de l'envelopper avant de le passer / de l'utiliser à d'autres endroits.
Chii
5
Private static final Letter [] VALUES ... est OK. C'est privé, donc c'est immuable. Vous n'avez besoin que de la méthode publique randomLetter () qui renvoie évidemment une seule valeur. Stephen C a raison.
helios
126

Une seule méthode est tout ce dont vous avez besoin pour toutes vos énumérations aléatoires:

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

Que vous utiliserez:

randomEnum(MyEnum.class);

Je préfère également utiliser SecureRandom comme:

private static final SecureRandom random = new SecureRandom();
Eldelshell
la source
1
Exactement ce que je cherchais. Je faisais comme la réponse acceptée et cela m'a laissé avec un code standard lorsque je devais randomiser à partir de mon deuxième Enum. De plus, il est parfois facile d'oublier SecureRandom. Merci.
Siamaster
Vous avez lu dans mes pensées, exactement ce que je cherchais à ajouter dans ma classe de test de générateur d'entités aléatoires. Merci pour l'aide
Roque Sosa
43

Combinant les suggestions de cletus et helios ,

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: Oops, j'ai oublié le paramètre de type borné, <E extends Enum<E>>.

trashgod
la source
1
Voir aussi Class Literals as Runtime-Type Tokens .
trashgod
1
Très vieille réponse, je sais, mais ne devrait-il pas être E extends Enum<E>?
Lino - Votez, ne dites pas merci
1
@Lino: édité pour plus de clarté; Je ne pense pas que ce soit nécessaire pour une inférence de type correcte de la limite de paramètre, mais j'aimerais une correction; notez aussi que new RandomEnum<>(Season.class)c'est autorisé depuis Java 7.
trashgod
Cette RandomEnumclasse unique serait bien comme une micro-bibliothèque, si vous vouliez la regrouper et la publier dans central.
Greg Chabala
34

Une seule ligne

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

D'accord avec Stphen C & helios. Le meilleur moyen d'extraire un élément aléatoire d'Enum est:

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
la source
7
Letter lettre = Letter.values()[(int)(Math.random()*Letter.values().length)];
anonyme
la source
5

C'est probablement le moyen le plus concis d'atteindre votre objectif. Tout ce que vous avez à faire est d'appeler Letter.getRandom()et vous obtiendrez une lettre d'énumération aléatoire.

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

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

Solution Kotlin simple

MyEnum.values().random()

random()est une fonction d'extension par défaut incluse dans la base Kotlin sur l' Collectionobjet. Lien vers la documentation Kotlin

Si vous souhaitez le simplifier avec une fonction d'extension, essayez ceci:

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

// Then call
random<MyEnum>()

Pour le rendre statique sur votre classe enum. Assurez-vous d'importer my.package.randomdans votre fichier enum

MyEnum.randomValue()

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

Si vous devez le faire à partir d'une instance de l'énumération, essayez cette extension

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

// Then call
MyEnum.VALUE.random() // or myEnumVal.random() 
Gibolt
la source
4

Il est probablement plus simple d'avoir une fonction pour choisir une valeur aléatoire dans un tableau. C'est plus générique et simple à appeler.

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

Appelez comme ça:

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

Voici une version qui utilise shuffle et streams

List<Direction> letters = Arrays.asList(Direction.values());
Collections.shuffle(letters);
return letters.stream().findFirst().get();
seitan majeur
la source
3

Si vous faites cela pour tester, vous pouvez utiliser Quickcheck ( c'est un port Java sur lequel j'ai travaillé ).

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

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

Il prend en charge tous les types primitifs, la composition de type, les collections, les différentes fonctions de distribution, les limites, etc. Il prend en charge les coureurs exécutant plusieurs valeurs:

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

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

L'avantage de Quickcheck est que vous pouvez définir des tests basés sur une spécification où le TDD simple fonctionne avec des scénarios.

Thomas Jung
la source
semble intrigant. Je vais devoir essayer.
Nick Heiner
Vous pouvez m'envoyer un mail si quelque chose ne fonctionne pas. Vous devez utiliser la version 0.5b.
Thomas Jung
2

Il est facile d'implémenter une fonction aléatoire sur l'énumération.

public enum Via {
    A, B;

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

et puis tu l'appelles depuis la classe dont tu as besoin comme ça

public class Guardia{
private Via viaActiva;

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

J'utiliserais ceci:

private static Random random = new Random();

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

Je suppose que cette méthode de retour sur une seule ligne est suffisamment efficace pour être utilisée dans un travail aussi simple:

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
la source