use std::{cell::RefCell, rc::Rc};
pub trait Suggest {
type Result;
fn suggest(&self, input: &str) -> Vec<Self::Result>;
}
impl<T> Suggest for Vec<Rc<RefCell<T>>>
where
T: AsRef<str>,
{
type Result = Rc<RefCell<T>>;
fn suggest(&self, input: &str) -> Vec<Rc<RefCell<T>>> {
let labeled_items = self
.iter()
.map(|i| (i.borrow().as_ref().to_lowercase(), Rc::clone(i)));
fuzzy(labeled_items, &input.to_lowercase())
}
}
impl Suggest for Vec<String> {
type Result = String;
fn suggest(&self, input: &str) -> Vec<String> {
let labeled_items = self.iter().map(|s| (s.to_lowercase(), s.clone()));
fuzzy(labeled_items, &input.to_lowercase())
}
}
impl<F, T> Suggest for F
where
F: Fn(&str) -> Vec<T>,
{
type Result = T;
fn suggest(&self, input: &str) -> Vec<Self::Result> {
self(input)
}
}
fn fuzzy<T>(items: impl Iterator<Item = (String, T)>, input: &str) -> Vec<T> {
if input.trim().is_empty() {
return items.map(|(_, item)| item).collect();
}
let filter_words: Vec<_> = input.split_whitespace().collect();
let mut scored: Vec<_> = items
.map(|(label, item)| {
let similarity = strsim::jaro_winkler(&label, input);
let bonus = filter_words.iter().all(|word| label.contains(*word)) as usize as f64;
(similarity + bonus, item)
})
.filter(|(score, _)| *score > 0.6)
.collect();
scored.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap());
scored.into_iter().map(|(_, item)| item).collect()
}