// Copyright 2018-2024 Developers of the Rand project. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! `IteratorRandom` #[allow(unused)] use super::IndexedRandom; use super::coin_flipper::CoinFlipper; use crate::{Rng, RngExt}; #[cfg(feature = "alloc")] use alloc::vec::Vec; /// Extension trait on iterators, providing random sampling methods. /// /// This trait is implemented on all iterators `I` where `I: Iterator + Sized` /// and provides methods for /// choosing one or more elements. You must `use` this trait: /// /// ``` /// use rand::seq::IteratorRandom; /// /// let faces = "😀😎😐😕😠😢"; /// println!("I am {}!", faces.chars().choose(&mut rand::rng()).unwrap()); /// ``` /// Example output (non-deterministic): /// ```none /// I am 😀! /// ``` pub trait IteratorRandom: Iterator + Sized { /// Uniformly sample one element /// /// Assuming that the [`Iterator::size_hint`] is correct, this method /// returns one uniformly-sampled random element of the slice, or `None` /// only if the slice is empty. Incorrect bounds on the `size_hint` may /// cause this method to incorrectly return `None` if fewer elements than /// the advertised `lower` bound are present and may prevent sampling of /// elements beyond an advertised `upper` bound (i.e. incorrect `size_hint` /// is memory-safe, but may result in unexpected `None` result and /// non-uniform distribution). /// /// With an accurate [`Iterator::size_hint`] and where [`Iterator::nth`] is /// a constant-time operation, this method can offer `O(1)` performance. /// Where no size hint is /// available, complexity is `O(n)` where `n` is the iterator length. /// Partial hints (where `lower > 0`) also improve performance. /// /// Note further that [`Iterator::size_hint`] may affect the number of RNG /// samples used as well as the result (while remaining uniform sampling). /// Consider instead using [`IteratorRandom::choose_stable`] to avoid /// [`Iterator`] combinators which only change size hints from affecting the /// results. /// /// # Example /// /// ``` /// use rand::seq::IteratorRandom; /// /// let words = "Mary had a little lamb".split(' '); /// println!("{}", words.choose(&mut rand::rng()).unwrap()); /// ``` fn choose(mut self, rng: &mut R) -> Option where R: Rng + ?Sized, { let (mut lower, mut upper) = self.size_hint(); let mut result = None; // Handling for this condition outside the loop allows the optimizer to eliminate the loop // when the Iterator is an ExactSizeIterator. This has a large performance impact on e.g. // seq_iter_choose_from_1000. if upper == Some(lower) { return match lower { 0 => None, 1 => self.next(), _ => self.nth(rng.random_range(..lower)), }; } let mut coin_flipper = CoinFlipper::new(rng); let mut consumed = 0; // Continue until the iterator is exhausted loop { if lower > 1 { let ix = coin_flipper.rng.random_range(..lower + consumed); let skip = if ix < lower { result = self.nth(ix); lower - (ix + 1) } else { lower }; if upper == Some(lower) { return result; } consumed += lower; if skip > 0 { self.nth(skip - 1); } } else { let elem = self.next(); if elem.is_none() { return result; } consumed += 1; if coin_flipper.random_ratio_one_over(consumed) { result = elem; } } let hint = self.size_hint(); lower = hint.0; upper = hint.1; } } /// Uniformly sample one element (stable) /// /// This method is very similar to [`choose`] except that the result /// only depends on the length of the iterator and the values produced by /// `rng`. Notably for any iterator of a given length this will make the /// same requests to `rng` and if the same sequence of values are produced /// the same index will be selected from `self`. This may be useful if you /// need consistent results no matter what type of iterator you are working /// with. If you do not need this stability prefer [`choose`]. /// /// Note that this method still uses [`Iterator::size_hint`] to skip /// constructing elements where possible, however the selection and `rng` /// calls are the same in the face of this optimization. If you want to /// force every element to be created regardless call `.inspect(|e| ())`. /// /// [`choose`]: IteratorRandom::choose // // Clippy is wrong here: we need to iterate over all entries with the RNG to // ensure that choosing is *stable*. // "allow(unknown_lints)" can be removed when switching to at least // rust-version 1.86.0, see: // https://rust-lang.github.io/rust-clippy/master/index.html#double_ended_iterator_last #[allow(unknown_lints)] #[allow(clippy::double_ended_iterator_last)] fn choose_stable(mut self, rng: &mut R) -> Option where R: Rng + ?Sized, { let mut consumed = 0; let mut result = None; let mut coin_flipper = CoinFlipper::new(rng); loop { // Currently the only way to skip elements is `nth()`. So we need to // store what index to access next here. // This should be replaced by `advance_by()` once it is stable: // https://github.com/rust-lang/rust/issues/77404 let mut next = 0; let (lower, _) = self.size_hint(); if lower >= 2 { let highest_selected = (0..lower) .filter(|ix| coin_flipper.random_ratio_one_over(consumed + ix + 1)) .last(); consumed += lower; next = lower; if let Some(ix) = highest_selected { result = self.nth(ix); next -= ix + 1; debug_assert!(result.is_some(), "iterator shorter than size_hint().0"); } } let elem = self.nth(next); if elem.is_none() { return result; } if coin_flipper.random_ratio_one_over(consumed + 1) { result = elem; } consumed += 1; } } /// Uniformly sample `amount` distinct elements into a buffer /// /// Collects values at random from the iterator into a supplied buffer /// until that buffer is filled. /// /// Although the elements are selected randomly, the order of elements in /// the buffer is neither stable nor fully random. If random ordering is /// desired, shuffle the result. /// /// Returns the number of elements added to the buffer. This equals the length /// of the buffer unless the iterator contains insufficient elements, in which /// case this equals the number of elements available. /// /// Complexity is `O(n)` where `n` is the length of the iterator. /// For slices, prefer [`IndexedRandom::sample`]. fn sample_fill(mut self, rng: &mut R, buf: &mut [Self::Item]) -> usize where R: Rng + ?Sized, { let amount = buf.len(); let mut len = 0; while len < amount { if let Some(elem) = self.next() { buf[len] = elem; len += 1; } else { // Iterator exhausted; stop early return len; } } // Continue, since the iterator was not exhausted for (i, elem) in self.enumerate() { let k = rng.random_range(..i + 1 + amount); if let Some(slot) = buf.get_mut(k) { *slot = elem; } } len } /// Uniformly sample `amount` distinct elements into a [`Vec`] /// /// This is equivalent to `sample_fill` except for the result type. /// /// Although the elements are selected randomly, the order of elements in /// the buffer is neither stable nor fully random. If random ordering is /// desired, shuffle the result. /// /// The length of the returned vector equals `amount` unless the iterator /// contains insufficient elements, in which case it equals the number of /// elements available. /// /// Complexity is `O(n)` where `n` is the length of the iterator. /// For slices, prefer [`IndexedRandom::sample`]. #[cfg(feature = "alloc")] fn sample(mut self, rng: &mut R, amount: usize) -> Vec where R: Rng + ?Sized, { let mut reservoir = Vec::from_iter(self.by_ref().take(amount)); // Continue unless the iterator was exhausted // // note: this prevents iterators that "restart" from causing problems. // If the iterator stops once, then so do we. if reservoir.len() == amount { for (i, elem) in self.enumerate() { let k = rng.random_range(..i + 1 + amount); if let Some(slot) = reservoir.get_mut(k) { *slot = elem; } } } reservoir } /// Deprecated: use [`Self::sample_fill`] instead #[deprecated(since = "0.10.0", note = "Renamed to `sample_fill`")] fn choose_multiple_fill(self, rng: &mut R, buf: &mut [Self::Item]) -> usize where R: Rng + ?Sized, { self.sample_fill(rng, buf) } /// Deprecated: use [`Self::sample`] instead #[cfg(feature = "alloc")] #[deprecated(since = "0.10.0", note = "Renamed to `sample`")] fn choose_multiple(self, rng: &mut R, amount: usize) -> Vec where R: Rng + ?Sized, { self.sample(rng, amount) } } impl IteratorRandom for I where I: Iterator + Sized {} #[cfg(test)] mod test { use super::*; #[cfg(all(feature = "alloc", not(feature = "std")))] use alloc::vec::Vec; #[derive(Clone)] struct UnhintedIterator { iter: I, } impl Iterator for UnhintedIterator { type Item = I::Item; fn next(&mut self) -> Option { self.iter.next() } } #[derive(Clone)] struct ChunkHintedIterator { iter: I, chunk_remaining: usize, chunk_size: usize, hint_total_size: bool, } impl Iterator for ChunkHintedIterator { type Item = I::Item; fn next(&mut self) -> Option { if self.chunk_remaining == 0 { self.chunk_remaining = core::cmp::min(self.chunk_size, self.iter.len()); } self.chunk_remaining = self.chunk_remaining.saturating_sub(1); self.iter.next() } fn size_hint(&self) -> (usize, Option) { ( self.chunk_remaining, if self.hint_total_size { Some(self.iter.len()) } else { None }, ) } } #[derive(Clone)] struct WindowHintedIterator { iter: I, window_size: usize, hint_total_size: bool, } impl Iterator for WindowHintedIterator { type Item = I::Item; fn next(&mut self) -> Option { self.iter.next() } fn size_hint(&self) -> (usize, Option) { ( core::cmp::min(self.iter.len(), self.window_size), if self.hint_total_size { Some(self.iter.len()) } else { None }, ) } } #[test] #[cfg_attr(miri, ignore)] // Miri is too slow fn test_iterator_choose() { let r = &mut crate::test::rng(109); fn test_iter + Clone>(r: &mut R, iter: Iter) { let mut chosen = [0i32; 9]; for _ in 0..1000 { let picked = iter.clone().choose(r).unwrap(); chosen[picked] += 1; } for count in chosen.iter() { // Samples should follow Binomial(1000, 1/9) // Octave: binopdf(x, 1000, 1/9) gives the prob of *count == x // Note: have seen 153, which is unlikely but not impossible. assert!( 72 < *count && *count < 154, "count not close to 1000/9: {}", count ); } } test_iter(r, 0..9); test_iter(r, [0, 1, 2, 3, 4, 5, 6, 7, 8].iter().cloned()); #[cfg(feature = "alloc")] test_iter(r, (0..9).collect::>().into_iter()); test_iter(r, UnhintedIterator { iter: 0..9 }); test_iter( r, ChunkHintedIterator { iter: 0..9, chunk_size: 4, chunk_remaining: 4, hint_total_size: false, }, ); test_iter( r, ChunkHintedIterator { iter: 0..9, chunk_size: 4, chunk_remaining: 4, hint_total_size: true, }, ); test_iter( r, WindowHintedIterator { iter: 0..9, window_size: 2, hint_total_size: false, }, ); test_iter( r, WindowHintedIterator { iter: 0..9, window_size: 2, hint_total_size: true, }, ); assert_eq!((0..0).choose(r), None); assert_eq!(UnhintedIterator { iter: 0..0 }.choose(r), None); } #[test] #[cfg_attr(miri, ignore)] // Miri is too slow fn test_iterator_choose_stable() { let r = &mut crate::test::rng(109); fn test_iter + Clone>(r: &mut R, iter: Iter) { let mut chosen = [0i32; 9]; for _ in 0..1000 { let picked = iter.clone().choose_stable(r).unwrap(); chosen[picked] += 1; } for count in chosen.iter() { // Samples should follow Binomial(1000, 1/9) // Octave: binopdf(x, 1000, 1/9) gives the prob of *count == x // Note: have seen 153, which is unlikely but not impossible. assert!( 72 < *count && *count < 154, "count not close to 1000/9: {}", count ); } } test_iter(r, 0..9); test_iter(r, [0, 1, 2, 3, 4, 5, 6, 7, 8].iter().cloned()); #[cfg(feature = "alloc")] test_iter(r, (0..9).collect::>().into_iter()); test_iter(r, UnhintedIterator { iter: 0..9 }); test_iter( r, ChunkHintedIterator { iter: 0..9, chunk_size: 4, chunk_remaining: 4, hint_total_size: false, }, ); test_iter( r, ChunkHintedIterator { iter: 0..9, chunk_size: 4, chunk_remaining: 4, hint_total_size: true, }, ); test_iter( r, WindowHintedIterator { iter: 0..9, window_size: 2, hint_total_size: false, }, ); test_iter( r, WindowHintedIterator { iter: 0..9, window_size: 2, hint_total_size: true, }, ); assert_eq!((0..0).choose(r), None); assert_eq!(UnhintedIterator { iter: 0..0 }.choose(r), None); } #[test] #[cfg_attr(miri, ignore)] // Miri is too slow fn test_iterator_choose_stable_stability() { fn test_iter(iter: impl Iterator + Clone) -> [i32; 9] { let r = &mut crate::test::rng(109); let mut chosen = [0i32; 9]; for _ in 0..1000 { let picked = iter.clone().choose_stable(r).unwrap(); chosen[picked] += 1; } chosen } let reference = test_iter(0..9); assert_eq!( test_iter([0, 1, 2, 3, 4, 5, 6, 7, 8].iter().cloned()), reference ); #[cfg(feature = "alloc")] assert_eq!(test_iter((0..9).collect::>().into_iter()), reference); assert_eq!(test_iter(UnhintedIterator { iter: 0..9 }), reference); assert_eq!( test_iter(ChunkHintedIterator { iter: 0..9, chunk_size: 4, chunk_remaining: 4, hint_total_size: false, }), reference ); assert_eq!( test_iter(ChunkHintedIterator { iter: 0..9, chunk_size: 4, chunk_remaining: 4, hint_total_size: true, }), reference ); assert_eq!( test_iter(WindowHintedIterator { iter: 0..9, window_size: 2, hint_total_size: false, }), reference ); assert_eq!( test_iter(WindowHintedIterator { iter: 0..9, window_size: 2, hint_total_size: true, }), reference ); } #[test] #[cfg(feature = "alloc")] fn test_sample_iter() { let min_val = 1; let max_val = 100; let mut r = crate::test::rng(401); let vals = (min_val..max_val).collect::>(); let small_sample = vals.iter().sample(&mut r, 5); let large_sample = vals.iter().sample(&mut r, vals.len() + 5); assert_eq!(small_sample.len(), 5); assert_eq!(large_sample.len(), vals.len()); // no randomization happens when amount >= len assert_eq!(large_sample, vals.iter().collect::>()); assert!( small_sample .iter() .all(|e| { **e >= min_val && **e <= max_val }) ); } #[test] fn value_stability_choose() { fn choose>(iter: I) -> Option { let mut rng = crate::test::rng(411); iter.choose(&mut rng) } assert_eq!(choose([].iter().cloned()), None); assert_eq!(choose(0..100), Some(33)); assert_eq!(choose(UnhintedIterator { iter: 0..100 }), Some(27)); assert_eq!( choose(ChunkHintedIterator { iter: 0..100, chunk_size: 32, chunk_remaining: 32, hint_total_size: false, }), Some(91) ); assert_eq!( choose(ChunkHintedIterator { iter: 0..100, chunk_size: 32, chunk_remaining: 32, hint_total_size: true, }), Some(91) ); assert_eq!( choose(WindowHintedIterator { iter: 0..100, window_size: 32, hint_total_size: false, }), Some(34) ); assert_eq!( choose(WindowHintedIterator { iter: 0..100, window_size: 32, hint_total_size: true, }), Some(34) ); } #[test] fn value_stability_choose_stable() { fn choose>(iter: I) -> Option { let mut rng = crate::test::rng(411); iter.choose_stable(&mut rng) } assert_eq!(choose([].iter().cloned()), None); assert_eq!(choose(0..100), Some(27)); assert_eq!(choose(UnhintedIterator { iter: 0..100 }), Some(27)); assert_eq!( choose(ChunkHintedIterator { iter: 0..100, chunk_size: 32, chunk_remaining: 32, hint_total_size: false, }), Some(27) ); assert_eq!( choose(ChunkHintedIterator { iter: 0..100, chunk_size: 32, chunk_remaining: 32, hint_total_size: true, }), Some(27) ); assert_eq!( choose(WindowHintedIterator { iter: 0..100, window_size: 32, hint_total_size: false, }), Some(27) ); assert_eq!( choose(WindowHintedIterator { iter: 0..100, window_size: 32, hint_total_size: true, }), Some(27) ); } #[test] fn value_stability_sample() { fn do_test>(iter: I, v: &[u32]) { let mut rng = crate::test::rng(412); let mut buf = [0u32; 8]; assert_eq!(iter.clone().sample_fill(&mut rng, &mut buf), v.len()); assert_eq!(&buf[0..v.len()], v); #[cfg(feature = "alloc")] { let mut rng = crate::test::rng(412); assert_eq!(iter.sample(&mut rng, v.len()), v); } } do_test(0..4, &[0, 1, 2, 3]); do_test(0..8, &[0, 1, 2, 3, 4, 5, 6, 7]); do_test(0..100, &[77, 95, 38, 23, 25, 8, 58, 40]); } }