¿Elegir un valor aleatorio de una enumeración?

161

Si tengo una enumeración como esta:

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

¿Cuál es la mejor manera de elegir uno al azar? No necesita ser a prueba de balas de calidad de producción, pero una distribución bastante uniforme sería buena.

Podría hacer algo como esto

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

Pero hay una manera mejor? Siento que esto es algo que se ha resuelto antes.

Nick Heiner
fuente
¿Qué crees que está mal con tu solución? Se ve bastante bien para mí.
Presidente James K. Polk
1
@GregS: el problema es que cada llamada Letter.values()a crear una nueva copia de la Lettermatriz de valores internos .
Stephen C

Respuestas:

144

Lo único que sugeriría es el almacenamiento en caché del resultado values()porque cada llamada copia una matriz. Además, no cree una Randomcada vez. Conserva uno. Aparte de eso, lo que estás haciendo está bien. Entonces:

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
fuente
8
Si lo considera útil, puede crear una clase de utilidad para hacerlo. Algo así como RandomEnum <T extiende Enum> con un constructor que recibe la Clase <T> para crear la lista.
helios
15
Realmente no veo el punto de convertir la values()matriz a una lista no modificable. El VALUESobjeto ya está encapsulado en virtud de ser declarado private. Sería más simple Y más eficiente hacerlo private static final Letter[] VALUES = ....
Stephen C
44
Las matrices en Java son mutables, por lo que si tiene un campo de matriz y lo devuelve en un método público, la persona que llama puede modificarlo y modifica el archivo privado, por lo que debe copiar defensivamente la matriz. Si llama a ese método muchas veces, puede ser un problema, por lo que lo coloca en una lista inmutable para evitar copias defensivas innecesarias.
cletus
1
@cletus: Enum.values ​​() devolverá una nueva matriz en cada invocación, por lo que no es necesario ajustarla antes de pasarla / usarla en otros lugares.
Chii
55
valores estáticos privados finales [] VALORES ... está bien. Es privado, así que es inmutable. Solo necesita el método public randomLetter () que obviamente devuelve un solo valor. Stephen C tiene razón.
helios
126

Un único método es todo lo que necesita para todas sus enumeraciones aleatorias:

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

Cuál usarás:

randomEnum(MyEnum.class);

También prefiero usar SecureRandom como:

private static final SecureRandom random = new SecureRandom();
Eldelshell
fuente
1
Exactamente lo que estaba buscando. Estaba haciendo como la respuesta aceptada y eso me dejó con un código repetitivo cuando necesitaba aleatorizar mi segundo Enum. Además, a veces es fácil olvidarse de SecureRandom. Gracias.
Siamaster
Leíste mi mente, exactamente lo que estaba buscando agregar en mi clase de prueba de generador de entidades aleatorias. Gracias por la ayuda
Roque Sosa
43

Combinando las sugerencias de cletus y 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)];
        }
    }
}

Editar: Vaya, se me olvidó el parámetro de tipo limitado, <E extends Enum<E>>.

trashgod
fuente
1
Muy antigua respuesta, lo sé, pero ¿no debería ser así E extends Enum<E>?
Lino - Vota no digas Gracias
1
@Lino: editado para mayor claridad; No creo que sea necesario para la inferencia de tipo correcta del parámetro enlazado, pero agradecería la corrección; tenga en cuenta también que new RandomEnum<>(Season.class)está permitido desde Java 7.
trashgod
Esta RandomEnumclase única sería buena como una micro biblioteca, si quisiera agruparla y publicarla en central.
Greg Chabala
34

Linea sola

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

De acuerdo con Stphen C y helios. La mejor manera de obtener elementos aleatorios de Enum es:

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
fuente
7
Letter lettre = Letter.values()[(int)(Math.random()*Letter.values().length)];
anónimo
fuente
5

Esta es probablemente la forma más concisa de lograr su objetivo. Todo lo que necesita hacer es llamar Letter.getRandom()y recibirá una carta de enumeración aleatoria.

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

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

Probablemente sea más fácil tener una función para elegir un valor aleatorio de una matriz. Esto es más genérico y es fácil de llamar.

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

Llame así:

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

Aquí una versión que usa shuffle y streams

List<Direction> letters = Arrays.asList(Direction.values());
Collections.shuffle(letters);
return letters.stream().findFirst().get();
seitán mayor
fuente
4

Solución simple de Kotlin

MyEnum.values().random()

random()es una función de extensión predeterminada incluida en la base Kotlin en el Collectionobjeto. Enlace de documentación de Kotlin

Si desea simplificarlo con una función de extensión, intente esto:

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

// Then call
random<MyEnum>()

Para hacerlo estático en su clase de enumeración. Asegúrese de importar my.package.randomen su archivo de enumeración

MyEnum.randomValue()

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

Si necesita hacerlo desde una instancia de la enumeración, pruebe esta extensión

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

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

Si hace esto para probar, podría usar Quickcheck ( este es un puerto Java en el que he estado trabajando ).

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

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

Admite todos los tipos primitivos, composición de tipos, colecciones, diferentes funciones de distribución, límites, etc. Admite corredores que ejecutan múltiples valores:

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

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

La ventaja de Quickcheck es que puede definir pruebas basadas en una especificación donde TDD simple funciona con escenarios.

Thomas Jung
fuente
se ve intrigante. Tendré que intentarlo.
Nick Heiner
Puedes enviarme un correo si algo no funciona. Deberías usar la versión 0.5b.
Thomas Jung
2

Es fácil implementar una función aleatoria en la enumeración.

public enum Via {
    A, B;

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

y luego lo llamas desde la clase lo necesitas así

public class Guardia{
private Via viaActiva;

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

Yo usaría esto:

private static Random random = new Random();

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

Supongo que este método de retorno de una sola línea es lo suficientemente eficiente como para ser utilizado en un trabajo tan 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
fuente