201 votes

Choisir une valeur aléatoire dans un enum ?

Si j'ai un enum comme ceci :

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

Quelle est la meilleure façon d'en choisir un au hasard ? Il n'est pas nécessaire que la qualité de la production soit irréprochable, mais une distribution assez égale 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 existe-t-il un meilleur moyen ? J'ai l'impression que c'est quelque chose qui a déjà été résolu auparavant.

0 votes

Qu'est-ce qui ne va pas avec votre solution ? Elle me semble plutôt bonne.

1 votes

@GregS - le problème est que chaque appel à Letter.values() doit créer une nouvelle copie du fichier interne Letter tableau de valeurs.

0 votes

Je ne sais pas non plus si vous devez créer un nouvel objet Random à chaque fois que vous exécutez cette fonction.

172voto

cletus Points 276888

La seule chose que je suggérerais est de mettre en cache le résultat de values() car chaque appel copie un tableau. De même, ne créez pas de Random à chaque fois. Gardez-en un. A part ça, ce que tu fais est bien. Donc :

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));
  }
}

8 votes

Si vous le jugez utile, vous pouvez créer une classe utilitaire pour cela. Quelque chose comme RandomEnum<T extends Enum> avec un constructeur recevant la Class<T> pour créer la liste.

16 votes

Je ne vois vraiment pas l'intérêt de convertir les values() en une liste non modifiable. Le site VALUES est déjà encapsulé du fait qu'il est déclaré private . Il serait plus simple ET plus efficace de le rendre private static final Letter[] VALUES = ... .

5 votes

Les tableaux en Java sont mutables, donc si vous avez un champ de tableau et que vous le renvoyez dans une méthode publique, l'appelant peut le modifier et cela modifie le champ privé, donc vous devez copier le tableau de manière défensive. Si vous appelez cette méthode un grand nombre de fois, cela peut poser un problème. Vous pouvez donc la placer dans une liste immuable pour éviter toute copie défensive inutile.

158voto

Eldelshell Points 1414

Une seule méthode suffit pour tous vos enums 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();

1 votes

Exactement ce que je recherchais. Je faisais comme la réponse acceptée et cela me laissait avec du code passe-partout lorsque j'avais besoin de randomiser à partir de ma deuxième Enum. De plus, il est facile d'oublier SecureRandom parfois. Merci.

0 votes

Vous avez lu dans mon esprit, exactement ce que je cherchais pour ajouter dans ma classe de test de générateur d'entités aléatoires. Merci pour votre aide

46voto

trashgod Points 136305

En combinant les suggestions de cletus y hélios ,

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>> .

1 votes

1 votes

Très vieille réponse je sais, mais ça ne devrait pas être E extends Enum<E> ?

1 votes

@Lino : Edité pour plus de clarté ; je ne pense pas que ce soit requis pour une inférence de type correcte de la limite du paramètre, mais je serais heureux d'être corrigé ; notez également que new RandomEnum<>(Season.class) est autorisé depuis Java 7.

10voto

anonymous Points 21
Letter lettre = Letter.values()[(int)(Math.random()*Letter.values().length)];

3voto

Thomas Jung Points 17692

Si vous faites cela pour tester, vous pouvez utiliser Quickcheck ( Voici un portage 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 types, les collections, les différentes fonctions de distribution, les limites, etc. Il supporte les runners exécutant des valeurs multiples :

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 simple TDD fonctionne avec des scénarios.

0 votes

Semble intriguant. Je vais devoir l'essayer.

0 votes

Vous pouvez m'envoyer un mail si quelque chose ne fonctionne pas. Vous devriez utiliser la version 0.5b.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X