เลือกค่าแบบสุ่มจาก enum หรือไม่


162

หากฉันมี Enum เช่นนี้:

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

วิธีที่ดีที่สุดในการเลือกแบบสุ่มคืออะไร ไม่จำเป็นต้องเป็นกระสุนคุณภาพการผลิต แต่การกระจายที่ค่อนข้างดีก็ดี

ฉันสามารถทำอะไรเช่นนี้

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

แต่จะมีวิธีที่ดีกว่า ฉันรู้สึกว่านี่เป็นสิ่งที่ถูกแก้ไขก่อนหน้านี้


คุณคิดอย่างไรกับวิธีแก้ปัญหาของคุณ? มันดูดีสำหรับฉัน
ประธานาธิบดี James K. Polk

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> โดยมี Constructor ที่ได้รับ Class <T> สำหรับการสร้างรายการ
helios

15
ฉันไม่เห็นจุดเปลี่ยนvalues()อาร์เรย์ไปเป็นรายการที่แก้ไขไม่ได้ วัตถุถูกห่อหุ้มอยู่แล้วโดยอาศัยอำนาจตามการประกาศVALUES มันจะง่ายและมีประสิทธิภาพมากขึ้นที่จะทำให้มันprivate private static final Letter[] VALUES = ...
สตีเฟ่นซี

4
อาร์เรย์ใน Java สามารถเปลี่ยนแปลงได้ดังนั้นหากคุณมีเขตข้อมูลอาร์เรย์และส่งกลับด้วยวิธีการสาธารณะผู้โทรสามารถแก้ไขได้และจะปรับเปลี่ยนการเก็บข้อมูลส่วนตัวดังนั้นคุณต้องคัดลอกอาร์เรย์อย่างละเอียด หากคุณเรียกวิธีการนั้นบ่อยครั้งอาจเป็นปัญหาดังนั้นคุณจึงใส่ไว้ในรายการที่ไม่เปลี่ยนรูปแทนการหลีกเลี่ยงการคัดลอกการป้องกันที่ไม่จำเป็น
cletus

1
@cletus: Enum.values ​​() จะส่งคืนอาร์เรย์ใหม่ในทุกการเรียกใช้ดังนั้นจึงไม่จำเป็นต้องห่อมันก่อนที่จะส่งผ่าน / ใช้งานในที่อื่น
Chii

5
จดหมายส่วนตัวสุดท้ายคงที่ [] ค่า ... ก็โอเค มันเป็นแบบส่วนตัวดังนั้นจึงไม่สามารถเปลี่ยนแปลงได้ คุณต้องการเมธอด public randomLetter () ซึ่งเห็นได้ชัดว่าคืนค่าเดียว Stephen C ถูกต้อง
helios

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

คุณอ่านใจของฉันสิ่งที่ฉันกำลังมองหาเพื่อเพิ่มในคลาสทดสอบเอนทิตีของฉันแบบสุ่ม ขอบคุณสำหรับความช่วยเหลือ
Roque Sosa

43

รวมข้อเสนอแนะของCletusและ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)];
        }
    }
}

แก้ไข: โอ๊ะฉันลืมพารามิเตอร์ประเภทที่ จำกัด<E extends Enum<E>>ไว้แล้ว


1
ดูเพิ่มเติมชั้นอักษรเป็น Runtime-Type ราชสกุล
trashgod

1
มากคำตอบเก่าฉันรู้ แต่ไม่ควรที่จะเป็นE extends Enum<E>?
Lino - โหวตไม่พูดขอบคุณ

1
@Lino: แก้ไขเพื่อความชัดเจน ฉันไม่คิดว่ามันเป็นสิ่งจำเป็นสำหรับการอนุมานประเภทที่ถูกต้องของพารามิเตอร์ที่ผูกไว้ แต่ฉันยินดีต้อนรับการแก้ไข; หมายเหตุที่new RandomEnum<>(Season.class)ได้รับอนุญาตตั้งแต่ Java 7
trashgod

RandomEnumคลาสเดียวนี้จะดีเหมือนห้องสมุดขนาดเล็กถ้าคุณต้องการรวมมันและเผยแพร่ไปยังส่วนกลาง
เกร็ก 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)];
  }
}


5

นี่อาจเป็นวิธีที่รัดกุมที่สุดในการบรรลุเป้าหมายของคุณสิ่งที่คุณต้องทำคือโทรLetter.getRandom()และคุณจะได้รับจดหมาย enum สุ่ม

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


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
Thomas Jung

2

มันเป็น eaiser ที่จะใช้ฟังก์ชั่นการสุ่มบน enum

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


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.