fuzzy-tools
v3.0.0
Published
Functions for fuzzy matching and items filtering
Maintainers
Readme
fuzzy-tools
- What is it?
- Quick Start
- Installation
- Import
- What is new?
- Match function
- Filter function
- Benchmark results
- Tests
What is it?
It is the fastest functions for fuzzy matching and items filtering.

https://codesandbox.io/s/fuzzytoolsexample-4r2ej
Quick Start
import { match, filter } from 'fuzzy-tools';
// Basic fuzzy match
match('fzz', 'fuzzy search');
// { score: 1 }
// Filter a list of strings
filter('fzz', ['fuzzy', 'funny', 'fazzy']);
// ['fuzzy', 'fazzy']Installation
npm install --save fuzzy-toolsImport
ESM (recommended):
import { match, filter, matchString, matchList } from 'fuzzy-tools';CommonJS:
const { match, filter, matchString, matchList } = require('fuzzy-tools');Default import:
import fuzzyTools from 'fuzzy-tools';
fuzzyTools.match('fzz', 'fuzzy');What is new?
v3.0.0
caseInsensitiveremoved (usecaseSensitive: trueinstead ofcaseInsensitive: false)masknot onlyString, butArray.of(String or RegExp). In case when it is array then search will be work as fuzzy but not by chars, but by words.['fuz', 'sea'](or['fuz', /sea/]) will not be found infuuuuzzzzzy seeeeearch, but will be found infuzzzzzy search.
Match function
match(mask, where, options):Object | null - returns result of string matching. Returns an object if mask matches where (e.g. 'fzz' in 'fuzzy'), null if it does not (e.g. 'fzza' in 'fuzzy').
Tests cases for match
Tests cases for matchList
Tests cases for matchString
Arguments
| args | type | default | note |
| - | - | - | - |
| mask | String or Array.of(String or RegExp) | no | provide what you want to find ('fzz' or ['fuz', 'sea']) |
| where | String or Array.of(String) | no | destination string or array of strings |
| options | Object | {...} | additional options (see below) |
maskString:
'fzz'will be found in'fuzzy search'Array.of(String or RegExp):
['fzz']will NOT be found in'fuzzy search'.[/fzz/]will NOT be found in'fuzzy search'.[/f/, /./, /zz/]will be found in'fuzzy search'.['fuz', 'sea']will be found in'fuzzy search'.['fuz', 'seaa']will NOT be found in'fuzzy search', because ALL words should be inwhere.match('fzz', 'fuzzy search'); // { score: 1 } match(['fzz'], 'fuzzy search'); // null match(['f', 'u', 'zz'], 'fuzzy search'); // { score: 1 } match(['f', /./, 'zz'], 'fuzzy search'); // { score: 1 } match(['f', /[a-z]/, 'zz'], 'fuzzy search'); // { score: 1 } match([/[a-x]{2}/, 'zz'], 'fuzzy search'); // { score: 1 } match(['fuz', 'rch'], 'fuzzy search'); // { score: 1 } match(['fuz', 'rch1111'], 'fuzzy search'); // null
where- String: result contains
scoreis score of matching.match('fuz', 'fuzzy'); // { score: 1 } - Array of strings or Object({ key: String, key2: String }): result contains
scoreis min of scores of each string (score = min(match(mask, item[0]), match(mask, item[1]), ...)).match('fuz', ['fuzzy', 'it is fuzzy']); // { score: 1, matches: { 0: { score: 1 }, 1: { score: 1 } } } match('fuz', { v1: 'fuzzy', v2: 'it is fuzzy' }); // { score: 1, matches: { v1: { score: 1 }, v2: { score: 1 } } } - Array of Object({ value: String, rate: Number(>0 and <=1) }): result contains
scoreis min of score of each value * rate (score = min(match(mask, item[0].value) / item[0].rate, ...))match('fuz', [ { value: 'fuzzy', rate: 0.75 }, { value: 'it is fuzzy', rate: 0.25 } ] ); match('fuz', { v1: { value: 'fuzzy', rate: 0.75 }, v2: { value: 'it is fuzzy', rate: 0.25 } } )
- String: result contains
options| option | type | default | description | | - | - | - |------------------------------------------------------------------------------------------------------------------| |
caseSensitive| Boolean |false| Whentrue, character case matters (e.g.'FZZ'will NOT match'fuzzy'). Whenfalse, case is ignored. | |withScore| Boolean |false| Whentrue,scoreis computed for matched strings. Whenfalse,scoreis always1. | |withWrapper| String or Function |null| When provided, result containswrapped— the original string with matched parts highlighted. | | | | | String: template with{?}placeholder (e.g.'<b>{?}</b>'→fzzinfuzzy→'<b>f</b>u<b>zz</b>y') | | | | | Function(word: String): String: (e.g.(word) => '<b>'+word+'</b>') | |withRanges| Boolean |false| Whentrue, result containsranges— array of{ begin: Number, end: Number }with matched character ranges. | |rates| Array or Object |{}| Weights for each item inwhere. Values must be> 0and<= 1. See examples below. |match('fuz', ['fuzzy', 'it is fuzzy'], { rates: [0.75, 0.25] } ) // or match('fuz', ['fuzzy', 'it is fuzzy'], { rates: { 0: 0.75, 1: 0.25 } } ) // equivalent to: match('fuz', { v1: { value: 'fuzzy', rate: 0.75 }, v2: { value: 'it is fuzzy', rate: 0.25 } } )
Result
score- from0.001toinfinity, lower is better. IfwithScoreisfalsethenscoreis always1.matches- present whenwhereis Array or Object. An object where each key is the index/key and each value is:{ score: Number, original: String, index: Number | String, [wrapped: String], [ranges: Array] }wrapped- present ifwithWrapperis set. Contains the original string with matched parts wrapped.ranges- present ifwithRangesistrue. Array of{ begin: Number, end: Number }.
import { match } from 'fuzzy-tools';
match('fzz', 'fuzzy', { withScore: true }); // { score: 1.74 }
match('fzz', ['fu', 'fuzza'], { withScore: true }) // { score: 1.74, matches: {1:{score: 1.74}} }
match('fzz', [{ value: 'fuzza', rate: 0.75 }, { value: 'fuzzy', rate: 0.10 }], { withScore: true })
// { score: 2.32, matches: {0: {score: 2.32}, 1: {score: 17.4}} }
// score = Min(1.74 / 0.75, 1.74 / 0.10)
match('fzz', 'fuzzy'); // { score: 1 }
match('fZZ', 'fuzzy', { caseSensitive: true }); // null
match('fZZ', 'fuzzy', { withWrapper: '<i>{?}</i>' });
// { score: 1, wrapped: '<i>f</i>u<i>zz</i>y' }
match('fZZ', ['fuzzy'], { withWrapper: '<i>{?}</i>' });
// { score: 1, matches: {0: {score: 1, original: 'fuzzy', index: 0, wrapped: '<i>f</i>u<i>zz</i>y'}}}
match('fZZ', { v: 'fuzzy' }, { withWrapper: '<i>{?}</i>' });
// { score: 1, matches: { v: {score: 1, original: 'fuzzy', index: 'v', wrapped: '<i>f</i>u<i>zz</i>y'}}}
match('fZZ', 'fuzzy', { withWrapper: w => `<b>${w}</b>` });
// { score: 1, wrapped: '<b>f</b>u<b>zz</b>y' }
match('fZZ', ['fuzzy'], { withWrapper: w => `<b>${w}</b>`, withScore: true });
// { score: 1, matches: {0: {score: 1, wrapped: '<b>f</b>u<b>zz</b>y', original: 'fuzzy'}}}
match('fZZ', 'fuzzy', { withRanges: true });
// { score: 1, ranges: [{begin: 0, end: 0}, {begin: 2, end: 3}]}
match('fZZ', ['fuzzy'], { withRanges: true });
// { score: 1, matches: {0: {score: 1, ranges: [{begin: 0, end: 0}, {begin: 2, end: 3}], original: 'fuzzy'}}}Match in string
import { match } from 'fuzzy-tools';
// or
// import { matchString } from 'fuzzy-tools';
match('fzz', 'fuzzy', { withScore: true }); // { score: 1.74 }
match('fzz', 'fuzzy'); // { score: 1 }
match('fZZ', 'fuzzy', { caseSensitive: true }); // null
match('fZZ', 'fuzzy', { withWrapper: '<i>{?}</i>', withScore: true });
// { score: 1.74, wrapped: '<i>f</i>u<i>zz</i>y' }
match('fZZ', 'fuzzy', { withWrapper: w => `<b>${w}</b>` });
// { score: 1, wrapped: '<b>f</b>u<b>zz</b>y' }
match('fZZ', 'fuzzy', { withRanges: true });
// { score: 1, ranges: [{begin: 0, end: 0}, {begin: 2, end: 3}]}Match in list of strings
import { match } from 'fuzzy-tools';
// or
// import { matchList } from 'fuzzy-tools';
match('fzz', ['fu', 'fuzza'], { withScore: true })
// { score: 1.74, matches: {1: {score: 1.74, original: 'fuzza', index: 1}} }
match('fzz', { v1: 'fu', v2: 'fuzza' })
// { score: 1, matches: {v2: {score: 1, original: 'fuzza', index: 'v2'}} }
match('fzz', [{ value: 'fuzza', rate: 0.75 }, { value: 'fuzzy', rate: 0.10 }], { withScore: true })
// {
// score: 2.3708148148148145,
// matches: {
// 0: { score: 2.3708148148148145, original: 'fuzza', rate: 0.75, index: 0 },
// 1: { score: 17.78111111111111, original: 'fuzzy', rate: 0.10, index: 1 }
// }
// }
// score = min(2.3708148148148145, 17.78111111111111)
// the same but more compact
match('fzz', ['fuzza', 'fuzzy'], { rates: [0.75, 0.10], withScore: true })
// {
// score: 2.3708148148148145,
// matches: {
// 0: { score: 2.3708148148148145, original: 'fuzza', rate: 0.75, index: 0 },
// 1: { score: 17.78111111111111, original: 'fuzzy', rate: 0.10, index: 1 }
// }
// }
// score = min(2.3708148148148145, 17.78111111111111)
match('fzz', {v1: { value: 'fuzza', rate: 0.75 }, v2: { value: 'fuzzy', rate: 0.10 } }, { withScore: true })
// {
// score: 2.3708148148148145,
// matches: {
// v1: { score: 2.3708148148148145, original: 'fuzza', rate: 0.75, index: 'v1' },
// v2: { score: 17.78111111111111, original: 'fuzzy', rate: 0.10, index: 'v2' }
// }
// }
// score = min(2.3708148148148145, 17.78111111111111)
// the same but more compact
match('fzz', { v1: 'fuzza', v2: 'fuzzy' }, { rates: { v1: 0.75, v2: 0.10 }, withScore: true })
// {
// score: 2.3708148148148145,
// matches: {
// v1: { score: 2.3708148148148145, original: 'fuzza', rate: 0.75, index: 'v1' },
// v2: { score: 17.78111111111111, original: 'fuzzy', rate: 0.10, index: 'v2' }
// }
// }
// score = min(2.3708148148148145, 17.78111111111111)
match('fZZ', ['fuzzy'], { caseSensitive: true });
// null
match('fZZ', ['fuzzy'], { withWrapper: '<i>{?}</i>' });
// { score: 1, matches: {0: {score: 1, index: 0, wrapped: '<i>f</i>u<i>zz</i>y', original: 'fuzzy'}}}
match('fZZ', ['fuzzy'], { withWrapper: w => `<b>${w}</b>`, withScore: true });
// { score: 1.74, matches: {0: {score: 1.74, index: 0, wrapped: '<b>f</b>u<b>zz</b>y', original: 'fuzzy'}}}
match('fZZ', ['fuzzy'], { withRanges: true });
// { score: 1.74, matches: {0: {score: 1.74, index: 0, ranges: [{begin: 0, end: 0}, {begin: 2, end: 3}], original: 'fuzzy'}}}Filter function
filter(mask: String | Array<String>, items: Array, options: Object): Array - returns list of matched items.
Tests cases
Arguments
| args | type | default | note |
| - | - | - | - |
| mask | String or Array<String> | no | what you want to find ('fzz') |
| items | Array | no | items list |
| options | Object | {...} | additional options (see below) |
mask- read more in Match functionitems- Array of Strings
- Array of Objects
options| option | type | default | description | | - | - | - |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |
extract| String, Array, Object or Function |null| Required to filter Array of Objects. Specifies which field(s) to match against. | | | | | String: field name — extracts that field and matches against the mask | | | | | Array: field names — each field is matched with rate1| | | | | Object:{ fieldName: rateNumber }— field name is key, rate is value | | | | | Function(item): String or Array — returns String, Array of Strings, Object of Strings, or Array of{ value: String, rate: Number }| |itemWrapper| Function |null| Function(item, matchResult, context): any — transforms each matched item before pushing into result. If it returns a falsy value (false,null,undefined,'',0), the item is skipped. | | | | | context:{ index: Number, result: Array }| |caseSensitive| Boolean |false| Whentrue, character case matters (e.g.'FZZ'will NOT match'fuzzy'). Whenfalse, case is ignored. | |withScore| Boolean |false| Whentrue,scoreis computed for matched strings. Whenfalse,scoreis always1. | |withWrapper| String or Function |null| When provided, match result for each item containswrapped— the extracted value with matched parts highlighted. | | | | | String: template with{?}placeholder (e.g.'<b>{?}</b>') | | | | | Function(word: String): String: (e.g.(word) => '<b>'+word+'</b>') | |withRanges| Boolean |false| Whentrue, match result for each item containsranges— array of{ begin: Number, end: Number }. |
Filter strings list
import { filter } from 'fuzzy-tools';
const data = ['fuzzy', 'fazzy', 'furry', 'funny', 'fuuuuuzzer'];
filter('fZZ', data);
// ['fuzzy', 'fazzy', 'fuuuuuzzer'];
filter('fZZ', data, { itemWrapper: item => item.toUpperCase() });
// ['FUZZY', 'FAZZY', 'FUUUUUZZER'];
filter('fZZ', data, { withWrapper: '<{?}>', itemWrapper: (item, match) => match.wrapped });
// ['<f>u<zz>y', '<f>a<zz>y', '<f>uuuuu<zz>er'];
filter('fZZ', data, { extract: (item) => item.slice(0, 4) });
// ['fuzzy', 'fazzy'];
filter('FZZ', data, { caseSensitive: true });
// [];
filter('FZZ', data, { caseSensitive: true, extract: item => item.toUpperCase() });
// ['fuzzy', 'fazzy', 'fuuuuuzzer'];Filter objects list
import { filter } from 'fuzzy-tools';
const data = [{ v: 'fuzzy' }, { v: 'funny' }, { v: 'fuuuuuzzer'}];
filter('fZZ', data, { extract: 'v' });
// [{ v: 'fuzzy' }, { v: 'fuuuuuzzer'}];
filter('fZZ', data, { extract: 'vvv' });
// [];
filter('fZZ', data, { extract: 'v', itemWrapper: item => item.v });
// ['fuzzy', 'fuuuuuzzer'];
filter('fZZ', data, { extract: 'v', withWrapper: '<{?}>', itemWrapper: (item, m) => m.wrapped });
// ['<f>u<zz>y', '<f>uuuuu<zz>er'];
// if extract contains more than 1 field, then result of matching will contain matches array
filter('fZZ', data, {
extract: ['v', 'v2'],
withWrapper: '<{?}>',
itemWrapper: (item, m) => m.matches[0].wrapped
});
// ['<f>u<zz>y', '<f>uuuuu<zz>er'];Benchmark results
Compare next libraries
- fuzzy-tools (this)
- fast-fuzzy (https://www.npmjs.com/package/fast-fuzzy)
- fuzzy (https://www.npmjs.com/package/fuzzy)
- fuzzyjs (https://www.npmjs.com/package/fuzzyjs)
- fuzzy.js (https://www.npmjs.com/package/fuzzy.js)
- fuse.js (https://www.npmjs.com/package/fuse.js)
- fuzzy-search (https://www.npmjs.com/package/fuzzy-search)
Methodology
- generate pairs mask (random length) and string (255 or 5000 chars length). 5000 pairs.
- call match function from each library
- compare results
Results
It is more real case - 5000 items with 255 length string

It is test case - 5000 items with 2000 length string. And it is not mistake, it is real results.

Benchmark project
https://codesandbox.io/s/fuzzytoolsbenchmarkexample-t9fk1?file=/src/index.js
