feat(service): Improved search result sorting via Jaro-Winkler

This commit is contained in:
Michael Aaron Murphy 2021-08-26 15:02:20 +02:00
parent 07b740a12f
commit b5514a6e24
2 changed files with 35 additions and 41 deletions

View file

@ -41,19 +41,14 @@ pub async fn main() {
while let Some(result) = requests.next().await {
match result {
Ok(request) => {
tracing::debug!("received request: {:?}", request);
match request {
Request::Activate(id) => app.activate(id).await,
Request::ActivateContext { id, context } => {
app.activate_context(id, context).await
}
Request::Context(id) => app.context(id).await,
Request::Search(query) => app.search(&query).await,
Request::Exit => break,
_ => (),
}
}
Ok(request) => match request {
Request::Activate(id) => app.activate(id).await,
Request::ActivateContext { id, context } => app.activate_context(id, context).await,
Request::Context(id) => app.context(id).await,
Request::Search(query) => app.search(&query).await,
Request::Exit => break,
_ => (),
},
Err(why) => {
tracing::error!("malformed JSON request: {}", why);
@ -231,7 +226,7 @@ impl<W: AsyncWrite + Unpin> App<W> {
|| query
.split_ascii_whitespace()
.any(|query| search_interest.contains(&*query))
|| strsim::damerau_levenshtein(&*query, &*search_interest) < 3;
|| strsim::jaro_winkler(&*query, &*search_interest) > 0.6;
if append {
let desc_source = path_string(&entry.src);

View file

@ -422,8 +422,9 @@ impl<O: futures::Sink<Response> + Unpin> Service<O> {
*no_sort = false;
} else {
active_search.sort_by(|a, b| {
fn calculate_weight(meta: &PluginSearchResult, query: &str) -> usize {
let mut weight = 0;
// Weight is calculated between 0.0 and 1.0, with higher values being most similar
fn calculate_weight(meta: &PluginSearchResult, query: &str) -> f64 {
let mut weight: f64 = 0.0;
let name = meta.name.to_ascii_lowercase();
let description = meta.description.to_ascii_lowercase();
@ -433,50 +434,48 @@ impl<O: futures::Sink<Response> + Unpin> Service<O> {
.map(|exec| exec.to_ascii_lowercase())
.unwrap_or_default();
if !name.starts_with(query) {
weight = 1;
if !name.contains(query) {
weight = strsim::damerau_levenshtein(&name, query)
.min(strsim::damerau_levenshtein(&description, query));
if let Some(keywords) = meta.keywords.as_ref() {
for keyword in keywords.iter() {
let keyword = keyword.to_ascii_lowercase();
weight = if keyword.starts_with(query)
|| keyword.contains(query)
{
1
} else {
weight.min(strsim::damerau_levenshtein(query, &keyword) + 1)
}
}
}
for name in name.split_ascii_whitespace() {
if name.starts_with(query) {
return 1.0;
}
}
if exec.contains(query) {
weight = if exec.starts_with(query) {
weight.min(2)
if exec.starts_with(query) {
return 1.0;
} else {
weight.min(strsim::damerau_levenshtein(query, &exec))
weight = strsim::jaro_winkler(query, &exec) - 0.1;
}
}
weight
.max(strsim::jaro_winkler(&name, query))
.max(strsim::jaro_winkler(&description, query) - 0.1)
.max(match meta.keywords.as_ref() {
Some(keywords) => keywords
.iter()
.flat_map(|word| word.split_ascii_whitespace())
.fold(0.0, |acc, keyword| {
let keyword = keyword.to_ascii_lowercase();
acc.max(strsim::jaro_winkler(query, &keyword) - 0.1)
}),
None => 0.0,
})
}
let a_weight = calculate_weight(&a.1, query);
let b_weight = calculate_weight(&b.1, query);
match a_weight.cmp(&b_weight) {
Ordering::Equal => {
match a_weight.partial_cmp(&b_weight) {
Some(Ordering::Equal) => {
let a_len = a.1.name.len();
let b_len = b.1.name.len();
a_len.cmp(&b_len)
}
other => other,
Some(Ordering::Less) => Ordering::Greater,
Some(Ordering::Greater) => Ordering::Less,
None => Ordering::Greater,
}
});