#![no_std]
extern crate alloc;
use alloc::{
borrow::Cow,
boxed::Box,
collections::BTreeMap,
string::{String, ToString},
vec::Vec,
};
use rand::seq::{IteratorRandom, SliceRandom};
#[allow(dead_code)]
#[cfg(all(feature = "default-rng", feature = "default-words"))]
pub fn petname(words: u8, separator: &str) -> Option<String> {
Petnames::default().generate_one(words, separator)
}
pub type Words<'a> = Cow<'a, [&'a str]>;
#[cfg(feature = "default-words")]
mod words {
include!(concat!(env!("OUT_DIR"), "/words.rs"));
}
pub trait Generator<'a> {
fn generate(&self, rng: &mut dyn rand::RngCore, words: u8, separator: &str) -> Option<String>;
#[cfg(feature = "default-rng")]
fn generate_one(&self, words: u8, separator: &str) -> Option<String> {
self.generate(&mut rand::thread_rng(), words, separator)
}
fn iter(
&'a self,
rng: &'a mut dyn rand::RngCore,
words: u8,
separator: &str,
) -> Box<dyn Iterator<Item = String> + 'a>
where
Self: Sized,
{
let names = Names { generator: self, rng, words, separator: separator.to_string() };
Box::new(names)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Petnames<'a> {
pub adjectives: Words<'a>,
pub adverbs: Words<'a>,
pub nouns: Words<'a>,
}
impl<'a> Petnames<'a> {
#[cfg(feature = "default-words")]
pub fn small() -> Self {
Self {
adjectives: Cow::from(&words::small::ADJECTIVES[..]),
adverbs: Cow::from(&words::small::ADVERBS[..]),
nouns: Cow::from(&words::small::NOUNS[..]),
}
}
#[cfg(feature = "default-words")]
pub fn medium() -> Self {
Self {
adjectives: Cow::from(&words::medium::ADJECTIVES[..]),
adverbs: Cow::from(&words::medium::ADVERBS[..]),
nouns: Cow::from(&words::medium::NOUNS[..]),
}
}
#[cfg(feature = "default-words")]
pub fn large() -> Self {
Self {
adjectives: Cow::from(&words::large::ADJECTIVES[..]),
adverbs: Cow::from(&words::large::ADVERBS[..]),
nouns: Cow::from(&words::large::NOUNS[..]),
}
}
pub fn new(adjectives: &'a str, adverbs: &'a str, nouns: &'a str) -> Self {
Self {
adjectives: Cow::Owned(adjectives.split_whitespace().collect()),
adverbs: Cow::Owned(adverbs.split_whitespace().collect()),
nouns: Cow::Owned(nouns.split_whitespace().collect()),
}
}
pub fn retain<F>(&mut self, mut predicate: F)
where
F: FnMut(&str) -> bool,
{
self.adjectives.to_mut().retain(|word| predicate(word));
self.adverbs.to_mut().retain(|word| predicate(word));
self.nouns.to_mut().retain(|word| predicate(word));
}
pub fn cardinality(&self, words: u8) -> u128 {
Lists::new(words)
.map(|list| match list {
List::Adverb => self.adverbs.len() as u128,
List::Adjective => self.adjectives.len() as u128,
List::Noun => self.nouns.len() as u128,
})
.reduce(u128::saturating_mul)
.unwrap_or(0u128)
}
}
impl<'a> Generator<'a> for Petnames<'a> {
fn generate(&self, rng: &mut dyn rand::RngCore, words: u8, separator: &str) -> Option<String> {
let name = itertools::intersperse(
Lists::new(words).filter_map(|list| match list {
List::Adverb => self.adverbs.choose(rng).copied(),
List::Adjective => self.adjectives.choose(rng).copied(),
List::Noun => self.nouns.choose(rng).copied(),
}),
separator,
)
.collect::<String>();
if name.is_empty() {
None
} else {
Some(name)
}
}
}
#[cfg(feature = "default-words")]
impl<'a> Default for Petnames<'a> {
fn default() -> Self {
Self::medium()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Alliterations<'a> {
groups: BTreeMap<char, Petnames<'a>>,
}
impl<'a> Alliterations<'a> {
pub fn retain<F>(&mut self, predicate: F)
where
F: FnMut(&char, &mut Petnames) -> bool,
{
self.groups.retain(predicate)
}
pub fn cardinality(&self, words: u8) -> u128 {
self.groups
.values()
.map(|petnames| petnames.cardinality(words))
.reduce(u128::saturating_add)
.unwrap_or(0u128)
}
}
impl<'a> From<Petnames<'a>> for Alliterations<'a> {
fn from(petnames: Petnames<'a>) -> Self {
let mut adjectives: BTreeMap<char, Vec<&str>> = group_words_by_first_letter(petnames.adjectives);
let mut adverbs: BTreeMap<char, Vec<&str>> = group_words_by_first_letter(petnames.adverbs);
let nouns: BTreeMap<char, Vec<&str>> = group_words_by_first_letter(petnames.nouns);
Alliterations {
groups: nouns.into_iter().fold(BTreeMap::default(), |mut acc, (first_letter, nouns)| {
acc.insert(
first_letter,
Petnames {
adjectives: adjectives.remove(&first_letter).unwrap_or_default().into(),
adverbs: adverbs.remove(&first_letter).unwrap_or_default().into(),
nouns: Cow::from(nouns),
},
);
acc
}),
}
}
}
impl<'a, GROUPS> From<GROUPS> for Alliterations<'a>
where
GROUPS: IntoIterator<Item = (char, Petnames<'a>)>,
{
fn from(groups: GROUPS) -> Self {
Self { groups: groups.into_iter().collect() }
}
}
fn group_words_by_first_letter(words: Words) -> BTreeMap<char, Vec<&str>> {
words.iter().fold(BTreeMap::default(), |mut acc, s| match s.chars().next() {
Some(first_letter) => {
acc.entry(first_letter).or_default().push(s);
acc
}
None => acc,
})
}
impl<'a> Generator<'a> for Alliterations<'a> {
fn generate(&self, rng: &mut dyn rand::RngCore, words: u8, separator: &str) -> Option<String> {
self.groups.values().choose(rng).and_then(|group| group.generate(rng, words, separator))
}
}
#[cfg(feature = "default-words")]
impl<'a> Default for Alliterations<'a> {
fn default() -> Self {
Petnames::default().into()
}
}
#[derive(Debug, PartialEq)]
enum List {
Adverb,
Adjective,
Noun,
}
#[derive(Debug, PartialEq)]
enum Lists {
Adverb(u8),
Adjective,
Noun,
Done,
}
impl Lists {
fn new(words: u8) -> Self {
match words {
0 => Self::Done,
1 => Self::Noun,
2 => Self::Adjective,
n => Self::Adverb(n - 3),
}
}
fn current(&self) -> Option<List> {
match self {
Self::Adjective => Some(List::Adjective),
Self::Adverb(_) => Some(List::Adverb),
Self::Noun => Some(List::Noun),
Self::Done => None,
}
}
fn advance(&mut self) {
*self = match self {
Self::Adverb(0) => Self::Adjective,
Self::Adverb(remaining) => Self::Adverb(*remaining - 1),
Self::Adjective => Self::Noun,
Self::Noun | Self::Done => Self::Done,
}
}
fn remaining(&self) -> usize {
match self {
Self::Adverb(n) => (n + 3) as usize,
Self::Adjective => 2,
Self::Noun => 1,
Self::Done => 0,
}
}
}
impl Iterator for Lists {
type Item = List;
fn next(&mut self) -> Option<Self::Item> {
let current = self.current();
self.advance();
current
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.remaining();
(remaining, Some(remaining))
}
}
struct Names<'a, GENERATOR>
where
GENERATOR: Generator<'a>,
{
generator: &'a GENERATOR,
rng: &'a mut dyn rand::RngCore,
words: u8,
separator: String,
}
impl<'a, GENERATOR> Iterator for Names<'a, GENERATOR>
where
GENERATOR: Generator<'a>,
{
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
self.generator.generate(self.rng, self.words, &self.separator)
}
}
#[cfg(test)]
mod tests {
#[test]
fn lists_sequences_adverbs_adjectives_then_names() {
let mut lists = super::Lists::new(4);
assert_eq!(super::Lists::Adverb(1), lists);
assert_eq!(Some(super::List::Adverb), lists.next());
assert_eq!(super::Lists::Adverb(0), lists);
assert_eq!(Some(super::List::Adverb), lists.next());
assert_eq!(super::Lists::Adjective, lists);
assert_eq!(Some(super::List::Adjective), lists.next());
assert_eq!(super::Lists::Noun, lists);
assert_eq!(Some(super::List::Noun), lists.next());
assert_eq!(super::Lists::Done, lists);
assert_eq!(None, lists.next());
}
#[test]
fn lists_size_hint() {
let mut lists = super::Lists::new(3);
assert_eq!((3, Some(3)), lists.size_hint());
assert!(lists.next().is_some());
assert_eq!((2, Some(2)), lists.size_hint());
assert!(lists.next().is_some());
assert_eq!((1, Some(1)), lists.size_hint());
assert!(lists.next().is_some());
assert_eq!((0, Some(0)), lists.size_hint());
assert_eq!(None, lists.next());
assert_eq!((0, Some(0)), lists.size_hint());
}
}