Pilih nilai acak dari enum?


161

Jika saya memiliki enum seperti ini:

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

Apa cara terbaik untuk memilih satu secara acak? Itu tidak perlu menjadi antipeluru kualitas produksi, tetapi distribusi yang adil akan lebih baik.

Saya bisa melakukan sesuatu seperti ini

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

Tetapi apakah ada cara yang lebih baik? Saya merasa ini adalah sesuatu yang sudah dipecahkan sebelumnya.


apa yang menurut Anda salah dengan solusi Anda? Terlihat bagus untukku.
Presiden James K. Polk

1
@Regs - masalahnya adalah bahwa setiap panggilan Letter.values()harus membuat salinan baru dari Letterarray nilai internal .
Stephen C

Jawaban:


144

Satu-satunya hal yang saya sarankan adalah hasil caching values()karena setiap panggilan menyalin array. Juga, jangan membuat Randomsetiap waktu. Simpan satu. Selain itu apa yang Anda lakukan baik-baik saja. Begitu:

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
Jika Anda menganggapnya berguna, Anda dapat membuat kelas utilitas untuk melakukan ini. Sesuatu seperti RandomEnum <T extends Enum> dengan konstruktor yang menerima Kelas <T> untuk membuat daftar.
helios

15
Saya benar-benar tidak melihat titik mengubah values()array ke daftar yang tidak dapat dimodifikasi. The VALUESobjek sudah dirumuskan berdasarkan yang dinyatakan private. Akan lebih sederhana DAN lebih efisien untuk membuatnya private static final Letter[] VALUES = ....
Stephen C

4
Array di Java bisa berubah, jadi jika Anda memiliki bidang array dan mengembalikannya dalam metode publik pemanggil dapat memodifikasinya dan memodifikasi pribadi yang diajukan sehingga Anda perlu menyalin array secara defensif. Jika Anda memanggil metode itu berkali-kali itu bisa menjadi masalah sehingga Anda memasukkannya ke dalam daftar yang tidak dapat diubah untuk menghindari penyalinan defensif yang tidak perlu.
cletus

1
@cletus: Enum.values ​​() akan mengembalikan array baru pada setiap permintaan, jadi tidak perlu membungkusnya sebelum meneruskan / menggunakannya di tempat lain.
Chii

5
private static final Letter [] VALUES ... tidak masalah. Ini pribadi sehingga tidak dapat ditentukan. Anda hanya perlu metode public randomLetter () yang jelas mengembalikan nilai tunggal. Stephen C benar.
helios

126

Hanya satu metode yang Anda butuhkan untuk semua enum acak Anda:

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

Yang akan Anda gunakan:

randomEnum(MyEnum.class);

Saya juga lebih suka menggunakan SecureRandom sebagai:

private static final SecureRandom random = new SecureRandom();

1
Persis apa yang saya cari. Saya melakukan seperti jawaban yang diterima dan itu meninggalkan saya dengan kode boilerplate ketika saya perlu mengacak dari Enum kedua saya. Juga, kadang-kadang mudah melupakan SecureRandom. Terima kasih.
Siamaster

Anda membaca pikiran saya, persis apa yang saya cari untuk ditambahkan dalam kelas uji entitas entitas acak saya. Terima kasih atas bantuannya
Roque Sosa

43

Menggabungkan saran dari cletus dan 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)];
        }
    }
}

Sunting: Ups, saya lupa parameter tipe yang dibatasi <E extends Enum<E>>,.



1
Saya tahu jawaban yang sangat lama, tetapi bukankah itu seharusnya E extends Enum<E>?
Lino - Vote jangan ucapkan Terima kasih

1
@Lino: Diedit untuk kejelasan; Saya tidak berpikir itu diperlukan untuk tipe inferensi yang benar dari parameter terikat, tapi saya akan menerima koreksi; perhatikan juga yang new RandomEnum<>(Season.class)diizinkan sejak Java 7.
trashgod

RandomEnumKelas tunggal ini akan menyenangkan sebagai perpustakaan mikro, jika Anda ingin menggabungkannya dan menerbitkannya ke pusat.
Greg Chabala


10

Setuju dengan Stphen C & helios. Cara yang lebih baik untuk mengambil elemen acak dari Enum adalah:

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

Ini mungkin cara paling ringkas untuk mencapai tujuan Anda. Yang perlu Anda lakukan adalah menelepon Letter.getRandom()dan Anda akan mendapatkan surat enum acak.

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

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

4

Mungkin paling mudah memiliki fungsi untuk memilih nilai acak dari sebuah array. Ini lebih umum, dan mudah untuk dipanggil.

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

Sebut seperti ini:

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

4

Di sini versi yang menggunakan shuffle dan stream

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

4

Solusi Kotlin Sederhana

MyEnum.values().random()

random()adalah fungsi ekstensi default yang disertakan dalam basis Kotlin pada Collectionobjek. Tautan Dokumentasi Kotlin

Jika Anda ingin menyederhanakannya dengan fungsi ekstensi, coba ini:

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

// Then call
random<MyEnum>()

Untuk membuatnya statis di kelas enum Anda. Pastikan untuk mengimpor my.package.randomfile enum Anda

MyEnum.randomValue()

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

Jika Anda perlu melakukannya dari instance enum, coba ekstensi ini

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

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

3

Jika Anda melakukan ini untuk pengujian, Anda dapat menggunakan Quickcheck ( ini adalah port Java yang telah saya kerjakan ).

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

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

Ini mendukung semua jenis primitif, komposisi jenis, koleksi, fungsi distribusi yang berbeda, batas dll. Ini memiliki dukungan untuk pelari yang menjalankan beberapa nilai:

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

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

Keuntungan Quickcheck adalah Anda dapat menentukan tes berdasarkan spesifikasi tempat TDD biasa bekerja dengan skenario.


terlihat menarik. Saya harus mencobanya.
Nick Heiner

Anda dapat mengirimi saya jika sesuatu tidak berfungsi. Anda harus menggunakan versi 0.5b.
Thomas Jung

2

Lebih mudah untuk mengimplementasikan fungsi acak pada enum.

public enum Via {
    A, B;

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

dan kemudian Anda menyebutnya dari kelas yang Anda butuhkan seperti ini

public class Guardia{
private Via viaActiva;

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

2

Saya akan menggunakan ini:

private static Random random = new Random();

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

1

Saya kira metode pengembalian satu-baris ini cukup efisien untuk digunakan dalam pekerjaan yang begitu sederhana:

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());
    }
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.