简体   繁体   English

如何从枚举中选择随机值?

[英]How do I choose a random value from an enum?

The spinners crate has an enum with a large selection of possible spinners. spinners crate 有一个枚举,其中包含大量可能的 spinners。

Here's the enum (with all values except the top and bottom 4 skipped):这是枚举(除了顶部和底部 4 之外的所有值都被跳过):

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

A new spinner is easy to create:一个新的微调器很容易创建:

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

However, I wish to select a spinner at random, and this does not work:但是,我希望 select 随机旋转一个微调器,但这不起作用:

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

Because:因为:

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

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

How can I choose an enum value at random, and use that to display a random spinner?我如何随机选择一个枚举值,并使用它来显示随机微调器?

Your own enum你自己的枚举

Like most abstractions in Rust, random value generation is powered by traits.像 Rust 中的大多数抽象一样,随机值生成由特征提供支持。 Implementing a trait is the same for any particular type, the only difference is exactly what the methods and types of the trait are.对于任何特定类型,实现 trait 都是一样的,唯一的区别就是 trait 的方法和类型。

Rand 0.5, 0.6, 0.7, and 0.8兰特 0.5、0.6、0.7 和 0.8

Implement Distribution using your enum as the type parameter.使用您的枚举作为类型参数实现Distribution You also need to choose a specific type of distribution;您还需要选择特定类型的分发; Standard is a good default choice. Standard是一个很好的默认选择。 Then use any of the methods to generate a value, such as rand::random :然后使用任何方法生成一个值,例如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兰特 0.4

Implement Rand for your enum, then use any of the methods to generate a value, such asRng::gen :为您的枚举实现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);
}

Derive派生

The rand_derive crate can remove the need for some of this boilerplate, but does not exist for Rand 0.5. 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);
}

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:您可以创建一个枚举数组并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);
}

You could replicate the entire enum locally, implement Rand for that, and then have a method that converts back into the other crates representation.您可以在本地复制整个枚举,为此实现Rand ,然后使用一种方法将其转换回其他 crate 表示。

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:您可以实现一个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);
}

See also:另见:

Since Shepmaster asked I can suggest another couple of options.既然Shepmaster 问我可以建议另外几个选项。

Unfortunately rng.choose(Spinners) cannot work because there is no way to iterate over enum values;不幸的是rng.choose(Spinners)不能工作,因为没有办法迭代枚举值; see: In Rust, is there a way to iterate through the values of an enum?请参阅: 在 Rust 中,有没有办法遍历枚举的值?

You could presumably use strum 's EnumIter to allow iteration.你大概可以用扫弦EnumIter允许迭代。 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.在 Rand 0.4 和 0.5 中, choose不支持迭代器,但您可以将所有选项收集到Vec或枚举并匹配索引。 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 ExactSizeIterator s).在 Rand 0.6 中,将有一个choose支持迭代器的变体,尽管它可能很慢(取决于我们是否可以针对ExactSizeIterator对其进行优化)。

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 :另一种选择是将 num 的FromPrimitive特征num-derive 一起使用

#[derive(FromPrimitive)]
enum Spinner { ... }

let choice = Spinner::from_u32(rng.gen_range(0, MAX)).unwrap();

Here in the future, we would probably use strum_macros::FromRepr and EnumCount: https://docs.rs/strum_macros/0.24.0/strum_macros/derive.FromRepr.html将来,我们可能会使用 strum_macros::FromRepr 和 EnumCount: https://docs.rs/strum_macros/0.24.0/strum_macros/derive.FromRepr.html

use strum::EnumCount;
use strum_macros::{EnumCount, FromRepr};

#[derive(FromRepr, Debug, PartialEq, EnumCount)]
enum Spinners{ Dots, Dots2, ... }

let randomInteger = 0;  //Worst RNG ever!
assert_eq!(Some(Spinners::Dots), Spinners::from_repr(randomInteger));


let spinner_enum =
    Spinners::from_repr(rng.gen_range(0..Spinners::COUNT))
    .unwrap_or(&Spinners::Dots9);

COUNT is created and assigned automatically by EnumCount. COUNT 由 EnumCount 自动创建和分配。 from_repr returns None when the number does not correspond to a variant, like maybe it is too high, or you have specified numbers for one or more variants leaving gaps. from_repr 返回 None 当数字不对应于变体时,例如它可能太高,或者您为一个或多个变体指定了数字留下空白。

Edit: The spinners crate may not work with FromRepr since it requires annotating the enum's definition and you cannot, as far as I know, unless you modify the code;编辑:spinners crate 可能无法与 FromRepr 一起使用,因为它需要注释枚举的定义,据我所知,除非您修改代码,否则您不能这样做; if you do, post a pull request, But if you have defined your own enum.如果这样做,请发布拉取请求,但如果您定义了自己的枚举。 then Bob's your uncle.那么鲍勃就是你的叔叔。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM