Выбрать случайное значение из перечисления?


162

Если у меня есть перечисление как это:

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

Каков наилучший способ выбрать один случайным образом? Это не должно быть пуленепробиваемым качеством продукции, но было бы неплохо равномерное распределение.

Я мог бы сделать что-то вроде этого

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

Но есть ли лучший способ? Я чувствую, что это то, что было решено раньше.


что вы думаете не так с вашим решением? Это выглядит довольно хорошо для меня.
Президент Джеймс К. Полк

1
@GregS - проблема в том, что каждый вызов Letter.values()должен создавать новую копию Letterмассива внутренних значений.
Стивен С

Ответы:


144

Единственное, что я хотел бы предложить, это кэшировать результат, values()потому что каждый вызов копирует массив. Кроме того, не создавайте Randomкаждый раз. Держи один. Кроме того, что вы делаете, это хорошо. Так:

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
Если вы считаете это полезным, вы можете создать вспомогательный класс для этого. Что-то вроде RandomEnum <T расширяет Enum> конструктором, получающим класс <T> для создания списка.
Гелиос

15
Я действительно не вижу смысла преобразовывать values()массив в неизменяемый список. VALUESОбъект уже инкапсулированные в силу декларируемых private. Было бы проще и эффективнее сделать это private static final Letter[] VALUES = ....
Стивен С

4
Массивы в Java являются изменяемыми, поэтому, если у вас есть поле массива и вы возвращаете его в публичном методе, вызывающая сторона может изменить его, и он изменяет приватное поле, поэтому вам нужно защищенно копировать массив. Если вы вызываете этот метод много раз, это может быть проблемой, поэтому вы помещаете его в неизменяемый список, чтобы избежать ненужного защитного копирования.
Клет

1
@cletus: Enum.values ​​() будет возвращать новый массив при каждом вызове, поэтому нет необходимости переносить его перед передачей / использованием в других местах.
Chii

5
private static final Letter [] VALUES ... все в порядке. Это личное, так что это незыблемо. Вам нужен только открытый метод randomLetter (), который, очевидно, возвращает одно значение. Стивен С. прав.
Гелиос

126

Один метод - это все, что вам нужно для всех ваших случайных перечислений:

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

Который вы будете использовать:

randomEnum(MyEnum.class);

Я также предпочитаю использовать SecureRandom как:

private static final SecureRandom random = new SecureRandom();

1
Именно то, что я искал. Я поступил как принятый ответ, и это оставило меня с шаблонным кодом, когда мне нужно было рандомизировать из моего второго Enum. Кроме того, иногда можно забыть о SecureRandom. Спасибо.
Siamaster

Вы читаете мои мысли, именно то, что я искал, чтобы добавить в мой тестовый класс генератора случайных объектов. Спасибо за помощь
Роке Соса

43

Объединяя предложения Клетуса и Гелиоса ,

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

Редактировать: Ой, я забыл параметр ограниченного типа <E extends Enum<E>>.



1
Я знаю очень старый ответ, но разве не так E extends Enum<E>?
Lino - Проголосуй, не говори спасибо

1
@Lino: отредактировано для ясности; Я не думаю, что это требуется для правильного вывода типа границы параметра, но я бы приветствовал исправление; обратите внимание также, что new RandomEnum<>(Season.class)это разрешено начиная с Java 7.
trashgod

Этот отдельный RandomEnumкласс был бы полезен в качестве микро-библиотеки, если вы хотите объединить его и опубликовать в центральной.
Грег


10

Согласитесь со Stphen C & helios. Лучший способ получить случайный элемент из 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)];
  }
}


5

Это, вероятно, самый краткий способ достижения вашей цели. Все, что вам нужно сделать, это позвонить, Letter.getRandom()и вы получите случайное перечисление.

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

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

5

Простое решение Kotlin

MyEnum.values().random()

random()является функцией расширения по умолчанию, включенной в базу Kotlin для Collectionобъекта. Kotlin Документация Ссылка

Если вы хотите упростить это с помощью функции расширения, попробуйте это:

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

// Then call
random<MyEnum>()

Чтобы сделать это статичным в вашем классе enum. Убедитесь, что вы импортировали my.package.randomфайл enum

MyEnum.randomValue()

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

Если вам нужно сделать это из экземпляра enum, попробуйте это расширение

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

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

4

Вероятно, проще всего иметь функцию для выбора случайного значения из массива. Это более общее и простое решение.

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

Звоните так:

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

4

Вот версия, которая использует shuffle и потоки

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

3

Если вы делаете это для тестирования, вы можете использовать Quickcheck ( это порт Java, над которым я работал ).

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

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

Он поддерживает все типы примитивов, состав типов, коллекции, различные функции распределения, границы и т. Д. Он имеет поддержку для бегунов, выполняющих несколько значений:

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

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

Преимущество Quickcheck заключается в том, что вы можете определять тесты на основе спецификации, в которой обычный TDD работает со сценариями.


выглядит интригующим Я должен попробовать.
Ник Хейнер

Вы можете написать мне, если что-то не работает. Вы должны использовать версию 0.5b.
Томас Юнг,

2

Легко реализовать случайную функцию в перечислении.

public enum Via {
    A, B;

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

а потом вы звоните из класса вам нужно это так

public class Guardia{
private Via viaActiva;

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

2

Я бы использовал это:

private static Random random = new Random();

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

1

Я предполагаю, что этот метод однострочного возврата достаточно эффективен, чтобы использовать его в такой простой работе:

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());
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.