Wybrać losową wartość z wyliczenia?

162

Jeśli mam takie wyliczenie:

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

Jaki jest najlepszy sposób na losowe wybranie jednego? Nie musi być kuloodporna, ale dość równa dystrybucja byłaby miła.

Mógłbym zrobić coś takiego

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

Ale czy jest lepszy sposób? Czuję, że jest to coś, co zostało już wcześniej rozwiązane.

Nick Heiner
źródło
jak myślisz, co jest nie tak z twoim rozwiązaniem? Jak dla mnie wygląda całkiem nieźle.
Prezydent James K. Polk
1
@GregS - problem polega na tym, że każde wywołanie Letter.values()musi tworzyć nową kopię wewnętrznej Lettertablicy wartości.
Stephen C

Odpowiedzi:

144

Jedyne, co zasugerowałbym, to buforowanie wyniku, values()ponieważ każde wywołanie kopiuje tablicę. Nie twórz też za Randomkażdym razem. Zatrzymaj jeden. Poza tym to, co robisz, jest w porządku. Więc:

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
źródło
8
Jeśli uznasz to za przydatne, możesz stworzyć do tego klasę narzędziową. Coś w rodzaju RandomEnum <T rozszerza Enum> z konstruktorem odbierającym Class <T> do utworzenia listy.
helios
15
Naprawdę nie widzę sensu konwertowania values()tablicy na niemodyfikowalną listę. VALUESObiekt jest już zamknięty na mocy zadeklarowane private. Byłoby prostsze i wydajniejsze, aby to zrobić private static final Letter[] VALUES = ....
Stephen C
4
Tablice w Javie są zmienne, więc jeśli masz pole tablicowe i zwracasz je w metodzie publicznej, obiekt wywołujący może je modyfikować i modyfikuje pole prywatne, więc musisz defensywnie skopiować tablicę. Jeśli wywołujesz tę metodę wiele razy, może to stanowić problem, więc zamiast tego umieszczasz ją na niezmiennej liście, aby uniknąć niepotrzebnego kopiowania obronnego.
cletus
1
@cletus: Enum.values ​​() zwróci nową tablicę przy każdym wywołaniu, więc nie ma potrzeby jej zawijania przed przekazaniem / użyciem w innych miejscach.
Chii
5
prywatna statyczna końcowa litera [] WARTOŚCI ... jest OK. To jest prywatne, więc jest niezmienne. Potrzebujesz tylko metody public randomLetter (), która oczywiście zwraca pojedynczą wartość. Stephen C ma rację.
helios
126

Jedna metoda to wszystko, czego potrzebujesz do wszystkich losowych wyliczeń:

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

Którego użyjesz:

randomEnum(MyEnum.class);

Wolę też używać SecureRandom jako:

private static final SecureRandom random = new SecureRandom();
Eldelshell
źródło
1
Dokładnie to, czego szukałem. Robiłem tak, jak zaakceptowana odpowiedź i to zostawiło mnie z kodem standardowym, kiedy musiałem przeprowadzić randomizację z mojego drugiego Enum. Poza tym czasami łatwo zapomnieć o SecureRandom. Dzięki.
Siamaster
Czytasz w moich myślach, dokładnie to, czego szukałem, aby dodać w mojej klasie testowej generatora jednostek losowych. Dzięki za pomoc
Roque Sosa
43

Łącząc sugestie Cletus i 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: Ups, zapomniałem parametr typu ograniczone, <E extends Enum<E>>.

trashgod
źródło
1
Zobacz także Literały klasowe jako tokeny typu runtime .
trashgod
1
Bardzo stara odpowiedź, którą znam, ale czy nie powinna E extends Enum<E>?
Lino - Głosuj nie mów dzięki,
1
@Lino: zredagowano dla przejrzystości; Myślę, że nie jest to wymagane do prawidłowego wnioskowania o typie powiązanego parametru, ale z zadowoleniem przyjąłbym korektę; zauważ również, że new RandomEnum<>(Season.class)jest to dozwolone od wersji Java 7.
trashgod
Ta pojedyncza RandomEnumklasa byłaby fajna jako mikro biblioteka, gdybyś chciał ją spakować i opublikować w centrali.
Greg Chabala
34

Pojedyncza linia

return Letter.values()[new Random().nextInt(Letter.values().length)];
Mohamed Taher Alrefaie
źródło
10

Zgadzam się ze Stphenem C & heliosem. Lepszym sposobem na pobranie losowego elementu z Enum jest:

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
źródło
7
Letter lettre = Letter.values()[(int)(Math.random()*Letter.values().length)];
anonimowy
źródło
5

Jest to prawdopodobnie najbardziej zwięzły sposób na osiągnięcie celu, wystarczy zadzwonić, Letter.getRandom()a otrzymasz losową literę wyliczenia.

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

    public static Letter getRandom() {
        return values()[(int) (Math.random() * values().length)];
    }
}
Adilli Adil
źródło
5

Proste rozwiązanie Kotlin

MyEnum.values().random()

random()jest domyślną funkcją rozszerzającą zawartą w bazie Kotlin na Collectionobiekcie. Link do dokumentacji Kotlin

Jeśli chcesz to uprościć za pomocą funkcji rozszerzenia, spróbuj tego:

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

// Then call
random<MyEnum>()

Aby uczynić go statycznym w klasie wyliczenia. Pamiętaj, aby zaimportować my.package.randomplik wyliczeniowy

MyEnum.randomValue()

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

Jeśli musisz to zrobić z instancji wyliczenia, wypróbuj to rozszerzenie

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

// Then call
MyEnum.VALUE.random() // or myEnumVal.random() 
Gibolt
źródło
4

Prawdopodobnie najłatwiej jest mieć funkcję, która wybiera losową wartość z tablicy. Jest to bardziej ogólne i łatwe do wywołania.

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

Zadzwoń tak:

MyEnum value = randomValue(MyEnum.values());
Joseph Thomson
źródło
4

Tutaj wersja, która używa odtwarzania losowego i strumieni

List<Direction> letters = Arrays.asList(Direction.values());
Collections.shuffle(letters);
return letters.stream().findFirst().get();
major seitan
źródło
3

Jeśli zrobisz to do testowania można użyć QuickCheck ( to jest port Java Pracuję na ).

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

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

Obsługuje wszystkie typy pierwotne, skład typów, kolekcje, różne funkcje dystrybucji, granice itp. Obsługuje elementy uruchamiające wykonujące wiele wartości:

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

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

Zaletą Quickcheck jest to, że można definiować testy na podstawie specyfikacji, w której zwykły TDD działa ze scenariuszami.

Thomas Jung
źródło
wygląda intrygująco. Muszę spróbować.
Nick Heiner
Możesz do mnie napisać, jeśli coś nie działa. Powinieneś użyć wersji 0.5b.
Thomas Jung
2

Łatwiej jest zaimplementować losową funkcję na wyliczeniu.

public enum Via {
    A, B;

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

a potem dzwonisz z klasy, w której potrzebujesz tego w ten sposób

public class Guardia{
private Via viaActiva;

public Guardia(){
    viaActiva = Via.viaAleatoria();
}
Folea
źródło
2

Użyłbym tego:

private static Random random = new Random();

public Object getRandomFromEnum(Class<? extends Enum<?>> clazz) {
    return clazz.values()[random.nextInt(clazz.values().length)];
}
Konstantin Pavlov
źródło
1

Wydaje mi się, że ta metoda powrotu w jednym wierszu jest na tyle skuteczna, że ​​można jej użyć w tak prostej pracy:

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
źródło