pax_global_header00006660000000000000000000000064132611342730014514gustar00rootroot0000000000000052 comment=1a63e88643b905e2023dcaea3e10cffc28a528a5 fuzzysort-1.1.1/000077500000000000000000000000001326113427300136135ustar00rootroot00000000000000fuzzysort-1.1.1/LICENSE000066400000000000000000000021051326113427300146160ustar00rootroot00000000000000MIT License Copyright (c) 2018 Stephen Kamenar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. fuzzysort-1.1.1/README.md000066400000000000000000000113421326113427300150730ustar00rootroot00000000000000# [fuzzysort](https://raw.github.com/farzher/fuzzysort/master/fuzzysort.js) Fast SublimeText-like fuzzy search for JavaScript. Sublime's fuzzy search is... sublime. I wish everything used it. So here's an open source js version. ## [Demo](https://rawgit.com/farzher/fuzzysort/master/test.html) https://rawgit.com/farzher/fuzzysort/master/test.html ![](http://i.imgur.com/1M6ZrgS.gif) ![](http://i.imgur.com/kdZxnJ0.png) ![](http://i.imgur.com/4kKfMK4.png) ![](http://i.imgur.com/K8KMgcn.png) ![](http://i.imgur.com/PFIp7WR.png) ## Installation Node ```sh npm i fuzzysort node > require('fuzzysort').single('t', 'test') { score: -3, indexes: [0], target: 'test' } ``` ## Installation Browser ```html ``` ## Usage ### `fuzzysort.single(search, target)` ```js var result = fuzzysort.single('query', 'some string that contains my query.') result.score // -59 result.indexes // [29, 30, 31, 32, 33] result.target // some string that contains my query. fuzzysort.highlight(result, '', '') // some string that contains my query. fuzzysort.single('query', 'irrelevant string') // null // exact match returns a score of 0. lower is worse fuzzysort.single('query', 'query').score // 0 ``` ### `fuzzysort.go(search, targets, options=null)` ```js fuzzysort.go('mr', ['Monitor.cpp', 'MeshRenderer.cpp']) // [{score: -18, target: "MeshRenderer.cpp"}, {score: -6009, target: "Monitor.cpp"}] ``` ### `fuzzysort.goAsync(search, targets, options=null)` ```js let promise = fuzzysort.goAsync('mr', ['Monitor.cpp', 'MeshRenderer.cpp']) promise.then(results => console.log(results)) if(invalidated) promise.cancel() ``` ##### Options ```js fuzzysort.go(search, targets, { threshold: -Infinity, // Don't return matches worse than this (higher is faster) limit: Infinity, // Don't return more results than this (lower is faster) allowTypo: true, // Allwos a snigle transpoes (false is faster) key: null, // For when targets are objects (see its example usage) keys: null, // For when targets are objects (see its example usage) scoreFn: null, // For use with `keys` (see its example usage) }) ``` #### `fuzzysort.highlight(result, open='', close='')` ```js fuzzysort.highlight(fuzzysort.single('tt', 'test'), '*', '*') // *t*es*t* ``` ## How To Go Fast ยท Performance Tips ```js // filter out targets that you don't need to search! especially long ones! targets = targets.filter(t => t.length < 1000) // if your targets don't change often, provide prepared targets instead of raw strings! targets = targets.map(t => fuzzysort.prepare(t)) const options = { limit: 100, // don't return more results than you need! allowTypo: false, // if you don't care about allowing typos threshold: -10000, // don't return garbage results } fuzzysort.go('gotta', targets, options) fuzzysort.go('go', targets, options) fuzzysort.go('fast', targets, options) ``` ### Advanced Usage Search a list of objects by key ```js fuzzysort.go('mr', [{file:'Monitor.cpp'}, {file:'MeshRenderer.cpp'}], {key: 'file'}) // When using `key`, the results will have an extra property, `obj`, which referencese the original obj // [{score: -18, target: "MeshRenderer.cpp", obj}, {score: -6009, target: "Monitor.cpp", obj}] ``` Search a list of objects, by multiple fields, with custom weights. ```js let objects = [{title:'Favorite Color', desc:'Chrome'}, {title:'Google Chrome', desc:'Launch Chrome'}] let results = fuzzysort.go('chr', objects, { keys: ['title', 'desc'], // Create a custom combined score to sort by. -100 to the desc score makes it a worse match scoreFn(a) => Math.max(a[0]?a[0].score:-1000, a[1]?a[1].score-100:-1000) }) var bestResult = results[0] // When using multiple `keys`, results are different. They're indexable to get each normal result fuzzysort.highlight(bestResult[0]) // 'Google Chrome' fuzzysort.highlight(bestResult[1]) // 'Launch Chrome' bestResult.obj.title // 'Google Chrome' ``` Multiple instances, each with different default options. ```js const strictsort = fuzzysort.new({threshold: -999}) ``` ### Changelog #### v1.1.0 - Added `allowTypo` as an option #### v1.0.0 - Inverted scores; they're now negative instead of positive, so that higher scores are better - Added ability to search objects by `key`/`keys` with custom weights - Removed the option to automatically highlight and exposed `fuzzysort.highlight` - Removed all options from `fuzzysort` and moved them into `fuzzysort.go` optional params #### v0.x.x - init fuzzysort-1.1.1/fuzzysort.js000066400000000000000000000622041326113427300162540ustar00rootroot00000000000000/* WHAT: SublimeText-like Fuzzy Search USAGE: fuzzysort.single('fs', 'Fuzzy Search') // {score: -16} fuzzysort.single('test', 'test') // {score: 0} fuzzysort.single('doesnt exist', 'target') // null fuzzysort.go('mr', ['Monitor.cpp', 'MeshRenderer.cpp']) // [{score: -18, target: "MeshRenderer.cpp"}, {score: -6009, target: "Monitor.cpp"}] fuzzysort.highlight(fuzzysort.single('fs', 'Fuzzy Search'), '', '') // Fuzzy Search */ // UMD (Universal Module Definition) for fuzzysort ;(function(root, UMD) { if(typeof define === 'function' && define.amd) define([], UMD) else if(typeof module === 'object' && module.exports) module.exports = UMD() else root.fuzzysort = UMD() })(this, function UMD() { function fuzzysortNew(instanceOptions) { var fuzzysort = { single: function(search, target, options) { if(!search) return null if(!isObj(search)) search = fuzzysort.getPreparedSearch(search) if(!target) return null if(!isObj(target)) target = fuzzysort.getPrepared(target) var allowTypo = options && options.allowTypo!==undefined ? options.allowTypo : instanceOptions && instanceOptions.allowTypo!==undefined ? instanceOptions.allowTypo : true var algorithm = allowTypo ? fuzzysort.algorithm : fuzzysort.algorithmNoTypo return algorithm(search, target, search[0]) // var threshold = options && options.threshold || instanceOptions && instanceOptions.threshold || -9007199254740991 // var result = algorithm(search, target, search[0]) // if(result === null) return null // if(result.score < threshold) return null // return result }, go: function(search, targets, options) { if(!search) return noResults search = fuzzysort.prepareSearch(search) var searchLowerCode = search[0] var threshold = options && options.threshold || instanceOptions && instanceOptions.threshold || -9007199254740991 var limit = options && options.limit || instanceOptions && instanceOptions.limit || 9007199254740991 var allowTypo = options && options.allowTypo!==undefined ? options.allowTypo : instanceOptions && instanceOptions.allowTypo!==undefined ? instanceOptions.allowTypo : true var algorithm = allowTypo ? fuzzysort.algorithm : fuzzysort.algorithmNoTypo var resultsLen = 0; var limitedCount = 0 var targetsLen = targets.length // This code is copy/pasted 3 times for performance reasons [options.keys, options.key, no keys] // options.keys if(options && options.keys) { var scoreFn = options.scoreFn || defaultScoreFn var keys = options.keys var keysLen = keys.length for(var i = targetsLen - 1; i >= 0; --i) { var obj = targets[i] var objResults = new Array(keysLen) for (var keyI = keysLen - 1; keyI >= 0; --keyI) { var key = keys[keyI] var target = getValue(obj, key) if(!target) { objResults[keyI] = null; continue } if(!isObj(target)) target = fuzzysort.getPrepared(target) objResults[keyI] = algorithm(search, target, searchLowerCode) } objResults.obj = obj // before scoreFn so scoreFn can use it var score = scoreFn(objResults) if(score === null) continue if(score < threshold) continue objResults.score = score if(resultsLen < limit) { q.add(objResults); ++resultsLen } else { ++limitedCount if(score > q.peek().score) q.replaceTop(objResults) } } // options.key } else if(options && options.key) { var key = options.key for(var i = targetsLen - 1; i >= 0; --i) { var obj = targets[i] var target = getValue(obj, key) if(!target) continue if(!isObj(target)) target = fuzzysort.getPrepared(target) var result = algorithm(search, target, searchLowerCode) if(result === null) continue if(result.score < threshold) continue // have to clone result so duplicate targets from different obj can each reference the correct obj result = {target:result.target, _targetLowerCodes:null, _nextBeginningIndexes:null, score:result.score, indexes:result.indexes, obj:obj} // hidden if(resultsLen < limit) { q.add(result); ++resultsLen } else { ++limitedCount if(result.score > q.peek().score) q.replaceTop(result) } } // no keys } else { for(var i = targetsLen - 1; i >= 0; --i) { var target = targets[i] if(!target) continue if(!isObj(target)) target = fuzzysort.getPrepared(target) var result = algorithm(search, target, searchLowerCode) if(result === null) continue if(result.score < threshold) continue if(resultsLen < limit) { q.add(result); ++resultsLen } else { ++limitedCount if(result.score > q.peek().score) q.replaceTop(result) } } } if(resultsLen === 0) return noResults var results = new Array(resultsLen) for(var i = resultsLen - 1; i >= 0; --i) results[i] = q.poll() results.total = resultsLen + limitedCount return results }, goAsync: function(search, targets, options) { var canceled = false var p = new Promise(function(resolve, reject) { if(!search) return resolve(noResults) search = fuzzysort.prepareSearch(search) var searchLowerCode = search[0] var q = fastpriorityqueue() var iCurrent = targets.length - 1 var threshold = options && options.threshold || instanceOptions && instanceOptions.threshold || -9007199254740991 var limit = options && options.limit || instanceOptions && instanceOptions.limit || 9007199254740991 var allowTypo = options && options.allowTypo!==undefined ? options.allowTypo : instanceOptions && instanceOptions.allowTypo!==undefined ? instanceOptions.allowTypo : true var algorithm = allowTypo ? fuzzysort.algorithm : fuzzysort.algorithmNoTypo var resultsLen = 0; var limitedCount = 0 function step() { if(canceled) return reject('canceled') var startMs = Date.now() // This code is copy/pasted 3 times for performance reasons [options.keys, options.key, no keys] // options.keys if(options && options.keys) { var scoreFn = options.scoreFn || defaultScoreFn var keys = options.keys var keysLen = keys.length for(; iCurrent >= 0; --iCurrent) { var obj = targets[iCurrent] var objResults = new Array(keysLen) for (var keyI = keysLen - 1; keyI >= 0; --keyI) { var key = keys[keyI] var target = getValue(obj, key) if(!target) { objResults[keyI] = null; continue } if(!isObj(target)) target = fuzzysort.getPrepared(target) objResults[keyI] = algorithm(search, target, searchLowerCode) } objResults.obj = obj // before scoreFn so scoreFn can use it var score = scoreFn(objResults) if(score === null) continue if(score < threshold) continue objResults.score = score if(resultsLen < limit) { q.add(objResults); ++resultsLen } else { ++limitedCount if(score > q.peek().score) q.replaceTop(objResults) } if(iCurrent%1000/*itemsPerCheck*/ === 0) { if(Date.now() - startMs >= 10/*asyncInterval*/) { isNode?setImmediate(step):setTimeout(step) return } } } // options.key } else if(options && options.key) { var key = options.key for(; iCurrent >= 0; --iCurrent) { var obj = targets[iCurrent] var target = getValue(obj, key) if(!target) continue if(!isObj(target)) target = fuzzysort.getPrepared(target) var result = algorithm(search, target, searchLowerCode) if(result === null) continue if(result.score < threshold) continue // have to clone result so duplicate targets from different obj can each reference the correct obj result = {target:result.target, _targetLowerCodes:null, _nextBeginningIndexes:null, score:result.score, indexes:result.indexes, obj:obj} // hidden if(resultsLen < limit) { q.add(result); ++resultsLen } else { ++limitedCount if(result.score > q.peek().score) q.replaceTop(result) } if(iCurrent%1000/*itemsPerCheck*/ === 0) { if(Date.now() - startMs >= 10/*asyncInterval*/) { isNode?setImmediate(step):setTimeout(step) return } } } // no keys } else { for(; iCurrent >= 0; --iCurrent) { var target = targets[iCurrent] if(!target) continue if(!isObj(target)) target = fuzzysort.getPrepared(target) var result = algorithm(search, target, searchLowerCode) if(result === null) continue if(result.score < threshold) continue if(resultsLen < limit) { q.add(result); ++resultsLen } else { ++limitedCount if(result.score > q.peek().score) q.replaceTop(result) } if(iCurrent%1000/*itemsPerCheck*/ === 0) { if(Date.now() - startMs >= 10/*asyncInterval*/) { isNode?setImmediate(step):setTimeout(step) return } } } } if(resultsLen === 0) return resolve(noResults) var results = new Array(resultsLen) for(var i = resultsLen - 1; i >= 0; --i) results[i] = q.poll() results.total = resultsLen + limitedCount resolve(results) } isNode?setImmediate(step):step() }) p.cancel = function() { canceled = true } return p }, highlight: function(result, hOpen, hClose) { if(result === null) return null if(hOpen === undefined) hOpen = '' if(hClose === undefined) hClose = '' var highlighted = '' var matchesIndex = 0 var opened = false var target = result.target var targetLen = target.length var matchesBest = result.indexes for(var i = 0; i < targetLen; ++i) { var char = target[i] if(matchesBest[matchesIndex] === i) { ++matchesIndex if(!opened) { opened = true highlighted += hOpen } if(matchesIndex === matchesBest.length) { highlighted += char + hClose + target.substr(i+1) break } } else { if(opened) { opened = false highlighted += hClose } } highlighted += char } return highlighted }, prepare: function(target) { if(!target) return return {target:target, _targetLowerCodes:fuzzysort.prepareLowerCodes(target), _nextBeginningIndexes:null, score:null, indexes:null, obj:null} // hidden }, prepareSlow: function(target) { if(!target) return return {target:target, _targetLowerCodes:fuzzysort.prepareLowerCodes(target), _nextBeginningIndexes:fuzzysort.prepareNextBeginningIndexes(target), score:null, indexes:null, obj:null} // hidden }, prepareSearch: function(search) { if(!search) return return fuzzysort.prepareLowerCodes(search) }, // Below this point is only internal code // Below this point is only internal code // Below this point is only internal code // Below this point is only internal code getPrepared: function(target) { if(target.length > 999) return fuzzysort.prepare(target) // don't cache huge targets var targetPrepared = preparedCache.get(target) if(targetPrepared !== undefined) return targetPrepared targetPrepared = fuzzysort.prepare(target) preparedCache.set(target, targetPrepared) return targetPrepared }, getPreparedSearch: function(search) { if(search.length > 999) return fuzzysort.prepareSearch(search) // don't cache huge searches var searchPrepared = preparedSearchCache.get(search) if(searchPrepared !== undefined) return searchPrepared searchPrepared = fuzzysort.prepareSearch(search) preparedSearchCache.set(search, searchPrepared) return searchPrepared }, algorithm: function(searchLowerCodes, prepared, searchLowerCode) { var targetLowerCodes = prepared._targetLowerCodes var searchLen = searchLowerCodes.length var targetLen = targetLowerCodes.length var searchI = 0 // where we at var targetI = 0 // where you at var typoSimpleI = 0 var matchesSimpleLen = 0 // very basic fuzzy match; to remove non-matching targets ASAP! // walk through target. find sequential matches. // if all chars aren't found then exit for(;;) { var isMatch = searchLowerCode === targetLowerCodes[targetI] if(isMatch) { matchesSimple[matchesSimpleLen++] = targetI ++searchI; if(searchI === searchLen) break searchLowerCode = searchLowerCodes[typoSimpleI===0?searchI : (typoSimpleI===searchI?searchI+1 : (typoSimpleI===searchI-1?searchI-1 : searchI))] } ++targetI; if(targetI >= targetLen) { // Failed to find searchI // Check for typo or exit // we go as far as possible before trying to transpose // then we transpose backwards until we reach the beginning for(;;) { if(searchI <= 1) return null // not allowed to transpose first char if(typoSimpleI === 0) { // we haven't tried to transpose yet --searchI var searchLowerCodeNew = searchLowerCodes[searchI] if(searchLowerCode === searchLowerCodeNew) continue // doesn't make sense to transpose a repeat char typoSimpleI = searchI } else { if(typoSimpleI === 1) return null // reached the end of the line for transposing --typoSimpleI searchI = typoSimpleI searchLowerCode = searchLowerCodes[searchI + 1] var searchLowerCodeNew = searchLowerCodes[searchI] if(searchLowerCode === searchLowerCodeNew) continue // doesn't make sense to transpose a repeat char } matchesSimpleLen = searchI targetI = matchesSimple[matchesSimpleLen - 1] + 1 break } } } var searchI = 0 var typoStrictI = 0 var successStrict = false var matchesStrictLen = 0 var nextBeginningIndexes = prepared._nextBeginningIndexes if(nextBeginningIndexes === null) nextBeginningIndexes = prepared._nextBeginningIndexes = fuzzysort.prepareNextBeginningIndexes(prepared.target) var firstPossibleI = targetI = matchesSimple[0]===0 ? 0 : nextBeginningIndexes[matchesSimple[0]-1] // Our target string successfully matched all characters in sequence! // Let's try a more advanced and strict test to improve the score // only count it as a match if it's consecutive or a beginning character! if(targetI !== targetLen) for(;;) { if(targetI >= targetLen) { // We failed to find a good spot for this search char, go back to the previous search char and force it forward if(searchI <= 0) { // We failed to push chars forward for a better match // transpose, starting from the beginning ++typoStrictI; if(typoStrictI > searchLen-2) break if(searchLowerCodes[typoStrictI] === searchLowerCodes[typoStrictI+1]) continue // doesn't make sense to transpose a repeat char targetI = firstPossibleI continue } --searchI var lastMatch = matchesStrict[--matchesStrictLen] targetI = nextBeginningIndexes[lastMatch] } else { var isMatch = searchLowerCodes[typoStrictI===0?searchI : (typoStrictI===searchI?searchI+1 : (typoStrictI===searchI-1?searchI-1 : searchI))] === targetLowerCodes[targetI] if(isMatch) { matchesStrict[matchesStrictLen++] = targetI ++searchI; if(searchI === searchLen) { successStrict = true; break } ++targetI } else { targetI = nextBeginningIndexes[targetI] } } } { // tally up the score & keep track of matches for highlighting later if(successStrict) { var matchesBest = matchesStrict; var matchesBestLen = matchesStrictLen } else { var matchesBest = matchesSimple; var matchesBestLen = matchesSimpleLen } var score = 0 var lastTargetI = -1 for(var i = 0; i < searchLen; ++i) { var targetI = matchesBest[i] // score only goes down if they're not consecutive if(lastTargetI !== targetI - 1) score -= targetI lastTargetI = targetI } if(!successStrict) { score *= 1000 if(typoSimpleI !== 0) score += -20/*typoPenalty*/ } else { if(typoStrictI !== 0) score += -20/*typoPenalty*/ } score -= targetLen - searchLen prepared.score = score prepared.indexes = new Array(matchesBestLen); for(var i = matchesBestLen - 1; i >= 0; --i) prepared.indexes[i] = matchesBest[i] return prepared } }, algorithmNoTypo: function(searchLowerCodes, prepared, searchLowerCode) { var targetLowerCodes = prepared._targetLowerCodes var searchLen = searchLowerCodes.length var targetLen = targetLowerCodes.length var searchI = 0 // where we at var targetI = 0 // where you at var matchesSimpleLen = 0 // very basic fuzzy match; to remove non-matching targets ASAP! // walk through target. find sequential matches. // if all chars aren't found then exit for(;;) { var isMatch = searchLowerCode === targetLowerCodes[targetI] if(isMatch) { matchesSimple[matchesSimpleLen++] = targetI ++searchI; if(searchI === searchLen) break searchLowerCode = searchLowerCodes[searchI] } ++targetI; if(targetI >= targetLen) return null // Failed to find searchI } var searchI = 0 var successStrict = false var matchesStrictLen = 0 var nextBeginningIndexes = prepared._nextBeginningIndexes if(nextBeginningIndexes === null) nextBeginningIndexes = prepared._nextBeginningIndexes = fuzzysort.prepareNextBeginningIndexes(prepared.target) var firstPossibleI = targetI = matchesSimple[0]===0 ? 0 : nextBeginningIndexes[matchesSimple[0]-1] // Our target string successfully matched all characters in sequence! // Let's try a more advanced and strict test to improve the score // only count it as a match if it's consecutive or a beginning character! if(targetI !== targetLen) for(;;) { if(targetI >= targetLen) { // We failed to find a good spot for this search char, go back to the previous search char and force it forward if(searchI <= 0) break // We failed to push chars forward for a better match --searchI var lastMatch = matchesStrict[--matchesStrictLen] targetI = nextBeginningIndexes[lastMatch] } else { var isMatch = searchLowerCodes[searchI] === targetLowerCodes[targetI] if(isMatch) { matchesStrict[matchesStrictLen++] = targetI ++searchI; if(searchI === searchLen) { successStrict = true; break } ++targetI } else { targetI = nextBeginningIndexes[targetI] } } } { // tally up the score & keep track of matches for highlighting later if(successStrict) { var matchesBest = matchesStrict; var matchesBestLen = matchesStrictLen } else { var matchesBest = matchesSimple; var matchesBestLen = matchesSimpleLen } var score = 0 var lastTargetI = -1 for(var i = 0; i < searchLen; ++i) { var targetI = matchesBest[i] // score only goes down if they're not consecutive if(lastTargetI !== targetI - 1) score -= targetI lastTargetI = targetI } if(!successStrict) score *= 1000 score -= targetLen - searchLen prepared.score = score prepared.indexes = new Array(matchesBestLen); for(var i = matchesBestLen - 1; i >= 0; --i) prepared.indexes[i] = matchesBest[i] return prepared } }, prepareLowerCodes: function(str) { var strLen = str.length var lowerCodes = [] // new Array(strLen) sparse array is too slow var lower = str.toLowerCase() for(var i = 0; i < strLen; ++i) lowerCodes[i] = lower.charCodeAt(i) return lowerCodes }, prepareBeginningIndexes: function(target) { var targetLen = target.length var beginningIndexes = []; var beginningIndexesLen = 0 var wasUpper = false var wasAlphanum = false for(var i = 0; i < targetLen; ++i) { var targetCode = target.charCodeAt(i) var isUpper = targetCode>=65&&targetCode<=90 var isAlphanum = isUpper || targetCode>=97&&targetCode<=122 || targetCode>=48&&targetCode<=57 var isBeginning = isUpper && !wasUpper || !wasAlphanum || !isAlphanum wasUpper = isUpper wasAlphanum = isAlphanum if(isBeginning) beginningIndexes[beginningIndexesLen++] = i } return beginningIndexes }, prepareNextBeginningIndexes: function(target) { var targetLen = target.length var beginningIndexes = fuzzysort.prepareBeginningIndexes(target) var nextBeginningIndexes = [] // new Array(targetLen) sparse array is too slow var lastIsBeginning = beginningIndexes[0] var lastIsBeginningI = 0 for(var i = 0; i < targetLen; ++i) { if(lastIsBeginning > i) { nextBeginningIndexes[i] = lastIsBeginning } else { lastIsBeginning = beginningIndexes[++lastIsBeginningI] nextBeginningIndexes[i] = lastIsBeginning===undefined ? targetLen : lastIsBeginning } } return nextBeginningIndexes }, cleanup: cleanup, new: fuzzysortNew, } return fuzzysort } // fuzzysortNew // This stuff is outside fuzzysortNew, because it's shared with instances of fuzzysort.new() var isNode = typeof require !== 'undefined' && typeof window === 'undefined' // var MAX_INT = Number.MAX_SAFE_INTEGER // var MIN_INT = Number.MIN_VALUE var preparedCache = new Map() var preparedSearchCache = new Map() var noResults = []; noResults.total = 0 var matchesSimple = []; var matchesStrict = [] function cleanup() { preparedCache.clear(); preparedSearchCache.clear(); matchesSimple = []; matchesStrict = [] } function defaultScoreFn(a) { var max = -9007199254740991 for (var i = a.length - 1; i >= 0; --i) { var result = a[i]; if(result === null) continue var score = result.score if(score > max) max = score } if(max === -9007199254740991) return null return max } // prop = 'key' 2.5ms optimized for this case, seems to be about as fast as direct obj[prop] // prop = 'key1.key2' 10ms // prop = ['key1', 'key2'] 27ms function getValue(obj, prop) { var tmp = obj[prop]; if(tmp !== undefined) return tmp var segs = prop if(!Array.isArray(prop)) segs = prop.split('.') var len = segs.length var i = -1 while (obj && (++i < len)) obj = obj[segs[i]] return obj } function isObj(x) { return typeof x === 'object' } // faster as a function // Hacked version of https://github.com/lemire/FastPriorityQueue.js var fastpriorityqueue=function(){var r=[],o=0,e={};function n(){for(var e=0,n=r[e],c=1;c>1]=r[e],c=1+(e<<1)}for(var a=e-1>>1;e>0&&n.score>1)r[e]=r[a];r[e]=n}return e.add=function(e){var n=o;r[o++]=e;for(var c=n-1>>1;n>0&&e.score>1)r[n]=r[c];r[n]=e},e.poll=function(){if(0!==o){var e=r[0];return r[0]=r[--o],n(),e}},e.peek=function(e){if(0!==o)return r[0]},e.replaceTop=function(o){r[0]=o,n()},e}; var q = fastpriorityqueue() // reuse this, except for async, it needs to make its own return fuzzysortNew() }) // UMD // TODO: (performance) wasm version!? // TODO: (performance) layout memory in an optimal way to go fast by avoiding cache misses // TODO: (performance) preparedCache is a memory leak // TODO: (like sublime) backslash === forwardslash // TODO: (performance) i have no idea how well optizmied the allowing typos algorithm is fuzzysort-1.1.1/index.d.ts000066400000000000000000000057361326113427300155270ustar00rootroot00000000000000declare namespace Fuzzysort { interface Result { /** * Higher is better * * 0 is a perfect match; -1000 is a bad match */ readonly score: number /** Your original target string */ readonly target: string /** Indexes of the matching target characters */ readonly indexes: number[] } interface Results extends ReadonlyArray { /** Total matches before limit */ readonly total: number } interface KeyResult extends Result { /** Your original object */ readonly obj: any } interface KeyResults extends ReadonlyArray { /** Total matches before limit */ readonly total: number } interface KeysResults extends ReadonlyArray> { /** * Higher is better * * 0 is a perfect match; -1000 is a bad match */ readonly score: number /** Your original object */ readonly obj: any /** Total matches before limit */ readonly total: number } interface Prepared { /** Your original target string */ readonly target: string } interface CancelablePromise extends Promise { cancel(): void } interface Options { /** Don't return matches worse than this (higher is faster) */ threshold?: number /** Don't return more results than this (lower is faster) */ limit?: number /** Allwos a snigle transpoes (false is faster) */ allowTypo?: boolean } interface KeyOptions extends Options { key: string | ReadonlyArray } interface KeysOptions extends Options { keys: ReadonlyArray> scoreFn?: (keysResult:ReadonlyArray) => number } interface Fuzzysort { /** * Help the algorithm go fast by providing prepared targets instead of raw strings */ prepare(target: string): Prepared | undefined highlight(result?: Result, highlightOpen?: string, highlightClose?: string): string | null single(search: string, target: string | Prepared): Result | null go(search: string, targets: (string | Prepared | undefined)[], options?: Options): Results go(search: string, targets: (string | Prepared | undefined)[], options: KeyOptions): KeyResults go(search: string, targets: (string | Prepared | undefined)[], options: KeysOptions): KeysResults goAsync(search: string, targets: (string | Prepared | undefined)[], options?: Options): CancelablePromise goAsync(search: string, targets: (string | Prepared | undefined)[], options: KeyOptions): CancelablePromise goAsync(search: string, targets: (string | Prepared | undefined)[], options: KeysOptions): CancelablePromise /** Returns a new instance of fuzzysort, which you can give different default options to */ 'new'(options?: Options): Fuzzysort } } declare module "fuzzysort" { const fuzzysort:Fuzzysort.Fuzzysort export = fuzzysort } fuzzysort-1.1.1/package.json000066400000000000000000000006171326113427300161050ustar00rootroot00000000000000{ "name": "fuzzysort", "version": "1.1.1", "description": "", "main": "fuzzysort.js", "scripts": { "test": "node test.js", "build": "uglifyjs fuzzysort.js -m -c -o fuzzysort.min.js" }, "author": "farzher", "repository": { "type": "git", "url": "https://github.com/farzher/fuzzysort.git" }, "license": "ISC", "devDependencies": { "benchmark": "^2.1.4" } }