From 64f273a6ae63b8050853b83a3ed8e5036be727bf Mon Sep 17 00:00:00 2001 From: Timo Hocker Date: Mon, 29 Jun 2020 13:08:23 +0200 Subject: [PATCH] search index function --- Jenkinsfile | 2 +- README.md | 42 +++++++++++++++++++++++++++++++++++-- index.js | 51 +++++++++++++++++++++++++++++++++++--------- test/index.js | 58 ++++++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 130 insertions(+), 23 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index e75c28a..4f122e4 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -5,7 +5,7 @@ pipeline { VERSION = VersionNumber([ versionNumberString: '${BUILDS_ALL_TIME}', - versionPrefix: '1.5.', + versionPrefix: '1.6.', worstResultForIncrement: 'SUCCESS' ]) } diff --git a/README.md b/README.md index 1fbbd50..7285303 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # @sapphirecode/utilities -version: 1.5.x +version: 1.6.x small utility functions to make much needed features easier to work with @@ -118,13 +118,51 @@ const result = util.recursive_filter( [ { filter: /foo bar/iu, - fields: ['name', 'name_2'] // fields will be joined with a space in between + field: ['name', 'name_2'] // fields will be joined with a space in between // {name: 'foo', name_2: 'bar'} will become 'foo bar' } ] ); ``` +to improve the performance of recursive_filter you can generate a search index before running the function + +```js +const to_filter = [ + {name: 'include_foo'}, + { + name: 'include_bar', + children: [{name: 'foo'}, {name: 'bar'}], + }, + { + name: 'baz', + children: [{name: 'include_foo'}, {name: 'bar'}], + }, + { + name: 'barbaz', + children: [{name: 'foo'}, {name: 'bar'}], + }, +]; + +util.filter_index( + to_filter, // object to index + 'name', // fields to search on (array or string depending on the amount of fields) + 'children', // field where the children are stored (default: children) + 'search_index' // field where the index should be stored (default: search_index) +) + +const filter = { + field: 'search_index', // filter based on the index field instead + filter: /^include_.*/iu, +}; + +const result = util.recursive_filter( + to_filter, + [filter], + 'children' +); +``` + ## License MIT © Timo Hocker diff --git a/index.js b/index.js index b74bdd0..21c47d5 100644 --- a/index.js +++ b/index.js @@ -78,11 +78,20 @@ function is_nil (obj) { || (typeof obj === 'number' && isNaN (obj)); } +function to_search_string (element, field) { + return Array.isArray (field) + ? field.map ((f) => element[f]) + .filter ((v) => typeof v !== 'undefined') + .join (' ') + : element[field]; +} + /** * filter nested objects * * @param {Array} input - * @param {Array<{field: string, filter: RegExp}>} filters + * @param {Array<{field: string|string[], filter: RegExp}>} filters + * @param {string} children_key field where children are stored * @returns {Array} filtered data */ function recursive_filter (input, filters, children_key = 'children') { @@ -93,14 +102,7 @@ function recursive_filter (input, filters, children_key = 'children') { data[i] = e; let match = true; for (const filter of filters) { - const is_multi_filter - = Array.isArray (filter.fields); - - const search_str = is_multi_filter - ? filter.fields.map ((f) => e[f]) - .filter ((v) => typeof v !== 'undefined') - .join (' ') - : e[filter.field]; + const search_str = to_search_string (e, filter.field); if (!filter.filter.test (search_str)) { match = false; @@ -125,11 +127,40 @@ function recursive_filter (input, filters, children_key = 'children') { return filtered; } +/** + * create a search index on an object to speed up the recursive_filter function + * + * @param {Array} input input object + * @param {string|string[]} field field or fields to index + * @param {string} children_key field where children are stored + * @param {string} index_field field to save index in + */ +function filter_index ( + input, + field, + children_key = 'children', + index_field = 'search_index' +) { + for (const e of input) { + let search_str = ''; + if (Array.isArray (e[children_key])) { + filter_index (e[children_key], field, children_key, index_field); + search_str += e[children_key] + .map ((v) => v[index_field]) + .filter ((v) => typeof v !== 'undefined') + .join (' '); + } + search_str += to_search_string (e, field); + e[index_field] = search_str; + } +} + module.exports = { truncate_decimal, try_parse_json, copy_object, run_regex, is_nil, - recursive_filter + recursive_filter, + filter_index }; diff --git a/test/index.js b/test/index.js index dbd6b23..ba5ae97 100644 --- a/test/index.js +++ b/test/index.js @@ -179,7 +179,7 @@ test ('recursive filter multifield', (t) => { ]; const filtered = [ { name: 'foo', f: 'include' } ]; const filter = { - fields: [ + field: [ 'name', 'f' ], @@ -189,15 +189,6 @@ test ('recursive filter multifield', (t) => { t.deepEqual (filtered, result); }); -test ('recursive filter multifield input error', (t) => { - t.notThrows (() => { - util.recursive_filter ( - [ { foo: 'bar' } ], - [ { fields: '', field: 'foo', filter: /a/u } ] - ); - }); -}); - test ('recursive filter undefined multifield', (t) => { const res = util.recursive_filter ( [ { foo: 'bar' } ], @@ -213,3 +204,50 @@ test ('recursive filter undefined multifield', (t) => { ); t.deepEqual (res, []); }); + +test ('recursive filter multifield index', (t) => { + const raw = [ + { name: 'foo', f: 'include' }, + { + name: 'include_bar', + children: [ + { name: 'foo' }, + { name: 'bar' } + ] + }, + { + name: 'baz', + children: [ + { name: 'include_foo' }, + { name: 'bar' } + ] + }, + { + name: 'barbaz', + children: [ + { name: 'foo' }, + { name: 'bar' } + ] + } + ]; + + const filtered = [ + { + name: 'foo', + f: 'include', + search_index: 'foo include' + } + ]; + + util.filter_index (raw, [ + 'name', + 'f' + ]); + + const filter = { + field: 'search_index', + filter: /foo include/ui + }; + const result = util.recursive_filter (raw, [ filter ]); + t.deepEqual (filtered, result); +});