fz-search
Version:
Fast aproximate string matching library for use in autocomplete, perform both search and highlight.
221 lines (165 loc) • 5.43 kB
JavaScript
'use strict';
extend(FuzzySearch.prototype, /** @lends {FuzzySearch.prototype} */ {
/**
*
* @param {Indexed} preparedItem
* @param {int} idx
*/
_storeAdd: function (preparedItem, idx) {
var keyList = keysFromIndexedItem(preparedItem);
if (keyList.length == 0) return;
// register idx on all appropriate key
for (var i = 0; i < keyList.length; i++) {
var key = keyList[i];
if (key in this.store) {
// append to existing array of index
this.store[key].push(idx);
}
else {
// Format is dict key => array of item index
this.store[key] = [idx];
}
}
},
/**
*
* @param {Query} preparedQuery
* @param {Array.<Indexed>} source
*/
_storeSearch: function (preparedQuery, source) {
// Scan query for index keys.
var keyList = keysFromQuery(preparedQuery);
if (keyList.length == 0) return [];
// return filtered source
var idAndCount = retrieveCount(keyList, this.store);
if (idAndCount.length == 0) return [];
// Get minimum quality and remap to original items.
var tresh = idAndCount[0].count * this.options.store_thresh;
idAndCount = FuzzySearch.filterGTE(idAndCount, "count", tresh);
return FuzzySearch.map(idAndCount,
function (x) { return source[x.id] },
this, this.options.store_max_results);
}
});
/**
*
* @param {Indexed} preparedItem
*/
function keysFromIndexedItem(preparedItem) {
// Process the nested structure of a prepared item in order to extract index keys.
var keyList = [];
var keyDict = {};
// item -> fields -> nodes -> word_tokens
var fields = preparedItem.fields;
for (var i = 0; i < fields.length; i++) {
var nodes = fields[i];
for (var j = 0; j < nodes.length; j++) {
var words = nodes[j];
for (var k = 0; k < words.length; k++) {
keysFromWord(words[k], keyList, keyDict)
}
}
}
return keyList;
}
/**
*
* @param {Query} query
*/
function keysFromQuery(query) {
var keyList = [];
var keyDict = {};
var i, j;
var words = query.words;
for (i = 0; i < words.length; i++) {
keysFromWord(words[i], keyList, keyDict)
}
var children = query.children;
for (i = 0; i < children.length; i++) {
words = children[i].words;
for (j = 0; j < words; j++) {
keysFromWord(words[j], keyList, keyDict)
}
}
return keyList;
}
function keysFromWord(word, keysList, existingDict) {
var len = word.length;
if (len == 0) return;
if (len >= 3) {
// 3o6, 3o5, 3o4, 3o3
select3(word, 6, keysList, existingDict)
}
if (len >= 2) {
// 2o4, 2o3,2o2
select2(word, 4, keysList, existingDict)
}
// 1o1 strategy: This index by first letter
union(word[0], keysList, existingDict);
}
function select2(str, maxlen, existingList, existingDict) {
var len = Math.min(str.length, maxlen);
for (var i = 0; i < len - 1; i++) {
for (var j = i + 1; j < len; j++) {
union(str[i] + str[j], existingList, existingDict)
}
}
return existingList;
}
function select3(str, maxlen, existingList, existingDict) {
var len = Math.min(str.length, maxlen);
for (var i = 0; i < len - 2; i++) {
for (var j = i + 1; j < len - 1; j++) {
for (var k = j + 1; k < len; k++) {
union(str[i] + str[j] + str[k], existingList, existingDict)
}
}
}
return existingList;
}
function union(word, existingList, existingDict) {
if (!(word in existingDict)) {
existingDict[word] = true;
existingList.push(word);
}
}
function retrieveCount(keys, store) {
// Dictionary idx => count
var countPerIndex = {};
if (keys.length == 0)
return [];
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
// Does the key exist in the index ?
if (key in store) {
// If so add every entry of that key into countPerIndex
// Also for each entry, maintain a count of matched keys.
var idxList = store[key];
for (var j = 0; j < idxList.length; j++) {
var idx = idxList[j];
if (idx in countPerIndex) {
countPerIndex[idx]++;
} else {
countPerIndex[idx] = 1;
}
}
}
}
// Transform countPerIndex into a sorted list of IdAndCount
var outList = [];
for (var id in countPerIndex) {
if (countPerIndex.hasOwnProperty(id)) {
outList.push(new IdAndCount(id, countPerIndex[id]));
}
}
// We can probably filterGte here.
// Custom sort decreasing order
outList = outList.sort(function (a, b) {
return b.count - a.count
});
return outList;
}
function IdAndCount(id, count) {
this.id = id;
this.count = count;
}