Text

Fuzzy search

Rank names against what someone typed, typos and all, edit distances, and did-you-mean.

Version 1.0.0 · 4 functions · 1 crate

#search#typos#autocomplete#text

Functions

What use fuzzy.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use fuzzy_score, fuzzy_rank from fuzzy (Quillo 0.6).

fuzzy_score(a: str, b: str) → float

How alike two strings are, 0 to 1, ignoring case: Jaro-Winkler, which favours a shared start.

fuzzy_rank(query: str, items: list, limit: int) → list

The items most like query, best first, as [{ item, score }]: a match at the start or inside an item counts most, then likeness. At most limit, none under 0.5.

edit_distance(a: str, b: str) → int

The edits (insertions, deletions, changes, swaps of neighbours) that turn a into b.

did_you_mean(word: str, items: list, max_edits: int) → str?

The item nearest word in edits, for "did you mean ...?", or null when none is within max_edits.

Get it

$ quillo install fuzzy               # fuzzy.qwer into this folder$ quillo install fuzzy --example     # and its example, fuzzy.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/fuzzy.qwer$ curl -O https://quillolang.org/plugins/files/fuzzy.qwe

Then, in your program, all of it or only what you call:

main.qwe
use fuzzy.qweruse fuzzy_score, fuzzy_rank from fuzzy      // Quillo 0.6: only these

It uses this crate, which cargo fetches the first time the plugin is built:

[dependencies]
strsim = "0.11"

Example

fuzzy.qwe
// A search box that forgives typos.////   quillo run fuzzy.qwe      then open http://localhost:8080/search?q=amsterdmuse "fuzzy.qwer"let cities = ["Amsterdam", "Rotterdam", "Antwerp", "Berlin", "Bern", "Barcelona", "Lisbon", "London"]route GET /search ? { q: str } = fuzzy_rank(q, cities, 3)route GET /mean ? { q: str } = { meant: did_you_mean(q, cities, 2) }
$ quillo run fuzzy.qwe$ curl 'localhost:8080/search?q=amsterdm'$ curl 'localhost:8080/mean?q=Lisbn'

Source

fuzzy.qwer
//! Fuzzy matching: rank names against what someone typed, typos and all, and edit distances.//! version: 1.0.0//!//! [dependencies]//! strsim = "0.11"use serde_json::{json, Value};/// How alike two strings are, 0 to 1, ignoring case: Jaro-Winkler, which favours a shared start.pub fn fuzzy_score(a: &str, b: &str) -> f64 {    strsim::jaro_winkler(&a.to_lowercase(), &b.to_lowercase())}/// The `items` most like `query`, best first, as `[{ item, score }]`: a match at the start or inside an item counts most, then likeness. At most `limit`, none under 0.5.pub fn fuzzy_rank(query: &str, items: Vec<String>, limit: i64) -> Vec<Value> {    let q = query.to_lowercase();    let mut scored: Vec<(f64, String)> = items.into_iter().map(|it| {        let l = it.to_lowercase();        let s = if l.starts_with(&q) { 1.0 } else if l.contains(&q) { 0.9 } else {            strsim::jaro_winkler(&q, &l).max(l.split_whitespace().map(|w| strsim::jaro_winkler(&q, w)).fold(0.0, f64::max) * 0.95)        };        (s, it)    }).filter(|(s, _)| *s >= 0.5).collect();    scored.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));    scored.into_iter().take(limit.max(0) as usize).map(|(s, it)| json!({ "item": it, "score": (s * 1000.0).round() / 1000.0 })).collect()}/// The edits (insertions, deletions, changes, swaps of neighbours) that turn `a` into `b`.pub fn edit_distance(a: &str, b: &str) -> i64 {    strsim::damerau_levenshtein(a, b) as i64}/// The item nearest `word` in edits, for "did you mean ...?", or null when none is within `max_edits`.pub fn did_you_mean(word: &str, items: Vec<String>, max_edits: i64) -> Option<String> {    items.into_iter().map(|it| (strsim::damerau_levenshtein(&word.to_lowercase(), &it.to_lowercase()) as i64, it))        .filter(|(d, _)| *d <= max_edits).min_by_key(|(d, _)| *d).map(|(_, it)| it)}
Thread
78Useful? Like it, or add to its thread below.

Thread 0 posts

On Fuzzy search: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.