How do I choose a random value from an enum?

后端 未结 2 1302
逝去的感伤
逝去的感伤 2021-01-08 00:22

The spinners crate has an enum with a large selection of possible spinners.

Here\'s the enum (with all values except the top and bottom 4 skipped):

p         


        
相关标签:
2条回答
  • 2021-01-08 01:02

    Since Shepmaster asked I can suggest another couple of options.

    Unfortunately rng.choose(Spinners) cannot work because there is no way to iterate over enum values; see: In Rust, is there a way to iterate through the values of an enum?

    You could presumably use strum's EnumIter to allow iteration. In Rand 0.4 and 0.5, choose does not support iterators, but you could either collect all options into a Vec or enumerate and match the index. In Rand 0.6, there will be a variant of choose supporting iterators, although it may quite slow (depending on whether we can optimise it for ExactSizeIterators).

    use rand::prelude::*;
    
    #[derive(EnumIter)]
    enum Spinner { ... }
    
    let mut rng = thread_rng();
    
    let options = Spinner::iter().collect::<Vec<_>>();
    let choice = rng.choose(&options);
    
    // or:
    let index = rng.gen_range(0, MAX);
    let choice = Spinner::iter().enumerate().filter(|(i, _)| i == index).map(|(_, x)| x).next().unwrap();
    
    // with Rand 0.6, though this may be slow:
    let choice = Spinner::iter().choose(&mut rng);
    // collecting may be faster; in Rand 0.6 this becomes:
    let choice = Spinner::iter().collect::<Vec<_>>().choose(&mut rng);
    

    Another option is to use num's FromPrimitive trait with num-derive:

    #[derive(FromPrimitive)]
    enum Spinner { ... }
    
    let choice = Spinner::from_u32(rng.gen_range(0, MAX)).unwrap();
    
    0 讨论(0)
  • 2021-01-08 01:23

    Your own enum

    Like most abstractions in Rust, random value generation is powered by traits. Implementing a trait is the same for any particular type, the only difference is exactly what the methods and types of the trait are.

    Rand 0.5, 0.6, 0.7, and 0.8

    Implement Distribution using your enum as the type parameter. You also need to choose a specific type of distribution; Standard is a good default choice. Then use any of the methods to generate a value, such as rand::random:

    use rand::{
        distributions::{Distribution, Standard},
        Rng,
    }; // 0.8.0
    
    #[derive(Debug)]
    enum Spinner {
        One,
        Two,
        Three,
    }
    
    impl Distribution<Spinner> for Standard {
        fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Spinner {
            // match rng.gen_range(0, 3) { // rand 0.5, 0.6, 0.7
            match rng.gen_range(0..=2) { // rand 0.8
                0 => Spinner::One,
                1 => Spinner::Two,
                _ => Spinner::Three,
            }
        }
    }
    
    fn main() {
        let spinner: Spinner = rand::random();
        println!("{:?}", spinner);
    }
    

    Rand 0.4

    Implement Rand for your enum, then use any of the methods to generate a value, such as Rng::gen:

    extern crate rand; // 0.4.2
    
    use rand::{Rand, Rng};
    
    #[derive(Debug)]
    enum Spinner {
        One,
        Two,
        Three,
    }
    
    impl Rand for Spinner {
        fn rand<R: Rng>(rng: &mut R) -> Self {
            match rng.gen_range(0, 3) {
                0 => Spinner::One,
                1 => Spinner::Two,
                _ => Spinner::Three,
            }
        }
    }
    
    fn main() {
        let mut rng = rand::thread_rng();
        let spinner: Spinner = rng.gen();
        println!("{:?}", spinner);
    }
    

    Derive

    The rand_derive crate can remove the need for some of this boilerplate, but does not exist for Rand 0.5.

    extern crate rand;
    #[macro_use]
    extern crate rand_derive;
    
    use rand::Rng;
    
    #[derive(Debug, Rand)]
    enum Spinner {
        One,
        Two,
        Three,
    }
        
    fn main() {
        let mut rng = rand::thread_rng();
        let spinner: Spinner = rng.gen();
        println!("{:?}", spinner);
    }
    

    Someone else's enum

    Since you don't control the enum, you have to copy something into your code in order to reference it. You could create an array of the enum and choose from that:

    use rand::seq::SliceRandom; // 0.8.0
    
    mod another_crate {
        #[derive(Debug)]
        pub enum Spinner {
            One,
            Two,
            Three,
        }
    }
    
    fn main() {
        let mut rng = rand::thread_rng();
        let spinners = [
            another_crate::Spinner::One,
            another_crate::Spinner::Two,
            another_crate::Spinner::Three,
        ];
        let spinner = spinners.choose(&mut rng).unwrap();
        println!("{:?}", spinner);
    }
    

    You could replicate the entire enum locally, implement Rand for that, and then have a method that converts back into the other crates representation.

    use rand::{
        distributions::{Distribution, Standard},
        Rng,
    }; // 0.8.0
    
    mod another_crate {
        #[derive(Debug)]
        pub enum Spinner {
            One,
            Two,
            Three,
        }
    }
    
    enum Spinner {
        One,
        Two,
        Three,
    }
    
    impl From<Spinner> for another_crate::Spinner {
        fn from(other: Spinner) -> another_crate::Spinner {
            match other {
                Spinner::One => another_crate::Spinner::One,
                Spinner::Two => another_crate::Spinner::Two,
                Spinner::Three => another_crate::Spinner::Three,
            }
        }
    }
    
    impl Distribution<Spinner> for Standard {
        fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Spinner {
            match rng.gen_range(0..=2) {
                0 => Spinner::One,
                1 => Spinner::Two,
                _ => Spinner::Three,
            }
        }
    }
    
    fn main() {
        let spinner = another_crate::Spinner::from(rand::random::<Spinner>());
        println!("{:?}", spinner);
    }
    

    You could count the number of spinners and do a match:

    use rand::Rng; // 0.8.0
    
    mod another_crate {
        #[derive(Debug)]
        pub enum Spinner {
            One,
            Two,
            Three,
        }
    }
    
    fn rando<R: Rng>(mut rng: R) -> another_crate::Spinner {
        match rng.gen_range(0..=2) {
            0 => another_crate::Spinner::One,
            1 => another_crate::Spinner::Two,
            _ => another_crate::Spinner::Three,
        }
    }
    
    fn main() {
        let mut rng = rand::thread_rng();
        let spinner = rando(&mut rng);
        println!("{:?}", spinner);
    }
    

    You can implement a newtype and implement the random generation for that:

    use rand::{distributions::Standard, prelude::*}; // 0.8.0
    
    mod another_crate {
        #[derive(Debug)]
        pub enum Spinner {
            One,
            Two,
            Three,
        }
    }
    
    struct RandoSpinner(another_crate::Spinner);
    
    impl Distribution<RandoSpinner> for Standard {
        fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> RandoSpinner {
            RandoSpinner(match rng.gen_range(0..=2) {
                0 => another_crate::Spinner::One,
                1 => another_crate::Spinner::Two,
                _ => another_crate::Spinner::Three,
            })
        }
    }
    
    fn main() {
        let RandoSpinner(spinner) = rand::random();
        println!("{:?}", spinner);
    }
    

    See also:

    • How do I implement a trait I don't own for a type I don't own?
    0 讨论(0)
提交回复
热议问题