Bir numaradan rastgele bir değer seçilsin mi?


162

Böyle bir numaralandırma varsa:

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

Birini rastgele seçmenin en iyi yolu nedir? Üretim kalitesinde kurşun geçirmez olması gerekmez, ancak oldukça eşit bir dağılım iyi olurdu.

Böyle bir şey yapabilirdim

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

Ama daha iyi bir yol var mı? Bunun daha önce çözülmüş bir şey olduğunu hissediyorum.


çözümünüzde neyin yanlış olduğunu düşünüyorsunuz? Bana çok iyi geliyor.
Başkan James K. Polk

1
@GregS - sorun, her çağrının Letter.values()dahili Letterdeğer dizisinin yeni bir kopyasını oluşturmak zorunda olmasıdır .
Stephen C

Yanıtlar:


144

Önerebileceğim tek şey, values()her çağrının bir diziyi kopyalaması nedeniyle sonucu önbelleğe almaktır. Ayrıca, Randomher seferinde yaratmayın . Birini sakla. Bunun dışında yaptığınız iyi. Yani:

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
Yararlı olduğunu düşünüyorsanız, bunu yapmak için bir yardımcı sınıf yapabilirsiniz. RandomEnum <T gibi bir şey Enum> 'u listeyi oluşturmak için <T> Sınıfını alan bir kurucu ile genişletir.
helios

15
values()Diziyi değiştirilemez bir listeye dönüştürme noktasını gerçekten görmüyorum . VALUESNesne zaten beyan olmasından dolayı kapsüllenir private. Bunu yapmak daha basit ve daha verimli olurdu private static final Letter[] VALUES = ....
Stephen C

4
Java'daki diziler değiştirilebilir, bu nedenle bir dizi alanınız varsa ve genel bir yöntemle döndürürseniz, arayan bunu değiştirebilir ve diziyi defansif olarak kopyalamanız gereken özel alanı değiştirir. Bu yöntemi birçok kez çağırırsanız, bir sorun olabilir, bu nedenle gereksiz savunma kopyalarını önlemek için bunu değiştirilemez bir listeye koyarsınız.
cletus

1
@cletus: Enum.values ​​() her çağrışımda yeni bir dizi döndürür, bu nedenle başka yerlerde geçirmeden / kullanmadan önce onu kaydırmaya gerek yoktur.
Chii

5
private static final Letter [] DEĞERLER ... tamam. Özeldir, bu yüzden aşılamaz. Yalnızca tek bir değer döndüren public randomLetter () yöntemine ihtiyacınız vardır. Stephen C haklı.
helios

126

Tüm rastgele numaralarınız için tek ihtiyacınız olan yöntem:

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

Hangisini kullanacaksınız:

randomEnum(MyEnum.class);

Ayrıca SecureRandom'u kullanmayı tercih ediyorum :

private static final SecureRandom random = new SecureRandom();

1
Tam aradığım şey. Kabul edilen cevap gibi yapıyordum ve ikinci Enum'umdan rasgele ayrılmam gerektiğinde beni ortak kodla bıraktı. Ayrıca, SecureRandom'u bazen unutmak kolaydır. Teşekkürler.
Siamaster

Zihnimi okudun, tam olarak rastgele varlıklar jeneratör test sınıfımda eklemek istediğim şey. Yardım için teşekkürler
Roque Sosa

43

Cletus ve helios önerilerini birleştirerek ,

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

Düzenleme: Hata! Sınırlı tip parametresini unuttum <E extends Enum<E>>.



1
Çok eski bir cevap biliyorum, ama olmamalı E extends Enum<E>mı?
Lino - Oy demeyin Teşekkürler

1
@Lino: Netlik için düzenlenmiştir; Ben bağlı parametre doğru tür çıkarım için gerekli olduğunu sanmıyorum , ama düzeltme hoş geldiniz; new RandomEnum<>(Season.class)Java 7'den beri buna da izin verildiğini unutmayın .
trashgod

RandomEnumEğer paketlemek ve merkezde yayınlamak istiyorsanız, bu tek sınıf bir mikro kütüphane olarak iyi olurdu.
Greg Chabala


10

Stphen C ve helios ile aynı fikirde. Enum'dan rastgele öğe almanın daha iyi bir yolu:

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

Bu muhtemelen hedefinize ulaşmanın en özlü yoludur.Tek yapmanız gereken aramak Letter.getRandom()ve rastgele bir numara mektubu alacaksınız.

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

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

5

Basit Kotlin Çözümü

MyEnum.values().random()

random()Collectionnesnenin Kotlin tabanına dahil edilen varsayılan bir uzantı işlevidir . Kotlin Belgeleri Bağlantısı

Bir uzantı işleviyle basitleştirmek isterseniz, şunu deneyin:

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

// Then call
random<MyEnum>()

Enum sınıfınızda statik hale getirmek için. my.package.randomNumaralandırma dosyanıza aldığınızdan emin olun

MyEnum.randomValue()

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

Bir numaralandırma örneğinden yapmanız gerekiyorsa, bu uzantıyı deneyin

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

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

4

Bir diziden rastgele bir değer seçme işlevine sahip olmak muhtemelen en kolay yoldur. Bu daha geneldir ve aramak kolaydır.

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

Şöyle arayın:

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

4

Burada karıştırma ve akış kullanan bir sürüm

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

3

Bunu test için yaparsanız Quickcheck kullanabilirsiniz ( bu, üzerinde çalıştığım bir Java bağlantı noktasıdır ).

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

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

Tüm ilkel türleri, tip kompozisyonunu, koleksiyonları, farklı dağıtım fonksiyonlarını, sınırları vb. Destekler. Birden fazla değeri yürüten koşucular için destek vardır:

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

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

Quickcheck'in avantajı, düz TDD'nin senaryolarla çalıştığı bir spesifikasyona göre testleri tanımlayabilmenizdir .


ilgi çekici görünüyor. Denemem gerekecek.
Nick Heiner

Bir şey işe yaramazsa bana mail atabilirsiniz. 0.5b sürümünü kullanmalısınız.
Thomas Jung

2

Enum üzerinde rastgele bir işlev uygulamak heveslidir.

public enum Via {
    A, B;

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

ve sonra buna ihtiyacınız olan sınıftan diyorsunuz

public class Guardia{
private Via viaActiva;

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

2

Bunu kullanırdım:

private static Random random = new Random();

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

1

Bu tek satırlı dönüş yönteminin böyle basit bir işte kullanılacak kadar verimli olduğunu düşünüyorum:

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());
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.