diff options
| -rw-r--r-- | benchmark/match-str.js | 46 | ||||
| -rw-r--r-- | paths.js | 26 | ||||
| -rw-r--r-- | pieces/match-util.js | 33 | ||||
| -rw-r--r-- | pieces/search.js | 791 |
4 files changed, 324 insertions, 572 deletions
diff --git a/benchmark/match-str.js b/benchmark/match-str.js new file mode 100644 index 0000000..7760b4e --- /dev/null +++ b/benchmark/match-str.js @@ -0,0 +1,46 @@ +const products = require('../data/products.json'); +const match = require('../pieces/match-util.js'); +var microtime = require('microtime'); + + +query = 'solokata'; + +function run() { + + var f0 = microtime.nowDouble(); + // for(let i = 0 ; i < 100 ; i++) + let result = matchQuery(query); + var f1 = microtime.nowDouble(); + + return { + t: f1-f0, + q: query, + result: result + } +} + + +function matchQuery(q) { + var result = []; + + for(let i = 0; i < products.length; i++) { + let found = false; + let similarity = 0; + products[i].kb.split(' ').forEach( w => { + let sim = match.resemblance(q, w, 2); + if (sim > 0.5) { + found = true; + similarity = (sim > similarity) ? sim : similarity; + } + }); + if (found) { + products[i].similarity = similarity; + result.push(products[i]); + } + } + + return result +} + + +module.exports = { run }
\ No newline at end of file @@ -1,8 +1,10 @@ const Router = require('koa-router'); + const kb = require('./pieces/kb-util.js'); const data = require('./pieces/prepare-streams'); const products = require('./data/products.json'); const bench = require('./benchmark/find.js'); +const macthStr = require('./benchmark/match-str.js'); // Prefix all routes with: /items const router = new Router({ @@ -48,16 +50,29 @@ const router = new Router({ // Routes + +router.get('/', (ctx, next) => { + ctx.body = { + success: true, + title: 'oseine', + description: 'oseine search engine is not elastic', + message: 'where are you now?' + } + next(); +}) + router.get('/search', (ctx, next) => { ctx.body = []; next(); }); router.get('/search/:title', (ctx, next) => { + // console.log(ctx); let words = kb.keyboardize(kb.clean(ctx.params.title)).split(' '); ctx.body = { params: ctx.params, - results: words + results: words, + nxt: next }; next(); }); @@ -105,7 +120,14 @@ router.get('/test/json', (ctx, next) => { // easy test route router.get('/bench/fe', (ctx, next) => { // easy test route // test anything ... // ctx.body = bench.compare(); - ctx.body = bench.byFor(); + ctx.body = bench.compare(); + next(); +}); + +router.get('/bench/match', (ctx, next) => { // easy test route + // test anything ... + // ctx.body = bench.compare(); + ctx.body = macthStr.run(); next(); }); diff --git a/pieces/match-util.js b/pieces/match-util.js index 1841d15..a13fddb 100644 --- a/pieces/match-util.js +++ b/pieces/match-util.js @@ -10,8 +10,11 @@ const createNgram = (word, n) => { // Ngram creation return vector; }; -/** check similarity between 2 words - * based on Ngram matches of N = n letters +/** similarity + * rates similarity between 2 words + * based on Ngram matches of N = n letters; + * implements a 2-dim check (all a-Ngrams vs all all b-Ngrams) + * * @param {string} a : first word * @param {string} b : second word * @param {int} n : Ngram base @@ -37,6 +40,29 @@ const similarity = (a, b, n) => { // Ngram match score return 0; }; +/** resemblance + * is an alternative similarity rating; + * implements an 1-dim Ngram similarity check + * and it's much faster than similarity() + */ +const resemblance = (a, b, n) => { + if (a.length > n && b.length >= a.length) { + const aNgram = createNgram(a, n); + let hits = 0; + for (let i = 0; i < aNgram.length; ++i) { + if (b.includes(aNgram[i])) { + hits++; + } + } + if (hits > 0) { + // rate resemblance based on hits and length-similarity + return (hits / aNgram.length) * (a.length / b.length); + } + } + return 0; +} + + /** is_exact_match * * check if a searching string -> query (string/latin in kb-format) @@ -62,4 +88,5 @@ module.exports = { exact, partial, similarity, -};
\ No newline at end of file + resemblance +} diff --git a/pieces/search.js b/pieces/search.js index 52ce4e3..0c05708 100644 --- a/pieces/search.js +++ b/pieces/search.js @@ -1,646 +1,303 @@ +const kb = require('./kb-util.js'); +const match = require('./match-util.js'); +const products = require('../data/products.json'); -/** Search Engine - * --------------------------------------------------------------------------- - * - * TODO: - * CRITICAL: (optimization) - * Search initialization uses quite a lot of network sources; - * thus it should be started in a later time; - * lets say ... after `x` seconds - * or... when document/core-ui-elements are ready - * - * @parametres (json) : options - * --- - * @var {string} products_json : endpoint od product descriptions - * @var {string} search_tag : selector of field that shall act as typeahead-suggestions - * @var {string} visualize_search_results_url : url that will visualize the sended "results-page" - * @var {int} max_list : max-size of (rated) results expected - * @var {int} tolerance - * @var {boolean} debug : if true sends several debug console messages; if false mesagges are eliminated - */ +/** VARIABLES + * may passed as module arguments + * ----------------------------------------------------------------------------- + *////////////////////////////////////////////////////////////////////////////// -retrosearch_module({ - products_json: 'https://storage.googleapis.com/pythia-files/uploads/json/emarket-products.json', - search_tag: '#tagsInput', - visualize_search_results_url: '/product_list', - max_list: 24, - tolerance: 42, - debug: ((location.hostname == 'localhost') || (location.hostname == '127.0.0.1')) -}); +var _allowFuzzy = true; // enable|disable fuzzy search +var n = 2; // Ngram base +var _fuzzyLimit = .5; // minimum bigram score for being considered a match +var max_list = 24; +var tolerance = 42; +var STORE = { id: 904 }; - -function retrosearch_module(options) { - - /** CONTENTS - * - * +1: Variables - * - * +2: Purify string functions - * + keyboardize - * + sanitize_GR - * + clean - * - * +3: Supplementary function (vanilla js) - * + ajax_get(url, callback) - * + createNgram (fuzzy) - * + checkSimilarity (fuzzy) - * + check_match - * + is_exact_match - * + match_one - * - * +4: Actual data loading (async) - * - * +5: Suggestions Engine (jQuery) - * + suggestions_engine - * - */ - - if (options.debug) console.log(`preparing retrosearch... (${+ new Date()})`); - - - /** 1. VARIABLES - * ------------------------------------------------------------------------- - *////////////////////////////////////////////////////////////////////////// - - var _kwlinks; // keyword links (word-connections; imported via ajax-get) - var _products = []; // all products (imported via ajax-get) - - // setup options - var _maxResults = options.max_list; // limit suggestions - var _blendProds = 4; // minimum final-produncts to blend with next-word suggestions - var _timeout_ms = 100; // time (in ms) for the search engine to find matches (before rendering) - var _allowFuzzy = true; // enable|disable fuzzy search - var _fuzzyLimit = .5; // minimum bigram score for being considered a match - var _Ngram_base = 2; // number of N in Ngram spliting algorithm - var _isReady = false; // whether the searchbox is ready to be used - - // product keywords - var keywordsURL = options.keywords_json; - - var cursor_on = { none: true }; // what product is highlighted; if not on product then { none: true } +var _kwlinks; // keyword links (word-connections; imported via ajax-get) +var _products = []; // all products (imported via ajax-get) + +// setup options +var _maxResults = options.max_list; // limit suggestions +var _blendProds = 4; // minimum final-produncts to blend with next-word suggestions +var _Ngram_base = 2; // number of N in Ngram spliting algorithm +var _isReady = false; // whether the searchbox is ready to be used + +// product keywords +var keywordsURL = options.keywords_json; + +var cursor_on = { none: true }; // what product is highlighted; if not on product then { none: true } - /** 3. SUPPLEMENTARY FUNCTIONS - * ------------------------------------------------------------------------- - *////////////////////////////////////////////////////////////////////////// +/** SUPPLEMENTARY FUNCTIONS + * ----------------------------------------------------------------------------- + *////////////////////////////////////////////////////////////////////////////// - // TODO: - // exclude some generic non-critial words when proccessing user's query - // ** example code to work with: - // var ignoredKeys_kb = []; // keywords to ignore (in kb-format) - // 'μας με σε για του της των από στο στον &'.split(' ').forEach(w => { ignoredKeys_kb.push(keyboardize(w)); }); - - // pure JS ajax GET request; return data as JSON - // no fancy things like UTF8; if needed use base64 - function ajax_get(url, callback) { - var xmlhttp = new XMLHttpRequest(); - xmlhttp.onreadystatechange = function() { - if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { - try { - var data = JSON.parse(xmlhttp.responseText); - } catch(err) { - if (options.debug) console.log(err.message + " in " + xmlhttp.responseText); - return; - } - callback(data); - } - }; - xmlhttp.open("GET", url, true); - xmlhttp.send(); - } +// callback function for sorting resulrs per r (=rating) property +function compare_rate(a,b) { + return (a.r < b.r); +} - // callback function for sorting resulrs per r (=rating) property - function compare_rate(a,b) { - return (a.r < b.r); - } - /** vwOverUnder - * vertical-wise over/under - * - * @param {document.element} el : element to check - * @param {document.element} container : container element (compairing) - * @return {int} .. [-1|0|+1] - * ..: 0 if element's heignt is completely into container's viewport - * ..: -1 if element's heignt is over container's viewport - * ..: +1 if element's heignt is under container's veiwport - */ - function vwOverUnder(el, container) { - let eR = el.getBoundingClientRect(); - let cR = container.getBoundingClientRect(); - console.log('er|cr', eR, cR); - - let result = -1; // default: element is under container's viewport - - if (eR.y >= cR.y && eR.y + eR.height <= cR.y + cR.height - options.tolerance) { - result = 0 // fully inside - - } else if (eR.y < cR.y) { - result = -1 - } - - return result; - } +/** SEARCH ENGINE + * ------------------------------------------------------------------------- + *////////////////////////////////////////////////////////////////////////// - - /** 4. ACTUAL DATA LOADING - * ------------------------------------------------------------------------- - *////////////////////////////////////////////////////////////////////////// +function matchWordInList(q, list = false) { + if (list !== false && list.length == 0) return []; // no results - // TODO: - // control completion of async loads via .then() rather by this custom structrure - // Need to rewrite the folllowing code ............................. from here - // ........................................................................... - // ........................................................................... - + var result = []; + var firstPass = false; - /** workline - * ------------------------------------------------------------------------- - * custom object/data-structure - * to track status/completion of async svents - * (it does the job using a special set method) - */ - var workline = { - - trackerJL : 0, - set jsonLoaded(x) { - this.trackerJL = x; - - // fire event on certain values - if (x == 2) { - if (options.debug) console.info('suggestion-engine requirements fulfilled'); - _isReady = true; - // code to execute - // ... - } - }, - get jsonLoaded() { return this.trackerJL; } - }; - - - /** LOAD DATA (from endoints) - * --------------------------------------------------------------------------- - */ + if (list === false) { + firstPass = true; // on first pass + list = products; // list is all products + } - - function load_store_products(storeID) { - let prData = getSessionObj('prd'); - if (prData !== null) { - _products = prData; - if (options.debug) console.log(`...products fetched from cache (${+ new Date()})`); - workline.jsonLoaded++; + for(let i = 0 ; i < list.length ; i++) { - } else { - ajax_get(options.products_json, function(data) { // get stor's _products - _products = data; - _products.forEach(p => {p.kb = keyboardize(p.w).toLowerCase()} ); - setSessionObj('prd', _products); - if (options.debug) console.log(`...products loaded; (${+ new Date()})`); - workline.jsonLoaded++; - CURRENT_STOREs_CATALOG = storeID; + // check exact + rate + + // else check partial + rate + + // else check similarity + rate + + let found = false; + let similarity = 0; + products[i].kb.split(' ').forEach( w => { + let sim = match.resemblance(q, w, 2); + if (sim > 0.5) { + found = true; + similarity = (sim > similarity) ? sim : similarity; + } }); - } + if (found) { + products[i].similarity = similarity; + products[i].rating = (firstPass) + ? similarity * 2.0 + : products[i].rating + similarity * 2.0 + result.push(products[i]); + } } - - var STORE = { id: 904 }; - - if (STORE.id != 0) load_store_products(STORE.id); - - // ........................................................................... - // ........................................................................... - // ................................................................ up to here + + return result +} - - - - /** 5. SEARCH ENGINE - * ------------------------------------------------------------------------- - *////////////////////////////////////////////////////////////////////////// - - workline.jsonLoaded++; // notify workline that jQuery is ready!! - - // suggestions engine ////////////////////////////////////////////////// - // --- - function suggestions_engine(qOrig) { - var results = []; // suggestions to respond - var pot = []; pot.length = 0; - - var space_ended = (qOrig.slice(-1) == ' ') ? true : false; - - // clean and sanitize and mark links onto q(uery) string - var q = keyboardize( sanitize_GR( clean_text(qOrig.trim()) ) ).trim(); - - // TODO: - // construct direct-linked words - // = do unequivocally replaces - // steps: - // 1. replace accented vowels with non accented ones - // 2. replace `/some pattern/gi , 'SOME-REPLACE-PATTERN'` - - var qAr = q.split(' '); // split to words - /// if (space_ended) qAr.push(' '); // if space-end existed, push a space to query array - /// - /// if (qAr.slice(-1) == "") { - /// qAr.pop(); - /// } - - if (options.debug) console.log('*** init SEARCH QUERY:', qOrig, 'search:', qAr); + +// suggestions engine ////////////////////////////////////////////////// +// --- +function suggestions_engine(query) { + var results = []; + var pot = []; pot.length = 0; + + // clean and sanitize and mark links onto q(uery) string + var q = kb.keyboardize( kb.sanitizeGR( kb.clean(query.trim()) ) ).trim(); - pot = _products; // potential results // NOTE: CRITICAL: BY REFERENCE + // TODO: + // construct direct-linked words + // = do unequivocally replaces + // steps: + // 1. replace accented vowels with non accented ones + // 2. replace `/some pattern/gi , 'SOME-REPLACE-PATTERN'` - var wi = 0; // word index (from list) - var wc = qAr.length; + var qAr = q.split(' '); // split to words + /// if (space_ended) qAr.push(' '); // if space-end existed, push a space to query array + /// + /// if (qAr.slice(-1) == "") { + /// qAr.pop(); + /// } - qAr.forEach( w => { - if (options.debug) console.log('...testing', w); - let sf = []; // (matches) so far - let mi; // position of match - wi++; + pot = _products; // potential results // NOTE: CRITICAL: BY REFERENCE - pot.forEach( it => { - let matched = false; - let tester = ' '+ it.kb + ' '; - - // reset previous history and ratings - if (wi == 1) { - it.r = 0; - it.history = []; - } - - - // rate word-match > start-match > simple-match - // ... up to 8 points - - if (tester.indexOf(' '+ w +' ') != -1) { - it.r += 9; - it.history.push({ w: w, rate: 9 }); - matched = true; - } - else if (tester.indexOf(' '+ w) != -1) { - it.r += 5; - it.history.push({ w: w, rate: 5 }); - matched = true; - } - else if (tester.indexOf(w) != -1) { - it.r += 2; - it.history.push({ w: w, rate: 2 }); - matched = true; - } - - // rate `near-to-start` matching .. up to 7p - // rate `earlyness` of word in query .. up to 7p - - if ((mi = tester.indexOf(' '+w)) != -1) { - let fc1 = 100 - ((mi < 99) ? mi : 99); // near-to-start factor - let fc2 = wc - wi + 1; // query earlyness factor - let r1 = Math.floor(7*fc1/100); - let r2 = Math.floor(7*fc2/wc); - - it.r += (r1 + r2); - it.history.push({ w: w, left: [fc1, r1], early: [fc2, r2] }); - } - if (matched) sf.push(it); - }); + var wi = 0; // word index (from list) + var wc = qAr.length; - if ((sf.length > (_maxResults + Math.floor(_maxResults/2))) - || (wi == 1) ) { - // ..if pot has a fair amount (= max + 50%) of results - // ..or these are results of '1st-query-word' - // set sf as new source - pot.lenght = 0; pot = []; - pot = JSON.parse(JSON.stringify(sf)); // copy by value - - } else { - // else.. keep the source list and increase of 'so-far rating' - // console.log('found small list', sf, pot) - pot.forEach( it => { - sf.forEach( si => { - if (it.id == si.id) { - it.r += 10; - it.history.push({ w: w, plus: '+10'}); - } - }); - }); - } - }); + qAr.forEach( w => { - // sort results, get max-list of best rated - results = (pot.length > _maxResults) - ? pot.sort(compare_rate).slice(0, _maxResults) - : pot.sort(compare_rate) + let sf = []; // (matches) so far + let mi; // position of match + wi++; - if (options.debug) console.log(results); - - return results; - } - - // suggestions caller (router function) - // arguments: - // * qOrig : original query string - // ** list : callback array structure to host results - // --- - var isuggest = function(qOrig, list) { - var results; - - qSanit = sanitize_GR(qOrig); // sanitize greek accended chars - - // TODO: - // check if all source-lists are ready - // if not you need to wait ... - // via async promishes or synced timouts - - /// // DEPRECATED: Suppport for eys-code search - /// // if query seems to be some king of 'code/id' - /// if (qSanit.length>2 && qSanit.match(/^[0-9]+$/) != null) { - /// results = search_by_code(qOrig); - /// - /// } else { - /// // string match procedure with suggestions engine - results = suggestions_engine(qOrig); - /// } - - list(results); - } + pot.forEach( it => { + let matched = false; + let tester = ' '+ it.kb + ' '; - // var rsfoot = document.createElement('div'); - // rsfoot.setAttribute("id", "retrosearch-footer"); - // $('#retrosearch-footer').html('Multi Search'); - - /* - jQuery(function() { // on document ready code ////////////////////////// - - // UI-dependent code - // uses reference to specific document element (passed via options) - // ----------------------------------------------------------------------- - const searchBox = $(options.search_tag); - searchBox.typeahead( - { - hint: true, - highlight: true, - minLength: 1 - }, - { - limit: _maxResults, // +1 for extra button - // name: 'kwlinks', - displayKey: 'w', - source: isuggest, - templates: { - suggestion: function(data) { - return `<div data-id="${data.id}">${data.w}<span>Alfa, Beta and Gamma</span></div>`; - }, - - footer: '<div class="tt-suggestion tt-selectable call-multi-search">Multi Searh</div>', - - empty: '<div class="-empty-">Δεν υπάρχει στο κωδικολόγιο του καταστήματος</div>' - } + // reset previous history and ratings + if (wi == 1) { + it.r = 0; + it.history = []; } - ) - .bind("typeahead:selected", function(obj, datum, name) { - if (datum.hasOwnProperty('id')) { // ** selected: PRODUCT - - // $('#js-add-product-to-order').attr('disabled', false); - // fill_fields(datum.id, datum.sc, datum.x, datum.w, 1, datum.bc, datum.eu, datum.stk, datum.img); - if (options.debug) console.log('selected: ', datum.id, datum.w); - // GOTO product page - window.location.href = 'product_list?product=id-' + datum.id; - - // $("#product-quantity").trigger('focus'); + // rate word-match > start-match > simple-match + // ... up to 8 points + + if (tester.indexOf(' '+ w +' ') != -1) { + it.r += 9; + it.history.push({ w: w, rate: 9 }); + matched = true; } - else { // selected: SUGGESTION - - $('.typeahead').typeahead('val','').trigger('blur'); - $('.typeahead').typeahead('val', datum.w +' ') - .trigger("query"); - // give some time to the engine to calculate results - // then fire focus again... - setTimeout(() => { $('.typeahead').trigger('focus'); }, _timeout_ms); + else if (tester.indexOf(' '+ w) != -1) { + it.r += 5; + it.history.push({ w: w, rate: 5 }); + matched = true; } - - }) - .bind("typeahead:cursorchange", function( event, obj) { - // track cursor-chane to handle special keys after a final product is selected - - if (typeof obj === 'object' - && !Array.isArray(obj) && obj !== null) { - - - // console.log(obj); - // var liObj = $(`.tt-menu .tt-dataset .tt-suggestion[data-id="${obj.id}"]`) - // var c = liObj.html(); - // liObj.html = c + '\n\n_'; - // liObj.html = c; - - var y = $('.tt-menu').scrollTop(); - let ou = vwOverUnder( - document.querySelector(`.tt-menu .tt-suggestion[data-id="${obj.id}"]`), - document.querySelector('.tt-menu') - ); - console.log('y:',y, 'obj:', obj, 'o/u:', ou); - $('.tt-menu').scrollTop( y - ou * options.tolerance); - - // TODO: - // need to park the cursorChanged-div into tt-menu veiwport (??) - // check: https://stackoverflow.com/questions/75002332/check-if-child-element-is-100-visible-inside-a-parent-div-that-has-overflow-hid - + else if (tester.indexOf(w) != -1) { + it.r += 2; + it.history.push({ w: w, rate: 2 }); + matched = true; } + // rate `near-to-start` matching .. up to 7p + // rate `earlyness` of word in query .. up to 7p - // TODO: - // handle exception where obj is undefined; - // this occures... - // when cusror returns from suggestions list back to the search field - - if (typeof obj === 'undefined') cursor_on = { none: true }; - else if (obj.hasOwnProperty('id')) cursor_on = obj; - else cursor_on = { none: true }; - }) - .bind('typeahead:opened', function(e) { - console.log('attach multisearch option if not exist'); - }); - - // TRACK user search attempt /////////////////////////////////////////////// - + if ((mi = tester.indexOf(' '+w)) != -1) { + let fc1 = 100 - ((mi < 99) ? mi : 99); // near-to-start factor + let fc2 = wc - wi + 1; // query earlyness factor + let r1 = Math.floor(7*fc1/100); + let r2 = Math.floor(7*fc2/wc); - // $('.typeahead').on('typeahead:opened', function(e) { - // console.log('attach multisearch option if not exist'); - // }); - - $('.typeahead').on('keyup', function(e) { - if (_isReady) { - // console.log('on:', cursor_on); - // console.log('key:', e.key); - if ((e.key == ' ') && cursor_on.hasOwnProperty('id')) { - - // product is actually selected - var datum = cursor_on; - - // fill_fields(datum.id, datum.sc, datum.x, datum.w, 1, datum.bc, datum.eu, datum.stk, datum.img); - $("#product-quantity").trigger('focus'); - e.preventDefault(); + it.r += (r1 + r2); + it.history.push({ w: w, left: [fc1, r1], early: [fc2, r2] }); + } + if (matched) sf.push(it); + }); - } else { - if (e.key === "Enter") { + if ((sf.length > (_maxResults + Math.floor(_maxResults/2))) + || (wi == 1) ) { + // ..if pot has a fair amount (= max + 50%) of results + // ..or these are results of '1st-query-word' + // set sf as new source + pot.lenght = 0; pot = []; + pot = JSON.parse(JSON.stringify(sf)); // copy by value - // if curson is not on some option - if ((typeof curson_on === 'undefined') || (curson_on.none == true)) { + } else { + // else.. keep the source list and increase of 'so-far rating' + // console.log('found small list', sf, pot) + pot.forEach( it => { + sf.forEach( si => { + if (it.id == si.id) { + it.r += 10; + it.history.push({ w: w, plus: '+10'}); + } + }); + }); + } + }); - // do a common search - if (options.debug) console.log('Do a Non-Suggestions search', searchBox.val()); + // sort results, get max-list of best rated + results = (pot.length > _maxResults) + ? pot.sort(compare_rate).slice(0, _maxResults) + : pot.sort(compare_rate) - // DEPRICATED: var location = encodeURI('/product_list?productSearch=%'+ searchBox.val() +'%'); + if (options.debug) console.log(results); - // search_results = common_search(searchBox.val()); - // TODO: - console.log('DO A COMMON_SEARCH'); + return results; +} - } else { - // launch a product page - var location = '/product_list?product=id-'+ cursor_on.id; - if (options.debug) console.log('product', cursor_on.id, cursor_on.w); +// sub-module (start) +//////////////////////////////////////////////////////////////////////////// - // TODO: - console.log('GO TO PRODUCT PAGE', location) - } - // window.location.href = location; - } - } - } - }); - - }); - */ - - // sub-module (start) - //////////////////////////////////////////////////////////////////////////// - - function update_common_search_results(q, results) { - let queries = getSessionObj('sr'); - let newSRlist = []; - let isnewQ = true; - if (queries === null) { - setSessionObj('sr', [{ - q: q, - result: result, - t: + new Date() - }]); - return true; +function update_common_search_results(q, results) { + let queries = getSessionObj('sr'); + let newSRlist = []; + let isnewQ = true; + if (queries === null) { + setSessionObj('sr', [{ + q: q, + result: result, + t: + new Date() + }]); + return true; - } else { + } else { - queries.forEach(it => { - if (it.q == q) { - newSRlist.push({ - q:q, - result: result, - t: + new Date() - }); - isnewQ = false; - } else { newSRlist.push(it); } - }); - - if (isnewQ) { + queries.forEach(it => { + if (it.q == q) { newSRlist.push({ q:q, result: result, t: + new Date() }); - } + isnewQ = false; + } else { newSRlist.push(it); } + }); - return true; + if (isnewQ) { + newSRlist.push({ + q:q, + result: result, + t: + new Date() + }); } + + return true; } +} - - //////////////////////////////////////////////////////////////////////////// - // sub-module (end) - - - function common_search(query) { - // clear ; sanitize ; split - var qAr = keyboardize( sanitize_GR( clean_text(query) ) ).toLowerCase().split(' '); - // if last item is empty, remove it - if ((qAr.slice(-1) == ' ') || (qAr.slice(-1) == '')) qAr.pop() +//////////////////////////////////////////////////////////////////////////// +// sub-module (end) - var results = _products; - // for each key fitler results - qAr.forEach( key => { - results = key_sublist(key, results) - }); +function common_search(query) { + // clear ; sanitize ; split + var qAr = keyboardize( sanitize_GR( clean_text(query) ) ).toLowerCase().split(' '); - // echo products (and prepare list to POST) - var list_ = []; - results.forEach( item => { - if (options.debug) console.log(item.id, ':', item.w); - list_.push(item.id) - }) + // if last item is empty, remove it + if ((qAr.slice(-1) == ' ') || (qAr.slice(-1) == '')) qAr.pop() - // *** TODO: keep results in local storage (or on session storage) + var results = _products; - // update_common_search_results(query, list_); + // for each key fitler results + qAr.forEach( key => { + results = key_sublist(key, results) + }); - let l = list_.join(','); - var url = encodeURI(`${options.visualize_search_results_url}?search=${query}&eys_code=${l}`); + // echo products (and prepare list to POST) + var list_ = []; + results.forEach( item => { + if (options.debug) console.log(item.id, ':', item.w); + list_.push(item.id) + }) - console.log('common search: search query > location = search') - window.location.href = encodeURI(`${options.visualize_search_results_url}?search=${query}`); + // *** TODO: keep results in local storage (or on session storage) - } + // update_common_search_results(query, list_); + + let l = list_.join(','); + var url = encodeURI(`${options.visualize_search_results_url}?search=${query}&eys_code=${l}`); + + console.log('common search: search query > location = search') + window.location.href = encodeURI(`${options.visualize_search_results_url}?search=${query}`); - /** return from list only items that include 'key' - */ - function key_sublist(key, list) { - var result = []; - list.forEach( item => { - if (item.kb.includes(key)) { - result.push(item); - } - }); - - return result; - } - } - -/** test script - * --- - * pl = getSessionObj('prd'); wl = ['coca', 'xvris', 'zaxarh', '2x1']; - * pot = pl; - * console.log(+ new Date()); - * wl.forEach( w => { - * let sf = []; - * for(i=0 ; i < pot.length; i++) { if (pot[i].kb.includes(w)) sf.push(pot[i]); } - * pot.lenght = 0; pot = sf; - * }); - * console.log(+ new Date(), pot); - * - * - ** test performance for vs foreach - * --- - * pl = getSessionObj('prd'); console.log('started', + new Date()); - * for(j=0; j<50; j++) { for(i=0; i<pl.length; i++) { let x = pl[i].kb; } } - * console.log('ended', + new Date()); - * - * pl = getSessionObj('prd'); console.log('started', + new Date()); - * for(j=0; j<50; j++) { pl.forEach( it => { let x = it.kb; }); } - * console.log('ended', + new Date()); -**/
\ No newline at end of file +/** return from list only items that include 'key' + */ +function key_sublist(key, list) { + var result = []; + list.forEach( item => { + if (item.kb.includes(key)) { + result.push(item); + } + }); + + return result; +} |
