列挙型からランダムな値を選びますか?


161

私がこのような列挙型を持っている場合:

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

ランダムに1つを選択する最良の方法は何ですか?本番品質の完全なものである必要はありませんが、かなり均等に配布することをお勧めします。

私はこのようなことができました

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

しかし、もっと良い方法はありますか?これは以前に解決された問題だと思います。


あなたの解決策の何が悪いと思いますか?それは私にはかなりよさそうだ。
James K. Polk大統領、

1
@GregS-問題は、への呼び出しごとLetter.values()に内部Letter値配列の新しいコピーを作成する必要があることです。
スティーブンC

回答:


143

私が提案する唯一のことはvalues()、各呼び出しが配列をコピーするため、結果をキャッシュすることです。また、Random毎回作成しないでください。1つ保持します。それ以外はあなたがやっていることで結構です。そう:

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のようなものは、リストを作成するためのClass <T>を受け取るコンストラクターでEnum>を拡張します。
helios

15
values()配列を変更不可能なリストに変換する意味が本当にわかりません。VALUESオブジェクトが既に宣言さによってカプセル化されますprivate。それを作る方がより簡単でより効率的でしょうprivate static final Letter[] VALUES = ...
スティーブンC

4
Javaの配列は変更可能であるため、配列フィールドがあり、それをパブリックメソッドで返す場合、呼び出し元はそれを変更でき、プライベートフィールドを変更するため、配列を防御的にコピーする必要があります。そのメソッドを何度も呼び出す場合は問題になる可能性があるため、不必要な防御コピーを回避するために、代わりに不変リストに入れてください。
クレタス

1
@cletus:Enum.values()は呼び出しのたびに新しい配列を返すため、他の場所で渡したり使用したりする前にラップする必要はありません。
Chii

5
private static final Letter [] VALUES ...は問題ありません。それはプライベートなので、不変です。明らかに単一の値を返すpublic randomLetter()メソッドのみが必要です。スティーブンCは正しいです。
helios

126

すべてのランダム列挙型に必要なのは1つのメソッドだけです。

    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
まさに私が探していたもの。私は受け入れられた答えのようにしていたので、2番目の列挙型からランダム化する必要があるとき、ボイラープレートコードが残りました。また、SecureRandomについては忘れがちです。ありがとう。
Siamaster

ランダムエンティティジェネレーターのテストクラスに追加したいことを正確に読んでください。助けてくれてありがとう
Roque Sosa

43

cletusheliosの提案を組み合わせて、

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>か?
リノ-投票しない

1
@Lino:明確にするために編集。パラメータバインドの正しい型推論には必要ないと思いますが、修正を歓迎します。new RandomEnum<>(Season.class)Java 7以降で許可されていることに注意してください
。–ゴミの神

この単一のRandomEnumクラスをバンドルして中央に公開したい場合は、マイクロライブラリとして最適です。
Greg Chabala


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

7
Letter lettre = Letter.values()[(int)(Math.random()*Letter.values().length)];

5

これはおそらくあなたの目標を達成するための最も簡潔な方法ですLetter.getRandom()

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

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

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();

4

シンプルなKotlinソリューション

MyEnum.values().random()

random()CollectionオブジェクトのベースKotlinに含まれるデフォルトの拡張関数です。Kotlinドキュメンテーションリンク

拡張関数を使ってそれを単純化したい場合は、これを試してください:

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

// Then call
random<MyEnum>()

列挙型クラスで静的にする。my.package.randomenumファイルにインポートしてください

MyEnum.randomValue()

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

列挙型のインスタンスから実行する必要がある場合は、この拡張機能を試してください

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

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

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がシナリオで機能する仕様に基づいてテストを定義できることです。


興味深く見えます。私はそれを試さなければならないでしょう。
Nick Heiner、

何かがうまくいかない場合は私にメールしてください。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());
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.