fz
Version:
Simple, fast, fuzzy string searching.
114 lines (70 loc) • 4.51 kB
Markdown
# ·𝕗𝕫·
🔍 *Simple, fast, fuzzy string searching.*
```js
fz('fuzzy', 'fz'); // true
```
## Motivation
A recent project I worked on required building out a fuzzy search interaction. Surveying the already available options led me to discover that the majority of existing implementations go with one of two techniques:
1. old-school `for`/`while` loops, checking character-by-character for matches
2. auto-generated regular expressions, created from the input query string
While benchmarking these approaches, it became clear that both of these techniques have merit, but under different circumstances. For example, given a use case where the query input remains relatively static between searches, the `RegExp` approach wins, hands down:
<img src='https://raw.githubusercontent.com/jmar777/fz/master/bench/charts/static-queries.png' alt='while loops vs. RegExp for static queries' width='80%' />
But when the conditions change such that the query input is highly dynamic and frequently changes between searches, the `while` loop actually fares better (primarily due to the underutilized cost of `RegExp` instantiation):
<img src='https://raw.githubusercontent.com/jmar777/fz/master/bench/charts/dynamic-queries.png' alt='while loops vs. RegExp for dynamic queries' width='80%' />
While not everyone requires a solution that tackles both dynamic and static search inputs, it seemed like a useful feature to bring to the table. Instead of choosing one of these techniques over the other, `fz` simply defaults to `while` loops, and if it detects that the same query is being used across successive calls, it internally optimizes to a `RegExp` approach.
While this internal optimization is a trivial one, the result is a solution that performs competitively (but not identically) with the more efficient solution for both of these use cases. For comparison, here is how `fz` stacks up for static query inputs:
<img src='https://raw.githubusercontent.com/jmar777/fz/master/bench/charts/static-queries-with-fz.png' alt='while loops vs. RegExp vs. fz for static queries' width='80%' />
And here it is for dynamic query inputs:
<img src='https://raw.githubusercontent.com/jmar777/fz/master/bench/charts/dynamic-queries-with-fz.png' alt='while loops vs. RegExp vs. fz for dynamic queries' width='80%' />
As can be seen above, `fz` does a decent job of keeping up for both static and dynamic input queries. If you're looking for a fuzzy search utility that removes some of the guesswork on which use case to optimize for, then `fz` might be a good option for you.
## Getting Started
### Installing `fz`
```sh
$ npm install fz --save
```
## API
### `fz(candidate, query)`
Performs a fuzzy search against `candidate`, using `query` as the search criteria.
`candidate` will be considered a match for `query` using the following criteria:
* Every character in `query` must have a match in `candidate`.
* The matching characters must appear in the same order.
* `candidate` *may* contain any number of non-matching characters, including in-between the matching characters.
* CASING is IgNoRed.
Alternatively, if you think more in terms of *regular expressions*, then `fz('foobar', 'fb')` will behave similarly to `/f.*b.*/i.test('foobar')` (with special handling for escape sequences and other special characters).
Please see the examples below for more clarification.
**Arguments:**
* `candidate` : *`String (Required)`*
The string value to search against.
* `query` : *`String (Required)`*
The fuzzy search criteria to use when determining whether or not `candidate` is a match.
**Returns:** `isMatch` : *`Boolean`*
If `candidate` is a match for `query`, then `true`, otherwise `false`.
**Examples:**
```javascript
const fz = require('fz');
fz('Wombat Developers Union', 'wdu') // true
fz('ninja pumpkin mutants', 'NPM') // true
fz('nebulus plasma muffin', 'mpn') // false
fz('foo', 'O') // true
fz('bar', 'bart') // false
fz('???', '') // true
fz('', '???') // false
fz('', '') // true
```
## Contributing
Pull requests are welcome, but I recommend filing an issue to discuss feature proposals first.
To get started:
1. Install dev dependencies:
```sh
$ npm install
```
2. To run the test suite:
```sh
$ npm test
```
3. To run the bench suite:
```sh
$ npm run bench
```
----
*A special shout-out and "thank you" goes to [Diego Rodríguez Baquero](https://github.com/DiegoRBaquero) for being awesome enough to hand over the `fz` package name on npm!*