UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

132 lines (108 loc) 3.85 kB
import { assert } from "../../assert.js"; import { BitSet } from "../../binary/BitSet.js"; import { max2 } from "../../math/max2.js"; import { min2 } from "../../math/min2.js"; /** * Calculate Jaro distance between two strings, this is a measure of string similarity. * Higher value means more similarity. * @param {string} first * @param {string} second * @param {number} first_length * @param {number} second_length * @return {number} */ export function string_jaro_distance( first, second, first_length, second_length ) { assert.isString(first,'first'); assert.isString(second,'second'); // TODO can do better for short strings under 32 characters by using int32 bitmask. This would avoid allocation entirely const matches1 = BitSet.fixedSize(first_length); const matches2 = BitSet.fixedSize(second_length); const matches = getMatching(first, second, matches1, matches2); if (matches <= 0) { return 0; } const transpositions = getTranspositions(first, second, matches1, matches2); return (matches / first_length + matches / second_length + (matches - transpositions) / matches) / 3; } /** * Find matching characters in both strings according to Jaro algorithm * @param {string} a1 * @param {string} a2 * @param {BitSet} matches1 * @param {BitSet} matches2 * @return {number} */ function getMatching(a1, a2, matches1, matches2) { const a1_length = a1.length; const a2_length = a2.length; // Window is modified to work with string of length 1 const matchWindow = max2( 0, Math.floor(max2(a1_length, a2_length) * 0.5) - 1 ); let matches = 0; // Loop to find matched characters: for (let index1 = 0; index1 < a1_length; index1++) { // Use the highest of the window diff and the min of the window and string 2 length: const start = max2(0, index1 - matchWindow); const end = min2(index1 + matchWindow + 1, a2_length); // Iterate second string index: for (let index2 = start; index2 < end; index2++) { // If second string character already matched, skip: if (matches2.get(index2)) { continue; } // If the characters don't match, skip: if (a1.charAt(index1) !== a2.charAt(index2)) { continue; } // Assume match if the above 2 checks don't continue: matches1.set(index1, true); matches2.set(index2, true); // Add matches by 1, break inner loop: ++matches; break; } } return matches; } /** * Calculate the number of transpositions between the two words * @param {string} a1 The first string to compare * @param {string} a2 The second string to compare * @param {BitSet} matches1 * @param {BitSet} matches2 * @returns {number} */ function getTranspositions( a1, a2, matches1, matches2 ) { let transpositions = 0; // Loop to find transpositions: const a1_length = a1.length; const a2_length = a2.length; for (let i1 = 0, i2 = 0; i1 < a1_length; i1++) { // If a non-matching character was found, skip: if (matches1.get(i1) === false) { continue; } // Move i2 index to the next match: while ( i2 < a2_length && matches2.get(i2) === false ) { i2++; } // If the characters don't match, increase transposition: if (a1.charAt(i1) !== a2.charAt(i2)) { transpositions++; } // Iterate i2 index normally: i2++; } return Math.floor(transpositions * 0.5); }