this_algorithm/words/src/lib.rs
2023-04-18 14:55:02 -04:00

202 lines
5.7 KiB
Rust

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<S>(maybe_word: S) -> Option<u16>
where
S: AsRef<str>,
{
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<S>(maybe_word: S) -> Option<&'static Word<'static>>
where
S: AsRef<str>,
{
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<const N: usize>(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<const N: usize>(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::<Vec<_>>();
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);
}
}
}
}
}