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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions