Skip to content

Commit

Permalink
Update README and docs
Browse files Browse the repository at this point in the history
  • Loading branch information
swarn committed Jan 29, 2023
1 parent 36df2bf commit 2d018a5
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 34 deletions.
66 changes: 44 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ A Lua port of [fzy](https://github.com/jhawthorn/fzy)'s fuzzy string matching
algorithm. This includes both a pure Lua implementation and a compiled C
implementation with a Lua wrapper.

## Why

## What does it do?

From the original `fzy`:

Expand All @@ -17,46 +18,67 @@ From the original `fzy`:
> consecutive letters and starts of words. This allows matching using acronyms
> or different parts of the path.
## Install
Let's give it a try:

``` sh
luarocks install fzy
``` lua
local fzy = require('fzy')
local haystacks = {'cab', 'ant/bat/cat', 'ant/bat/ace'}
local needle = 'abc'
local result = fzy.filter(needle, haystacks)
```

Or, just download a copy of `fzy_lua.lua` and drop it in your project.
Here is what `result` looks like:
``` lua
{
{2, {1, 5, 9}, 2.63},
{3, {1, 5, 10}, 1.725}
}
```
Which tells us:

## Usage
- We get a result from `filter` for each match. The string at index 1, `cab`,
did not match the query, because `abc` is not a subsequence of `cab`.

`score(needle, haystack)`
- The string at index 2 matched with a score of 2.63. It matched characters at
the following positions:

``` lua
local fzy = require('fzy')
ant/bat/cat
^ ^ ^
1 5 9

fzy.score("amuser", "app/models/user.rb") -- 5.595
fzy.score("amuser", "app/models/customer.rb") -- 3.655
```
- The string at index 3 matched with a score of 1.725. It matched characters at
the following positions:

`positions(needle, haystack)`
ant/bat/ace
^ ^ ^
1 5 10

``` lua
fzy.positions("amuser", "app/models/user.rb") -- { 1, 5, 12, 13, 14, 15 }
-- ^ ^ ^^^^
fzy.positions("amuser", "app/models/customer.rb") -- { 1, 5, 13, 14, 18, 19 }
-- ^ ^ ^^ ^^
This match has a lower score than the previous string because `fzy` tries to
find what you intended, and one way it does that is by favoring matches at
the beginning of words.


## Install

``` sh
luarocks install fzy
```

NB: `score` and `positions` should only be called with a `needle` that is a
subsequence of the `haystack`, which you can check with the `has_match`
function.
Or, just download a copy of `fzy_lua.lua` and drop it in your project.


## Usage

See [the docs](docs/fzy.md).

See [the docs](docs/fzy.md) for more information.

## Testing

```sh
busted test/test.lua
```


## Thanks

John Hawthorn wrote the original `fzy`. The native implementation here is
Expand Down
67 changes: 55 additions & 12 deletions docs/fzy.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ A Lua module for the [fzy][] fuzzy string matching algorithm.
| function | description |
|-------------------------------|---------------------------------------------------|
| `has_match(needle, haystack)` | Check if `needle` is a subsequence of `haystack`. |
| `score(needle, haystack)` | Compute a matching score. |
| `score(needle, haystack)` | Compute a score for a matching `needle`. |
| `positions(needle, haystack)` | Compute the locations where fzy matches a string. |
| `filter(needle, haystacks)` | Match many strings at once. |

Expand Down Expand Up @@ -41,29 +41,44 @@ false

### `fzy.score(needle, haystack[, case_sensitive])`

Get a score for a needle that matches a haystack.

> **Warning**
> The `needle` must be a subsequence of the `haystack`! This is verified by
> using the `has_match` function. These functions are split for performance
> reasons. Either use `has_match` before each use of `score`, or use the
> `filter` function to do it automatically.
**Parameters**
* **needle** (*string*): must be a subequence of `haystack`, or the result is
* **needle** (*string*): must be a subsequence of `haystack`, or the result is
undefined.
* **haystack** (*string*)
* **case_sensitive** (*bool, optional*) – defaults to false

**Returns**
* *number*, where higher numbers indicate better matches.

``` lua
> fzy.score("amuser", "app/models/user.rb")
5.595
> fzy.score("amuser", "app/models/customer.rb")
3.655
```


### `fzy.positions(needle, haystack[, case_sensitive])`

Determine where each character of the `needle` is matched to the `haystack` in
the optimal match.

``` lua
> p, s = fzy.positions("ab", "*a*b*b")
> require'pl.pretty'.write(p, '')
{2,4}
```
> **Warning**
> The `needle` must be a subsequence of the `haystack`! This is verified by
> using the `has_match` function. These functions are split for performance
> reasons. Either use `has_match` before each use of `positions`, or use the
> `filter` function to do it automatically.
**Parameters**
* **needle** (*string*): must be a subequence of `haystack`, or the result is
* **needle** (*string*): must be a subsequence of `haystack`, or the result is
undefined.
* **haystack** (*string*)
* **case_sensitive** (*bool, optional*) – defaults to false
Expand All @@ -73,6 +88,13 @@ the optimal match.
character of `needle` in `haystack`.
* **score** (*number*): the same matching score returned by `score`

``` lua
> fzy.positions("amuser", "app/models/user.rb") -- { 1, 5, 12, 13, 14, 15 }
-- ^ ^ ^^^^
> fzy.positions("amuser", "app/models/customer.rb") -- { 1, 5, 13, 14, 18, 19 }
-- ^ ^ ^^ ^^
```


### `fzy.filter(needle, haystacks[, case_sensitive])`

Expand All @@ -89,24 +111,32 @@ iterating over the `haystacks` and calling those functions for each string.
**Parameters**
* **needle** (*string*): unlike the other functions, the `needle` need not be
a subsequence of any of the strings in the `haystack`.
* **haystack** (*{string, ...}*)
* **haystacks** (*{string, ...}*)
* **case_sensitive** (*bool, optional*) – defaults to false

**Returns**
* *{{idx, positions, score}, ...}*, an array with one entry per matching line
in `haystacks`, each entry giving the index of the line in `haystacks` as
well as the equivalent to the return value of `positions` for that line.

``` lua
> haystacks = {'cab', 'ant/bat/cat', 'ant/bat/ace'}
> needle = 'abc'
> fzy.filter(needle, haystacks)
-- { {2, {1, 5, 9}, 2.63}, {3, {1, 5, 10}, 1.725} }
```


## Special Values

`fzy.get_score_min()`: The lowest value returned by `score`, which is only returned
for an empty `needle`, or `haystack` larger than than `get_max_length`.
for an empty `needle`, or a `haystack` longer than than `get_max_length`.

`fzy.get_score_max()`: The score returned for exact matches. This is the
highest possible score.

`fzy.get_max_length()`: The maximum size for which `fzy` will evaluate scores.
`fzy.get_max_length()`: The maximum length of a `haystack` for which `fzy` will
evaluate scores.

`fzy.get_score_floor()`: For matches that don't return `get_score_min`, their
score will be greater than than this value.
Expand All @@ -118,10 +148,23 @@ score will be less than this value.
implementation, "lua" or "native".


### Implementations
## Implementations

The lua implementation of fzy is in `fzy_lua`, and the C implementation is in
`fzy_native`. When you `require('fzy')`, it automatically loads the native
version. If that fails, it will fall back on the lua version. This is transparent;
in either case, all functions will be available as `fzy.*`.


## FAQ

This library isn't popular enough to have *frequently* asked questions, but this
has come up:

> Why not just call `has_match` inside `score` and `positions`?
This implementation of `fzy` was originally written for use in text editors.
When you visually update the results of fuzzy finding across a large codebase
with every keypress, every nanosecond counts. Those applications want to
separate the matching, scoring, and position highlighting functions. Unless you
know you have reason to do otherwise, I recommend using the `filter` function.

0 comments on commit 2d018a5

Please sign in to comment.