use rustc_hash::FxHashSet;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SubstitutionSetChar {
allowed: FxHashSet<(char, char)>,
}
impl SubstitutionSetChar {
#[inline]
pub fn new() -> Self {
Self {
allowed: FxHashSet::default(),
}
}
#[inline]
pub fn with_capacity(capacity: usize) -> Self {
Self {
allowed: FxHashSet::with_capacity_and_hasher(capacity, Default::default()),
}
}
#[inline]
pub fn allow(&mut self, a: char, b: char) {
self.allowed.insert((a, b));
}
#[inline]
pub fn contains(&self, a: char, b: char) -> bool {
self.allowed.contains(&(a, b))
}
pub fn from_pairs(pairs: &[(char, char)]) -> Self {
let mut set = Self::with_capacity(pairs.len());
for &(a, b) in pairs {
set.allow(a, b);
}
set
}
#[inline]
pub fn len(&self) -> usize {
self.allowed.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.allowed.is_empty()
}
#[inline]
pub fn clear(&mut self) {
self.allowed.clear();
}
pub fn diacritics_latin() -> Self {
Self::from_pairs(&[
('á', 'a'),
('a', 'á'),
('à', 'a'),
('a', 'à'),
('â', 'a'),
('a', 'â'),
('ä', 'a'),
('a', 'ä'),
('ã', 'a'),
('a', 'ã'),
('å', 'a'),
('a', 'å'),
('æ', 'a'),
('a', 'æ'),
('é', 'e'),
('e', 'é'),
('è', 'e'),
('e', 'è'),
('ê', 'e'),
('e', 'ê'),
('ë', 'e'),
('e', 'ë'),
('í', 'i'),
('i', 'í'),
('ì', 'i'),
('i', 'ì'),
('î', 'i'),
('i', 'î'),
('ï', 'i'),
('i', 'ï'),
('ó', 'o'),
('o', 'ó'),
('ò', 'o'),
('o', 'ò'),
('ô', 'o'),
('o', 'ô'),
('ö', 'o'),
('o', 'ö'),
('õ', 'o'),
('o', 'õ'),
('ø', 'o'),
('o', 'ø'),
('œ', 'o'),
('o', 'œ'),
('ú', 'u'),
('u', 'ú'),
('ù', 'u'),
('u', 'ù'),
('û', 'u'),
('u', 'û'),
('ü', 'u'),
('u', 'ü'),
('ñ', 'n'),
('n', 'ñ'),
('ç', 'c'),
('c', 'ç'),
('ß', 's'),
('s', 'ß'),
('Á', 'A'),
('A', 'Á'),
('À', 'A'),
('A', 'À'),
('Â', 'A'),
('A', 'Â'),
('Ä', 'A'),
('A', 'Ä'),
('Ã', 'A'),
('A', 'Ã'),
('Å', 'A'),
('A', 'Å'),
('Æ', 'A'),
('A', 'Æ'),
('É', 'E'),
('E', 'É'),
('È', 'E'),
('E', 'È'),
('Ê', 'E'),
('E', 'Ê'),
('Ë', 'E'),
('E', 'Ë'),
('Í', 'I'),
('I', 'Í'),
('Ì', 'I'),
('I', 'Ì'),
('Î', 'I'),
('I', 'Î'),
('Ï', 'I'),
('I', 'Ï'),
('Ó', 'O'),
('O', 'Ó'),
('Ò', 'O'),
('O', 'Ò'),
('Ô', 'O'),
('O', 'Ô'),
('Ö', 'O'),
('O', 'Ö'),
('Õ', 'O'),
('O', 'Õ'),
('Ø', 'O'),
('O', 'Ø'),
('Œ', 'O'),
('O', 'Œ'),
('Ú', 'U'),
('U', 'Ú'),
('Ù', 'U'),
('U', 'Ù'),
('Û', 'U'),
('U', 'Û'),
('Ü', 'U'),
('U', 'Ü'),
('Ñ', 'N'),
('N', 'Ñ'),
('Ç', 'C'),
('C', 'Ç'),
])
}
pub fn greek_case_insensitive() -> Self {
Self::from_pairs(&[
('Α', 'α'),
('α', 'Α'), ('Β', 'β'),
('β', 'Β'), ('Γ', 'γ'),
('γ', 'Γ'), ('Δ', 'δ'),
('δ', 'Δ'), ('Ε', 'ε'),
('ε', 'Ε'), ('Ζ', 'ζ'),
('ζ', 'Ζ'), ('Η', 'η'),
('η', 'Η'), ('Θ', 'θ'),
('θ', 'Θ'), ('Ι', 'ι'),
('ι', 'Ι'), ('Κ', 'κ'),
('κ', 'Κ'), ('Λ', 'λ'),
('λ', 'Λ'), ('Μ', 'μ'),
('μ', 'Μ'), ('Ν', 'ν'),
('ν', 'Ν'), ('Ξ', 'ξ'),
('ξ', 'Ξ'), ('Ο', 'ο'),
('ο', 'Ο'), ('Π', 'π'),
('π', 'Π'), ('Ρ', 'ρ'),
('ρ', 'Ρ'), ('Σ', 'σ'),
('σ', 'Σ'), ('Σ', 'ς'),
('ς', 'Σ'), ('Τ', 'τ'),
('τ', 'Τ'), ('Υ', 'υ'),
('υ', 'Υ'), ('Φ', 'φ'),
('φ', 'Φ'), ('Χ', 'χ'),
('χ', 'Χ'), ('Ψ', 'ψ'),
('ψ', 'Ψ'), ('Ω', 'ω'),
('ω', 'Ω'), ])
}
pub fn cyrillic_case_insensitive() -> Self {
Self::from_pairs(&[
('А', 'а'),
('а', 'А'), ('Б', 'б'),
('б', 'Б'), ('В', 'в'),
('в', 'В'), ('Г', 'г'),
('г', 'Г'), ('Д', 'д'),
('д', 'Д'), ('Е', 'е'),
('е', 'Е'), ('Ё', 'ё'),
('ё', 'Ё'), ('Ж', 'ж'),
('ж', 'Ж'), ('З', 'з'),
('з', 'З'), ('И', 'и'),
('и', 'И'), ('Й', 'й'),
('й', 'Й'), ('К', 'к'),
('к', 'К'), ('Л', 'л'),
('л', 'Л'), ('М', 'м'),
('м', 'М'), ('Н', 'н'),
('н', 'Н'), ('О', 'о'),
('о', 'О'), ('П', 'п'),
('п', 'П'), ('Р', 'р'),
('р', 'Р'), ('С', 'с'),
('с', 'С'), ('Т', 'т'),
('т', 'Т'), ('У', 'у'),
('у', 'У'), ('Ф', 'ф'),
('ф', 'Ф'), ('Х', 'х'),
('х', 'Х'), ('Ц', 'ц'),
('ц', 'Ц'), ('Ч', 'ч'),
('ч', 'Ч'), ('Ш', 'ш'),
('ш', 'Ш'), ('Щ', 'щ'),
('щ', 'Щ'), ('Ъ', 'ъ'),
('ъ', 'Ъ'), ('Ы', 'ы'),
('ы', 'Ы'), ('Ь', 'ь'),
('ь', 'Ь'), ('Э', 'э'),
('э', 'Э'), ('Ю', 'ю'),
('ю', 'Ю'), ('Я', 'я'),
('я', 'Я'), ])
}
pub fn japanese_hiragana_katakana() -> Self {
Self::from_pairs(&[
('あ', 'ア'),
('ア', 'あ'), ('い', 'イ'),
('イ', 'い'), ('う', 'ウ'),
('ウ', 'う'), ('え', 'エ'),
('エ', 'え'), ('お', 'オ'),
('オ', 'お'), ('か', 'カ'),
('カ', 'か'), ('き', 'キ'),
('キ', 'き'), ('く', 'ク'),
('ク', 'く'), ('け', 'ケ'),
('ケ', 'け'), ('こ', 'コ'),
('コ', 'こ'), ('さ', 'サ'),
('サ', 'さ'), ('し', 'シ'),
('シ', 'し'), ('す', 'ス'),
('ス', 'す'), ('せ', 'セ'),
('セ', 'せ'), ('そ', 'ソ'),
('ソ', 'そ'), ('た', 'タ'),
('タ', 'た'), ('ち', 'チ'),
('チ', 'ち'), ('つ', 'ツ'),
('ツ', 'つ'), ('て', 'テ'),
('テ', 'て'), ('と', 'ト'),
('ト', 'と'), ])
}
}
impl Default for SubstitutionSetChar {
#[inline]
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_empty() {
let set = SubstitutionSetChar::new();
assert_eq!(set.len(), 0);
assert!(set.is_empty());
}
#[test]
fn test_allow_and_contains() {
let mut set = SubstitutionSetChar::new();
set.allow('α', 'β');
assert!(set.contains('α', 'β'));
assert!(!set.contains('β', 'α'));
set.allow('β', 'α'); assert!(set.contains('β', 'α'));
}
#[test]
fn test_unicode_characters() {
let mut set = SubstitutionSetChar::new();
set.allow('α', 'β');
assert!(set.contains('α', 'β'));
set.allow('你', '好');
assert!(set.contains('你', '好'));
set.allow('👋', '🤚');
assert!(set.contains('👋', '🤚'));
}
#[test]
fn test_from_pairs() {
let set = SubstitutionSetChar::from_pairs(&[('é', 'e'), ('ñ', 'n'), ('ü', 'u')]);
assert_eq!(set.len(), 3);
assert!(set.contains('é', 'e'));
assert!(set.contains('ñ', 'n'));
assert!(set.contains('ü', 'u'));
}
#[test]
fn test_clear() {
let mut set = SubstitutionSetChar::from_pairs(&[('α', 'β'), ('γ', 'δ')]);
assert_eq!(set.len(), 2);
set.clear();
assert_eq!(set.len(), 0);
assert!(set.is_empty());
}
#[test]
fn test_diacritics_latin() {
let diacritics = SubstitutionSetChar::diacritics_latin();
assert!(!diacritics.is_empty());
assert!(diacritics.contains('é', 'e'));
assert!(diacritics.contains('e', 'é'));
assert!(diacritics.contains('ñ', 'n'));
assert!(diacritics.contains('n', 'ñ'));
assert!(diacritics.contains('ü', 'u'));
assert!(diacritics.contains('u', 'ü'));
}
#[test]
fn test_greek_case_insensitive() {
let greek = SubstitutionSetChar::greek_case_insensitive();
assert!(!greek.is_empty());
assert!(greek.contains('Α', 'α'));
assert!(greek.contains('α', 'Α'));
assert!(greek.contains('Β', 'β'));
assert!(greek.contains('β', 'Β'));
}
#[test]
fn test_cyrillic_case_insensitive() {
let cyrillic = SubstitutionSetChar::cyrillic_case_insensitive();
assert!(!cyrillic.is_empty());
assert!(cyrillic.contains('А', 'а'));
assert!(cyrillic.contains('а', 'А'));
assert!(cyrillic.contains('Я', 'я'));
assert!(cyrillic.contains('я', 'Я'));
}
#[test]
fn test_japanese_hiragana_katakana() {
let japanese = SubstitutionSetChar::japanese_hiragana_katakana();
assert!(!japanese.is_empty());
assert!(japanese.contains('あ', 'ア'));
assert!(japanese.contains('ア', 'あ'));
assert!(japanese.contains('か', 'カ'));
assert!(japanese.contains('カ', 'か'));
}
#[test]
fn test_with_capacity() {
let set = SubstitutionSetChar::with_capacity(100);
assert_eq!(set.len(), 0);
}
#[test]
fn test_duplicate_pairs() {
let mut set = SubstitutionSetChar::new();
set.allow('α', 'β');
set.allow('α', 'β');
assert_eq!(set.len(), 1);
}
#[test]
fn test_clone() {
let set1 = SubstitutionSetChar::diacritics_latin();
let set2 = set1.clone();
assert_eq!(set1.len(), set2.len());
assert_eq!(set1, set2);
}
#[test]
fn test_debug() {
let set = SubstitutionSetChar::from_pairs(&[('α', 'β')]);
let debug_str = format!("{:?}", set);
assert!(debug_str.contains("SubstitutionSetChar"));
}
}