From 381cb335a41cc3be58ac2ec04e011bffcb79e1d4 Mon Sep 17 00:00:00 2001 From: Karthik Sethuraman Date: Wed, 12 Aug 2015 00:05:34 -0700 Subject: [PATCH 1/6] Current version of fuzzy with new algorithm --- lib/fuzzy.js | 104 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 87 insertions(+), 17 deletions(-) diff --git a/lib/fuzzy.js b/lib/fuzzy.js index a2a91eb..1d037c7 100644 --- a/lib/fuzzy.js +++ b/lib/fuzzy.js @@ -54,27 +54,97 @@ fuzzy.match = function(pattern, string, opts) { // For each character in the string, either add it to the result // or wrap in template if it's the next string in the pattern - for(var idx = 0; idx < len; idx++) { - ch = string[idx]; - if(compareString[idx] === pattern[patternIdx]) { - ch = pre + ch + post; - patternIdx += 1; - - // consecutive characters should increase the score more than linearly - currScore += 1 + currScore; - } else { - currScore = 0; - } - totalScore += currScore; - result[result.length] = ch; + // for(var idx = 0; idx < len; idx++) { + // ch = string[idx]; + // if(compareString[idx] === pattern[patternIdx]) { + // ch = pre + ch + post; + // patternIdx += 1; + + // // consecutive characters should increase the score more than linearly + // currScore += 1 + currScore; + // } else { + // currScore = 0; + // } + // totalScore += currScore; + // result[result.length] = ch; + // } + + // // return rendered string if we have a match for every char + // if(patternIdx === pattern.length) { + // return {rendered: result.join(''), score: totalScore}; + // } + + // return null; + + var patternCache = fuzzy.traverse(string, pattern, []); + if(!patternCache) { + return null; + } + + return {rendered: fuzzy.render(string, patternCache.cache, pre, post), score: patternCache.score}; +}; + +fuzzy.traverse = function(string, pattern, patternCache) { + if(!patternCache) { + patternCache = []; + } + + if(pattern.length === 0) { + return {score : fuzzy.calculateScore(patternCache), cache : patternCache.slice()}; } - // return rendered string if we have a match for every char - if(patternIdx === pattern.length) { - return {rendered: result.join(''), score: totalScore}; + if(string.length === 0 || pattern.length > string.length) { + return undefined; } - return null; + var c1 = pattern[0]; + var index = string.indexOf(c1); + var best, temp; + + while(index > -1) { + patternCache.push(index); + temp = fuzzy.traverse(string.substring(index), pattern.substring(1), patternCache); + patternCache.pop(); + + if(!temp) { + return best; + } + + if(!best || best.score < temp.score) { + best = temp; + } + + index = string.indexOf(c1, index); + } + + return best; + +}; + +fuzzy.calculateScore = function(patternCache) { + var score = 0; + var temp = 1; + patternCache.forEach(function(index, i) { + if(i > 0) { + if(patternCache[i-1] + 1 === index) { + temp += temp + 1; + } else { + temp = 1; + } + } + + score += temp; + }); + return score; +}; + +fuzzy.render = function(string, indices, pre, post) { + var rendered = string.substring(0, indices[0]); + indices.forEach(function(index, i) { + rendered += pre + string[index] + post + + string.substring(index + 1, (indices[i+1]) ? indices[i+1] : string.length); + }); + return rendered; }; // The normal entry point. Filters `arr` for matches against `pattern`. From 7ac7486747cbb3023ad1a150c81f92a43bbe9f70 Mon Sep 17 00:00:00 2001 From: Karthik Sethuraman Date: Wed, 12 Aug 2015 00:24:24 -0700 Subject: [PATCH 2/6] Fixed bugs in code, passes basic tests --- fuzzy-min.js | 2 +- lib/fuzzy.js | 20 ++++++++------------ 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/fuzzy-min.js b/fuzzy-min.js index 6c7e1c4..736e360 100644 --- a/fuzzy-min.js +++ b/fuzzy-min.js @@ -1 +1 @@ -(function(){var root=this;var fuzzy={};if(typeof exports!=="undefined"){module.exports=fuzzy}else{root.fuzzy=fuzzy}fuzzy.simpleFilter=function(pattern,array){return array.filter(function(string){return fuzzy.test(pattern,string)})};fuzzy.test=function(pattern,string){return fuzzy.match(pattern,string)!==null};fuzzy.match=function(pattern,string,opts){opts=opts||{};var patternIdx=0,result=[],len=string.length,totalScore=0,currScore=0,pre=opts.pre||"",post=opts.post||"",compareString=opts.caseSensitive&&string||string.toLowerCase(),ch,compareChar;pattern=opts.caseSensitive&&pattern||pattern.toLowerCase();for(var idx=0;idxstring.length-stringIndex){return undefined}var c=pattern[patternIndex];var index=string.indexOf(c,stringIndex);var best,temp;while(index>-1){patternCache.push(index);temp=fuzzy.traverse(string,pattern,index+1,patternIndex+1,patternCache);patternCache.pop();if(!temp){return best}if(!best||best.score0){if(patternCache[i-1]+1===index){temp+=temp+1}else{temp=1}}score+=temp});return score};fuzzy.render=function(string,indices,pre,post){var rendered=string.substring(0,indices[0]);indices.forEach(function(index,i){rendered+=pre+string[index]+post+string.substring(index+1,indices[i+1]?indices[i+1]:string.length)});return rendered};fuzzy.filter=function(pattern,arr,opts){opts=opts||{};return arr.reduce(function(prev,element,idx,arr){var str=element;if(opts.extract){str=opts.extract(element)}var rendered=fuzzy.match(pattern,str,opts);if(rendered!=null){prev[prev.length]={string:rendered.rendered,score:rendered.score,index:idx,original:element}}return prev},[]).sort(function(a,b){var compare=b.score-a.score;if(compare)return compare;return a.index-b.index})}})(); diff --git a/lib/fuzzy.js b/lib/fuzzy.js index 1d037c7..507f3ce 100644 --- a/lib/fuzzy.js +++ b/lib/fuzzy.js @@ -76,7 +76,7 @@ fuzzy.match = function(pattern, string, opts) { // return null; - var patternCache = fuzzy.traverse(string, pattern, []); + var patternCache = fuzzy.traverse(compareString, pattern, 0, 0, []); if(!patternCache) { return null; } @@ -84,26 +84,22 @@ fuzzy.match = function(pattern, string, opts) { return {rendered: fuzzy.render(string, patternCache.cache, pre, post), score: patternCache.score}; }; -fuzzy.traverse = function(string, pattern, patternCache) { - if(!patternCache) { - patternCache = []; - } - - if(pattern.length === 0) { +fuzzy.traverse = function(string, pattern, stringIndex, patternIndex, patternCache) { + if(pattern.length === patternIndex) { return {score : fuzzy.calculateScore(patternCache), cache : patternCache.slice()}; } - if(string.length === 0 || pattern.length > string.length) { + if(string.length === stringIndex || pattern.length - patternIndex > string.length - stringIndex) { return undefined; } - var c1 = pattern[0]; - var index = string.indexOf(c1); + var c = pattern[patternIndex]; + var index = string.indexOf(c, stringIndex); var best, temp; while(index > -1) { patternCache.push(index); - temp = fuzzy.traverse(string.substring(index), pattern.substring(1), patternCache); + temp = fuzzy.traverse(string, pattern, index+1, patternIndex+1, patternCache); patternCache.pop(); if(!temp) { @@ -114,7 +110,7 @@ fuzzy.traverse = function(string, pattern, patternCache) { best = temp; } - index = string.indexOf(c1, index); + index = string.indexOf(c, index+1); } return best; From 5a399437c7cf7799b7cf11fa17449b20f55a3778 Mon Sep 17 00:00:00 2001 From: Karthik Sethuraman Date: Wed, 12 Aug 2015 00:27:36 -0700 Subject: [PATCH 3/6] Made test not 'pending' --- test/fuzzy.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/fuzzy.test.js b/test/fuzzy.test.js index 8a0d9c2..582d844 100644 --- a/test/fuzzy.test.js +++ b/test/fuzzy.test.js @@ -53,12 +53,12 @@ describe('fuzzy', function(){ // appear toward the beginning of the string a bit higher }); // TODO: implement this test - xit('should prefer consecutive characters even if they come after the first match', function(){ + it('should prefer consecutive characters even if they come after the first match', function(){ var opts = {pre: '<', post: '>'}; var result = fuzzy.match('bass', 'bodacious bass', opts).rendered; expect(result).to.equal('bodacious '); }); - xit('should prefer consecutive characters in a match even if we need to break up into a substring', function(){ + it('should prefer consecutive characters in a match even if we need to break up into a substring', function(){ var opts = {pre: '<', post: '>'}; var result = fuzzy.match('reic', 'reactive rice', opts).rendered; expect(result).to.equal('active re'); From 38c39162036fc01756022b6fa091d932ea988cde Mon Sep 17 00:00:00 2001 From: Karthik Sethuraman Date: Wed, 12 Aug 2015 10:23:57 -0700 Subject: [PATCH 4/6] Commenting + remove useless code --- lib/fuzzy.js | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/lib/fuzzy.js b/lib/fuzzy.js index 507f3ce..4171911 100644 --- a/lib/fuzzy.js +++ b/lib/fuzzy.js @@ -52,30 +52,6 @@ fuzzy.match = function(pattern, string, opts) { pattern = opts.caseSensitive && pattern || pattern.toLowerCase(); - // For each character in the string, either add it to the result - // or wrap in template if it's the next string in the pattern - // for(var idx = 0; idx < len; idx++) { - // ch = string[idx]; - // if(compareString[idx] === pattern[patternIdx]) { - // ch = pre + ch + post; - // patternIdx += 1; - - // // consecutive characters should increase the score more than linearly - // currScore += 1 + currScore; - // } else { - // currScore = 0; - // } - // totalScore += currScore; - // result[result.length] = ch; - // } - - // // return rendered string if we have a match for every char - // if(patternIdx === pattern.length) { - // return {rendered: result.join(''), score: totalScore}; - // } - - // return null; - var patternCache = fuzzy.traverse(compareString, pattern, 0, 0, []); if(!patternCache) { return null; @@ -85,10 +61,15 @@ fuzzy.match = function(pattern, string, opts) { }; fuzzy.traverse = function(string, pattern, stringIndex, patternIndex, patternCache) { + + // if the pattern search at end if(pattern.length === patternIndex) { + + // calculate socre and copy the cache containing the indices where it's found return {score : fuzzy.calculateScore(patternCache), cache : patternCache.slice()}; } + // if string at end or remaining pattern > remaining string if(string.length === stringIndex || pattern.length - patternIndex > string.length - stringIndex) { return undefined; } @@ -102,6 +83,7 @@ fuzzy.traverse = function(string, pattern, stringIndex, patternIndex, patternCac temp = fuzzy.traverse(string, pattern, index+1, patternIndex+1, patternCache); patternCache.pop(); + // if downstream traversal failed, return best answer so far if(!temp) { return best; } @@ -114,7 +96,6 @@ fuzzy.traverse = function(string, pattern, stringIndex, patternIndex, patternCac } return best; - }; fuzzy.calculateScore = function(patternCache) { From d298cb8121c94303aadcbdfb3f398926a1aa800b Mon Sep 17 00:00:00 2001 From: Karthik Sethuraman Date: Mon, 24 Aug 2015 11:40:12 -0700 Subject: [PATCH 5/6] Added null / undefined check in filter --- lib/fuzzy.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/fuzzy.js b/lib/fuzzy.js index 4171911..9b10393 100644 --- a/lib/fuzzy.js +++ b/lib/fuzzy.js @@ -155,6 +155,10 @@ fuzzy.filter = function(pattern, arr, opts) { var str = element; if(opts.extract) { str = opts.extract(element); + + if(!str) { // take care of undefineds / nulls / etc. + str = ''; + } } var rendered = fuzzy.match(pattern, str, opts); if(rendered != null) { From f4fe1384c122b44ec11a770a1e46417f1deb9b3f Mon Sep 17 00:00:00 2001 From: Karthik Sethuraman Date: Sat, 27 Feb 2016 23:19:28 -0800 Subject: [PATCH 6/6] Cleanup some comments and consistently return null --- lib/fuzzy.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/fuzzy.js b/lib/fuzzy.js index 9b10393..4399862 100644 --- a/lib/fuzzy.js +++ b/lib/fuzzy.js @@ -65,13 +65,13 @@ fuzzy.traverse = function(string, pattern, stringIndex, patternIndex, patternCac // if the pattern search at end if(pattern.length === patternIndex) { - // calculate socre and copy the cache containing the indices where it's found + // calculate score and copy the cache containing the indices where it's found return {score : fuzzy.calculateScore(patternCache), cache : patternCache.slice()}; } // if string at end or remaining pattern > remaining string if(string.length === stringIndex || pattern.length - patternIndex > string.length - stringIndex) { - return undefined; + return null; } var c = pattern[patternIndex];