use crate::search::conf::SuggestionItem;
use crate::search::tokenizer::levenshtein_distance;
use rapidhash::RapidHashMap as HashMap;
use std::cmp::Ordering;
pub const DEFAULT_SUG_LIMIT: usize = 10;
#[derive(Debug, Clone, Default)]
pub struct SuggestionDict {
pub entries: HashMap<String, (f64, Option<String>)>,
}
impl SuggestionDict {
#[inline]
pub fn new() -> Self {
Self::default()
}
pub fn sug_add(
&mut self,
string: &str,
score: f64,
incr: bool,
payload: Option<String>,
) -> usize {
let entry = self
.entries
.entry(string.to_string())
.or_insert((0.0, None));
if incr {
entry.0 += score;
} else {
entry.0 = score;
}
if payload.is_some() {
entry.1 = payload;
}
self.entries.len()
}
pub fn sug_get(
&self,
prefix: &str,
fuzzy: bool,
withscores: bool,
withpayloads: bool,
max: Option<usize>,
) -> Vec<SuggestionItem> {
let limit = max.unwrap_or(DEFAULT_SUG_LIMIT);
let prefix_lower = prefix.to_lowercase();
let mut matched: Vec<(&str, f64, Option<&str>)> = self
.entries
.iter()
.filter_map(|(s, (score, payload))| {
let s_str = s.as_str();
if s_str.len() >= prefix.len() && s_str[..prefix.len()].eq_ignore_ascii_case(prefix)
{
Some((s_str, *score, payload.as_deref()))
} else {
let s_lower = s.to_lowercase();
if s_lower.starts_with(&prefix_lower)
|| (fuzzy && levenshtein_distance(&s_lower, &prefix_lower) <= 1)
{
Some((s_str, *score, payload.as_deref()))
} else {
None
}
}
})
.collect();
let cmp_fn = |a: &(&str, f64, Option<&str>), b: &(&str, f64, Option<&str>)| {
b.1.partial_cmp(&a.1)
.unwrap_or(Ordering::Equal)
.then_with(|| a.0.cmp(b.0))
};
if matched.len() > limit {
matched.select_nth_unstable_by(limit, cmp_fn);
matched.truncate(limit);
}
matched.sort_by(cmp_fn);
matched
.into_iter()
.map(|(s, score, payload)| SuggestionItem {
string: s.to_string(),
score: if withscores { score } else { 0.0 },
payload: if withpayloads {
payload.map(str::to_string)
} else {
None
},
})
.collect()
}
#[inline]
pub fn sug_del(&mut self, string: &str) -> bool {
self.entries.remove(string).is_some()
}
#[inline]
pub fn sug_len(&self) -> usize {
self.entries.len()
}
}