diff --git a/src/global_rng.rs b/src/global_rng.rs index 0c960ea..d2a611c 100644 --- a/src/global_rng.rs +++ b/src/global_rng.rs @@ -120,6 +120,20 @@ pub fn uppercase() -> char { with_rng(|r| r.uppercase()) } +/// Choose an item from an iterator at random. +/// +/// This function may have an unexpected result if the `len()` property of the +/// iterator does not match the actual number of items in the iterator. If +/// the iterator is empty, this returns `None`. +#[inline] +pub fn choice(iter: I) -> Option +where + I: IntoIterator, + I::IntoIter: ExactSizeIterator, +{ + with_rng(|r| r.choice(iter)) +} + /// Generates a random digit in the given `base`. /// /// Digits are represented by `char`s in ranges 0-9 and a-z. diff --git a/src/lib.rs b/src/lib.rs index c47160b..cefac4a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -279,18 +279,14 @@ impl Rng { #[inline] pub fn alphabetic(&mut self) -> char { const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - let len = CHARS.len() as u8; - let i = self.u8(..len); - CHARS[i as usize] as char + *self.choice(CHARS).unwrap() as char } /// Generates a random `char` in ranges a-z, A-Z and 0-9. #[inline] pub fn alphanumeric(&mut self) -> char { const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - let len = CHARS.len() as u8; - let i = self.u8(..len); - CHARS[i as usize] as char + *self.choice(CHARS).unwrap() as char } /// Generates a random `bool`. @@ -403,9 +399,7 @@ impl Rng { #[inline] pub fn lowercase(&mut self) -> char { const CHARS: &[u8] = b"abcdefghijklmnopqrstuvwxyz"; - let len = CHARS.len() as u8; - let i = self.u8(..len); - CHARS[i as usize] as char + *self.choice(CHARS).unwrap() as char } /// Initializes this generator with the given seed. @@ -420,6 +414,29 @@ impl Rng { self.0 } + /// Choose an item from an iterator at random. + /// + /// This function may have an unexpected result if the `len()` property of the + /// iterator does not match the actual number of items in the iterator. If + /// the iterator is empty, this returns `None`. + #[inline] + pub fn choice(&mut self, iter: I) -> Option + where + I: IntoIterator, + I::IntoIter: ExactSizeIterator, + { + let mut iter = iter.into_iter(); + + // Get the item at a random index. + let len = iter.len(); + if len == 0 { + return None; + } + let index = self.usize(0..len); + + iter.nth(index) + } + /// Shuffles a slice randomly. #[inline] pub fn shuffle(&mut self, slice: &mut [T]) { @@ -529,9 +546,7 @@ impl Rng { #[inline] pub fn uppercase(&mut self) -> char { const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - let len = CHARS.len() as u8; - let i = self.u8(..len); - CHARS[i as usize] as char + *self.choice(CHARS).unwrap() as char } /// Generates a random `char` in the given range. diff --git a/tests/smoke.rs b/tests/smoke.rs index f0ae448..93e658f 100644 --- a/tests/smoke.rs +++ b/tests/smoke.rs @@ -118,3 +118,13 @@ fn with_seed() { b.seed(7); assert_eq!(a.u64(..), b.u64(..)); } + +#[test] +fn choice() { + let items = [1, 4, 9, 5, 2, 3, 6, 7, 8, 0]; + let mut r = fastrand::Rng::new(); + + for item in &items { + while r.choice(&items).unwrap() != item {} + } +}