Escolha um valor aleatório de uma enumeração?

161

Se eu tenho um enum como este:

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

Qual é a melhor maneira de escolher uma aleatoriamente? Não precisa ser à prova de balas com qualidade de produção, mas uma distribuição bastante uniforme seria boa.

Eu poderia fazer algo assim

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

Mas existe uma maneira melhor? Eu sinto que isso é algo que já foi resolvido antes.

Nick Heiner
fonte
o que você acha que está errado com sua solução? Parece muito bom para mim.
Presidente James K. Polk
1
@ GregS - o problema é que cada chamada Letter.values()para criar uma nova cópia da Lettermatriz de valor interno .
Stephen C

Respostas:

143

A única coisa que eu sugeriria é armazenar em cache o resultado values()porque cada chamada copia uma matriz. Além disso, não crie Randomsempre. Mantenha um. Fora isso, o que você está fazendo está bem. Assim:

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));
  }
}
cleto
fonte
8
Se você considerar útil, poderá criar uma classe de utilitário para fazer isso. Algo como RandomEnum <T estende Enum> com um construtor recebendo a Classe <T> para criar a lista.
helios
15
Realmente não vejo o ponto de converter a values()matriz em uma lista não modificável. O VALUESobjeto já está encapsulado em virtude de ser declarado private. Seria mais simples e mais eficiente fazê-lo private static final Letter[] VALUES = ....
Stephen C
4
As matrizes em Java são mutáveis; portanto, se você tiver um campo de matriz e retorná-lo em um método público, o chamador poderá modificá-lo e modificar o arquivado particular, para que você precise copiar defensivamente a matriz. Se você chamar esse método várias vezes, pode ser um problema; por isso, coloque-o em uma lista imutável para evitar cópias defensivas desnecessárias.
Cletus
1
@cletus: Enum.values ​​() retornará uma nova matriz a cada chamada, portanto não há necessidade de agrupá-la antes de passar / usá-la em outros lugares.
29210 Chii
5
carta estática final privada [] VALUES ... está OK. É privado, por isso é imutável. Você só precisa do método público randomLetter () que obviamente retorna um único valor. Stephen C está certo.
helios
126

Um único método é tudo o que você precisa para todas as suas enumerações aleatórias:

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

Que você usará:

randomEnum(MyEnum.class);

Também prefiro usar o SecureRandom como:

private static final SecureRandom random = new SecureRandom();
Eldelshell
fonte
1
Exatamente o que eu estava procurando. Eu estava agindo como a resposta aceita e isso me deixou com o código padrão quando precisei aleatoriamente do meu segundo Enum. Além disso, às vezes é fácil esquecer o SecureRandom. Obrigado.
Siamaster 30/07/19
Você leu minha mente, exatamente o que eu estava procurando para adicionar na minha classe de teste de gerador de entidades aleatórias. Obrigado pela ajuda
Roque Sosa
43

Combinando as sugestões de cletus e 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: Opa, esqueci o parâmetro do tipo delimitado <E extends Enum<E>>,.

trashgod
fonte
1
Resposta muito antiga, eu sei, mas não deveria ser E extends Enum<E>?
Lino - O voto não diz obrigado
1
@Lino: Editado para maior clareza; Eu não acho que seja necessário para a inferência correta de tipo do parâmetro ligado, mas eu gostaria de receber a correção; Note também que new RandomEnum<>(Season.class)é permitido desde Java 7.
trashgod
Essa RandomEnumclasse única seria legal como uma micro biblioteca, se você quisesse agrupá-la e publicá-la na central.
Greg Chabala
34

Única linha

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

Concordo com Stphen C & helios. A melhor maneira de buscar o elemento aleatório do Enum é:

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

Esta é provavelmente a maneira mais concisa de atingir seu objetivo. Tudo o que você precisa fazer é ligar Letter.getRandom()e você receberá uma carta enum aleatória.

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

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

Provavelmente é mais fácil ter uma função para escolher um valor aleatório de uma matriz. Isso é mais genérico e é fácil de chamar.

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

Ligue assim:

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

Aqui uma versão que usa shuffle e streams

List<Direction> letters = Arrays.asList(Direction.values());
Collections.shuffle(letters);
return letters.stream().findFirst().get();
major seitan
fonte
4

Solução Kotlin simples

MyEnum.values().random()

random()é uma função de extensão padrão incluída no Kotlin base no Collectionobjeto. Link da documentação do Kotlin

Se você deseja simplificá-lo com uma função de extensão, tente o seguinte:

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

// Then call
random<MyEnum>()

Para torná-lo estático na sua classe enum. Certifique-se de importar my.package.randomno seu arquivo enum

MyEnum.randomValue()

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

Se você precisar fazer isso a partir de uma instância da enum, tente esta extensão

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

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

Se você fizer isso para testar, poderá usar o Quickcheck ( esta é uma porta Java em que estou trabalhando ).

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

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

Ele suporta todos os tipos primitivos, composição de tipos, coleções, diferentes funções de distribuição, limites etc. Tem suporte para corredores executando vários valores:

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

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

A vantagem do Quickcheck é que você pode definir testes com base em uma especificação em que o TDD comum trabalha com cenários.

Thomas Jung
fonte
parece intrigante. Vou ter que tentar.
31510 Nick Heiner
Você pode me enviar um email se algo não funcionar. Você deve usar a versão 0.5b.
Thomas Jung
2

É ansioso para implementar uma função aleatória no enum.

public enum Via {
    A, B;

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

e então você chama da classe que você precisa assim

public class Guardia{
private Via viaActiva;

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

Eu usaria isso:

private static Random random = new Random();

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

Eu acho que esse método de retorno de linha única é eficiente o suficiente para ser usado em um trabalho tão simples:

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
fonte