use std::collections::HashSet;
/*
split_words
Splits free text into words by scanning characters and
breaking on any non-alphabetic character.
We use char::is_alphabetic(), which is Unicode-aware and
does not depend on regex Unicode properties.
*/
fn split_words(text: &str) -> Vec<String> {
let mut words: Vec<String> = Vec::new();
let mut current: String = String::new();
for ch in text.chars() {
if ch.is_alphabetic() {
// Part of a word
current.push(ch);
} else {
// Separator: end of a word (if any)
if !current.is_empty() {
words.push(current.clone());
current.clear();
}
}
}
// Last word, if the text ends with a letter
if !current.is_empty() {
words.push(current);
}
words
}
/*
remove_duplicate_words
Removes duplicate words while preserving:
- original order
- original casing of first occurrence
- case-insensitive comparison
Uses HashSet<String> for O(1) lookup.
*/
fn remove_duplicate_words(text: &str) -> String {
let words: Vec<String> = split_words(text);
let mut seen: HashSet<String> = HashSet::new();
let mut unique: Vec<String> = Vec::new();
for word in words {
// Unicode-aware lowercase key
let key: String = word.to_lowercase();
if !seen.contains(&key) {
seen.insert(key);
unique.push(word); // preserve original casing
}
}
// Reassemble into a space-separated string
unique.join(" ")
}
fn main() {
let input: &str =
"Hello, hello! This is a test. A TEST, hello universe... \
UNIVERSE! Hello; *** Is Anybody There?";
let output: String = remove_duplicate_words(input);
println!("{}", output);
}
/*
run:
Hello This is a test universe Anybody There
*/