add letterset

This commit is contained in:
mehbark 2026-07-07 14:29:40 -04:00
commit 81a9ac109e
5 changed files with 203 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

7
Cargo.lock generated Normal file
View 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
View file

@ -0,0 +1,6 @@
[package]
name = "dynamic-pangramming"
version = "0.1.0"
edition = "2024"
[dependencies]

184
src/letterset.rs Normal file
View 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
View file

@ -0,0 +1,5 @@
mod letterset;
fn main() {
println!("Hello, world!");
}