Scegli un valore casuale da un enum?


162

Se ho un enum come questo:

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

Qual è il modo migliore per sceglierne uno a caso? Non deve essere a prova di proiettile di qualità di produzione, ma una distribuzione abbastanza uniforme sarebbe piacevole.

Potrei fare qualcosa del genere

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

Ma c'è un modo migliore? Sento che questo è qualcosa che è stato risolto prima.


cosa pensi sia sbagliato nella tua soluzione? Mi sembra abbastanza buono.
Presidente James K. Polk,

1
@GregS: il problema è che ogni chiamata a Letter.values()deve creare una nuova copia Letterdell'array di valori interno .
Stephen C

Risposte:


144

L'unica cosa che vorrei suggerire è la memorizzazione nella cache del risultato values()perché ogni chiamata copia un array. Inoltre, non creare un Randomogni volta. Conservane uno. Diverso da quello che stai facendo va bene. Così:

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
Se lo ritieni utile, puoi creare una classe di utilità per farlo. Qualcosa come RandomEnum <T estende Enum> con un costruttore che riceve la Classe <T> per creare l'elenco.
helios

15
Non vedo davvero il punto di convertire l' values()array in un elenco non modificabile. L' VALUESoggetto è già incapsulato in virtù della sua dichiarazione private. Sarebbe più semplice ed efficiente renderlo private static final Letter[] VALUES = ....
Stephen C

4
Gli array in Java sono modificabili, quindi se si dispone di un campo array e lo si restituisce in un metodo pubblico, il chiamante può modificarlo e modifica il file privato, quindi è necessario copiare in modo difensivo l'array. Se si chiama questo metodo molte volte, può essere un problema, quindi inserirlo in un elenco immutabile invece di evitare inutili copie difensive.
cletus,

1
@cletus: Enum.values ​​() restituirà un nuovo array ad ogni invocazione, quindi non è necessario avvolgerlo prima di passarlo / usarlo in altri luoghi.
Chii,

5
lettera finale statica privata [] VALORI ... è OK. È privato quindi è immutabile. Hai solo bisogno del metodo randomLetter () pubblico che ovviamente restituisce un singolo valore. Stephen C ha ragione.
helios

126

Un unico metodo è tutto ciò che serve per tutti i tuoi enumerazioni casuali:

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

Che utilizzerai:

randomEnum(MyEnum.class);

Preferisco anche usare SecureRandom come:

private static final SecureRandom random = new SecureRandom();

1
Esattamente quello che stavo cercando. Stavo facendo come la risposta accettata e questo mi ha lasciato con il codice della caldaia quando avevo bisogno di randomizzare dal mio secondo Enum. Inoltre, a volte è facile dimenticare SecureRandom. Grazie.
Siamastro,

Mi leggi nella mente, esattamente quello che stavo cercando di aggiungere nella mia classe di test del generatore di entità casuali. Grazie per l'aiuto
Roque Sosa,

43

Combinando i suggerimenti di cletus ed elios ,

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, ho dimenticato il parametro di tipo limitato, <E extends Enum<E>>.



1
Risposta molto vecchia, lo so, ma non dovrebbe essere E extends Enum<E>?
Lino - Vota non dire Grazie

1
@Lino: a cura di chiarezza; Non credo sia necessario per la corretta inferenza del tipo del parametro associato, ma accetterei con favore la correzione; si noti inoltre che new RandomEnum<>(Season.class)è consentito dal Java 7.
trashgod

Questa singola RandomEnumclasse sarebbe gradita come una micro biblioteca, se volessi raggrupparla e pubblicarla in centrale.
Greg Chabala,


10

Concordo con Stphen C & helios. Il modo migliore per recuperare elementi casuali da 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)];
  }
}

7
Letter lettre = Letter.values()[(int)(Math.random()*Letter.values().length)];

5

Questo è probabilmente il modo più conciso per raggiungere il tuo obiettivo. Tutto quello che devi fare è chiamare Letter.getRandom()e riceverai una lettera enum casuale.

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

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

5

Soluzione Kotlin semplice

MyEnum.values().random()

random()è una funzione di estensione predefinita inclusa in Kotlin di base Collectionsull'oggetto. Link alla documentazione di Kotlin

Se desideri semplificarlo con una funzione di estensione, prova questo:

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

// Then call
random<MyEnum>()

Per renderlo statico nella tua classe enum. Assicurati di importare my.package.randomnel tuo file enum

MyEnum.randomValue()

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

Se devi farlo da un'istanza dell'enum, prova questa estensione

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

// Then call
MyEnum.VALUE.random() // or myEnumVal.random() 

4

Probabilmente è più semplice avere una funzione per scegliere un valore casuale da un array. Questo è più generico ed è semplice da chiamare.

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

Chiama così:

MyEnum value = randomValue(MyEnum.values());

4

Qui una versione che utilizza shuffle e stream

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

3

Se lo fai per i test potresti usare Quickcheck ( questa è una porta Java su cui sto lavorando ).

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

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

Supporta tutti i tipi primitivi, composizione dei tipi, raccolte, diverse funzioni di distribuzione, limiti ecc. Ha il supporto per i corridori che eseguono più valori:

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

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

Il vantaggio di Quickcheck è che è possibile definire test basati su una specifica in cui TDD semplice funziona con gli scenari.


sembra intrigante. Dovrò provarlo.
Nick Heiner,

Puoi scrivermi se qualcosa non funziona. È necessario utilizzare la versione 0.5b.
Thomas Jung,

2

È più desideroso implementare una funzione casuale sull'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 poi lo chiami dalla classe, ti serve così

public class Guardia{
private Via viaActiva;

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

2

Vorrei usare questo:

private static Random random = new Random();

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

1

Immagino che questo metodo di ritorno a riga singola sia abbastanza efficiente per essere utilizzato in un lavoro così semplice:

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());
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.