diff --git a/README.md b/README.md
index 6a84396..56a7e88 100644
--- a/README.md
+++ b/README.md
@@ -3,23 +3,21 @@
-
-
-__Note:__ This feature is actually not enabled by default. Read here how to enable. + +#### Lexical Pre-Scored Dictionary / Context-based Map +The index consists of an in-memory pre-scored dictionary as its base. The biggest complexity of these algorithm occurs during the calculation of intersections. As a consequence each additional term in the query has a significant potential to increase complexity. A contextual map comes into play __when the query contains more than 1 term__ and increase effect for each additional term by cutting down the complexity for the intersection calculations. Instead of an increase, the complexity is lowered for each additional term. The contextual index itself is also based on a pre-scored dictionary and follows a memory-friendly strategy. -#### Compare BulkSearch vs. FlexSearch +
Type | +Complexity | +
Each single term query: | +1 | +
Lexical Pre-Scored Dictionary (Solo): | +TERM_COUNT * TERM_MATCHES | +
Lexical Pre-Scored Dictionary + Context-based Map: | +TERM_MATCHES / TERM_COUNT | +
- | BulkSearch - | FlexSearch + | + | BulkSearch | +FlexSearch |
Access | @@ -435,7 +462,7 @@ __Note:__ This feature is actually not enabled by default. ReadWeaks |
|
-
|
+
|
|
The configuration profile. Choose your preferation. @@ -1629,7 +1660,7 @@ FlexSearch ist highly customizable. Make use of the the right | depth |
false - {number:0-9} + {number} |
Enable/Disable contextual indexing and also sets contextual distance of relevance. | threshold |
false - {number:0-9} + {number} |
Enable/Disable the threshold of minimum relevance all results should have. Note: It is also possible to set a lower threshold for indexing and pass a higher value when calling index.search(options). |
resolution | ++ {number} + | +Sets the scoring resolution (default: 9). | +|||
stemmer |
@@ -1664,7 +1703,7 @@ FlexSearch ist highly customizable. Make use of the the right | ||||
rtl |
+ rtl |
true false @@ -1966,6 +2005,8 @@ The required memory for the index depends on several options: |
=d)for(g=f._ctx[p]||(f._ctx[p]=t()),p=this.c[p]||(this.c[p]= -C(10-(d||0))),k=e-B,m=e+B+1,0>k&&(k=0),m>q&&(m=q);kc&&(g=g.slice(0,c)));e=g}return e};e.prototype.clear=function(){return this.destroy().init()}; -e.prototype.destroy=function(){this.g=this.c=this.a=null;return this};const w={icase:function(a){return a.toLowerCase()}};return e}(!1),this); +'use strict';(function(d,l,v){let t;(t=v.define)&&t.amd?t([],function(){return l}):(t=v.modules)?t[d.toLowerCase()]=l:"object"===typeof exports?module.exports=l:v[d]=l})("FlexSearch",function(){function d(a,c){const b=c?c.id:a&&a.id;this.id=b||0===b?b:G++;this.init(a,c);l(this,"index",function(){return this.b});l(this,"length",function(){return Object.keys(this.b).length})}function l(a,c,b){Object.defineProperty(a,c,{get:b})}function v(a,c){for(let b=0;b =g&&(a=a[h-(e+.5>>0)],a=a[b]||(a[b]=[]),a[a.length]=f);return e}function A(a,c){if(a){const b=Object.keys(a);for(let f=0,e=b.length;f a?1:a?-1:0}function I(a,c){a=a.length-c.length;return 0>a?-1:a?1:0}function z(a){return"function"=== +typeof a}function y(a){return"undefined"===typeof a}function C(a){const c=Array(a);for(let b=0;b +k;c--)m=p.substring(k,c),t(q,f,m,a,b,h,e,r-1)}break;default:if(g=t(q,f,p,a,1,h,e,r-1),n&&1 =e)for(g=f._ctx[p]||(f._ctx[p]=u()),p=this.f[p]||(this.f[p]=C(r-(e||0))),h=d-n,m=d+n+1,0>h&&(h=0),m>x&&(m=x);h c&&(g=g.slice(0,c)));d=g}return d};d.prototype.clear=function(){return this.destroy().init()};d.prototype.destroy=function(){this.h=this.f=this.b=null;return this};const w={icase:function(a){return a.toLowerCase()}};return d}(!1),this); diff --git a/dist/flexsearch.min.js b/dist/flexsearch.min.js index 5facd5b..df8a329 100644 --- a/dist/flexsearch.min.js +++ b/dist/flexsearch.min.js @@ -1,37 +1,38 @@ /* - FlexSearch v0.5.0 + FlexSearch v0.5.1 Copyright 2019 Nextapps GmbH Author: Thomas Wilkerling Released under the Apache 2.0 Licence https://github.com/nextapps-de/flexsearch */ -'use strict';(function(x,F,f){let y;(y=f.define)&&y.amd?y([],function(){return F}):(y=f.modules)?y[x.toLowerCase()]=F:"object"===typeof exports?module.exports=F:f[x]=F})("FlexSearch",function U(x){function f(a,b){const c=b?b.id:a&&a.id;this.id=c||0===c?c:V++;this.init(a,b);K(this,"index",function(){return this.b});K(this,"length",function(){return Object.keys(this.b).length})}function y(a,b,c,d){this.s!==this.f&&(this.j=this.j.concat(c),this.s++,d&&this.j.length>=d&&(this.s=this.f),this.F&&this.s=== -this.f&&(this.cache&&this.m.set(b,this.j),this.F(this.j),this.j=[]));return this}function A(a,b){a=a.concat.apply([],a);b&&(G(b)||(z=b.split(":"),1 =k&&(a=a[9-(e+.5>>0)],a=a[c]||(a[c]=[]),a[a.length]=d); -return e}function N(a,b){if(a){const c=Object.keys(a);for(let d=0,e=c.length;d a?1:a?-1:0}function Z(a,b){a=a.length-b.length;return 0>a?-1:a?1:0}function X(a,b){a=a[z];b=b[z];return ab?1:0}function W(a,b){const c=z.length;for(let d=0;d b?1:0}function aa(a,b,c,d){let e=[],g;const k=a.length;if(1 b&&(e=e.slice(0,b));return e}function J(a){return"string"===typeof a}function G(a){return"function"===typeof a}function D(a){return"object"=== -typeof a}function B(a){return"undefined"===typeof a}function P(a){const b=Array(a);for(let c=0;c=this.l.length&&(this.C=0),this.l[this.C].postMessage({add:!0,id:a,content:b}),this.b[g]=""+this.C,c&&c(),this;if(!e){if(this.async&&"function"!==typeof importScripts){let e=this;g=new Promise(function(c){setTimeout(function(){e.add(a, -b,null,d,!0);e=null;c()})});if(c)g.then(c);else return g;return this}if(c)return this.add(a,b,null,d,!0),c(),this}b=this.encode(b);if(!b.length)return this;c=this.c;e=G(c)?c(b):b.split(R);const m=q();m._ctx=q();const n=this.threshold,r=this.depth,t=this.h,v=e.length,L=this.D;for(let b=0;b f;c--)l=k.substring(f,c),I(t,m,l,a,b,h,n)}break;default:if(p=I(t,m,k,a,1,h,n),r&&1 =n)for(p=m._ctx[k]||(m._ctx[k]=q()),k=this.i[k]||(this.i[k]=P(10-(n||0))),h=b-r,l=b+r+1,0>h&&(h=0),l>v&&(l=v);h r&&(a=m));for(r=n?1:0;r c;d--)e=f[d-1],f[d]=e,b[e]=d;f[c]=a;b[a]=c}}}return b};return a}();return f}(function(){const x={},F="undefined"!==typeof Blob&&"undefined"!==typeof URL&&URL.createObjectURL; -return function(f,y,A,K,h){A=F?URL.createObjectURL(new Blob(["("+A.toString()+")()"],{type:"text/javascript"})):f+".min.js";f+="-"+y;x[f]||(x[f]=[]);x[f][h]=new Worker(A);x[f][h].onmessage=K;return x[f][h]}}()),this); +'use strict';(function(w,F,g){let y;(y=g.define)&&y.amd?y([],function(){return F}):(y=g.modules)?y[w.toLowerCase()]=F:"object"===typeof exports?module.exports=F:g[w]=F})("FlexSearch",function U(w){function g(a,b){const c=b?b.id:a&&a.id;this.id=c||0===c?c:V++;this.init(a,b);K(this,"index",function(){return this.b});K(this,"length",function(){return Object.keys(this.b).length})}function y(a,b,c,d){this.u!==this.g&&(this.l=this.l.concat(c),this.u++,d&&this.l.length>=d&&(this.u=this.g),this.G&&this.u=== +this.g&&(this.cache&&this.o.set(b,this.l),this.G(this.l),this.l=[]));return this}function B(a,b){a=a.concat.apply([],a);b&&(G(b)||(z=b.split(":"),1 =k&&(a=a[h-(e+.5>>0)],a=a[c]||(a[c]=[]),a[a.length]= +d);return e}function N(a,b){if(a){const c=Object.keys(a);for(let d=0,e=c.length;d a?1:a?-1:0}function Z(a,b){a=a.length-b.length;return 0>a?-1:a?1:0}function X(a,b){a=a[z];b=b[z];return ab?1:0}function W(a,b){const c=z.length;for(let d=0;d b?1:0}function aa(a,b,c,d,e){c=[];let l;const k=a.length;if(1 b&&(c=c.slice(0,b));return c}function H(a){return"string"===typeof a}function x(a){return a.constructor=== +Array}function G(a){return"function"===typeof a}function A(a){return"object"===typeof a}function v(a){return"undefined"===typeof a}function P(a){const b=Array(a);for(let c=0;c=this.m.length&&(this.D=0),this.m[this.D].postMessage({add:!0,id:a,content:b}),this.b[l]=""+this.D,c&&c(),this;if(!e){if(this.async&&"function"!==typeof importScripts){let e=this;l=new Promise(function(c){setTimeout(function(){e.add(a,b,null,d,!0);e=null;c()})});if(c)l.then(c);else return l;return this}if(c)return this.add(a,b,null,d,!0),c(),this}b=this.encode(b);if(!b.length)return this;c=this.f; +e=G(c)?c(b):b.split(R);const m=r();m._ctx=r();const p=this.threshold,C=this.depth,t=this.c,q=this.i,D=e.length,L=this.F;for(let b=0;b g;c--)n=k.substring(g,c),J(q,m,n,a,b,f,p,t-1)}break;default:if(h=J(q, +m,k,a,1,f,p,t-1),C&&1 =p)for(h=m._ctx[k]||(m._ctx[k]=r()),k=this.j[k]||(this.j[k]=P(t-(p||0))),f=b-C,n=b+C+1,0>f&&(f=0),n>D&&(n=D);f c;d--)e=f[d-1],f[d]=e,b[e]=d;f[c]=a;b[a]=c}}}return b};return a}();return g}(function(){const w= +{},F="undefined"!==typeof Blob&&"undefined"!==typeof URL&&URL.createObjectURL;return function(g,y,B,K,f){B=F?URL.createObjectURL(new Blob(["("+B.toString()+")()"],{type:"text/javascript"})):g+".min.js";g+="-"+y;w[g]||(w[g]=[]);w[g][f]=new Worker(B);w[g][f].onmessage=K;return w[g][f]}}()),this); diff --git a/dist/flexsearch.node.js b/dist/flexsearch.node.js index 29ef3cd..9ea08db 100644 --- a/dist/flexsearch.node.js +++ b/dist/flexsearch.node.js @@ -1,33 +1,34 @@ /* - FlexSearch v0.5.0 + FlexSearch v0.5.1 Copyright 2019 Nextapps GmbH Author: Thomas Wilkerling Released under the Apache 2.0 Licence https://github.com/nextapps-de/flexsearch */ -'use strict';(function(h,w,C){let f;(f=C.define)&&f.amd?f([],function(){return w}):(f=C.modules)?f[h.toLowerCase()]=w:"object"===typeof exports?module.exports=w:C[h]=w})("FlexSearch",function(){function h(a,b){const c=b?b.id:a&&a.id;this.id=c||0===c?c:R++;this.init(a,b);C(this,"index",function(){return this.b});C(this,"length",function(){return Object.keys(this.b).length})}function w(a,b){a=a.concat.apply([],a);b&&(D(b)||(x=b.split(":"),1 =l&&(a=a[9-(e+.5>>0)],a=a[c]||(a[c]=[]),a[a.length]=d);return e}function K(a,b){if(a){const c=Object.keys(a);for(let d=0,e=c.length;d a?1:a?-1:0}function V(a,b){a=a.length-b.length;return 0>a?-1:a?1:0}function T(a,b){a=a[x];b=b[x];return ab?1:0}function S(a,b){const c=x.length;for(let d= -0;d b?1:0}function W(a,b,c,d){let e=[],g;const l=a.length;if(1 b&&(e=e.slice(0,b));return e}function I(a){return"string"===typeof a}function D(a){return"function"===typeof a}function z(a){return"object"===typeof a}function B(a){return"undefined"===typeof a}function M(a){const b=Array(a);for(let c=0;cm;c--)n=l.substring(m,c),H(r,h,n,a,b,f,p)}break;default:if(k=H(r,h,l,a,1,f,p),E&&1 =p)for(k=h._ctx[l]||(h._ctx[l]=u()),l=this.h[l]||(this.h[l]=M(10-(p||0))),f=b-E,n=b+E+1,0>f&&(f=0),n>t&&(n=t);f h&&(a=m));for(h=p?1:0;h c;d--)e=f[d-1],f[d]=e,b[e]=d;f[c]=a;b[a]=c}}}return b};return a}();return h}(!1),this); +'use strict';(function(h,y,D){let e;(e=D.define)&&e.amd?e([],function(){return y}):(e=D.modules)?e[h.toLowerCase()]=y:"object"===typeof exports?module.exports=y:D[h]=y})("FlexSearch",function(){function h(a,b){const c=b?b.id:a&&a.id;this.id=c||0===c?c:S++;this.init(a,b);D(this,"index",function(){return this.c});D(this,"length",function(){return Object.keys(this.c).length})}function y(a,b){a=a.concat.apply([],a);b&&(E(b)||(z=b.split(":"),1=g&&(a=a[k-(f+.5>>0)],a=a[c]||(a[c]=[]),a[a.length]=d);return f}function L(a,b){if(a){const c=Object.keys(a);for(let d=0,f=c.length;d a?1:a?-1:0}function W(a,b){a=a.length-b.length;return 0>a?-1:a?1:0}function U(a,b){a=a[z];b=b[z];return ab?1:0}function T(a,b){const c=z.length;for(let d= +0;d b?1:0}function X(a,b,c,d,f){c=[];let l;const g=a.length;if(1 b&&(c=c.slice(0,b));return c}function F(a){return"string"===typeof a}function x(a){return a.constructor===Array}function E(a){return"function"===typeof a}function A(a){return"object"===typeof a}function v(a){return"undefined"===typeof a}function N(a){const b=Array(a);for(let c=0;cm;c--)n=g.substring(m,c),H(I,p,n,a,b,e,q,u-1)}break;default:if(k=H(I,p,g,a,1,e,q,u-1),h&&1=q)for(k=p._ctx[g]||(p._ctx[g]=t()),g=this.i[g]||(this.i[g]=N(u-(q||0))),e=b-h,n=b+h+1,0>e&&(e=0),n>B&&(n=B);e c;d--)f=e[d-1],e[d]=f,b[f]=d;e[c]=a;b[a]=c}}}return b};return a}();return h}(!1),this); diff --git a/flexsearch.js b/flexsearch.js index 12f13e0..b9b9d4a 100644 --- a/flexsearch.js +++ b/flexsearch.js @@ -1,5 +1,5 @@ /**! - * @preserve FlexSearch v0.5.0 + * @preserve FlexSearch v0.5.1 * Copyright 2019 Nextapps GmbH * Author: Thomas Wilkerling * Released under the Apache 2.0 Licence @@ -41,15 +41,17 @@ async: false, worker: false, rtl: false, + doc: false, + paging: false, - // minimum scoring (0 - 9) + // maximum scoring + resolution: 9, + + // minimum scoring threshold: 0, // contextual depth - depth: 0, - - // multi-field documents - doc: false + depth: 0 }; /** @@ -62,39 +64,46 @@ "memory": { encode: SUPPORT_ENCODER ? "extra" : "icase", tokenize: "strict", - threshold: 7 + threshold: 0, + resolution: 1 }, "speed": { encode: "icase", tokenize: "strict", - threshold: 7, + threshold: 1, + resolution: 3, depth: 2 }, "match": { encode: SUPPORT_ENCODER ? "extra" : "icase", - tokenize: "full" + tokenize: "full", + threshold: 1, + resolution: 3 }, "score": { encode: SUPPORT_ENCODER ? "extra" : "icase", tokenize: "strict", - threshold: 5, + threshold: 1, + resolution: 9, depth: 4 }, "balance": { encode: SUPPORT_ENCODER ? "balance" : "icase", tokenize: "strict", - threshold: 6, + threshold: 0, + resolution: 3, depth: 3 }, - "fastest": { + "fast": { encode: "icase", tokenize: "strict", - threshold: 9, + threshold: 8, + resolution: 9, depth: 1 } }; @@ -114,19 +123,6 @@ let id_counter = 0; - /** - * @enum {number} - */ - - /* - const enum_task = { - - add: 0, - update: 1, - remove: 2 - }; - */ - /** * NOTE: This could make issues on some languages (chinese, korean, thai) * TODO: extract all language-specific stuff from the core @@ -295,6 +291,14 @@ return this; } + const tag_options = { + + "encode": false, + "tokenize": function(doc){ + return [doc]; + } + }; + /** * @param {Object |string=} options * @param {Object =} settings @@ -432,6 +436,23 @@ custom ); + /** @private */ + this.resolution = ( + + is_undefined(custom = options["resolution"]) ? + + custom = preset.resolution || + this.resolution || + defaults.resolution + : + custom + ); + + if(custom <= this.threshold){ + + this.resolution = this.threshold + 1; + } + /** @private */ this.depth = ( @@ -444,6 +465,27 @@ custom ); + /** @private */ + this.paging = ( + + is_undefined(custom = options["paging"]) ? + + this.paging || + defaults.paging + : + custom + ); + + // TODO: provide boost + + /** @private */ + /* + this.boost = ( + + (custom = options["boost"]) ? custom : 0 + ); + */ + if(SUPPORT_SUGGESTIONS){ /** @private */ @@ -511,7 +553,7 @@ // initialize primary index /** @private */ - this._map = create_object_array(10 - (this.threshold || 0)); + this._map = create_object_array(this.resolution - (this.threshold || 0)); /** @private */ this._ctx = create_object(); /** @private */ @@ -529,12 +571,36 @@ let field = doc["field"]; let tag = doc["tag"]; - doc["id"] = doc["id"].split(":"); + if(!is_array(doc["id"])){ + + doc["id"] = doc["id"].split(":"); + } if(SUPPORT_WHERE && tag){ this._tag = create_object(); + let field_custom = create_object(); + + if(field){ + + if(is_string(field)){ + + field_custom[field] = options; + } + else if(is_array(field)){ + + for(let i = 0; i < field.length; i++){ + + field_custom[field[i]] = options; + } + } + else if(is_object(field)){ + + field_custom = field; + } + } + if(!is_array(tag)){ doc["tag"] = tag = [tag]; @@ -542,30 +608,57 @@ for(let i = 0; i < tag.length; i++){ - this._tag[tag[i]] = create_object(); + //TODO: delegate tag indexes to intersection + //field_custom[tag[i]] = tag_options; - tag[i] = tag[i].split(":"); + this._tag[tag[i]] = create_object(); } + + this._tags = tag; + + field = field_custom; } if(field){ + let has_custom; + if(!is_array(field)){ - doc["field"] = field = [field]; + if(is_object(field)){ + + has_custom = field; + doc["field"] = field = get_keys(field); + } + else{ + + doc["field"] = field = [field]; + } } for(let i = 0; i < field.length; i++){ - ref[field[i]] = i; - field[i] = field[i].split(":"); - index[i] = new FlexSearch(options); - index[i]._doc = this._doc; + if(!is_array(field[i])){ - if(SUPPORT_WHERE && tag){ + if(has_custom){ - index[i]._tag = this._tag; + options = has_custom[field[i]]; + } + + ref[field[i]] = i; + field[i] = field[i].split(":"); } + + // TODO: move fields to main index to provide pagination + + index[i] = new FlexSearch(options); + index[i]._doc = this._doc; + + // if(SUPPORT_WHERE && tag){ + // + // index[i]._tag = this._tag; + // index[i]._tags = tag; + // } } } } @@ -826,6 +919,7 @@ const threshold = this.threshold; const depth = this.depth; + const resolution = this.resolution; const map = this._map; const word_length = words.length; const rtl = this.rtl; @@ -863,7 +957,8 @@ id, rtl ? 1 : (length - a) / length, context_score, - threshold + threshold, + resolution - 1 ); } @@ -885,7 +980,8 @@ id, rtl ? (a + 1) / length : 1, context_score, - threshold + threshold, + resolution - 1 ); } @@ -909,7 +1005,8 @@ id, partial_score, context_score, - threshold + threshold, + resolution - 1 ); } } @@ -930,13 +1027,14 @@ // TODO compute and pass distance of ngram sequences as the initial score for each word 1, context_score, - threshold + threshold, + resolution - 1 ); if(depth && (word_length > 1) && (score >= threshold)){ const ctxDupes = dupes["_ctx"][value] || (dupes["_ctx"][value] = create_object()); - const ctxTmp = this._ctx[value] || (this._ctx[value] = create_object_array(10 - (threshold || 0))); + const ctxTmp = this._ctx[value] || (this._ctx[value] = create_object_array(resolution - (threshold || 0))); let x = i - depth; let y = i + depth + 1; @@ -953,8 +1051,9 @@ words[x], id, 0, - 10 - (x < i ? i - x : x - i), - threshold + resolution - (x < i ? i - x : x - i), + threshold, + resolution - 1 ); } } @@ -986,24 +1085,24 @@ /** * @param {!string} job - * @param docs + * @param doc * @param {Function=} callback * @returns {*} */ - FlexSearch.prototype.handle_docs = function(job, docs, callback){ + FlexSearch.prototype.handle_docs = function(job, doc, callback){ - if(is_array(docs)){ + if(is_array(doc)){ - for(let i = 0, len = docs.length; i < len; i++){ + for(let i = 0, len = doc.length; i < len; i++){ if(i === len - 1){ - return this.handle_docs(job, docs[i], callback); + return this.handle_docs(job, doc[i], callback); } else{ - this.handle_docs(job, docs[i]); + this.handle_docs(job, doc[i]); } } } @@ -1014,23 +1113,37 @@ let tree = this.doc["id"]; let id; let tag; + let tag_key; + let tag_value; for(let i = 0; i < tree.length; i++){ - id = (id || docs)[tree[i]]; + id = (id || doc)[tree[i]]; } if(tags){ for(let i = 0; i < tags.length; i++){ - tag = (tag || docs)[tags[i]]; + tag_key = tags[i]; + + const tag_split = tag_key.split(":"); + + for(let i = 0; i < tag_split.length; i++){ + + tag_value = (tag_value || doc)[tag_split[i]]; + } + + tag_value = "@" + tag_value; } + + tag = this._tag[tag_key]; + tag = tag[tag_value] || (tag[tag_value] = []); } if(job === "remove"){ - delete this._doc["@" + id]; + delete this._doc[id]; for(let z = 0, length = index.length; z < length; z++){ @@ -1047,6 +1160,11 @@ tree = this.doc["field"]; + if(tag){ + + tag[tag.length] = doc; // tag[tag.length] = id; + } + for(let i = 0, len = tree.length; i < len; i++){ const branch = tree[i]; @@ -1054,20 +1172,23 @@ for(let x = 0; x < branch.length; x++){ - content = (content || docs)[branch[x]]; + content = (content || doc)[branch[x]]; } - this._doc["@" + id] = docs; + this._doc[id] = doc; const self = index[i]; - const fn = ( - job === "add" ? + let fn; - self.add - : - self.update - ); + if(job === "add"){ + + fn = self.add; + } + else{ + + fn = self.update; + } if(i === len - 1){ @@ -1199,7 +1320,7 @@ profile_start("remove"); } - for(let z = 0; z < (10 - (this.threshold || 0)); z++){ + for(let z = 0; z < (this.resolution - (this.threshold || 0)); z++){ remove_index(this._map[z], id); } @@ -1257,6 +1378,8 @@ } /** + * TODO: move fields to main index to provide pagination + * * @param {!string|Object|Array