use std::fmt::Display; include!(concat!(env!("OUT_DIR"), "/codegen.rs")); #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] /// A word struct pub struct Word<'a> { /// The word itself pub word: &'a str, /// The binary representation of this number /// /// The words are responsible for 13 bits of data, so this is fine to fit in a u16 pub number: u16, } impl Display for Word<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.word) } } /// Helper function that gets the mapped number from a word /// /// ```rust /// use words::get_number; /// /// assert!(get_number("ThE").is_some()); /// assert!(get_number("AsDf").is_none()); /// ``` pub fn get_number(maybe_word: S) -> Option where S: AsRef, { get_word(maybe_word).map(|w| w.number) } /// Gets a word from the word map /// /// ```rust /// use words::get_word; /// /// assert!(get_word("THE").is_some()); /// assert!(get_word("the").is_some()); /// assert!(get_word("tHe").is_some()); /// assert!(get_word("ASDFASDF").is_none()); /// ``` pub fn get_word(maybe_word: S) -> Option<&'static Word<'static>> where S: AsRef, { WORD_MAP .get(&maybe_word.as_ref().trim().to_ascii_uppercase()) .copied() } /// Gets multiple words given their index, but without repitition by skipping words if they would match /// /// For example, if the numeric value this should encode is [0, 0, 0], /// the index of the words would be [0, 1, 2] /// /// [1, 2, 3] => word number [1, 3, 5] /// [3, 2, 1] => word number [3, 2, 1] /// [1, 1, 0] => word number [1, 2, 0] pub fn get_words_multi(word_idx: [u16; N]) -> [&'static Word<'static>; N] { // The vec of words to return let mut words = Vec::with_capacity(N); // The effective index of every word let mut seen_effective_idx = Vec::with_capacity(N); for w_idx in word_idx { let mut w_effective = w_idx; seen_effective_idx.sort(); for other_effective_idx in seen_effective_idx.iter() { if &w_effective >= other_effective_idx { w_effective += 1; } } seen_effective_idx.push(w_effective); words.push(NUMBER_TO_WORDS[w_effective as usize][0]); } words .try_into() .expect("get_words_multi return vec did not match size") } pub fn get_word_numbers_multi(words: [&Word<'static>; N]) -> [u16; N] { let mut values = Vec::with_capacity(N); let mut seen_effective_idx = words.iter().map(|w| w.number).collect::>(); seen_effective_idx.sort(); for w_e in words.iter().rev().map(|w| w.number) { let mut w_idx = w_e; if let Some(idx) = seen_effective_idx .iter() .position(|other_e| *other_e == w_idx) { seen_effective_idx.remove(idx); } for other_e in seen_effective_idx.iter().rev() { if w_idx >= *other_e { w_idx -= 1; } } values.push(w_idx); } values.reverse(); // pub fn get_idx_from_multi(words: &[&'static Word<'static>>]) -> Vec<&'static Word<'static>> { values .try_into() .expect("get_words_multi return vec did not match size") } #[cfg(test)] mod tests { use super::*; #[test] fn test_length() { assert!(WORDS.len() >= 4096 + 2); assert!(NUMBER_TO_WORDS.len() >= 4096 + 2); for i in 0..4096 + 2_u16 { assert_eq!(NUMBER_TO_WORDS[i as usize][0].number, i); } } #[test] fn test_equivalence() { // Test equivalence macro_rules! te { ($word1:ident, $word2: ident) => { eprintln!("Checking if {:?} is a word", stringify!($word1)); assert!(get_word(stringify!($word1)).is_some()); eprintln!("Checking if {:?} is a word", stringify!($word2)); assert!(get_word(stringify!($word2)).is_some()); eprintln!("Checking equivalence"); assert_eq!( get_word(stringify!($word1)).unwrap().number, get_word(stringify!($word1)).unwrap().number ); }; } // Homonyms // te!(blue, blew); te!(yellow, hello); // te!(days, daze); // te!(day, days); // Plurals // te!(sent, sense); // te!(sent, scents); // te!(sent, cents); } #[test] fn test_get_words_multi() { for w1i in 0..=5 { for w2i in 0..=5 { for w3i in 0..=5 { let input = [w1i, w2i, w3i]; eprintln!("Testing getting word {input:?}"); let effective = get_words_multi(input); let [w1e, w2e, w3e] = effective; let output = get_word_numbers_multi(effective); // let [w1o, w2o, w30] = output; eprintln!("{:?} => {:?} => {:?}", input, effective, output); // Make sure the effective values are not the same assert_ne!(w1e, w2e); assert_ne!(w2e, w3e); assert_ne!(w1e, w3e); // Make sure the encoded words are not the same assert_ne!(w1e.number, w2e.number); assert_ne!(w2e.number, w3e.number); assert_ne!(w1e.number, w3e.number); assert_ne!(w1e.word, w2e.word); assert_ne!(w2e.word, w3e.word); assert_ne!(w1e.word, w3e.word); // Make sure that once decoded again, the results match assert_eq!(input, output); } } } } }