How to choose a random value from an enumeration?

The lure box has an enumeration with a large selection of possible baubles.

Here's an enumeration (with all values ​​except the top and bottom 4 missing):

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

The 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 want to choose a spinner at random, and this does not work:

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

As:

 error[E0423]: expected value, found enum 'Spinners' let spinner_enum = rng.choose(Spinners).unwrap_or(&Spinners::Dots9); ^^^^^^^^ not a value 

How to select a random enum value and use it to display a random counter?

+10
source share
2 answers

Your own listing

Like most abstractions in Rust, random value generation is trait-based. The implementation of the characteristic is the same for any particular type, the only difference is which methods and types of the characteristic are.

Rand 0.5, 0.6, and 0.7

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

 use rand::{ distributions::{Distribution, Standard}, Rng, }; // 0.7.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) { 0 => Spinner::One, 1 => Spinner::Two, _ => Spinner::Three, } } } fn main() { let spinner: Spinner = rand::random(); println!("{:?}", spinner); } 

Rand 0.4

Implement Rand for your enumeration, then use any of the methods to generate the value, for example, 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); } 

get

rand_derive may remove the need for this template, 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 enum

Since you do not control the enumeration, you must copy something into your code in order to reference it. You can create an array of enumerations and choose from this:

 use rand::seq::SliceRandom; // 0.7.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 can replicate the entire enumeration locally, implement Rand for this, and then have a method that converts back to the representation of other boxes.

 use rand::{ distributions::{Distribution, Standard}, Rng, }; // 0.7.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, 3) { 0 => Spinner::One, 1 => Spinner::Two, _ => Spinner::Three, } } } fn main() { let spinner = another_crate::Spinner::from(rand::random::<Spinner>()); println!("{:?}", spinner); } 

You can count the number of spinners and make a match:

 use rand::Rng; // 0.7.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, 3) { 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 new type and implement random generation for this:

 use rand::{distributions::Standard, prelude::*}; // 0.7.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, 3) { 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:

+16
source

As Shepmaster asked, I can offer a couple more options.

Unfortunately, rng.choose(Spinners) cannot work, because there is no way to rng.choose(Spinners) over enum values; see: is there any way in Rust to iterate over enumeration values?

You could supposedly use the strum EnumIter to iterate. In rands 0.4 and 0.5, choose does not support iterators, but you can either collect all the options in Vec or list and map the index. Rand 0.6 will have a choose option for supporting iterators, although it can be quite slow (depending on whether we can optimize it for 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 FromPrimitive trait with num-derived :

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

Source: https://habr.com/ru/post/1275002/


All Articles