use super::constants::{BOUNDED_WORD_REGEX, STOP_WORDS};
use regex::Captures;
use std::borrow::Cow;
pub(crate) fn preprocess(text: &str) -> Vec<String> {
let text = remove_punctuation(text);
let text = normalize_case(text);
let text = remove_stop_words(&text);
text.split_whitespace()
.map(|word| word.to_string())
.collect::<Vec<_>>()
}
fn normalize_case<'a, S: Into<Cow<'a, str>>>(input: S) -> Cow<'a, str> {
let input = input.into();
let first = input.find(char::is_uppercase);
if let Some(first_idx) = first {
let mut output = String::from(&input[..first_idx]);
output.reserve(input.len() - first_idx);
for c in input[first_idx..].chars() {
if c.is_uppercase() {
output.push(c.to_lowercase().next().unwrap())
} else {
output.push(c)
}
}
Cow::Owned(output)
} else {
input
}
}
fn remove_punctuation(text: &str) -> String {
text.replace(
[
'!', '\\', '"', '#', '$', '%', '&', '(', ')', '*', '+', ':', ';', '<', '=', '>', '?',
'@', '[', ']', '^', '{', '}', '|', '~', ',', '\'', '“', '”', '’', '‘', '’', '‘', '/',
'–', '—', '.',
],
" ",
)
}
fn remove_stop_words(text: &str) -> String {
BOUNDED_WORD_REGEX
.replace_all(text, |caps: &Captures| {
let word = &caps[0];
if !STOP_WORDS.contains(&word) {
word.to_owned()
} else {
String::new()
}
})
.into()
}
pub(super) fn inverse_document_frequency(num_docs: f32, doc_frequency: f32) -> f32 {
f32::log10(num_docs / doc_frequency)
}
pub(super) fn tf_idf_score(term_frequency: f32, idf: f32) -> f32 {
term_frequency * idf
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_remove_punctuation() {
let tester = "!\\\"#$%&()*+/:;<=>?@[]^{}|~,.'“”’‘–—\n‘’";
assert_eq!(
remove_punctuation(tester),
" \n "
);
}
#[test]
fn test_normalize_case() {
let tester = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
assert_eq!(normalize_case(tester), "abcdefghijklmnopqrstuvwxyz");
}
#[test]
fn test_remove_stopwords() {
let all_words = STOP_WORDS
.iter()
.map(|&word| word.to_string())
.collect::<Vec<_>>()
.join(" ");
let removed = remove_stop_words(&all_words).replace(' ', "");
assert_eq!(removed, "'d'll'm''s'ven'tn‘tn’t‘d‘ll‘m‘‘s‘ve’d’ll’m’’s’ve");
}
#[test]
fn test_preprocess_results() {
let tester = "WHY are Y'all YELLing?";
assert_eq!(&preprocess(tester), &["y", "all", "yelling"]);
}
#[test]
fn idf_returns_expected_value() {
let num_docs = 10_000_000_f32;
let num_occurrences = 1_000_f32;
let abs_diff = (inverse_document_frequency(num_docs, num_occurrences) - 4.0).abs();
assert!(abs_diff <= f32::EPSILON);
}
#[test]
fn tf_idf_returns_expected_value() {
let term_freq = 0.03_f32;
let num_docs = 10_000_000_f32;
let num_occurrences = 1_000_f32;
let idf = inverse_document_frequency(num_docs, num_occurrences);
let abs_diff = (tf_idf_score(term_freq, idf) - 0.12).abs();
assert!(abs_diff <= f32::EPSILON);
}
}