-
-
Notifications
You must be signed in to change notification settings - Fork 50
/
liquidmetal.js
121 lines (106 loc) · 3.74 KB
/
liquidmetal.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/**
* LiquidMetal, version: 1.2.1 (2012-04-21)
*
* 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-2012, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org)
*/
var LiquidMetal = (function() {
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_-";
return {
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;
}
})();