choices.js
Version:
A vanilla JS customisable text input/select box plugin
88 lines (73 loc) • 2.17 kB
text/typescript
import { Options } from '../interfaces';
import { Searcher, SearchResult } from '../interfaces/search';
function kmpSearch(pattern: string, text: string): number {
if (pattern.length === 0) {
return 0; // Immediate match
}
// Compute longest suffix-prefix table
const lsp = [0]; // Base case
for (let i = 1; i < pattern.length; i++) {
let j = lsp[i - 1]; // Start by assuming we're extending the previous LSP
while (j > 0 && pattern.charAt(i) !== pattern.charAt(j)) {
j = lsp[j - 1];
}
if (pattern.charAt(i) === pattern.charAt(j)) {
j++;
}
lsp.push(j);
}
// Walk through text string
let j = 0; // Number of chars matched in pattern
for (let i = 0; i < text.length; i++) {
while (j > 0 && text.charAt(i) !== pattern.charAt(j)) {
j = lsp[j - 1]; // Fall back in the pattern
}
if (text.charAt(i) === pattern.charAt(j)) {
j++; // Next char matched, increment position
if (j === pattern.length) {
return i - (j - 1);
}
}
}
return -1; // Not found
}
export class SearchByKMP<T extends object> implements Searcher<T> {
_fields: string[];
_haystack: T[] = [];
constructor(config: Options) {
this._fields = config.searchFields;
}
index(data: T[]): void {
this._haystack = data;
}
reset(): void {
this._haystack = [];
}
isEmptyIndex(): boolean {
return !this._haystack.length;
}
search(_needle: string): SearchResult<T>[] {
const fields = this._fields;
if (!fields || !fields.length || !_needle) {
return [];
}
const needle = _needle.toLowerCase();
const results: SearchResult<T>[] = [];
let count = 0;
for (let i = 0, j = this._haystack.length; i < j; i++) {
const obj = this._haystack[i];
for (let k = 0, l = this._fields.length; k < l; k++) {
const field = this._fields[k];
if (field in obj && kmpSearch(needle, (obj[field] as string).toLowerCase()) !== -1) {
results.push({
item: obj[field],
score: count,
rank: count + 1,
});
count++;
}
}
}
return results;
}
}