【问题标题】:How do I choose a random value from an enum?如何从枚举中选择随机值?
【发布时间】:2018-07-07 11:40:58
【问题描述】:

spinners 板条箱有一个 enum,其中包含大量可能的微调器。

这是枚举(除了顶部和底部 4 之外的所有值都被跳过):

pub enum Spinners {
    Dots,
    Dots2,
    Dots3,
    Dots4,
    ...
    Shark,
    Dqpb,
    Weather,
    Christmas,
}

一个新的微调器很容易创建:

extern crate spinners;

use spinners::{Spinner, Spinners};
use std::thread::sleep;
use std::time::Duration;

fn main() {
    let sp = Spinner::new(Spinners::Dots9, "Waiting for 3 seconds".into());
    sleep(Duration::from_secs(3));
    sp.stop();
}

但是,我希望随机选择一个微调器,这不起作用:

let spinner_enum = rng.choose(Spinners).unwrap_or(&Spinners::Dots9);

因为:

error[E0423]: expected value, found enum `Spinners`

let spinner_enum = rng.choose(Spinners).unwrap_or(&Spinners::Dots9);
                              ^^^^^^^^ not a value

如何随机选择一个枚举值,并使用它来显示随机微调器?

【问题讨论】:

    标签: enums rust


    【解决方案1】:

    你自己的枚举

    像 Rust 中的大多数抽象一样,随机值生成是由特征驱动的。对于任何特定类型,实现 trait 都是相同的,唯一的区别是 trait 的方法和类型是什么。

    兰德 0.5、0.6、0.7 和 0.8

    使用您的枚举作为类型参数来实现Distribution。您还需要选择特定类型的分发; Standard 是一个不错的默认选择。然后使用任意一种方法生成一个值,比如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);
    }
    

    兰德 0.4

    为您的枚举实现Rand,然后使用任何方法生成一个值,例如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);
    }
    

    导出

    rand_derive crate 可以消除对这些样板的需要,但对于 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);
    }
    

    别人的枚举

    由于您不控制枚举,因此您必须将 something 复制到您的代码中才能引用它。您可以从中创建一个枚举数组和choose

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

    您可以在本地复制整个枚举,为此实现 Rand,然后有一个方法可以转换回其他 crates 表示。

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

    您可以数数旋转器的数量并进行匹配:

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

    您可以实现一个 newtype 并为此实现随机生成:

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

    另见:

    【讨论】:

    • 我最终使用了match-方法并手动记下所有枚举。感觉不是最理想的,但它确实有效。感谢您的建议和可能的解决方案!
    • 还发现了strum crate,它似乎有可能在一定程度上缓解这个问题。
    • 不幸的是,我认为 rand-deriverand 0.5 开始已被弃用,looking at the readme.
    • @Shepmaster 可以确定合理的优先规则。例如,其他语言允许您隐藏现有功能,然后调用 super() 来访问它(如果存在)。我个人不喜欢 Rust 以“安全”的名义施加的许多看似武断的约束,好像真的没有其他合理的解决方案。
    • @process91 我从the crate-level docs 开始,看到random 的链接,看到它有一个Standard: Distribution&lt;T&gt; 的边界。我知道T 将是我们的自定义类型,所以我查看了Distribution。它只有one required method,所以我实现了它。只能使用Rng的方法,如gen_range
    【解决方案2】:

    由于Shepmaster asked,我可以建议另外几个选项。

    不幸的是,rng.choose(Spinners) 无法工作,因为无法迭代枚举值;见:In Rust, is there a way to iterate through the values of an enum?

    您大概可以使用strumEnumIter 来允许迭代。在 Rand 0.4 和 0.5 中,choose 不支持迭代器,但您可以将所有选项收集到 Vec 或枚举并匹配索引。在 Rand 0.6 中,将有一个变体 choose 支持迭代器,尽管它可能会很慢(取决于我们是否可以针对 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);
    

    另一种选择是使用 num 的 FromPrimitive traitnum-derive

    #[derive(FromPrimitive)]
    enum Spinner { ... }
    
    let choice = Spinner::from_u32(rng.gen_range(0, MAX)).unwrap();
    

    【讨论】:

    • 虽然这可能很慢 — 为什么它会比 rand::seq::sample_iter "specialized" for Option 而不是 Vec 慢?
    • 哎呀,我忘了sample_iter(我们正在为 0.6 完全重新设计这个 API)。是的,它的性能应该与 0.6 中的 IteratorRandom::choose 相似,但是对于大型枚举,两者都会比其他方法慢得多。除了我们might be able to optimise this,因为在这样的示例中,迭代器长度是已知的。
    • 为什么选择随机元素需要迭代?在数学和编程的一般背景下:如果你知道枚举的长度,那么挑选一个随机元素应该不需要任何迭代。
    猜你喜欢
    • 1970-01-01
    • 2020-09-10
    • 2019-03-20
    • 2013-10-20
    • 2016-05-18
    • 2014-08-06
    • 1970-01-01
    相关资源
    最近更新 更多