const MAX_VARIANTS: usize = 4;
const STOPWORDS: &[&str] = &[
"a", "an", "and", "are", "at", "be", "by", "code", "do", "does", "for", "from", "happen",
"happens", "how", "in", "is", "it", "of", "on", "or", "our", "that", "the", "their", "this",
"to", "we", "what", "when", "where", "which", "who", "why", "with",
];
pub fn variants(concept: &str, keywords: &[String]) -> Vec<String> {
if !keywords.is_empty() {
return keywords.iter().take(MAX_VARIANTS).cloned().collect();
}
let stripped = strip_stopwords(concept);
let fallback = if stripped.is_empty() {
concept.to_owned()
} else {
stripped
};
vec![fallback]
}
fn strip_stopwords(concept: &str) -> String {
concept
.split_whitespace()
.filter(|word| {
let bare: String = word
.chars()
.filter(|c| c.is_alphanumeric())
.collect::<String>()
.to_lowercase();
!STOPWORDS.contains(&bare.as_str())
})
.collect::<Vec<_>>()
.join(" ")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn keywords_pass_through_capped_at_four() {
let keywords: Vec<String> = ["a", "b", "c", "d", "e"]
.iter()
.map(|s| s.to_string())
.collect();
assert_eq!(variants("ignored", &keywords), keywords[..4].to_vec());
}
#[test]
fn fallback_strips_stopwords() {
assert_eq!(
variants("where does the websocket reconnection happen?", &[]),
vec!["websocket reconnection"] );
}
#[test]
fn all_stopword_concept_falls_back_to_itself() {
assert_eq!(variants("where is it", &[]), vec!["where is it"]);
}
}