How to generate random values in certain range but exclude certain numbers

Hi, I would like to generate a random number (u8) between 0..=15, but with certain numbers (e.g. 0 and 2) excluded (i.e. they will never be generated). What is the cleanest/most idiomatic way to do this? I thought maybe there is a way I can express this sort of range (0..=15 turns into 1..=1 and 3..=15 somehow, which is a range) idiomatically. Is that possible?

Discontinuous ranges aren't a thing in Rust. I can think of two ways of doing this:

  1. Generate on 0..16 and retry if you get an excluded value.
  2. Generate x ∈ 0..14 then add 1 if x >= 0, then add 1 if x >= 2 (i.e. create the gaps after generation).

I'd probably do the second, as it should always take about the same amount of time for each sample, whereas 1 could require a theoretically unbounded number of attempts.

4 Likes

An idea that just popped into my mind: I could store all possible values in a vector, and then generate a random number that just represents the index into that vector. What do you think?

1 Like

Whichever algorithm you end up using, the most idiomatic way to implement it is probably to make your own type that implements Distribution<T>, so that it can be used with Rng::sample(). For example:

use rand::{
    self,
    distributions::{Distribution, Uniform},
    thread_rng, Rng,
};

struct Filter<Dist, Test> {
    dist: Dist,
    test: Test,
}

impl<T, Dist, Test> Distribution<T> for Filter<Dist, Test>
where
    Dist: Distribution<T>,
    Test: Fn(&T) -> bool,
{
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> T {
        loop {
            let x = self.dist.sample(rng);
            if (self.test)(&x) {
                return x;
            }
        }
    }
}

fn main() {
    let mut rng = thread_rng();
    let dist = Filter {
        dist: Uniform::new(0, 15),
        test: |x: &_| (x != &0) & (x != &2),
    };

    for _ in 0..32 {
        let x: i32 = rng.sample(&dist);
        dbg!(x);
    }
}

Edit: Ran through Rustfmt

4 Likes

That was my first idea when reading your question. I just would use an array and not a vec

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.