Lint and format src/compiler/utils/fuzzymatch.js

pull/8569/head
Simon Holthausen 3 years ago
parent 546e43598b
commit 5b130d1b85

@ -3,9 +3,9 @@
* @param {string[]} names * @param {string[]} names
*/ */
export default function fuzzymatch(name, names) { export default function fuzzymatch(name, names) {
const set = new FuzzySet(names); const set = new FuzzySet(names);
const matches = set.get(name); const matches = set.get(name);
return matches && matches[0] && matches[0][0] > 0.7 ? matches[0][1] : null; return matches && matches[0] && matches[0][0] > 0.7 ? matches[0][1] : null;
} }
// adapted from https://github.com/Glench/fuzzyset.js/blob/master/lib/fuzzyset.js // adapted from https://github.com/Glench/fuzzyset.js/blob/master/lib/fuzzyset.js
// BSD Licensed // BSD Licensed
@ -18,20 +18,18 @@ const GRAM_SIZE_UPPER = 3;
* @param {string} str2 * @param {string} str2
*/ */
function _distance(str1, str2) { function _distance(str1, str2) {
if (str1 === null && str2 === null) { if (str1 === null && str2 === null) {
throw 'Trying to compare two null values'; throw 'Trying to compare two null values';
} }
if (str1 === null || str2 === null) if (str1 === null || str2 === null) return 0;
return 0; str1 = String(str1);
str1 = String(str1); str2 = String(str2);
str2 = String(str2); const distance = levenshtein(str1, str2);
const distance = levenshtein(str1, str2); if (str1.length > str2.length) {
if (str1.length > str2.length) { return 1 - distance / str1.length;
return 1 - distance / str1.length; } else {
} return 1 - distance / str2.length;
else { }
return 1 - distance / str2.length;
}
} }
// helper functions // helper functions
@ -40,31 +38,28 @@ function _distance(str1, str2) {
* @param {string} str2 * @param {string} str2
*/ */
function levenshtein(str1, str2) { function levenshtein(str1, str2) {
/**
/** * @type {number[]}
* @type {number[]} */
*/ const current = [];
const current = []; let prev;
let prev; let value;
let value; for (let i = 0; i <= str2.length; i++) {
for (let i = 0; i <= str2.length; i++) { for (let j = 0; j <= str1.length; j++) {
for (let j = 0; j <= str1.length; j++) { if (i && j) {
if (i && j) { if (str1.charAt(j - 1) === str2.charAt(i - 1)) {
if (str1.charAt(j - 1) === str2.charAt(i - 1)) { value = prev;
value = prev; } else {
} value = Math.min(current[j], current[j - 1], prev) + 1;
else { }
value = Math.min(current[j], current[j - 1], prev) + 1; } else {
} value = i + j;
} }
else { prev = current[j];
value = i + j; current[j] = value;
} }
prev = current[j]; }
current[j] = value; return current.pop();
}
}
return current.pop();
} }
const non_word_regex = /[^\w, ]+/; const non_word_regex = /[^\w, ]+/;
@ -73,18 +68,18 @@ const non_word_regex = /[^\w, ]+/;
* @param {any} gram_size * @param {any} gram_size
*/ */
function iterate_grams(value, gram_size = 2) { function iterate_grams(value, gram_size = 2) {
const simplified = '-' + value.toLowerCase().replace(non_word_regex, '') + '-'; const simplified = '-' + value.toLowerCase().replace(non_word_regex, '') + '-';
const len_diff = gram_size - simplified.length; const len_diff = gram_size - simplified.length;
const results = []; const results = [];
if (len_diff > 0) { if (len_diff > 0) {
for (let i = 0; i < len_diff; ++i) { for (let i = 0; i < len_diff; ++i) {
value += '-'; value += '-';
} }
} }
for (let i = 0; i < simplified.length - gram_size + 1; ++i) { for (let i = 0; i < simplified.length - gram_size + 1; ++i) {
results.push(simplified.slice(i, i + gram_size)); results.push(simplified.slice(i, i + gram_size));
} }
return results; return results;
} }
/** /**
@ -92,19 +87,18 @@ function iterate_grams(value, gram_size = 2) {
* @param {any} gram_size * @param {any} gram_size
*/ */
function gram_counter(value, gram_size = 2) { function gram_counter(value, gram_size = 2) {
// return an object where key=gram, value=number of occurrences // return an object where key=gram, value=number of occurrences
const result = {}; const result = {};
const grams = iterate_grams(value, gram_size); const grams = iterate_grams(value, gram_size);
let i = 0; let i = 0;
for (i; i < grams.length; ++i) { for (i; i < grams.length; ++i) {
if (grams[i] in result) { if (grams[i] in result) {
result[grams[i]] += 1; result[grams[i]] += 1;
} } else {
else { result[grams[i]] = 1;
result[grams[i]] = 1; }
} }
} return result;
return result;
} }
/** /**
@ -112,157 +106,150 @@ function gram_counter(value, gram_size = 2) {
* @param {any} b * @param {any} b
*/ */
function sort_descending(a, b) { function sort_descending(a, b) {
return b[0] - a[0]; return b[0] - a[0];
} }
/** */ /** */
class FuzzySet { class FuzzySet {
/**
/** * @default {}
* @default {} */
*/ exact_set = {};
exact_set = {};
/**
/** * @default {}
* @default {} */
*/ match_dict = {};
match_dict = {};
/**
/** * @default {}
* @default {} */
*/ items = {};
items = {}; constructor(arr) {
constructor(arr) { // initialization
// initialization for (let i = GRAM_SIZE_LOWER; i < GRAM_SIZE_UPPER + 1; ++i) {
for (let i = GRAM_SIZE_LOWER; i < GRAM_SIZE_UPPER + 1; ++i) { this.items[i] = [];
this.items[i] = []; }
} // add all the items to the set
// add all the items to the set for (let i = 0; i < arr.length; ++i) {
for (let i = 0; i < arr.length; ++i) { this.add(arr[i]);
this.add(arr[i]); }
} }
}
/**
/** * @param {string} value
* @param {string} value */
*/ add(value) {
add(value) { const normalized_value = value.toLowerCase();
const normalized_value = value.toLowerCase(); if (normalized_value in this.exact_set) {
if (normalized_value in this.exact_set) { return false;
return false; }
} let i = GRAM_SIZE_LOWER;
let i = GRAM_SIZE_LOWER; for (i; i < GRAM_SIZE_UPPER + 1; ++i) {
for (i; i < GRAM_SIZE_UPPER + 1; ++i) { this._add(value, i);
this._add(value, i); }
} }
}
/**
/** * @param {string} value
* @param {string} value * @param {number} gram_size
* @param {number} gram_size */
*/ _add(value, gram_size) {
_add(value, gram_size) { const normalized_value = value.toLowerCase();
const normalized_value = value.toLowerCase(); const items = this.items[gram_size] || [];
const items = this.items[gram_size] || []; const index = items.length;
const index = items.length; items.push(0);
items.push(0); const gram_counts = gram_counter(normalized_value, gram_size);
const gram_counts = gram_counter(normalized_value, gram_size); let sum_of_square_gram_counts = 0;
let sum_of_square_gram_counts = 0; let gram;
let gram; let gram_count;
let gram_count; for (gram in gram_counts) {
for (gram in gram_counts) { gram_count = gram_counts[gram];
gram_count = gram_counts[gram]; sum_of_square_gram_counts += Math.pow(gram_count, 2);
sum_of_square_gram_counts += Math.pow(gram_count, 2); if (gram in this.match_dict) {
if (gram in this.match_dict) { this.match_dict[gram].push([index, gram_count]);
this.match_dict[gram].push([index, gram_count]); } else {
} this.match_dict[gram] = [[index, gram_count]];
else { }
this.match_dict[gram] = [[index, gram_count]]; }
} const vector_normal = Math.sqrt(sum_of_square_gram_counts);
} items[index] = [vector_normal, normalized_value];
const vector_normal = Math.sqrt(sum_of_square_gram_counts); this.items[gram_size] = items;
items[index] = [vector_normal, normalized_value]; this.exact_set[normalized_value] = value;
this.items[gram_size] = items; }
this.exact_set[normalized_value] = value;
} /**
* @param {string} value
/** */
* @param {string} value get(value) {
*/ const normalized_value = value.toLowerCase();
get(value) { const result = this.exact_set[normalized_value];
const normalized_value = value.toLowerCase(); if (result) {
const result = this.exact_set[normalized_value]; return [[1, result]];
if (result) { }
return [[1, result]]; let results = [];
} // start with high gram size and if there are no results, go to lower gram sizes
let results = []; for (let gram_size = GRAM_SIZE_UPPER; gram_size >= GRAM_SIZE_LOWER; --gram_size) {
// start with high gram size and if there are no results, go to lower gram sizes results = this.__get(value, gram_size);
for (let gram_size = GRAM_SIZE_UPPER; gram_size >= GRAM_SIZE_LOWER; --gram_size) { if (results) {
results = this.__get(value, gram_size); return results;
if (results) { }
return results; }
} return null;
} }
return null;
} /**
* @param {string} value
/** * @param {number} gram_size
* @param {string} value */
* @param {number} gram_size __get(value, gram_size) {
*/ const normalized_value = value.toLowerCase();
__get(value, gram_size) { const matches = {};
const normalized_value = value.toLowerCase(); const gram_counts = gram_counter(normalized_value, gram_size);
const matches = {}; const items = this.items[gram_size];
const gram_counts = gram_counter(normalized_value, gram_size); let sum_of_square_gram_counts = 0;
const items = this.items[gram_size]; let gram;
let sum_of_square_gram_counts = 0; let gram_count;
let gram; let i;
let gram_count; let index;
let i; let other_gram_count;
let index; for (gram in gram_counts) {
let other_gram_count; gram_count = gram_counts[gram];
for (gram in gram_counts) { sum_of_square_gram_counts += Math.pow(gram_count, 2);
gram_count = gram_counts[gram]; if (gram in this.match_dict) {
sum_of_square_gram_counts += Math.pow(gram_count, 2); for (i = 0; i < this.match_dict[gram].length; ++i) {
if (gram in this.match_dict) { index = this.match_dict[gram][i][0];
for (i = 0; i < this.match_dict[gram].length; ++i) { other_gram_count = this.match_dict[gram][i][1];
index = this.match_dict[gram][i][0]; if (index in matches) {
other_gram_count = this.match_dict[gram][i][1]; matches[index] += gram_count * other_gram_count;
if (index in matches) { } else {
matches[index] += gram_count * other_gram_count; matches[index] = gram_count * other_gram_count;
} }
else { }
matches[index] = gram_count * other_gram_count; }
} }
} const vector_normal = Math.sqrt(sum_of_square_gram_counts);
} let results = [];
} let match_score;
const vector_normal = Math.sqrt(sum_of_square_gram_counts); // build a results list of [score, str]
let results = []; for (const match_index in matches) {
let match_score; match_score = matches[match_index];
// build a results list of [score, str] results.push([match_score / (vector_normal * items[match_index][0]), items[match_index][1]]);
for (const match_index in matches) { }
match_score = matches[match_index]; results.sort(sort_descending);
results.push([match_score / (vector_normal * items[match_index][0]), items[match_index][1]]); let new_results = [];
} const end_index = Math.min(50, results.length);
results.sort(sort_descending); // truncate somewhat arbitrarily to 50
let new_results = []; for (let i = 0; i < end_index; ++i) {
const end_index = Math.min(50, results.length); new_results.push([_distance(results[i][1], normalized_value), results[i][1]]);
// truncate somewhat arbitrarily to 50 }
for (let i = 0; i < end_index; ++i) { results = new_results;
new_results.push([_distance(results[i][1], normalized_value), results[i][1]]); results.sort(sort_descending);
} new_results = [];
results = new_results; for (let i = 0; i < results.length; ++i) {
results.sort(sort_descending); if (results[i][0] == results[0][0]) {
new_results = []; new_results.push([results[i][0], this.exact_set[results[i][1]]]);
for (let i = 0; i < results.length; ++i) { }
if (results[i][0] == results[0][0]) { }
new_results.push([results[i][0], this.exact_set[results[i][1]]]); return new_results;
} }
}
return new_results;
}
} }

Loading…
Cancel
Save