Start working on actual algorithm code
This commit is contained in:
parent
31550c0586
commit
2bed778e16
9
Cargo.lock
generated
9
Cargo.lock
generated
@ -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"
|
||||
|
@ -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
11
this_algorithm/Cargo.toml
Normal 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
172
this_algorithm/src/lib.rs
Normal 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());
|
||||
}
|
||||
}
|
1
this_algorithm/tests/common/mod.rs
Normal file
1
this_algorithm/tests/common/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
|
18
this_algorithm/tests/display.rs
Normal file
18
this_algorithm/tests/display.rs
Normal 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"
|
||||
// );
|
||||
// }
|
@ -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,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user