Skip to content

Instantly share code, notes, and snippets.

@eivindingebrigtsen
Created April 2, 2018 19:13
Show Gist options
  • Save eivindingebrigtsen/8738d6de1699d66a02cf99b6b3865ded to your computer and use it in GitHub Desktop.
Save eivindingebrigtsen/8738d6de1699d66a02cf99b6b3865ded to your computer and use it in GitHub Desktop.
/**
* LiquidMetal, version: 1.3.0 (2014-08-19)
*
* A mimetic poly-alloy of Quicksilver's scoring algorithm, essentially
* LiquidMetal.
*
* For usage and examples, visit:
* http://github.com/rmm5t/liquidmetal
*
* Licensed under the MIT:
* http://www.opensource.org/licenses/mit-license.php
*
* Copyright (c) 2009-2017, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org)
*/
(function(global) {
var SCORE_NO_MATCH = 0.0;
var SCORE_MATCH = 1.0;
var SCORE_TRAILING = 0.8;
var SCORE_TRAILING_BUT_STARTED = 0.9;
var SCORE_BUFFER = 0.85;
var WORD_SEPARATORS = " \t_-";
var LiquidMetal = {
lastScore: null,
lastScoreArray: null,
score: function(string, abbrev) {
// short circuits
if (abbrev.length === 0) return SCORE_TRAILING;
if (abbrev.length > string.length) return SCORE_NO_MATCH;
// match & score all
var allScores = [];
var search = string.toLowerCase();
abbrev = abbrev.toLowerCase();
this._scoreAll(string, search, abbrev, -1, 0, [], allScores);
// complete miss
if (allScores.length == 0) return 0;
// sum per-character scores into overall scores,
// selecting the maximum score
var maxScore = 0.0, maxArray = [];
for (var i = 0; i < allScores.length; i++) {
var scores = allScores[i];
var scoreSum = 0.0;
for (var j = 0; j < string.length; j++) { scoreSum += scores[j]; }
if (scoreSum > maxScore) {
maxScore = scoreSum;
maxArray = scores;
}
}
// normalize max score by string length
// s. t. the perfect match score = 1
maxScore /= string.length;
// record maximum score & score array, return
this.lastScore = maxScore;
this.lastScoreArray = maxArray;
return maxScore;
},
_scoreAll: function(string, search, abbrev, searchIndex, abbrIndex, scores, allScores) {
// save completed match scores at end of search
if (abbrIndex == abbrev.length) {
// add trailing score for the remainder of the match
var started = (search.charAt(0) == abbrev.charAt(0));
var trailScore = started ? SCORE_TRAILING_BUT_STARTED : SCORE_TRAILING;
fillArray(scores, trailScore, scores.length, string.length);
// save score clone (since reference is persisted in scores)
allScores.push(scores.slice(0));
return;
}
// consume current char to match
var c = abbrev.charAt(abbrIndex);
abbrIndex++;
// cancel match if a character is missing
var index = search.indexOf(c, searchIndex);
if (index == -1) return;
// match all instances of the abbreviaton char
var scoreIndex = searchIndex; // score section to update
while ((index = search.indexOf(c, searchIndex+1)) != -1) {
// score this match according to context
if (isNewWord(string, index)) {
scores[index-1] = 1;
fillArray(scores, SCORE_BUFFER, scoreIndex+1, index-1);
}
else if (isUpperCase(string, index)) {
fillArray(scores, SCORE_BUFFER, scoreIndex+1, index);
}
else {
fillArray(scores, SCORE_NO_MATCH, scoreIndex+1, index);
}
scores[index] = SCORE_MATCH;
// consume matched string and continue search
searchIndex = index;
this._scoreAll(string, search, abbrev, searchIndex, abbrIndex, scores, allScores);
}
}
};
function isUpperCase(string, index) {
var c = string.charAt(index);
return ("A" <= c && c <= "Z");
}
function isNewWord(string, index) {
var c = string.charAt(index-1);
return (WORD_SEPARATORS.indexOf(c) != -1);
}
function fillArray(array, value, from, to) {
for (var i = from; i < to; i++) { array[i] = value; }
return array;
}
// Export as AMD...
if (typeof define === 'function' && define.amd) {
define(function () { return LiquidMetal; });
}
// ...or as a node module
else if (typeof module !== 'undefined' && module.exports) {
module.exports = LiquidMetal;
}
else {
global.LiquidMetal = LiquidMetal;
}
})(typeof window !== 'undefined' ? window : this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment