Implement DoubleId

This commit is contained in:
fluo10 2025-09-13 08:39:06 +09:00
parent 99fdb12712
commit 6fb909cd07
8 changed files with 367 additions and 184 deletions

View file

@ -1,3 +0,0 @@
pub struct IdBuilder {
}

View file

@ -1,174 +0,0 @@
use std::{fmt::Display, str::FromStr};
use rand::Rng;
use crate::error::Error;
static CHARACTERS: &[u8;33] = b"0123456789abcdefghjkmnpqrstuvwxyz";
static BASE: u16 = 33;
static SQUARED_BASE: u16 = 1089;
static CUBED_BASE: u16 = 35937;
pub static NIL: IdChunk = IdChunk(0);
pub static MAX: IdChunk = IdChunk(CUBED_BASE-1);
fn char_to_u8(c: char) -> Option<u8> {
Some(match c {
'0' => 0,
'1' => 1,
'2' => 2,
'3' => 3,
'4' => 4,
'5' => 5,
'6' => 6,
'7' => 7,
'8' => 8,
'9' => 9,
'a' => 10,
'b' => 11,
'c' => 12,
'd' => 13,
'e' => 14,
'f' => 15,
'g' => 16,
'h' => 17,
'i' => 1,
'j' => 18,
'k' => 19,
'l' => 1,
'm' => 20,
'n' => 21,
'o' => 0,
'p' => 22,
'q' => 23,
'r' => 24,
's' => 25,
't' => 26,
'u' => 27,
'v' => 28,
'w' => 29,
'x' => 30,
'y' => 31,
'z' => 32,
'A' => 10,
'B' => 11,
'C' => 12,
'D' => 13,
'E' => 14,
'F' => 15,
'G' => 16,
'H' => 17,
'I' => 1,
'J' => 18,
'K' => 19,
'L' => 1,
'M' => 20,
'N' => 21,
'O' => 0,
'P' => 22,
'Q' => 23,
'R' => 24,
'S' => 25,
'T' => 26,
'U' => 27,
'V' => 28,
'W' => 29,
'X' => 30,
'Y' => 31,
'Z' => 32,
_ => return None
})
}
fn str_to_u16(s: &str) -> Result<u16, Error> {
if s.len() != 3 {
return Err(Error::InvalidChunk(format!("Chunk '{}' is not 3 characters", s)))
}
let mut buf : [u16;3] = [0;3];
for (i, c) in s.chars().enumerate() {
buf[i] = BASE.pow((2 - i) as u32) * (char_to_u8(c).ok_or(Error::InvalidChunk(format!("Invalid char: {}", c)))? as u16);
}
Ok(buf.iter().sum())
}
fn u16_to_string(int: u16) -> Result<String, Error> {
if int >= CUBED_BASE {
return Err(Error::OutsideOfRange(int))
}
let first_char = char::from(CHARACTERS[usize::try_from(int / SQUARED_BASE).unwrap()]);
let second_char = char::from(CHARACTERS[usize::try_from((int % SQUARED_BASE)/ BASE).unwrap()]);
let third_char = char::from(CHARACTERS[usize::try_from(int % BASE).unwrap()]);
Ok(format!("{}{}{}", first_char, second_char, third_char))
}
#[derive(Clone, Debug, PartialEq)]
pub struct IdChunk(u16);
impl IdChunk {
pub fn new<R: Rng + ?Sized>(rng: &mut R) -> Self {
Self(rng.gen_range(0..CUBED_BASE))
}
}
impl Display for IdChunk {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
todo!()
}
}
impl FromStr for IdChunk {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
todo!()
}
}
impl TryFrom<u16> for IdChunk {
type Error = Error;
fn try_from(value: u16) -> Result<Self, Self::Error> {
todo!()
}
}
impl From<IdChunk> for u16 {
fn from(value: IdChunk) -> Self {
value.0
}
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_eq_chunk(s: &str, i: u16) {
assert_eq!(s, &u16_to_string(i).unwrap());
assert_eq!(i, str_to_u16(s).unwrap())
}
#[test]
fn test_nil() {
assert_eq_chunk("000", 0);
}
#[test]
fn test_max() {
assert_eq_chunk("zzz", CUBED_BASE-1);
}
fn assert_random<R>(rand: &mut R)
where
R: Rng
{
let chunk = IdChunk::new(rand);
let s = chunk.to_string();
assert_eq!(chunk,IdChunk::from_str(&s).unwrap())
}
fn random_x10() {
let mut rng = rand::thread_rng();
for _ in 0..10 {
assert_random(&mut rng);
}
}
}

View file

@ -1,4 +0,0 @@
pub struct Config {
pub enable_nil: bool,
pub enable_max: bool,
}

View file

@ -0,0 +1,129 @@
use std::{fmt::Display, str::FromStr};
use rand::{distributions::Standard, prelude::Distribution, Rng};
use crate::{Error, Id, SingleId};
#[derive(Debug, Clone, PartialEq)]
pub struct DoubleId{
inner: (SingleId, SingleId)
}
impl DoubleId {
#[cfg(test)]
pub fn validate(&self) -> bool {
self.inner.0.validate() && self.inner.1.validate() && (u32::from(self.clone()) < Self::SIZE)
}
}
impl Id for DoubleId{
type SizeType = u32;
const SIZE: Self::SizeType = (SingleId::SIZE as u32).pow(2);
/// ```
/// use caretta_id::{Id, DoubleId};
/// use std::str::FromStr;
///
/// assert_eq!(DoubleId::NIL, DoubleId::from_str("000-000").unwrap());
/// assert_eq!(DoubleId::NIL, DoubleId::try_from(0).unwrap());
/// ```
const NIL: Self = Self{
inner: (SingleId::NIL, SingleId::NIL)
};
/// ```
/// use caretta_id::{Id, DoubleId};
/// use std::str::FromStr;
///
/// assert_eq!(DoubleId::MAX, DoubleId::from_str("zzz-zzz").unwrap());
/// assert_eq!(DoubleId::MAX, DoubleId::try_from(1291467968).unwrap());
/// ```
const MAX: Self = Self{
inner: (SingleId::MAX, SingleId::MAX)
};
}
impl Display for DoubleId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}-{}", self.inner.0, self.inner.1)
}
}
impl FromStr for DoubleId {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self {
inner : match s.len() {
7 => {
match s.chars().nth(3).unwrap() {
'-' => {
Ok((SingleId::from_str(&s[0..3])?, SingleId::from_str(&s[4..7])?))
},
x => {
Err(Error::InvalidDelimiter(x))
}
}
}
6 => {
Ok((SingleId::from_str(&s[0..3])?,SingleId::from_str(&s[3..6])?))
}
_ => Err(Error::InvalidLength(s.to_string()))
}?
})
}
}
impl Distribution<DoubleId> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> DoubleId {
DoubleId {
inner: (rng.r#gen(), rng.r#gen())
}
}
}
impl TryFrom<u32> for DoubleId {
type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> {
if value < Self::SIZE {
Ok(Self{
inner: (
SingleId::try_from(u16::try_from(value/(SingleId::SIZE as u32)).unwrap())?,
SingleId::try_from(u16::try_from(value % (SingleId::SIZE as u32)).unwrap())?
)})
} else {
Err(Error::OutsideOfRange(value as u64))
}
}
}
impl From<DoubleId> for u32 {
fn from(value: DoubleId) -> Self {
u32::from(u16::from(value.inner.0)) * u32::from(SingleId::SIZE) + u32::from(u16::from(value.inner.1))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_random<R>(rand: &mut R)
where
R: Rng
{
let chunk: SingleId = rand.r#gen();
let s = chunk.to_string();
assert_eq!(chunk,SingleId::from_str(&s).unwrap())
}
#[test]
fn random_x10() {
let mut rng = rand::thread_rng();
for _ in 0..10 {
assert_random(&mut rng);
}
}
}

View file

@ -1,8 +1,12 @@
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {
#[error("Outside of range: {0}")] #[error("Outside of range: {0}")]
OutsideOfRange(u16), OutsideOfRange(u64),
#[error("Invalid chunk: {0}")] #[error("Invalid chunk: {0}")]
InvalidChunk(String), InvalidChunk(String),
#[error("Invalid delemeter: {0}")]
InvalidDelimiter(char),
#[error("Invalid length: {0}")]
InvalidLength(String)
} }

View file

@ -1,6 +1,21 @@
mod chunk; mod single;
mod double; mod double;
mod error; mod error;
mod single;
mod triple; mod triple;
use rand::Rng;
pub use single::*;
pub use double::*;
pub use triple::*;
pub use error::*;
const DOUBLE_ID_SIZE: u32 = (SingleId::SIZE as u32).pow(2);
const TRIPLE_ID_SIZE: u64 = (SingleId::SIZE as u64).pow(3);
pub trait Id {
type SizeType;
const NIL: Self;
const MAX: Self;
const SIZE: Self::SizeType;
}

View file

@ -0,0 +1,210 @@
use std::{fmt::Display, str::FromStr};
use rand::{distributions::{uniform::UniformInt, Standard}, prelude::Distribution, Rng};
use crate::{error::Error, Id};
const CHARACTERS: &[u8;33] = b"0123456789abcdefghjkmnpqrstuvwxyz";
const BASE: u16 = 33;
const SQUARED_BASE: u16 = BASE.pow(2);
const CUBED_BASE: u16 = BASE.pow(3);
fn char_to_u8(c: char) -> Option<u8> {
Some(match c {
'0' => 0,
'1' => 1,
'2' => 2,
'3' => 3,
'4' => 4,
'5' => 5,
'6' => 6,
'7' => 7,
'8' => 8,
'9' => 9,
'a' => 10,
'b' => 11,
'c' => 12,
'd' => 13,
'e' => 14,
'f' => 15,
'g' => 16,
'h' => 17,
'i' => 1,
'j' => 18,
'k' => 19,
'l' => 1,
'm' => 20,
'n' => 21,
'o' => 0,
'p' => 22,
'q' => 23,
'r' => 24,
's' => 25,
't' => 26,
'u' => 27,
'v' => 28,
'w' => 29,
'x' => 30,
'y' => 31,
'z' => 32,
'A' => 10,
'B' => 11,
'C' => 12,
'D' => 13,
'E' => 14,
'F' => 15,
'G' => 16,
'H' => 17,
'I' => 1,
'J' => 18,
'K' => 19,
'L' => 1,
'M' => 20,
'N' => 21,
'O' => 0,
'P' => 22,
'Q' => 23,
'R' => 24,
'S' => 25,
'T' => 26,
'U' => 27,
'V' => 28,
'W' => 29,
'X' => 30,
'Y' => 31,
'Z' => 32,
_ => return None
})
}
fn str_to_u16(s: &str) -> Result<u16, Error> {
if s.len() != 3 {
return Err(Error::InvalidChunk(format!("Chunk '{}' is not 3 characters", s)))
}
let mut buf : [u16;3] = [0;3];
for (i, c) in s.chars().enumerate() {
buf[i] = BASE.pow((2 - i) as u32) * (char_to_u8(c).ok_or(Error::InvalidChunk(format!("Invalid char: {}", c)))? as u16);
}
Ok(buf.iter().sum())
}
fn u16_to_string(int: u16) -> Result<String, Error> {
if int >= CUBED_BASE {
return Err(Error::OutsideOfRange(int as u64))
}
let first_char = char::from(CHARACTERS[usize::try_from(int / SQUARED_BASE).unwrap()]);
let second_char = char::from(CHARACTERS[usize::try_from((int % SQUARED_BASE)/ BASE).unwrap()]);
let third_char = char::from(CHARACTERS[usize::try_from(int % BASE).unwrap()]);
Ok(format!("{}{}{}", first_char, second_char, third_char))
}
#[derive(Clone, Debug, PartialEq)]
pub struct SingleId{
inner: u16
}
impl SingleId {
#[cfg(test)]
pub fn validate(&self) -> bool {
self.inner < Self::SIZE
}
}
impl Id for SingleId {
type SizeType = u16;
const SIZE: Self::SizeType = CUBED_BASE;
/// ```
/// use caretta_id::{SingleId, Id};
/// use std::str::FromStr;
///
/// assert_eq!(SingleId::NIL, SingleId::from_str("000").unwrap());
/// assert_eq!(SingleId::NIL, SingleId::try_from(0).unwrap());
/// ```
const NIL: SingleId = SingleId{
inner: 0
};
/// ```
/// use caretta_id::{Id, SingleId};
/// use std::str::FromStr;
///
/// assert_eq!(SingleId::MAX, SingleId::from_str("zzz").unwrap());
/// assert_eq!(SingleId::MAX, SingleId::try_from(35936).unwrap());
/// ```
const MAX: SingleId = SingleId{
inner: Self::SIZE-1
};
}
impl Display for SingleId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", u16_to_string(self.inner).unwrap())
}
}
impl FromStr for SingleId {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self{
inner: str_to_u16(s)?
})
}
}
impl Distribution<SingleId> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> SingleId {
SingleId {
inner: rng.gen_range(0..SingleId::SIZE)
}
}
}
impl TryFrom<u16> for SingleId {
type Error = Error;
fn try_from(value: u16) -> Result<Self, Self::Error> {
if value < Self::SIZE {
Ok(Self{inner: value})
} else {
Err(Error::OutsideOfRange(value as u64))
}
}
}
impl From<SingleId> for u16 {
fn from(value: SingleId) -> Self {
value.inner
}
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_random<R>(rand: &mut R)
where
R: Rng
{
let chunk: SingleId = rand.r#gen();
assert!(chunk.validate());
let s = chunk.to_string();
assert_eq!(chunk,SingleId::from_str(&s).unwrap());
let i = u16::from(chunk.clone());
assert_eq!(chunk, SingleId::try_from(i).unwrap());
}
#[test]
fn random_x10() {
let mut rng = rand::thread_rng();
for _ in 0..10 {
assert_random(&mut rng);
}
}
}

View file

@ -0,0 +1,6 @@
use crate::{Error, SingleId};
pub struct TripleId {
inner: (SingleId, SingleId, SingleId)
}