diff --git a/README.md b/README.md index 326007e..2650b2f 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,6 @@ Options: -d, --dir Use custom word lists by specifying a directory containing `adjectives.txt`, `adverbs.txt`, and `nouns.txt` --count Generate multiple names; or use --stream to generate continuously [default: 1] --stream Stream names continuously - --non-repeating Do not generate the same name more than once -l, --letters Maximum number of letters in each word; 0 for unlimited [default: 0] -a, --alliterate Generate names where each word begins with the same letter -A, --alliterate-with Generate names where each word begins with the given letter diff --git a/src/cli.rs b/src/cli.rs index 898df46..7fe3fee 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -36,10 +36,6 @@ pub struct Cli { #[arg(long, conflicts_with = "count")] pub stream: bool, - /// Do not generate the same name more than once - #[arg(long)] - pub non_repeating: bool, - /// Maximum number of letters in each word; 0 for unlimited #[arg(short, long, value_name = "LETTERS", default_value_t = 0)] pub letters: usize, diff --git a/src/lib.rs b/src/lib.rs index b4b0249..0b95a1f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,7 +60,6 @@ extern crate alloc; use alloc::{ borrow::Cow, string::{String, ToString}, - vec::Vec, }; use itertools::Itertools; @@ -252,40 +251,6 @@ impl<'a> Petnames<'a> { { Names { petnames: self, rng, words, separator: separator.to_string() } } - - /// Iterator yielding unique – i.e. non-repeating – petnames. - /// - /// # Examples - /// - /// ```rust - /// # #[cfg(all(feature = "default-rng", feature = "default-words"))] - /// let mut rng = rand::thread_rng(); - /// # #[cfg(all(feature = "default-rng", feature = "default-words"))] - /// let petnames = petname::Petnames::default(); - /// # #[cfg(all(feature = "default-rng", feature = "default-words"))] - /// let mut iter = petnames.iter_non_repeating(&mut rng, 4, "_"); - /// # #[cfg(all(feature = "default-rng", feature = "default-words"))] - /// println!("name: {}", iter.next().unwrap()); - /// ``` - /// - pub fn iter_non_repeating( - &'a self, - rng: &'a mut RNG, - words: u8, - separator: &str, - ) -> impl Iterator + 'a - where - RNG: rand::Rng, - { - let lists: Vec<&'a Words<'a>> = Lists::new(words) - .map(|list| match list { - List::Adverb => &self.adverbs, - List::Adjective => &self.adjectives, - List::Noun => &self.nouns, - }) - .collect(); - NamesProduct::shuffled(&lists, rng, separator) - } } #[cfg(feature = "default-words")] @@ -394,123 +359,6 @@ where } } -/// Iterator yielding petnames from the product of given word lists. -/// -/// This can be used to ensure that only unique names are produced. -struct NamesProduct<'a, ITERATOR> -where - ITERATOR: Iterator>, -{ - iters: Vec<(ITERATOR, Option<&'a str>)>, - separator: String, - capacity: usize, - size: Option, -} - -impl<'a> NamesProduct<'a, core::iter::Cycle>>> { - /// Shuffles each of the given `lists` with `rng`, then cycles through the - /// product of the lists, joining with `separator`. The leftmost list will - /// cycle most rapidly. - fn shuffled(lists: &[&'a Words<'a>], rng: &'a mut RNG, separator: &str) -> Self - where - RNG: rand::Rng, - { - NamesProduct { - iters: lists - .iter() - .map(|words| { - let mut list: Vec> = Vec::with_capacity(words.len().saturating_add(1)); - list.extend(words.iter().map(|word| Some(*word))); - list.shuffle(rng); // Could be expensive. - list.push(None); // Cycle marker. - (list.into_iter().cycle(), None) - }) - .collect(), - separator: separator.to_string(), - capacity: Self::capacity(lists, separator), - size: match lists { - [] => Some(0), - ls => ls.iter().try_fold(1usize, |acc, list| acc.checked_mul(list.len())), - }, - } - } - - fn capacity(lists: &[&'a Words<'a>], separator: &str) -> usize { - ( - // Sum of the length of the longest possible word in each word list. - lists - .iter() - .filter_map(|words| words.iter().map(|word| word.len()).max()) - .fold(0usize, |sum, len| sum.saturating_add(len)) - // The total length of all separators. Careful not to wrap usize. - + (separator.len().saturating_mul(lists.len().saturating_sub(1))) - ) - // Things run _much_ quicker when the capacity is a power of 2. Memory - // alignment? If so it may be enough to align at, say, 8 bytes, but this - // works for now. - .checked_next_power_of_two() - // In case there are no lists, or they're all empty... or we have - // calculated that we need more than usize::MAX capacity. - .unwrap_or(0) - } -} - -impl<'a, ITERATOR> Iterator for NamesProduct<'a, ITERATOR> -where - ITERATOR: Iterator>, -{ - type Item = String; - - fn size_hint(&self) -> (usize, Option) { - (self.size.unwrap_or(0), self.size) - } - - fn next(&mut self) -> Option { - let mut bump = true; // Request advance of next iterator. - for (iter, word) in self.iters.iter_mut() { - if bump || word.is_none() { - match iter.next() { - None => { - // This shouldn't happen because we expect the iterators - // to cycle. However, if it does, we're definitely done. - return None; - } - Some(None) => { - // This is the cycle end marker. We want to get another - // new word from this iterator, and advance the *next* - // iterator too. - match iter.next() { - None => return None, - Some(None) => return None, - Some(s) => *word = s, - } - bump = true - } - Some(s) => { - // We have a new word from this iterator, so we do not - // yet need to advance the next iterator. - *word = s; - bump = false - } - } - } - } - if bump { - // We reached the end of the last iterator, hence we're done. - None - } else { - // Keep track of the number of names remaining. - self.size = self.size.map(|s| s.saturating_sub(1)); - // We may be able to construct a name! - self.iters.iter().try_fold(String::with_capacity(self.capacity), |acc, (_, w)| match (acc, *w) { - (s, Some(w)) if s.is_empty() => Some(s + w), - (s, Some(w)) => Some(s + &self.separator + w), - _ => None, - }) - } - } -} - #[cfg(test)] mod tests { #[test] diff --git a/src/main.rs b/src/main.rs index a558ee2..bc73d8a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -123,11 +123,7 @@ fn run(cli: Cli) -> Result<(), Error> { let count = if cli.stream { None } else { Some(cli.count) }; // Get an iterator for the names we want to print out. - if cli.non_repeating { - printer(&mut writer, petnames.iter_non_repeating(&mut rng, cli.words, &cli.separator), count) - } else { - printer(&mut writer, petnames.iter(&mut rng, cli.words, &cli.separator), count) - } + printer(&mut writer, petnames.iter(&mut rng, cli.words, &cli.separator), count) } fn printer(writer: &mut OUT, names: NAMES, count: Option) -> Result<(), Error> diff --git a/tests/basic.rs b/tests/basic.rs index bb05d34..d442e4b 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -77,66 +77,3 @@ fn petnames_iter_yields_names() { let mut iter: Box> = Box::new(names); assert_eq!(Some("bar.foo.baz".to_string()), iter.next()); } - -#[test] -fn petnames_iter_non_repeating_yields_unique_names() { - let mut rng = StepRng::new(0, 1); - let petnames = Petnames::new("a1 a2", "b1 b2 b3", "c1 c2"); - let names: Vec = petnames.iter_non_repeating(&mut rng, 3, ".").collect(); - assert_eq!( - vec![ - "b2.a2.c2", "b3.a2.c2", "b1.a2.c2", "b2.a1.c2", "b3.a1.c2", "b1.a1.c2", "b2.a2.c1", "b3.a2.c1", - "b1.a2.c1", "b2.a1.c1", "b3.a1.c1", "b1.a1.c1" - ], - names - ) -} - -#[test] -fn petnames_iter_non_repeating_provides_size_hint() { - let mut rng = StepRng::new(0, 1); - let petnames = Petnames::new("a1 a2", "b1 b2 b3", "c1 c2"); - let iter = petnames.iter_non_repeating(&mut rng, 3, "."); - assert_eq!((12, Some(12)), iter.size_hint()); - assert_eq!((9, Some(9)), iter.skip(3).size_hint()); -} - -#[test] -fn petnames_iter_non_repeating_provides_size_hint_that_saturates() { - let mut rng = StepRng::new(0, 1); - let petnames = Petnames::new("a1 a2", "b1 b2", "c1 c2"); - let iter = petnames.iter_non_repeating(&mut rng, 3, "."); - assert_eq!((0, Some(0)), iter.skip(10).size_hint()); -} - -#[test] -fn petnames_iter_non_repeating_provides_size_hint_that_is_zero_when_any_list_is_empty() { - let mut rng = StepRng::new(0, 1); - let petnames = Petnames::new("", "b1 b2", "c1 c2"); - let iter = petnames.iter_non_repeating(&mut rng, 3, "."); - assert_eq!((0, Some(0)), iter.size_hint()); -} - -#[test] -fn petnames_iter_non_repeating_provides_size_hint_that_is_zero_when_no_word_lists_are_given() { - let mut rng = StepRng::new(0, 1); - let petnames = Petnames::new("a1 a2", "b1 b2", "c1 c2"); - let iter = petnames.iter_non_repeating(&mut rng, 0, "."); - assert_eq!((0, Some(0)), iter.size_hint()); -} - -#[test] -fn petnames_iter_non_repeating_yields_nothing_when_any_word_list_is_empty() { - let mut rng = StepRng::new(0, 1); - let petnames = Petnames::new("a1 a2", "", "c1 c2"); - let names: Vec = petnames.iter_non_repeating(&mut rng, 3, ".").collect(); - assert_eq!(Vec::::new(), names); -} - -#[test] -fn petnames_iter_non_repeating_yields_nothing_when_no_word_lists_are_given() { - let mut rng = StepRng::new(0, 1); - let petnames = Petnames::new("a1 a2", "b1 b2", "c1 c2"); - let names: Vec = petnames.iter_non_repeating(&mut rng, 0, ".").collect(); - assert_eq!(Vec::::new(), names); -}