pub mod utils {
use atoi::atoi;
use rand::{rng, seq::SliceRandom};
use unicode_segmentation::UnicodeSegmentation;
const fn build_valid_codepoints() -> [bool; 403] {
let mut table = [false; 403];
let mut i = 48usize;
while i < 58 {
table[i] = true;
i += 1;
} let mut i = 65usize;
while i < 91 {
table[i] = true;
i += 1;
} let mut i = 97usize;
while i < 123 {
table[i] = true;
i += 1;
} let mut i = 192usize;
while i < 247 {
table[i] = true;
i += 1;
} let mut i = 248usize;
while i < 256 {
table[i] = true;
i += 1;
} table[338] = true;
table[339] = true; table[352] = true;
table[353] = true; table[376] = true; table[381] = true;
table[382] = true; table[402] = true; table
}
static VALID_CODEPOINTS: [bool; 403] = build_valid_codepoints();
fn get_valid_end_index(s: &str) -> Option<usize> {
let trimmed: &str = s.trim();
let g: Vec<&str> = trimmed.graphemes(true).collect::<Vec<&str>>();
for (index, character) in g.iter().rev().enumerate() {
if let Some(c) = character.chars().next() {
let codepoint: usize = c as usize;
if codepoint < VALID_CODEPOINTS.len() && VALID_CODEPOINTS[codepoint] {
return Some(g.len() - index - 1);
}
}
}
None
}
fn get_valid_start_index(s: &str) -> Option<usize> {
let trimmed: &str = s.trim();
let g: Vec<&str> = trimmed.graphemes(true).collect::<Vec<&str>>();
for (index, character) in g.iter().enumerate() {
if let Some(c) = character.chars().next() {
let codepoint: usize = c as usize;
if codepoint < VALID_CODEPOINTS.len() && VALID_CODEPOINTS[codepoint] {
return Some(index);
}
}
}
None
}
fn has_apostrophes(s: &str) -> bool {
let g: Vec<&str> = s.graphemes(true).collect::<Vec<&str>>();
let mut it: std::slice::Iter<'_, &str> = g.iter();
let index: Option<usize> = it.position(|&r| r == "'" || r == "\u{2019}" || r == "\u{2018}");
index.is_some()
}
fn has_hyphens(s: &str) -> bool {
let g: Vec<&str> = s.graphemes(true).collect::<Vec<&str>>();
let mut it: std::slice::Iter<'_, &str> = g.iter();
let index: Option<usize> = it.position(|&r| r == "-");
index.is_some()
}
fn handle_apostrophe_string(s: &str) -> String {
let normalized = s.replace('\u{2019}', "'").replace('\u{2018}', "'");
let mut v: Vec<String> = Vec::new();
let it: std::str::Split<'_, &str> = normalized.split("'");
for part in it {
v.push(scramble_word(part.to_owned()));
}
v.join("'")
}
fn handle_hyphenated_string(s: &str) -> String {
let mut coll: Vec<String> = Vec::new();
let it: std::str::Split<'_, &str> = s.split("-");
for part in it {
coll.push(scramble_word(part.to_owned()));
}
coll.join("-")
}
fn handle_apostrophe_and_hyphenated_string(s: &str) -> String {
let normalized = s.replace('\u{2019}', "'").replace('\u{2018}', "'");
let mut v1: Vec<String> = Vec::new();
let mut v2: Vec<String> = Vec::new();
let it: std::str::Split<'_, &str> = normalized.split("-");
for i in it {
let st = i.split("'");
for s in st {
v2.push(scramble_word(s.to_owned()));
}
v1.push(v2.join("'"));
v2.clear();
}
v1.join("-")
}
fn starts_with_digit(s: &str) -> bool {
let atoi_str: Option<u64> = atoi::<u64>(s.as_bytes());
atoi_str.is_some()
}
pub fn scramble_word(s: String) -> String {
let input_as_str: &str = s.as_str();
let g: Vec<&str> = input_as_str.graphemes(true).collect::<Vec<&str>>();
if has_apostrophes(&s) && has_hyphens(&s) {
return handle_apostrophe_and_hyphenated_string(&s);
}
if has_apostrophes(&s) {
return handle_apostrophe_string(&s);
}
if has_hyphens(&s) {
return handle_hyphenated_string(&s);
}
if g.len() <= 3 || g.len() > 15 || starts_with_digit(s.as_str()) {
return s;
}
let start_index = match get_valid_start_index(input_as_str) {
Some(i) => i,
None => return s,
};
let end_index = match get_valid_end_index(input_as_str) {
Some(i) => i,
None => return s,
};
if start_index == end_index {
return s;
}
let first = &g[0..=start_index];
let middle = &g[start_index + 1..end_index];
let last = &g[end_index..];
let mut mtv = middle.to_vec();
mtv.shuffle(&mut rng());
let middle_scrambled = &mtv[..];
let concatenated = [first, middle_scrambled, last].concat();
concatenated.join("")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_starts_with_digit() {
let lst1 = ["hello", " ", "_123"];
for item in lst1.iter() {
let result = starts_with_digit(item);
assert_eq!(result, false);
}
let lst2 = ["12345", "3.1415", "12/22/1986", "36-26-36"];
for item in lst2.iter() {
let result = starts_with_digit(item);
assert_eq!(result, true);
}
}
#[test]
fn test_get_valid_start_index() {
let mut map: std::collections::HashMap<&'static str, usize> =
std::collections::HashMap::new();
map.insert("hello", 0usize);
map.insert(" hello", 0usize); map.insert("__hello", 2usize);
map.insert("❤️ to everyone", 2usize);
for (word, index) in map.iter() {
assert_eq!(get_valid_start_index(word), Some(*index));
}
}
#[test]
fn test_get_valid_end_index() {
let mut map: std::collections::HashMap<&'static str, usize> =
std::collections::HashMap::new();
map.insert("hello", 4usize);
map.insert("__hello", 6usize);
map.insert("__ hello", 7usize);
map.insert("to everyone❤️", 10usize);
for (word, index) in map.iter() {
assert_eq!(get_valid_end_index(word), Some(*index));
}
}
#[test]
fn test_has_apostrophes() {
let lst1 = ["doesn't", "won't", "couldn't", "O'Shag-hennesey"];
for item in lst1.iter() {
let result = has_apostrophes(item);
assert_eq!(result, true);
}
let lst1_smart = ["doesn\u{2019}t", "won\u{2019}t", "couldn\u{2019}t"];
for item in lst1_smart.iter() {
let result = has_apostrophes(item);
assert_eq!(result, true);
}
let lst1_open = ["O\u{2018}Brien", "wouldn\u{2018}t"];
for item in lst1_open.iter() {
let result = has_apostrophes(item);
assert_eq!(result, true);
}
let lst2 = ["foo", "bar", "baz"];
for item in lst2.iter() {
let result = has_apostrophes(item);
assert_eq!(result, false);
}
}
#[test]
fn test_single_apostrophe_string() {
let s: &'static str = "O'Shaghennessy"; let result: String = handle_apostrophe_string(s);
let parts: Vec<&str> = result.split("'").collect();
let first_word: &&str = parts.get(0).unwrap();
let first_word_grapheme: Vec<&str> = first_word.graphemes(true).collect::<Vec<&str>>();
let second_word: &&str = parts.get(1).unwrap();
let second_word_grapheme: Vec<&str> =
second_word.graphemes(true).collect::<Vec<&str>>();
let first_word_first_char: &str = *first_word_grapheme.get(0).unwrap();
let second_word_first_char: &str = *second_word_grapheme.get(0).unwrap();
let second_word_last_char: &str = *second_word_grapheme
.get(second_word_grapheme.len() - 1)
.unwrap();
assert_eq!(first_word_first_char, "O");
assert_eq!(second_word_first_char, "S");
assert_eq!(second_word_last_char, "y");
}
#[test]
fn test_double_apostrophe_string() {
let s: &'static str = "woulda'coulda'shoulda";
let result: String = handle_apostrophe_string(s);
let parts: Vec<&str> = result.split("'").collect();
let first_word: &&str = parts.get(0).unwrap();
let first_word_grapheme: Vec<&str> = first_word.graphemes(true).collect::<Vec<&str>>();
let second_word: &&str = parts.get(1).unwrap();
let second_word_grapheme: Vec<&str> =
second_word.graphemes(true).collect::<Vec<&str>>();
let third_word: &&str = parts.get(2).unwrap();
let third_word_grapheme: Vec<&str> = third_word.graphemes(true).collect::<Vec<&str>>();
let first_word_first_char: &str = *first_word_grapheme.get(0).unwrap();
let first_word_last_char: &str = *first_word_grapheme
.get(first_word_grapheme.len() - 1)
.unwrap();
let second_word_first_char: &str = *second_word_grapheme.get(0).unwrap();
let second_word_last_char: &str = *second_word_grapheme
.get(second_word_grapheme.len() - 1)
.unwrap();
let third_word_first_char: &str = *third_word_grapheme.get(0).unwrap();
let third_word_last_char: &str = *third_word_grapheme
.get(third_word_grapheme.len() - 1)
.unwrap();
assert_eq!(first_word_first_char, "w"); assert_eq!(first_word_last_char, "a"); assert_eq!(second_word_first_char, "c"); assert_eq!(second_word_last_char, "a"); assert_eq!(third_word_first_char, "s"); assert_eq!(third_word_last_char, "a"); }
#[test]
fn test_has_hyphens() {
let lst1 = ["Spanish-speaking", "all-or-nothing", "dipsy-doo-dunkaroo"];
for item in lst1.iter() {
let result = has_hyphens(item);
assert_eq!(result, true);
}
let lst2 = ["Spanish", "all", "dipsy"];
for item in lst2.iter() {
let result = has_hyphens(item);
assert_eq!(result, false);
}
}
#[test]
fn test_single_hyphen_string() {
let s: &'static str = "nitty-gritty";
let result: String = handle_hyphenated_string(s);
let parts: Vec<&str> = result.split("-").collect();
let first_word: &&str = parts.get(0).unwrap();
let first_word_grapheme: Vec<&str> = first_word.graphemes(true).collect::<Vec<&str>>();
let second_word: &&str = parts.get(1).unwrap();
let second_word_grapheme: Vec<&str> =
second_word.graphemes(true).collect::<Vec<&str>>();
let first_word_first_char: &str = *first_word_grapheme.get(0).unwrap();
let first_word_last_char: &str = *first_word_grapheme
.get(first_word_grapheme.len() - 1)
.unwrap();
let second_word_first_char: &str = *second_word_grapheme.get(0).unwrap();
let second_word_last_char: &str = *second_word_grapheme
.get(second_word_grapheme.len() - 1)
.unwrap();
assert_eq!(first_word_first_char, "n");
assert_eq!(first_word_last_char, "y");
assert_eq!(second_word_first_char, "g");
assert_eq!(second_word_last_char, "y");
}
#[test]
fn test_double_hyphen_string() {
let s: &'static str = "over-the-counter";
let result: String = handle_hyphenated_string(s);
let parts: Vec<&str> = result.split("-").collect();
let first_word: &&str = parts.get(0).unwrap();
let first_word_grapheme: Vec<&str> = first_word.graphemes(true).collect::<Vec<&str>>();
let second_word: &&str = parts.get(1).unwrap();
let second_word_grapheme: Vec<&str> =
second_word.graphemes(true).collect::<Vec<&str>>();
let third_word: &&str = parts.get(2).unwrap();
let third_word_grapheme: Vec<&str> = third_word.graphemes(true).collect::<Vec<&str>>();
let first_word_first_char: &str = *first_word_grapheme.get(0).unwrap();
let first_word_last_char: &str = *first_word_grapheme
.get(first_word_grapheme.len() - 1)
.unwrap();
let second_word_first_char: &str = *second_word_grapheme.get(0).unwrap();
let second_word_last_char: &str = *second_word_grapheme
.get(second_word_grapheme.len() - 1)
.unwrap();
let third_word_first_char: &str = *third_word_grapheme.get(0).unwrap();
let third_word_last_char: &str = *third_word_grapheme
.get(third_word_grapheme.len() - 1)
.unwrap();
assert_eq!(first_word_first_char, "o"); assert_eq!(first_word_last_char, "r"); assert_eq!(second_word_first_char, "t"); assert_eq!(second_word_last_char, "e"); assert_eq!(third_word_first_char, "c"); assert_eq!(third_word_last_char, "r"); }
#[test]
fn test_triple_hyphen_string() {
let s: &'static str = "head-in-the-clouds";
let result: String = handle_hyphenated_string(s);
let parts: Vec<&str> = result.split("-").collect();
assert_eq!(parts.get(1), Some("in").as_ref());
assert_eq!(parts.get(2), Some("the").as_ref());
let fourth_word: &&str = parts.get(3).unwrap();
assert_eq!(fourth_word.chars().nth(0), Some('c'));
assert_eq!(fourth_word.chars().nth(5), Some('s'));
}
#[test]
fn test_dont_scramble_short_words() {
let mut map: std::collections::HashMap<String, String> =
std::collections::HashMap::new();
map.insert(String::from(""), String::from(""));
map.insert(String::from("a"), String::from("a"));
map.insert(String::from("the"), String::from("the"));
map.insert(String::from("for"), String::from("for"));
map.insert(String::from("and"), String::from("and"));
map.insert(String::from("12/22/1986"), String::from("12/22/1986"));
for (word, matcher) in map.iter() {
assert_eq!(scramble_word(word.to_string()), matcher.to_string());
}
}
#[test]
fn test_dont_scramble_long_words() {
let mut map: std::collections::HashMap<String, String> =
std::collections::HashMap::new();
map.insert(
String::from("antidisestablishmentarianism"),
String::from("antidisestablishmentarianism"),
);
map.insert(
String::from("anthropomorphism"),
String::from("anthropomorphism"),
);
map.insert(
String::from("unconstitutional"),
String::from("unconstitutional"),
);
map.insert(
String::from("multidimensional"),
String::from("multidimensional"),
);
for (word, matcher) in map.iter() {
assert_eq!(scramble_word(word.to_string()), matcher.to_string());
}
}
#[test]
fn test_one_valid_ascii_char() {
let mut map: std::collections::HashMap<String, String> =
std::collections::HashMap::new();
map.insert(String::from("_a"), String::from("_a"));
map.insert(String::from("a_"), String::from("a_"));
map.insert(String::from("___a___"), String::from("___a___"));
map.insert(String::from("a...,,,"), String::from("a...,,,"));
map.insert(String::from("!@#$%a"), String::from("!@#$%a"));
for (word, matcher) in map.iter() {
assert_eq!(scramble_word(word.to_string()), matcher.to_string());
}
}
}
}