Loading pageā¦
Rust walkthroughs
Loading pageā¦
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.