Created
March 14, 2018 15:42
-
-
Save Nemo64/a1564dab541ec7f29494a6d32fba4574 to your computer and use it in GitHub Desktop.
some utilities for searching though arrays
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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