How does rand::seq::IteratorRandom::choose enable random sampling from arbitrary iterators?
rand::seq::IteratorRandom::choose implements reservoir sampling (specifically Algorithm R) to select a random element from an iterator without knowing the iterator's length in advance, using constant memory and a single pass through the data. The key insight is that the method must work with any iteratorâfinite or infinite, sized or unsizedâso it cannot collect all elements and pick one. Instead, it maintains a "reservoir" of the currently selected element and replaces it with each subsequent element with probability 1/n, where n is the number of elements seen so far. This ensures every element has an equal probability of being selected by the end. The trait extends Iterator with the choose method, meaning any iterator can be randomly sampled with .choose(&mut rng), making it ergonomic for one-off selection without intermediate allocations.
Basic Random Selection
use rand::seq::IteratorRandom;
use rand::thread_rng;
fn main() {
let items = vec
!["apple", "banana", "cherry", "date", "elderberry"];
let mut rng = thread_rng();
// Select one random element
if let Some(choice) = items.iter().choose(&mut rng) {
println!("Random fruit: {}", choice);
}
}choose returns Option<&Item>âNone only if the iterator is empty.
Working with Arbitrary Iterators
use rand::seq::IteratorRandom;
use rand::thread_rng;
fn main() {
let mut rng = thread_rng();
// Works with any iterator, not just collections
let even_choice = (0..100).filter(|x| x % 2 == 0).choose(&mut rng);
println!("Random even number: {:?}", even_choice);
// Works with map transformations
let doubled = (1..=10).map(|x| x * 2).choose(&mut rng);
println!("Random doubled: {:?}", doubled);
// Works with iterators that don't implement ExactSizeIterator
let filtered = (1..).filter(|x| x % 7 == 0).take(1000).choose(&mut rng);
println!("Random multiple of 7: {:?}", filtered);
}The trait works with any Iterator, regardless of size hints or ExactSizeIterator.
The Reservoir Sampling Algorithm
use rand::Rng;
fn manual_choose<I, R>(iter: I, rng: &mut R) -> Option<I::Item>
where
I: Iterator,
R: Rng,
{
let mut chosen = None;
let mut count = 0u64;
for item in iter {
count += 1;
// First element is always chosen
// Subsequent elements replace with probability 1/count
if count == 1 {
chosen = Some(item);
} else if rng.gen_range(0..count) == 0 {
chosen = Some(item);
}
}
chosen
}
fn main() {
use rand::thread_rng;
let mut rng = thread_rng();
let result = (1..=100).choose(&mut rng);
println!("Result: {:?}", result);
let manual_result = manual_choose(1..=100, &mut rng);
println!("Manual result: {:?}", manual_result);
}The algorithm ensures each element has 1/n probability of being selected after n elements.
Probability Distribution
use rand::seq::IteratorRandom;
use rand::thread_rng;
use std::collections::HashMap;
fn main() {
let mut rng = thread_rng();
let mut counts: HashMap<i32, u32> = HashMap::new();
// Run many trials to verify uniform distribution
for _ in 0..100_000 {
if let Some(choice) = (1..=5).choose(&mut rng) {
*counts.entry(choice).or_insert(0) += 1;
}
}
for i in 1..=5 {
let count = counts.get(&i).unwrap_or(&0);
let percentage = (*count as f64 / 100_000.0) * 100.0;
println!("{}: {:.1}%", i, percentage);
}
}Each element has equal probability of being selected.
Choosing Multiple Elements
use rand::seq::IteratorRandom;
use rand::thread_rng;
fn main() {
let mut rng = thread_rng();
// Choose multiple elements
let chosen: Vec<_> = (1..=100).choose_multiple(&mut rng, 5);
println!("5 random numbers: {:?}", chosen);
// Choose up to the length of the iterator
let chosen: Vec<_> = (1..=3).choose_multiple(&mut rng, 10);
println!("Chosen from small set: {:?}", chosen);
// Returns at most 3 elements since that's all there are
// Works with any iterator
let words = vec
!["alpha", "beta", "gamma", "delta", "epsilon"];
let chosen: Vec<_> = words.iter().choose_multiple(&mut rng, 3);
println!("3 random words: {:?}", chosen);
}choose_multiple uses reservoir sampling to select multiple elements uniformly.
Weighted Random Selection
use rand::seq::IteratorRandom;
use rand::thread_rng;
fn main() {
let mut rng = thread_rng();
let items = vec
!["a", "b", "c", "d"];
let weights = vec
![1u32, 2, 3, 4];
// Weighted selection - elements with higher weights are more likely
let chosen = items.iter().choose_weighted(&mut rng, |item| {
let idx = items.iter().position(|&x| x == *item).unwrap();
weights[idx]
});
println!("Weighted choice: {:?}", chosen);
// Distribution test
let mut counts: HashMap<&str, u32> = HashMap::new();
for _ in 0..10_000 {
let choice = items.iter()
.choose_weighted(&mut rng, |&&item| {
let idx = items.iter().position(|&x| x == item).unwrap();
weights[idx]
})
.unwrap();
*counts.entry(choice).or_insert(0) += 1;
}
for (item, count) in counts {
println!("{}: {} (expected ~{})", item, count, 10_000 * weights[items.iter().position(|&x| x == item).unwrap()] / 10);
}
}choose_weighted samples proportionally to provided weights.
Custom Iterator Types
use rand::seq::IteratorRandom;
use rand::thread_rng;
// Custom iterator that yields Fibonacci numbers
struct Fibonacci {
a: u64,
b: u64,
}
impl Fibonacci {
fn new() -> Self {
Self { a: 0, b: 1 }
}
}
impl Iterator for Fibonacci {
type Item = u64;
fn next(&mut self) -> Option<Self::Item> {
let current = self.a;
self.a = self.b;
self.b = current + self.b;
Some(current)
}
}
fn main() {
let mut rng = thread_rng();
// Choose from an infinite iterator
let chosen = Fibonacci::new().take(100).choose(&mut rng);
println!("Random Fibonacci number: {:?}", chosen);
// You could even choose from the infinite sequence
// (but it would never terminate)
// let infinite_choice = Fibonacci::new().choose(&mut rng); // Never returns!
}choose works with custom iteratorsâjust implement Iterator.
Efficiency Considerations
use rand::seq::IteratorRandom;
use rand::thread_rng;
use std::time::Instant;
fn main() {
let mut rng = thread_rng();
// compare: collecting all vs. choose
let start = Instant::now();
let _ = (0..1_000_000).collect::<Vec<_>>().choose(&mut rng);
let collect_time = start.elapsed();
let start = Instant::now();
let _ = (0..1_000_000).choose(&mut rng);
let choose_time = start.elapsed();
println!("collect + choose: {:?}", collect_time);
println!("choose directly: {:?}", choose_time);
println!("choose uses O(1) memory; collect uses O(n) memory");
}choose uses constant memory; collecting uses linear memory.
Choosing with Cloned Elements
use rand::seq::IteratorRandom;
use rand::thread_rng;
fn main() {
let mut rng = thread_rng();
let items = vec
!["alpha".to_string(), "beta".to_string(), "gamma".to_string()];
// choose returns &String
let reference = items.iter().choose(&mut rng);
println!("Reference: {:?}", reference);
// choose_cloned returns String
let cloned = items.iter().choose_cloned(&mut rng);
println!("Cloned: {:?}", cloned);
// Alternative: collect then choose
let owned = items.into_iter().collect::<Vec<_>>().choose(&mut rng);
println!("Owned: {:?}", owned);
}choose_cloned avoids borrowing when you need ownership of the selected element.
Choosing from File Lines
use rand::seq::IteratorRandom;
use rand::thread_rng;
use std::io::BufRead;
fn main() -> std::io::Result<()> {
let mut rng = thread_rng();
// Read lines from stdin
let stdin = std::io::stdin();
let reader = stdin.lock();
// Choose a random line without loading all lines into memory
let random_line = reader.lines()
.filter_map(|line| line.ok())
.choose(&mut rng);
match random_line {
Some(line) => println!("Random line: {}", line),
None => println!("No lines available"),
}
Ok(())
}choose works with streaming data that doesn't fit in memory.
Implementing Random Selection for Your Types
use rand::seq::IteratorRandom;
use rand::thread_rng;
#[derive(Debug)]
struct Card {
suit: &'static str,
rank: &'static str,
}
fn main() {
let mut rng = thread_rng();
let deck: Vec<Card> = vec
![
Card { suit: "hearts", rank: "ace" },
Card { suit: "hearts", rank: "king" },
Card { suit: "diamonds", rank: "queen" },
Card { suit: "clubs", rank: "jack" },
Card { suit: "spades", rank: "10" },
];
// Draw one card
let drawn = deck.iter().choose(&mut rng);
println!("Drawn: {:?}", drawn);
// Draw a hand of 3 cards
let hand: Vec<_> = deck.iter().choose_multiple(&mut rng, 3);
println!("Hand: {:?}", hand);
}Any iterator over your types can use choose.
Performance on Different Iterator Types
use rand::seq::IteratorRandom;
use rand::thread_rng;
use std::time::Instant;
fn main() {
let mut rng = thread_rng();
// Vector iterator - knows size, but choose still uses reservoir sampling
let vec = (0..100_000).collect::<Vec<_>>();
let start = Instant::now();
let _ = vec.iter().choose(&mut rng);
println!("Vec: {:?}", start.elapsed());
// Range iterator - doesn't know exact size
let start = Instant::now();
let _ = (0..100_000).choose(&mut rng);
println!("Range: {:?}", start.elapsed());
// Filtered iterator - can't know size
let start = Instant::now();
let _ = (0..100_000).filter(|x| x % 2 == 0).choose(&mut rng);
println!("Filtered: {:?}", start.elapsed());
}choose has consistent behavior regardless of iterator type.
Edge Cases
use rand::seq::IteratorRandom;
use rand::thread_rng;
fn main() {
let mut rng = thread_rng();
// Empty iterator returns None
let empty: Vec<i32> = vec
![];
let result = empty.iter().choose(&mut rng);
println!("Empty: {:?}", result); // None
// Single element iterator
let single = vec
!["only"];
let result = single.iter().choose(&mut rng);
println!("Single: {:?}", result); // Some("only")
// choose_multiple with amount > length
let small = vec
![1, 2, 3];
let chosen: Vec<_> = small.iter().choose_multiple(&mut rng, 10);
println!("More than available: {:?}", chosen); // [1, 2, 3] in random order
}choose handles edge cases gracefully.
Choose with Mutable References
use rand::seq::IteratorRandom;
use rand::thread_rng;
fn main() {
let mut rng = thread_rng();
let mut numbers = vec
![1, 2, 3, 4, 5];
// Get mutable reference to chosen element
if let Some(chosen) = numbers.iter_mut().choose(&mut rng) {
*chosen += 100;
}
println!("Modified: {:?}", numbers);
}iter_mut().choose() returns Option<&mut Item> for mutation.
Choosing from HashMap
use rand::seq::IteratorRandom;
use rand::thread_rng;
use std::collections::HashMap;
fn main() {
let mut rng = thread_rng();
let mut scores: HashMap<&str, u32> = HashMap::new();
scores.insert("alice", 95);
scores.insert("bob", 87);
scores.insert("charlie", 92);
// Choose a random key-value pair
if let Some((name, score)) = scores.iter().choose(&mut rng) {
println!("Random entry: {} -> {}", name, score);
}
// Choose a random key
if let Some(key) = scores.keys().choose(&mut rng) {
println!("Random key: {}", key);
}
// Choose a random value
if let Some(value) = scores.values().choose(&mut rng) {
println!("Random value: {}", value);
}
}choose works with any collection that implements Iterator.
Using with Custom RNG
use rand::seq::IteratorRandom;
use rand::rngs::StdRng;
use rand::SeedableRng;
fn main() {
// Deterministic RNG for reproducible results
let mut rng = StdRng::seed_from_u64(42);
let first_run: Vec<_> = (1..=100).choose_multiple(&mut rng, 5);
let mut rng = StdRng::seed_from_u64(42);
let second_run: Vec<_> = (1..=100).choose_multiple(&mut rng, 5);
println!("First: {:?}", first_run);
println!("Second: {:?}", second_run);
println!("Same seed = same results: {}", first_run == second_run);
}Seeded RNG produces reproducible selections.
Synthesis
Algorithm behavior:
| Elements Seen | Probability New Element is Chosen |
|---|---|
| 1 | 1/1 = 100% |
| 2 | 1/2 = 50% |
| 3 | 1/3 â 33% |
| n | 1/n |
Each element has equal 1/n final probability.
Method comparison:
| Method | Returns | Memory | Use Case |
|---|---|---|---|
choose |
Option<Item> |
O(1) | Single element |
choose_cloned |
Option<Item> (owned) |
O(1) | Single owned element |
choose_multiple |
Vec<Item> |
O(k) | Multiple elements |
choose_weighted |
Option<Item> |
O(1) | Weighted selection |
Key insight: IteratorRandom::choose solves a fundamental problemâselecting uniformly from an unknown-sized sequence using only a single pass. The reservoir sampling algorithm (Algorithm R) guarantees that after processing n elements, each element has exactly 1/n probability of being selected, proven by induction: element 1 is chosen initially with probability 1, then replaced by element 2 with probability 1/2 (so element 1's final probability is 1 Ă 1/2 = 1/2), and so on. This matters because real-world iterators often don't (or can't) know their sizeâa file's lines, a network stream's messages, or a filter chain's outputâmaking "collect and pick" impossible or prohibitively expensive. The trait extension pattern (trait IteratorRandom: Iterator) means any iterator gains this capability automatically, and the Rng parameter lets you control randomness source for testing or reproducibility. The weighted variant extends this to non-uniform distributions while maintaining the single-pass guarantee. For selecting multiple elements, choose_multiple uses reservoir sampling variant (Algorithm R for multiple reservoirs) that samples k elements with equal probabilityâa different algorithm than repeating choose k times, which would bias toward later elements.
