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",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "this_algorithm"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"s2",
|
||||||
|
"thiserror",
|
||||||
|
"words",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "this_algoritm"
|
name = "this_algoritm"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -7,6 +7,7 @@ edition = "2021"
|
|||||||
members = [
|
members = [
|
||||||
".",
|
".",
|
||||||
"./words",
|
"./words",
|
||||||
|
"./this_algorithm",
|
||||||
]
|
]
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# 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"));
|
include!(concat!(env!("OUT_DIR"), "/codegen.rs"));
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
/// A word struct
|
/// A word struct
|
||||||
pub struct Word<'a> {
|
pub struct Word<'a> {
|
||||||
/// The word itself
|
/// The word itself
|
||||||
@ -13,10 +14,10 @@ pub struct Word<'a> {
|
|||||||
pub number: u16,
|
pub number: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
impl Display for Word<'_> {
|
||||||
pub struct Address<'a> {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
number: u32,
|
write!(f, "{}", self)
|
||||||
words: [Word<'a>; 3],
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function that gets the mapped number from a word
|
/// Helper function that gets the mapped number from a word
|
||||||
@ -24,10 +25,10 @@ pub struct Address<'a> {
|
|||||||
/// ```rust
|
/// ```rust
|
||||||
/// use words::get_number;
|
/// use words::get_number;
|
||||||
///
|
///
|
||||||
/// assert!(get_number("ThE").is_ok());
|
/// assert!(get_number("ThE").is_some());
|
||||||
/// assert!(get_number("AsDf").is_err());
|
/// 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
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
{
|
{
|
||||||
@ -39,25 +40,16 @@ where
|
|||||||
/// ```rust
|
/// ```rust
|
||||||
/// use words::get_word;
|
/// use words::get_word;
|
||||||
///
|
///
|
||||||
/// assert!(get_word("THE").is_ok());
|
/// assert!(get_word("THE").is_some());
|
||||||
/// assert!(get_word("the").is_ok());
|
/// assert!(get_word("the").is_some());
|
||||||
/// assert!(get_word("tHe").is_ok());
|
/// assert!(get_word("tHe").is_some());
|
||||||
/// assert!(get_word("ASDFASDF").is_err());
|
/// 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
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
{
|
{
|
||||||
WORD_MAP
|
WORD_MAP
|
||||||
.get(&maybe_word.as_ref().trim().to_ascii_uppercase())
|
.get(&maybe_word.as_ref().trim().to_ascii_uppercase())
|
||||||
.copied()
|
.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