Skip to content

Instantly share code, notes, and snippets.

@Nemo64
Created March 14, 2018 15:42
Show Gist options
  • Save Nemo64/a1564dab541ec7f29494a6d32fba4574 to your computer and use it in GitHub Desktop.
Save Nemo64/a1564dab541ec7f29494a6d32fba4574 to your computer and use it in GitHub Desktop.
some utilities for searching though arrays
(function ($, _) {
"use strict";
var escapeRegExp = function (str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
};
var characterGlue = '[^a-zA-Z0-9]*';
var charMap = (function() {
var map = [
['ae', 'a', 'à', 'á', 'â', 'ã', 'ä', 'æ'],
['e', 'è', 'é', 'ê', 'ë'],
['i', 'ì', 'í', 'î', 'ï'],
['oe', 'o', 'ò', 'ó', 'ô', 'õ', 'ō','ö', 'ø', 'œ'],
['ue', 'u', 'ù', 'ú', 'û', 'ū','ü'],
['Ae', 'A', 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ'],
['E', 'È', 'É', 'Ê', 'Ë'],
['I', 'Ì', 'Í', 'Î', 'Ï'],
['Oe', 'O', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', 'Ø', 'Œ'],
['Ue', 'U', 'Ù', 'Ú', 'Û', 'Ü'],
['Ç', 'C'],
['Ð', 'D'],
['Ł', 'L'],
['Ñ', 'N'],
['Ý', 'Y'],
['Þ', 'Th'],
['ß', 'ss'],
['å', 'aa'],
['ç', 'c'],
['ð', 'd'],
['ł', 'l'],
['ñ', 'n', 'ń'],
['ś', 's'],
['ý', 'y'],
['þ', 'th'],
['ÿ', 'y'],
['ż', 'z']
];
var result = {};
_.each(map, function(item) {
var gluedItem = _.map(item, function (itemCharacter) {
return itemCharacter.split('').join(characterGlue);
}).join('|');
_.each(item, function(char) {
result[char] = '(' + gluedItem + ')';
});
});
return result;
})();
var charMapRegEx = (function() {
var charMapkeys = _.chain(charMap)
.keys()
.filter(function (key) {
return key.length > 1;
})
.map(escapeRegExp)
.sortBy(function (key) {
return -key.length
}).values();
charMapkeys.push('\\\\?[\\s\\S]');
return new RegExp('(' + charMapkeys.join('|') + ')', 'g');
})();
var replaceCharMapChar = function (match) {
if (charMap[match] !== undefined) {
return charMap[match] + characterGlue;
}
if (/[^a-zA-Z0-9]+/.test(match)) {
return '.*';
}
return match + characterGlue;
};
var expressionMapCache = {};
function createExpressionsFromSearchWord(searchWord) {
searchWord = String(searchWord);
if (expressionMapCache[searchWord] !== undefined) {
return expressionMapCache[searchWord];
}
var escapedSword = escapeRegExp(searchWord);
var mappedEscapedSword = escapedSword.replace(charMapRegEx, replaceCharMapChar);
var escapedSwordParts = _.reject(mappedEscapedSword.split(/\.\*/g), _.isEmpty);
var mappedEscapedSwordParts = '(' + escapedSwordParts.join('|') + ')';
var result = _.map([
// (<= 25%)
'(' + mappedEscapedSwordParts + '.*){1,}', // somehow contain 1 or more parts the a part of the sword
'(' + mappedEscapedSwordParts + '.*){2,}', // somehow contain 2 or more parts the a part of the sword
'(' + mappedEscapedSwordParts + '.*){3,}', // somehow contain 3 or more parts the a part of the sword
'(' + mappedEscapedSwordParts + '.*){4,}', // somehow contain 3 or more parts the a part of the sword
// (<= 37,5%) somehow contain the sword
mappedEscapedSword,
escapedSword,
// (<= 50%) a word begins with the sword
'\\W' + mappedEscapedSword,
'\\W' + escapedSword,
// (<= 62.5%) complete string begins with sword
'^' + mappedEscapedSword,
'^' + escapedSword,
// (<= 75%) exact word match
'\\W' + mappedEscapedSword + '(\\W|$)',
'\\W' + escapedSword + '(\\W|$)',
// (<= 87.5%) complete string begins with sword word
'^' + mappedEscapedSword + '\\W',
'^' + escapedSword + '\\W',
// (<= 100%) exact match
'^' + mappedEscapedSword + '$',
'^' + escapedSword + '$'
], function (string) {
return new RegExp(string, 'i');
});
// TODO maybe limit cache size?
return expressionMapCache[searchWord] = result;
}
window.fulltextSearch = {
match: function (value, searchWord) {
var regexPriorityMap = createExpressionsFromSearchWord(searchWord);
// quickly handle the no match case
if (!regexPriorityMap[0].test(value)) {
return 0;
}
// go though the priority list in reverse to find the highest matching index
// going though it forward would make some expressions impossible (like contains 4 parts etc)
var lastMatchingIndex = _.findLastIndex(regexPriorityMap, function (expression) {
return expression.test(value);
});
// return a number between 0 and 1 for how strong the match is
return (lastMatchingIndex + 1) / regexPriorityMap.length;
},
filterAndSortList: function filterAndSortList (list, searchWord, context) {
if (!_.isFunction(context)) {
context = _.identity;
}
list = _.sortBy(list, function (item) {
var value = context(item);
return window.fulltextSearch.match(value, searchWord);
});
return list;
}
};
})(jQuery, _);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment