Виберіть випадкову величину з перерахунку?


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масиву значень.
Стівен C

Відповіді:


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 = ....
Стівен C

4
Масиви на Java є змінними, тому якщо у вас є поле масиву і повертаєте його у відкритому методі, абонент може змінити його та змінити приватний поданий файл, тому вам потрібно захищати копіювати масив. Якщо ви закликаєте цей метод багато разів, це може бути проблемою, тому ви помістите його в незмінний список, щоб уникнути зайвого захисного копіювання.
кліт

1
@cletus: Enum.values ​​() поверне новий масив під час кожного виклику, тому немає необхідності обгортати його перед тим, як передавати / використовувати його в інших місцях.
Chii

5
приватний статичний підсумковий лист [] 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. Дякую.
Сіамастер

Ви читаєте мій погляд, саме те, що я шукав, щоб додати у свій тестовий клас генератора випадкових сутностей. Дякуємо за допомогу
Roque Sosa

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

Просте рішення Котліна

MyEnum.values().random()

random()є функцією розширення за замовчуванням, включеною в базовий Kotlin Collectionоб'єкта.Довідка по документації Котліна

Якщо ви хочете спростити її за допомогою функції розширення, спробуйте:

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

// Then call
random<MyEnum>()

Щоб стати статичним у вашому класі перерахунків. Переконайтесь, що імпортуєте 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

Тут версія, що використовує перетасування та потоки

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.