202 lines
5.7 KiB
Rust
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|