pub const ALPHABET: Alphabet = Alphabet {
alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZ".as_bytes(),
};
pub struct Alphabet<'letters> {
alphabet: &'letters [u8],
}
impl<'a> Alphabet<'a> {
pub fn new(alphabet: &'a str) -> anyhow::Result<Self> {
let mut chars = alphabet.chars().collect::<Vec<_>>();
chars.dedup();
if chars.len() != alphabet.len() {
anyhow::bail!("Duplicate letter in alphabet: {alphabet}");
}
if alphabet.len() != 26 {
anyhow::bail!("Invalid alphabet length: {alphabet}");
}
if alphabet.chars().any(|letter| !letter.is_alphabetic()) {
anyhow::bail!("Invalid character found in alphabet: {alphabet}");
}
Ok(Self { alphabet: alphabet.as_bytes() })
}
pub fn index_of(&self, letter: char) -> Option<AlphabetIndex> {
let letter = letter.to_ascii_uppercase();
let code = letter as u8;
letter.is_ascii_uppercase().then(|| {
for (index, character) in self.alphabet.iter().enumerate() {
if character == &code {
return AlphabetIndex { value: index as u8 };
}
}
unreachable!()
})
}
pub fn letter_at<T: std::borrow::Borrow<AlphabetIndex>>(&self, index: T) -> char {
self.alphabet[**index.borrow() as usize] as char
}
pub fn letters(&self) -> String {
self.alphabet.iter().map(|letter| *letter as char).collect()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AlphabetIndex {
value: u8,
}
impl std::ops::Deref for AlphabetIndex {
type Target = u8;
fn deref(&self) -> &Self::Target {
&self.value
}
}
pub trait TryIntoAlphabetIndex {
type Output;
fn try_into_alphabet_index(self) -> anyhow::Result<Self::Output>;
}
impl TryFrom<u8> for AlphabetIndex {
type Error = anyhow::Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
(0..26)
.contains(&value)
.then_some(Self { value })
.ok_or_else(|| anyhow::anyhow!("Alphabet index out of range: {value}"))
}
}
impl TryFrom<i32> for AlphabetIndex {
type Error = anyhow::Error;
fn try_from(value: i32) -> Result<Self, Self::Error> {
(0..26)
.contains(&value)
.then_some(Self { value: value as u8 })
.ok_or_else(|| anyhow::anyhow!("Alphabet index out of range: {value}"))
}
}
impl TryIntoAlphabetIndex for (u8, u8, u8) {
type Output = (AlphabetIndex, AlphabetIndex, AlphabetIndex);
fn try_into_alphabet_index(self) -> anyhow::Result<Self::Output> {
Ok((self.0.try_into()?, self.1.try_into()?, self.2.try_into()?))
}
}
impl TryIntoAlphabetIndex for (i32, i32, i32) {
type Output = (AlphabetIndex, AlphabetIndex, AlphabetIndex);
fn try_into_alphabet_index(self) -> anyhow::Result<Self::Output> {
Ok((self.0.try_into()?, self.1.try_into()?, self.2.try_into()?))
}
}
impl std::ops::AddAssign<i32> for AlphabetIndex {
fn add_assign(&mut self, rhs: i32) {
*self = AlphabetIndex {
value: (self.value + rhs as u8) % 26,
}
}
}
impl std::ops::Add<AlphabetIndex> for AlphabetIndex {
type Output = AlphabetIndex;
fn add(self, rhs: AlphabetIndex) -> Self::Output {
AlphabetIndex {
value: (self.value + rhs.value) % 26,
}
}
}
impl std::ops::Sub<AlphabetIndex> for AlphabetIndex {
type Output = AlphabetIndex;
fn sub(self, rhs: AlphabetIndex) -> Self::Output {
AlphabetIndex {
value: ((self.value as i32 - rhs.value as i32 + 26) % 26) as u8,
}
}
}