Skip to content

Stable version of rand::seq::IteratorRandom::choose #1051

@kevincox

Description

@kevincox

Background

What is your motivation?

Provide a way to produce predictable results for (a function similar to) rand::seq::IteratorRandom::choose no matter what type of iterator it is.

Right now the behaviour of rand::seq::IteratorRandom::choose depends on the type of Iterator that is passed in. This is mentioned in the docs as an "optimization" however it isn't clear that this is a behaviour affecting "optimization". Furthermore there is no good alternative function which doesn't have this "optimization".

Example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=a62f77f38f4a0b66df57106d0cf6a4a4

extern crate rand; // 0.7.3
extern crate rand_pcg; // 0.2.1

fn choose(i: &mut dyn Iterator<Item=u32>) -> u32 {
    let mut rng = rand_pcg::Pcg32::new(0xcafef00dd15ea5e5, 0xa02bdbf7bb3c0a7);
    rand::seq::IteratorRandom::choose(i, &mut rng).unwrap()
}

fn main() {
    dbg!(choose(&mut (0..32))); // 5
    dbg!(choose(&mut (0..32).filter(|_| true))); // 3
}

What type of application is this? numerical simulation

Feature request

  1. Clarify the documentation to emphasize that rand::seq::IteratorRandom::choose has different behaviour depending on the return value of the size_hint method.
  2. Provide an alternative function that will select the same element (including Rng state) for any iterator of the same length.

Workarounds

Unify type:

One option to get a consistent result is ensure that you are always working with the same type. For example the following always collects to a Vec.

fn stable_choose_collect<T>(i: impl Iterator<Item=T>, mut rng: impl rand::Rng) -> Option<T> {
    rand::seq::IteratorRandom::choose(i.collect::<Vec<_>>().into_iter(), &mut rng)
}

Get rid of size_hint.

It would be more "proper" to make a wrapper type but since rand::seq::IteratorRandom::choose only performs its "optimization" if lower == upper we just need to make those not equal. The easiest way to do this is is call .filter(|_| true) on the iterator. Because it can't tell how many elements you will filter it will set the lower bound to 0.

fn stable_choose_no_hint<T>(i: impl Iterator<Item=T>, mut rng: impl rand::Rng) -> Option<T> {
    rand::seq::IteratorRandom::choose(i.filter(|_| true), &mut rng)
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions