use std::collections::HashMap;
use std::collections::HashSet;
use super::super::hash::bm25_hash;
use super::super::punctuation::is_bare_punctuation;
use super::super::stemmer::stem;
use super::super::stopwords::Stopwords;
use super::term_frequency;
use super::tokenizer;
use crate::sparse::SparseEmbedding;
const TOKEN_MAX_LENGTH: usize = 40;
fn stem_tokens(tokens: Vec<String>, stopwords: &Stopwords) -> Vec<String> {
tokens
.into_iter()
.filter(|token| !is_bare_punctuation(token))
.filter(|token| !stopwords.contains(token))
.filter(|token| token.chars().count() <= TOKEN_MAX_LENGTH)
.map(|token| stem(&token))
.filter(|stemmed| !stemmed.is_empty())
.collect()
}
fn tokenize_and_stem(text: &str, stopwords: &Stopwords) -> Vec<String> {
let cleaned = tokenizer::remove_non_alphanumeric(text);
stem_tokens(tokenizer::tokenize(&cleaned), stopwords)
}
pub(crate) fn embed_document(text: &str, stopwords: &Stopwords) -> SparseEmbedding {
let stems = tokenize_and_stem(text, stopwords);
let doc_len = stems.len() as u32;
let mut counts: HashMap<String, u32> = HashMap::new();
for stemmed in stems {
*counts.entry(stemmed).or_insert(0) += 1;
}
SparseEmbedding::from_pairs(counts.into_iter().map(|(stemmed, count)| {
(
bm25_hash(&stemmed),
term_frequency::saturation(count, doc_len),
)
}))
}
pub(crate) fn embed_query(text: &str, stopwords: &Stopwords) -> SparseEmbedding {
let stems: HashSet<String> = tokenize_and_stem(text, stopwords).into_iter().collect();
SparseEmbedding::from_pairs(
stems
.into_iter()
.map(|stemmed| (bm25_hash(&stemmed), 1.0f32)),
)
}
#[cfg(test)]
fn test_stopwords() -> Stopwords {
Stopwords::parse(include_str!("../english_stopwords.txt"))
}
#[cfg(test)]
mod tests;