Skip to content

Commit b1a177b

Browse files
authored
Merge pull request #42 from atom-ide-community/independent-tree
2 parents bd467dd + a47ed63 commit b1a177b

8 files changed

+303
-69
lines changed

README.md

+82-8
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ Fast fuzzy-search - the native replacement for `fuzzaldrin-plus`
66
* Fuzzaldrin plus is an awesome library that provides fuzzy-search that is more targeted towards filenames.
77
* Fuzzaldrin-plus-fast is a rewrite of the library in native C++ to make it fast. The goal is to make it a few hundred millisecond filter times for a dataset with 1M entries. This performance is helpful in Atom's fuzzy finder to open files from large projects such as Chrome/Mozilla.
88

9-
Fuzzaldrin-plus-fast also provides an additional `filterTree` function which allows to fuzzy filter text in nested tree-like objects.
9+
### Extra featuers
10+
Fuzzaldrin-plus-fast:
11+
- provides `filterTree` function which allows to fuzzy filter text in nested tree-like objects.
12+
- allows setting the candidates only once using `ArrayFilterer` and `TreeFilterer` classes, and then, perform `filter` multiple times. This is much more efficient than calling the `filter` or `filterTree` functions directly every time.
1013

1114
# How performance is improved?
1215
Fuzzaldrin-plus-fast achieves 10x-20x performance improvement over Fuzzaldrin plus for chromium project with 300K files. This high performance is achieved using the following techniques.
@@ -63,6 +66,41 @@ candidates = [
6366
results = filter(candidates, 'me', {key: 'name'}) // [{name: 'Me', id: 2}, {name: 'Maybe', id: 3}]
6467
```
6568

69+
**Performance Note**: use `ArrayFilterer` class if you call the `filter` function multiple times on a certain set of candidates. `filter` internally uses this class, however, in each call it sets the candidates from scratch which can slow down the process.
70+
71+
### ArrayFilterer class
72+
73+
ArrayFilterer is a class that allows to set the `candidates` only once and perform filtering on them multiple times. This is much more efficient than calling the `filter` function directly.
74+
```typescript
75+
export class ArrayFilterer<T> {
76+
constructor()
77+
78+
/** The method to set the candidates that are going to be filtered
79+
* @param candidates An array of tree objects.
80+
* @param dataKey (optional) if `candidates` is an array of objects, pass the key in the object which holds the data. dataKey can be the options object passed to `filter` method (but this is deprecated).
81+
*/
82+
setCandidates<T>(candidates: Array<T>, dataKey?: string): void
83+
84+
/** The method to perform the filtering on the already set candidates
85+
* @param query A string query to match each candidate against.
86+
* @param options options
87+
* @return returns an array of candidates sorted by best match against the query.
88+
*/
89+
filter(query: string, options: IFilterOptions<T>): Array<T>
90+
}
91+
```
92+
93+
Example:
94+
```Javascript
95+
const { ArrayFilterer } = require('fuzzaldrin-plus-fast')
96+
97+
const arrayFilterer = new ArrayFilterer()
98+
arrayFilterer.setCandidates(['Call', 'Me', 'Maybe']) // set candidates only once
99+
// call filter multiple times
100+
arrayFilterer.filter('me')
101+
arrayFilterer.filter('all')
102+
```
103+
66104
### filterTree(candidates, query, dataKey, childrenKey, options = {})
67105

68106
Sort and filter the given Tree candidates by matching them against the given query.
@@ -96,6 +134,49 @@ const candidates = [
96134
results = filter(candidates, 'hello', {key: 'name'}) // [ { data: 'hello', index: 2, level: 0 }, { data: 'helloworld', index: 0, level: 0 } ]
97135
```
98136

137+
**Performance Note**: use `TreeFilterer` class if you call the `filterTree` function multiple times on a certain set of candidates. `filterTree` internally uses this class, however, in each call it sets the candidates from scratch which can slow down the process.
138+
139+
### TreeFilterer class
140+
`TreeFilterer` is a class that allows to set the `candidates` only once and perform filtering on them multiple times. This is much more efficient than calling the `filterTree` function directly.
141+
142+
```typescript
143+
export class TreeFilterer<T> {
144+
constructor()
145+
146+
/** The method to set the candidates that are going to be filtered
147+
* @param candidates An array of tree objects.
148+
* @param dataKey the key of the object (and its children) which holds the data (defaults to `"data"`)
149+
* @param childrenKey the key of the object (and its children) which hold the children (defaults to `"children"`)
150+
*/
151+
setCandidates<T>(candidates: Array<T>, dataKey?: string, childrenKey?: string): void
152+
153+
/** The method to perform the filtering on the already set candidates
154+
* @param query A string query to match each candidate against.
155+
* @param options options
156+
* @return An array of candidate objects in form of `{data, index, level}` sorted by best match against the query. Each objects has the address of the object in the tree using `index` and `level`.
157+
*/
158+
filter(query: string, options: IFilterOptions<object>): TreeFilterResult[]
159+
}
160+
```
161+
162+
Example:
163+
```Javascript
164+
const { TreeFilterer } = require('fuzzaldrin-plus-fast')
165+
166+
const arrayFilterer = new TreeFilterer()
167+
168+
const candidates = [
169+
{data: "bye1", children: [{data: "hello"}]},
170+
{data: "Bye2", children: [{data: "_bye4"}, {data: "hel"}]},
171+
{data: "eye"},
172+
]
173+
arrayFilterer.setCandidates(candidates, "data", "children") // set candidates only once
174+
175+
// call filter multiple times
176+
arrayFilterer.filter('hello')
177+
arrayFilterer.filter('bye')
178+
```
179+
99180
### score(string, query, options = {})
100181

101182
Score the given string against the given query.
@@ -155,13 +236,6 @@ In all the above functions, you can pass an optional object with the following k
155236
}
156237
```
157238

158-
### New()
159-
Initializes the native binding
160-
```js
161-
const { New } = require('fuzzaldrin-plus-fast')
162-
New()
163-
```
164-
165239
# Info for Developers
166240
## How to release the package to npm?
167241

fuzzaldrin.js

+46-17
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,27 @@ function parseOptions(options, query) {
1616
return options
1717
}
1818

19-
class FuzzaldrinPlusFast {
19+
/** Array Filter */
20+
21+
export class ArrayFilterer {
2022
constructor() {
2123
this.obj = new binding.Fuzzaldrin()
2224
}
2325

24-
setCandidates(candidates, options = {}) {
26+
setCandidates(candidates, dataKey = undefined) {
2527
this.candidates = candidates
26-
if (options.key)
27-
candidates = candidates.map((item) => item[options.key])
28-
return this.obj.setCandidates(candidates)
28+
29+
if (dataKey) {
30+
if (typeof dataKey == "string") {
31+
candidates = candidates.map((item) => item[dataKey])
32+
}
33+
// @deprecated pass the key as the second argument as a string
34+
else if (dataKey.key) { // an object (options) containing the key
35+
candidates = candidates.map((item) => item[dataKey.key])
36+
}
37+
}
38+
39+
return this.obj.setArrayFiltererCandidates(candidates)
2940
}
3041

3142
filter(query, options = {}) {
@@ -34,30 +45,46 @@ class FuzzaldrinPlusFast {
3445
Boolean(options.usePathScoring), Boolean(options.useExtensionBonus))
3546
return res.map((ind) => this.candidates[ind])
3647
}
37-
38-
filterTree(candidatesTrees, query, dataKey = "data", childrenKey = "children", options = {}) {
39-
options = parseOptions(options)
40-
return this.obj.filterTree(candidatesTrees, query, dataKey, childrenKey, options.maxResults,
41-
Boolean(options.usePathScoring), Boolean(options.useExtensionBonus))
42-
}
4348
}
4449

45-
export const New = () => new FuzzaldrinPlusFast()
50+
/**
51+
* @deprecated use ArrayFilterer or TreeFilterer instead class instead
52+
*/
53+
export const New = () => new ArrayFilterer()
4654

4755
export function filter (candidates, query, options = {}) {
4856
if (!candidates || !query)
4957
return []
50-
const obj = new FuzzaldrinPlusFast()
51-
obj.setCandidates(candidates, options)
52-
return obj.filter(query, options)
58+
const arrayFilterer = new ArrayFilterer()
59+
arrayFilterer.setCandidates(candidates, options)
60+
return arrayFilterer.filter(query, options)
5361
}
5462

63+
/** Tree Filter */
64+
65+
export class TreeFilterer {
66+
constructor() {
67+
this.obj = new binding.Fuzzaldrin()
68+
}
69+
70+
setCandidates(candidates, dataKey = "data", childrenKey = "children") {
71+
this.candidates = candidates
72+
return this.obj.setTreeFiltererCandidates(candidates, dataKey, childrenKey)
73+
}
74+
75+
filter(query, options = {}) {
76+
options = parseOptions(options)
77+
return this.obj.filterTree(query, options.maxResults,
78+
Boolean(options.usePathScoring), Boolean(options.useExtensionBonus))
79+
}
80+
}
5581

5682
export function filterTree(candidatesTrees, query, dataKey = "data", childrenKey = "children", options = {}) {
5783
if (!candidatesTrees || !query)
5884
return []
59-
const obj = new FuzzaldrinPlusFast()
60-
return obj.filterTree(candidatesTrees, query, dataKey, childrenKey, options)
85+
const treeFilterer = new TreeFilterer()
86+
treeFilterer.setCandidates(candidatesTrees, dataKey, childrenKey)
87+
return treeFilterer.filter(query, options)
6188
}
6289

6390
export function score (candidate, query, options = {}) {
@@ -68,6 +95,8 @@ export function score (candidate, query, options = {}) {
6895
Boolean(options.usePathScoring), Boolean(options.useExtensionBonus))
6996
}
7097

98+
/** Other functions */
99+
71100
export function match (string, query, options = {}) {
72101
if (!string || !query)
73102
return []

spec/array-filterer-spec.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const { ArrayFilterer } = require('../fuzzaldrin-dist')
2+
3+
describe('ArrayFilterer', function () {
4+
it('is possible to set candidates only once and filter multiple times', function(){
5+
const arrayFilterer = new ArrayFilterer()
6+
arrayFilterer.setCandidates(['Call', 'Me', 'Maybe']) // set candidates only once
7+
// call filter multiple times
8+
expect(arrayFilterer.filter('me')).toEqual(['Me', 'Maybe'])
9+
expect(arrayFilterer.filter('all')).toEqual(['Call'])
10+
})
11+
});

spec/tree-filterer-spec.js

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
const { TreeFilterer } = require("../fuzzaldrin-dist")
2+
const DeepEqual = require('deep-equal');
3+
4+
describe("ArrayFilterer", function() {
5+
it('is possible to set candidates only once and filter multiple times', function() {
6+
7+
const arrayFilterer = new TreeFilterer()
8+
9+
const candidates = [
10+
{data: "bye1", children: [{data: "hello"}]},
11+
{data: "Bye2", children: [{data: "_bye4"}, {data: "hel"}]},
12+
{data: "eye"},
13+
]
14+
arrayFilterer.setCandidates(candidates, "data", "children") // set candidates only once
15+
16+
17+
// call filter multiple times
18+
19+
expect(DeepEqual(
20+
arrayFilterer.filter('hello'),
21+
[ { data: 'hello', index: 0, level: 1 } ]
22+
)).toBe(true)
23+
24+
25+
expect(DeepEqual(
26+
arrayFilterer.filter('bye'),
27+
[
28+
{ data: 'bye1', index: 0, level: 0 },
29+
{ data: '_bye4', index: 0, level: 1 },
30+
{ data: 'Bye2', index: 1, level: 0 }
31+
]
32+
)).toBe(true)
33+
34+
35+
})
36+
})

0 commit comments

Comments
 (0)