Start working on actual algorithm code

This commit is contained in:
Austen Adler 2023-02-15 21:16:43 -05:00
parent 31550c0586
commit 2bed778e16
7 changed files with 227 additions and 23 deletions

9
Cargo.lock generated
View File

@ -276,6 +276,15 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "this_algorithm"
version = "0.1.0"
dependencies = [
"s2",
"thiserror",
"words",
]
[[package]]
name = "this_algoritm"
version = "0.1.0"

View File

@ -7,6 +7,7 @@ edition = "2021"
members = [
".",
"./words",
"./this_algorithm",
]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

11
this_algorithm/Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "this_algorithm"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
s2 = "0.0.12"
thiserror = "1.0.38"
words={path="../words"}

172
this_algorithm/src/lib.rs Normal file
View File

@ -0,0 +1,172 @@
#![warn(clippy::all, clippy::pedantic, clippy::nursery)]
#![allow(clippy::cast_possible_truncation, clippy::multiple_crate_versions)]
#![allow(dead_code, unused_imports)]
use std::{
ascii::AsciiExt,
fmt::Display,
ops::{Add, AddAssign},
str::FromStr,
};
use thiserror::Error;
use s2::cellid::CellID;
use words::Word;
pub type Number = u32;
pub type Version = u8;
const V0_MAX_NUMBER: u32 = 1024;
#[derive(Error, Debug, Eq, PartialEq)]
pub enum Error {
#[error("Word does not exist")]
WordDoesNotExist(String),
#[error("Invalid version")]
InvalidVersion(Version),
#[error("Unimplemented version")]
UnimplementedVersion(Version),
#[error("Number out of range")]
NumberOutOfRange(Number),
#[error("Invalid encoding")]
InvalidEncoding,
#[error("Wrong number of components")]
WrongComponentCount,
#[error("No number component")]
NoNumberComponent,
#[error("Empty string given")]
Empty,
}
/// An encoded this_algorithm address
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Address<'a> {
number: Number,
words: [&'a Word<'a>; 3],
}
impl FromStr for Address<'_> {
type Err = Error;
/// Try to parse `&str` as an `Address`. The format can be one of:
///
/// * `0000 WORD0 WORD1 WORD2` (regular)
/// * `WORD2 WORD1 WORD0 0000` (reversed)
fn from_str(s: &str) -> Result<Self, Self::Err> {
if !s.is_ascii() {
return Err(Error::InvalidEncoding);
}
let s = s.trim().to_ascii_uppercase();
let components = s.split_ascii_whitespace().collect::<Vec<_>>();
// Make sure our indexing can't fail
if components.is_empty() {
return Err(Error::Empty);
}
// Check if either the beginning or end is a number
let (reverse, number) = if let Ok(number) = components.first().unwrap().parse::<Number>() {
// The number is the first component
(false, number)
} else if let Ok(number) = components.last().unwrap().parse::<Number>() {
// The number is the last component
(true, number)
} else {
return Err(Error::NoNumberComponent);
};
// A vec of the non-number component
let other_components: Vec<&str> = if reverse {
components.into_iter().rev().skip(1).collect::<Vec<_>>()
} else {
components.into_iter().skip(1).collect::<Vec<_>>()
};
match extract_version(number) {
0 => Self::parse_v0(number, other_components),
ver => Err(Error::InvalidVersion(ver)),
}
}
}
impl Address<'_> {
fn parse_v0(number: Number, word_components: Vec<&str>) -> Result<Self, Error> {
if number > V0_MAX_NUMBER {
return Err(Error::NumberOutOfRange(number));
}
if word_components.len() != 3 {
return Err(Error::WrongComponentCount);
}
// Convert each word component into a word, returning an error if any do not match
let words = TryInto::<[&'static Word; 3]>::try_into(
word_components
.iter()
.map(|w| words::get_word(w).ok_or_else(|| Error::WordDoesNotExist(w.to_string())))
.collect::<Result<Vec<&'static Word>, Error>>()?,
)
// This unwrap is okay because we just checked to make sure the number of word components was 3
.unwrap();
Ok(Self { number, words })
}
}
fn extract_version(number: Number) -> Version {
((number >> 10) & 0b11) as Version
}
impl Display for Address<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} {} {} {}",
self.number, self.words[0], self.words[1], self.words[2]
)
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! w {
($word:tt) => {
words::get_word($word).unwrap()
};
}
macro_rules! addr {
($number:tt, $word0:tt, $word1:tt, $word2:tt) => {
Address {
number: $number,
words: [w!($word0), w!($word1), w!($word2)],
}
};
}
#[test]
fn test_extract_version() {
assert_eq!(extract_version(0b0000_0000_0000), 0b00);
assert_eq!(extract_version(0b1100_0000_0000), 0b11);
assert_eq!(extract_version(0b0100_0000_0000), 0b01);
assert_eq!(extract_version(0b1000_0000_0000), 0b10);
assert_eq!(extract_version(0b11 << 10), 0b11);
assert_eq!(extract_version(0b00 << 10), 0b00);
assert_eq!(extract_version(0b10 << 10), 0b10);
assert_eq!(extract_version(0b01 << 10), 0b01);
}
#[test]
fn test_parse_v0() {
assert_eq!(
Address::parse_v0(1000, vec!["apple", "orange", "grape"]),
Ok(addr![1000, "apple", "orange", "grape"])
);
assert!(Address::parse_v0(1000, vec!["ASDF", "orange", "grape"]).is_err());
assert!(Address::parse_v0(10_000, vec!["apple", "orange", "grape"]).is_err());
}
}

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,18 @@
use words::Word;
mod common;
// #[test]
// fn test_address() {
// let addr = Address {
// number: 1234,
// };
// assert_eq!(
// (Word {
// word: "asdf",
// number: 0,
// })
// .to_string(),
// "asdf"
// );
// }

View File

@ -1,7 +1,8 @@
use thiserror::Error;
use std::fmt::Display;
include!(concat!(env!("OUT_DIR"), "/codegen.rs"));
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, Eq, PartialEq)]
/// A word struct
pub struct Word<'a> {
/// The word itself
@ -13,10 +14,10 @@ pub struct Word<'a> {
pub number: u16,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Address<'a> {
number: u32,
words: [Word<'a>; 3],
impl Display for Word<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self)
}
}
/// Helper function that gets the mapped number from a word
@ -24,10 +25,10 @@ pub struct Address<'a> {
/// ```rust
/// use words::get_number;
///
/// assert!(get_number("ThE").is_ok());
/// assert!(get_number("AsDf").is_err());
/// assert!(get_number("ThE").is_some());
/// assert!(get_number("AsDf").is_none());
/// ```
pub fn get_number<S>(maybe_word: S) -> Result<u16, Error>
pub fn get_number<S>(maybe_word: S) -> Option<u16>
where
S: AsRef<str>,
{
@ -39,25 +40,16 @@ where
/// ```rust
/// use words::get_word;
///
/// assert!(get_word("THE").is_ok());
/// assert!(get_word("the").is_ok());
/// assert!(get_word("tHe").is_ok());
/// assert!(get_word("ASDFASDF").is_err());
/// 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) -> Result<&'static Word<'static>, Error>
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()
.ok_or(Error::WordNotFound)
}
#[derive(Error, Debug)]
pub enum Error {
#[error("Word not found")]
WordNotFound,
#[error("The requested number is out of bounds")]
NumberOutOfBounds,
}