Expand description
Frizbee is a SIMD typo-resistant fuzzy string matcher written in Rust. The core of the algorithm uses Smith-Waterman with affine gaps, similar to FZF. In the included benchmark, with typo resistance disabled, it outperforms Nucleo by ~4x and FZF by ~5x and supports multithreading, see benchmarks. When matching against unicode, it outperforms Nucleo and FZF by 20x.
Used by blink.cmp, skim, and fff. Special thank you to stefanboca and ii14!
For commercial support, please contact me. I’d be happy to work with you directly! Also, please consider sponsoring me.
The core of the algorithm is Smith-Waterman with affine gaps and row-wise parallelism via SIMD. Besides the parallelism, this is the basis of other popular fuzzy matching algorithms like FZF and Nucleo. The main properties of Smith-Waterman are:
- Always finds the best alignment
- Supports insertion (unmatched char in haystack, basis of fuzzy matching)
- Supports deletion (unmatched char in needle, basis of typo-resistance)
- Supports substitution (haystack and needle char mismatch, basis of typo-resistance)
§Example: using Matcher
Matcher compiles the pattern once, allocates memory for the Smith Waterman matrix,
and reuses the selected SIMD backend.
Ideally, only construct these at most once per list. They’re cheap to construct, but end up being expensive if you construct them for each item in your list.
use frizbee::{Config, Matcher};
let needle = "fBr";
let haystacks = ["fooBar", "foo_bar", "barfoo", "prelude", "println!"];
let mut matcher = Matcher::new(needle, &Config::default());
let matches = matcher.match_list(&haystacks);
// or in parallel (8 threads)
let matches = matcher.match_list_parallel(&haystacks, 8);§Example: using multi-pattern queries
Matcher::from_query parses whitespace-separated atoms. Atom syntax can
control the matching mode:
fuzzy substring prefix suffix exact negated (combines with others)
foo 'foo ^foo foo$ ^foo$ !foouse frizbee::{Config, Matcher};
let haystacks = ["foo", "barfoo", "foobar", "bar/foo"];
let mut matcher = Matcher::from_query("foo !^bar", &Config::default());
let matches = matcher.match_list(&haystacks);§Example: using explicit Patterns
If query syntax is not a good fit, build patterns directly and pass them to
Matcher::from_patterns or Matcher::new (if you only have one pattern).
use frizbee::{Config, Matcher, Matching, Pattern};
let patterns = [
Pattern::new("foo", None, false),
Pattern::new("bar", Some(Matching::Prefix), true),
];
let haystacks = ["foo", "barfoo", "foobar"];
let mut matcher = Matcher::from_patterns(&patterns, &Config::default());
let matches = matcher.match_list(&haystacks);§Example: using FuzzyMatchExt
The iterator API is convenient when chaining with other iterator adapters,
but it is slower than matching a full list with Matcher::match_list.
use frizbee::{iter::FuzzyMatchExt, Config, radix_sort_matches};
let haystacks = ["fooBar", "foo_bar", "prelude", "println!"];
let mut matches: Vec<_> = haystacks
.iter()
.fuzzy_match("fBr", &Config::default())
.collect();
radix_sort_matches(&mut matches);Modules§
- iter
- Iterator extension for fuzzy matching
- k_merge
- Merges multiple pre-sorted runs of
crate::Matches into a single sortedVecusing the k-way merge algorithm specialized forcrate::Matches.
Structs§
- Config
- Match
- Result of a fuzzy match, containing the score and index in the haystack
- Match
Indices - Like
Matchbut includes the indices of the chars in the haystack that matched the needle in reverse order - Matcher
- Primary entrypoint for fuzzy matching
- Pattern
- A single pattern to match, parsed from syntax like
!^foo - Scoring
Enums§
- Case
Matching - Matching
- Selects the matching algorithm
- Sort
Strategy - Unicode
Matching
Functions§
- radix_
sort_ matches - Sorts a slice of
Matchvalues in-place by descendingscoreusing a stable radix sort. This assumes that the matches are already sorted by index.