fuzzy-tools
Version:
Functions for fuzzy matching and items filtering
97 lines (87 loc) • 2.88 kB
JavaScript
import { defaultOptions, isFunction } from './utils';
function computeScore(begin, end, fullLength, wordNumber) {
const wordLen = end - begin + 1;
const kd = (1 / fullLength) * wordLen;
const kp = begin || 0.001;
const kw = 1 + (1 / fullLength) * wordNumber;
return kd * kp * kw;
}
export function matchString(what, where, options) {
if (!what || !where) return null;
const {
caseSensitive,
withScore,
withWrapper,
withRanges
} = defaultOptions(options);
const isWords = Array.isArray(what);
if (isWords && what.length == 0) return null;
const preparedWhat = caseSensitive
? (isWords ? what : String(what))
: (isWords ? what.map(it => String(it).toLocaleLowerCase()) : String(what).toLocaleLowerCase());
const originalWhere = String(where);
if (!preparedWhat || !originalWhere || (!isWords && preparedWhat.length > originalWhere.length)) {
return null;
}
// preparedWhere will be undefined if caseSensitive is true, it is needed to save memory
const preparedWhere = caseSensitive ? undefined : originalWhere.toLocaleLowerCase();
let wrapped = null;
let ranges = null;
let chunkBegin = 0;
let scoreList = [];
const wrapperFunc =
!withWrapper || isFunction(withWrapper)
? withWrapper
: (w) => withWrapper.replace('{?}', w);
const wordAction = (prev, next) => {
if (prev < 0) {
if (withWrapper) {
wrapped = next > 0 ? originalWhere.slice(0, next) : '';
}
if (withRanges) {
ranges = [];
}
chunkBegin = next;
} else if (next - prev > 1) {
if (withWrapper) {
const chunk = originalWhere.slice(chunkBegin, prev + 1);
wrapped += wrapperFunc(chunk) + originalWhere.slice(prev + 1, next);
}
if (withRanges) {
ranges.push({
begin: chunkBegin,
end: Math.min(prev, originalWhere.length - 1)
});
}
if (withScore) {
scoreList.push(
computeScore(chunkBegin, prev, preparedWhat.length, scoreList.length)
);
}
chunkBegin = next;
}
};
let pos = -1;
for (let i = 0; i < preparedWhat.length; i++) {
const chunk = isWords ? preparedWhat[i] : preparedWhat.charAt(i);
let nextPos = (preparedWhere || originalWhere).indexOf(chunk, pos + 1);
if (nextPos < 0) return null;
if (isWords && chunk.length > 1) {
wordAction(pos, nextPos);
nextPos = nextPos + chunk.length - 1;
pos = nextPos - 1;
}
wordAction(pos, nextPos);
pos = nextPos;
}
wordAction(pos, pos + originalWhere.length);
return Object.assign(
{
score: withScore
? scoreList.reduce((p, c) => p + c, 0)
: 1
},
withWrapper ? { wrapped } : {},
withRanges ? { ranges } : {}
);
}