add letterset
This commit is contained in:
commit
81a9ac109e
5 changed files with 203 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/target
|
||||||
7
Cargo.lock
generated
Normal file
7
Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dynamic-pangramming"
|
||||||
|
version = "0.1.0"
|
||||||
6
Cargo.toml
Normal file
6
Cargo.toml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
[package]
|
||||||
|
name = "dynamic-pangramming"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
184
src/letterset.rs
Normal file
184
src/letterset.rs
Normal file
|
|
@ -0,0 +1,184 @@
|
||||||
|
use std::{fmt, ops::Deref};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Debug)]
|
||||||
|
pub struct LetterSet(u32);
|
||||||
|
|
||||||
|
/// Immutable set of English letters
|
||||||
|
impl LetterSet {
|
||||||
|
pub const EMPTY: Self = Self(0);
|
||||||
|
pub const FULL: Self = Self((1 << 26) - 1);
|
||||||
|
|
||||||
|
pub const fn has(self, letter: u8) -> bool {
|
||||||
|
if let Some(index) = Self::letter_index(letter) {
|
||||||
|
((self.0 >> index) & 1) == 1
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn from_slice(letters: &[u8]) -> Self {
|
||||||
|
let mut out = Self::EMPTY;
|
||||||
|
let mut i = 0;
|
||||||
|
while i < letters.len() {
|
||||||
|
out = out.add(letters[i]);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn add(self, letter: u8) -> Self {
|
||||||
|
if let Some(index) = Self::letter_index(letter) {
|
||||||
|
Self(self.0 | (1 << index))
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: impl IntoIterator
|
||||||
|
pub fn items(self) -> impl Iterator<Item = u8> {
|
||||||
|
(b'a'..=b'z').filter(move |l| self.has(*l))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn intersection(self, other: Self) -> Self {
|
||||||
|
Self(self.0 & other.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn union(self, other: Self) -> Self {
|
||||||
|
Self(self.0 | other.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn difference(self, subtrahend: Self) -> Self {
|
||||||
|
Self(self.0 ^ (self.0 & subtrahend.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn negation(self) -> Self {
|
||||||
|
Self::FULL.difference(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::cast_possible_truncation, reason = "len is at most 26")]
|
||||||
|
pub const fn len(self) -> u8 {
|
||||||
|
self.0.count_ones() as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn letter_index(letter: u8) -> Option<u8> {
|
||||||
|
if letter.is_ascii_alphabetic() {
|
||||||
|
let lower = letter.to_ascii_lowercase();
|
||||||
|
let index = lower - b'a';
|
||||||
|
Some(index)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for LetterSet {
|
||||||
|
#[allow(clippy::cast_possible_truncation, reason = "it can't")]
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{{")?;
|
||||||
|
for (i, l) in self.items().enumerate() {
|
||||||
|
write!(f, "{}", char::from(l))?;
|
||||||
|
if ((i + 1) as u8) < self.len() {
|
||||||
|
write!(f, ", ")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(f, "}}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> FromIterator<B> for LetterSet
|
||||||
|
where
|
||||||
|
B: Deref<Target = u8>,
|
||||||
|
{
|
||||||
|
fn from_iter<T: IntoIterator<Item = B>>(iter: T) -> Self {
|
||||||
|
let mut out = Self::EMPTY;
|
||||||
|
for l in iter {
|
||||||
|
out = out.add(*l);
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const A: LetterSet = LetterSet::from_slice(b"a");
|
||||||
|
const B: LetterSet = LetterSet::from_slice(b"b");
|
||||||
|
const C: LetterSet = LetterSet::from_slice(b"c");
|
||||||
|
const AB: LetterSet = LetterSet::from_slice(b"ab");
|
||||||
|
const AC: LetterSet = LetterSet::from_slice(b"ac");
|
||||||
|
const BC: LetterSet = LetterSet::from_slice(b"bc");
|
||||||
|
const ABC: LetterSet = LetterSet::from_slice(b"abc");
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty() {
|
||||||
|
assert_eq!(LetterSet::EMPTY.to_string(), "{}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn full() {
|
||||||
|
assert_eq!(
|
||||||
|
LetterSet::FULL.to_string(),
|
||||||
|
"{a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn intersection() {
|
||||||
|
assert_eq!(A.intersection(B), LetterSet::EMPTY);
|
||||||
|
assert_eq!(B.intersection(A), LetterSet::EMPTY);
|
||||||
|
|
||||||
|
assert_eq!(A.intersection(AB), A);
|
||||||
|
assert_eq!(B.intersection(AB), B);
|
||||||
|
assert_eq!(AB.intersection(A), A);
|
||||||
|
assert_eq!(AB.intersection(B), B);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn union() {
|
||||||
|
assert_eq!(A.union(A), A);
|
||||||
|
assert_eq!(A.union(B), AB);
|
||||||
|
assert_eq!(A.union(C), AC);
|
||||||
|
assert_eq!(A.union(AB), AB);
|
||||||
|
assert_eq!(B.union(AB), AB);
|
||||||
|
assert_eq!(AB.union(C), ABC);
|
||||||
|
assert_eq!(C.union(AB), ABC);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn difference() {
|
||||||
|
assert_eq!(AB.difference(B), A);
|
||||||
|
assert_eq!(AB.difference(A), B);
|
||||||
|
assert_eq!(ABC.difference(A), BC);
|
||||||
|
assert_eq!(A.difference(A), LetterSet::EMPTY);
|
||||||
|
assert_eq!(ABC.difference(ABC), LetterSet::EMPTY);
|
||||||
|
assert_eq!(
|
||||||
|
LetterSet::FULL.difference(LetterSet::FULL),
|
||||||
|
LetterSet::EMPTY
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn negation() {
|
||||||
|
assert_eq!(LetterSet::EMPTY.negation(), LetterSet::FULL);
|
||||||
|
assert_eq!(LetterSet::FULL.negation(), LetterSet::EMPTY);
|
||||||
|
assert_eq!(ABC.negation().negation(), ABC);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn len() {
|
||||||
|
assert_eq!(LetterSet::FULL.len(), 26);
|
||||||
|
assert_eq!(LetterSet::EMPTY.len(), 0);
|
||||||
|
assert_eq!(A.len(), 1);
|
||||||
|
assert_eq!(B.len(), 1);
|
||||||
|
assert_eq!(AB.len(), 2);
|
||||||
|
assert_eq!(ABC.len(), 3);
|
||||||
|
assert_eq!(ABC.negation().len(), 26 - 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add() {
|
||||||
|
assert_eq!(A.add(b'b'), AB);
|
||||||
|
assert_eq!(AB.add(b'c'), ABC);
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/main.rs
Normal file
5
src/main.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
mod letterset;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue