What you'll learn. How to attach a value to every dictionary term — turning a fuzzy set into a fuzzy map — and how to use those values to filter, prioritize, and rank near-match results. The running example is IDE code completion, where each identifier carries a scope ID and we want only the completions visible in the current scope. You'll also see why filtering during traversal (rather than after) is the path to a 10–100× speedup on large dictionaries, which motivates the value-filtered pruning diagram.
A plain dictionary answers "is $s$ within distance $k$ of $W$?". A fuzzy map
additionally stores a value V per term, so a match returns both the term and its
payload. PathMapDictionary<V> (feature pathmap-backend) is one such backend: it maps
each term to a value of your choosing (here u32 scope IDs) and is built with
from_terms_with_values. Retrieve a stored value with get_value(&term).
Terms defined. A value is arbitrary data associated with a key term (an integer scope ID, a document-ID set, a definition, …). A scope in an IDE is a region of code over which a name is visible (the standard library, the current file, an imported module). "Fuzzy map" = dictionary-with-values queried by edit distance.
The example contrasts strategies for "find typo-tolerant matches, but only in the current scope":
k, then drop those whose
value fails the predicate. Simple, but you pay to generate matches you immediately
throw away.For small dictionaries the three are indistinguishable; for 10k+ terms strategy 3 is where the 10–100× win lives, because work again tracks the surviving frontier.
Carrying the scope ID as a value means a single dictionary serves every scope — no per-context rebuild — and the same payload can drive ordering (e.g. prefer local names over imports) after the fuzzy match, all from one query.
examples/fuzzy_maps_code_completion.rs(term, value) pairsEach identifier is tagged with a scope ID (1 = std, 2 = local, 3 = imports). The
dictionary is a PathMapDictionary<u32>; the transducer drives fuzzy queries over it.
use libdictenstein::pathmap::PathMapDictionary;
use liblevenshtein::prelude::*;
let identifiers_with_scopes = vec![
("println", 1u32), ("print", 1), ("format", 1), // std
("process_data", 2), ("parse_input", 2), ("print_results", 2), // local
("fetch_data", 3), ("parse_json", 3), ("format_date", 3), // imports
];
let dict: PathMapDictionary<u32> =
PathMapDictionary::from_terms_with_values(identifiers_with_scopes.clone());
let transducer = Transducer::new(dict.clone(), Algorithm::Standard);
query_with_distance yields candidates; dict.get_value(&term) recovers each term's
scope so we can keep only the current scope (2). This is the baseline the example
times:
let query = "prin";
let current_scope = 2u32;
let mut results = Vec::new();
for candidate in transducer.query_with_distance(query, 2) {
if let Some(scope_id) = dict.get_value(&candidate.term) {
if scope_id == current_scope { // filter by value
results.push((candidate.term, candidate.distance, scope_id));
}
}
}
filter_mapThe same logic across all three scopes reads cleanly as an iterator adapter — match, fetch value, keep on predicate:
for scope_id in 1..=3 {
let matches: Vec<_> = transducer
.query_with_distance(query, 2)
.filter_map(|c| {
dict.get_value(&c.term)
.filter(|&s| s == scope_id)
.map(|s| (c.term, c.distance, s))
})
.collect();
// … report matches for this scope …
}
Because the value travels with each match, you can impose a priority over scopes (local > std > imports) and break ties by edit distance, then alphabetically:
let mut all: Vec<_> = transducer
.query_with_distance(query, 2)
.filter_map(|c| dict.get_value(&c.term).map(|s| (c.term, c.distance, s)))
.collect();
let scope_priority = |s: u32| match s { 2 => 0, 1 => 1, 3 => 2, _ => 3 };
all.sort_by(|a, b| {
scope_priority(a.2)
.cmp(&scope_priority(b.2)) // 1) scope priority
.then(a.1.cmp(&b.1)) // 2) edit distance
.then(a.0.cmp(&b.0)) // 3) alphabetical
});
The example measures post-filtering vs manual iteration and reports the fraction of matches the scope filter removes — the lever that, at scale, value-aware traversal turns into a 10–100× speedup by never descending unmatched-scope subtrees.
This example requires the pathmap-backend feature:
cargo run --example fuzzy_maps_code_completion --features pathmap-backend
crates.io note.
pathmap-backenduses a git dependency, so it is unavailable from a plaincrates.ioinstall — build from source to enable it.
PathMapDictionary<V> via from_terms_with_values) stores a value per
term; get_value(&term) recovers it for a match.← 04 · Queries & Unicode · Next: 06 · Contextual Completion →
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |