Last active
April 27, 2022 13:21
-
-
Save exsesx/6bb8d2761bef2f52666e1219454731fe to your computer and use it in GitHub Desktop.
Javascript String utilities. Can be used to calculate similarity of two strings by Levenshtein distance algorithm or Dice's Coefficient.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const editDistance = Symbol('editDistance') | |
| const Levenshtein = Symbol('Levenshtein') | |
| const Dice = Symbol('Dice') | |
| class StringUtils { | |
| static get similarityAlgorithms () { return ['Levenshtein', 'Dice'] } | |
| /** | |
| * @param {string} firstString | |
| * @param {string} secondString | |
| * @param {('Levenshtein'|'Dice')} algorithm | |
| */ | |
| static similarity (firstString, secondString, algorithm) { | |
| if (!algorithm) { | |
| throw new Error('Similarity algorithm required') | |
| } | |
| switch (algorithm) { | |
| case 'Levenshtein': | |
| return this[Levenshtein](firstString, secondString) | |
| case 'Dice': | |
| return this[Dice](firstString, secondString) | |
| default: | |
| throw new TypeError(`Invalid algorithm. Expected ${this.similarityAlgorithms}, but found ${algorithm}`) | |
| } | |
| } | |
| // Finds degree of similarity between two strings, based on Levenshtein distance | |
| static [Levenshtein] (s1, s2) { | |
| let longer = s1 | |
| let shorter = s2 | |
| if (s1.length < s2.length) { | |
| longer = s2 | |
| shorter = s1 | |
| } | |
| let longerLength = longer.length | |
| if (longerLength === 0) { | |
| return 1.0 | |
| } | |
| return (longerLength - this[editDistance](longer, shorter)) / parseFloat(longerLength) | |
| } | |
| static [editDistance] (s1, s2) { | |
| s1 = s1.toLowerCase() | |
| s2 = s2.toLowerCase() | |
| let costs = [] | |
| for (let i = 0; i <= s1.length; i++) { | |
| let lastValue = i | |
| for (let j = 0; j <= s2.length; j++) { | |
| if (i === 0) { costs[j] = j } else { | |
| if (j > 0) { | |
| let newValue = costs[j - 1] | |
| if (s1.charAt(i - 1) !== s2.charAt(j - 1)) { | |
| newValue = Math.min(Math.min(newValue, lastValue), | |
| costs[j]) + 1 | |
| } | |
| costs[j - 1] = lastValue | |
| lastValue = newValue | |
| } | |
| } | |
| } | |
| if (i > 0) { costs[s2.length] = lastValue } | |
| } | |
| return costs[s2.length] | |
| } | |
| // Finds degree of similarity between two strings, based on Dice's Coefficient, which is mostly better than Levenshtein distance. | |
| static [Dice] (first, second) { | |
| first = first.replace(/\s+/g, '') | |
| second = second.replace(/\s+/g, '') | |
| if (!first.length && !second.length) return 1 // if both are empty strings | |
| if (!first.length || !second.length) return 0 // if only one is empty string | |
| if (first === second) return 1 // identical | |
| if (first.length === 1 && second.length === 1) return 0 // both are 1-letter strings | |
| if (first.length < 2 || second.length < 2) return 0 // if either is a 1-letter string | |
| let firstBigrams = new Map() | |
| for (let i = 0; i < first.length - 1; i++) { | |
| const bigram = first.substr(i, 2) | |
| const count = firstBigrams.has(bigram) | |
| ? firstBigrams.get(bigram) + 1 | |
| : 1 | |
| firstBigrams.set(bigram, count) | |
| }; | |
| let intersectionSize = 0 | |
| for (let i = 0; i < second.length - 1; i++) { | |
| const bigram = second.substr(i, 2) | |
| const count = firstBigrams.has(bigram) | |
| ? firstBigrams.get(bigram) | |
| : 0 | |
| if (count > 0) { | |
| firstBigrams.set(bigram, count - 1) | |
| intersectionSize++ | |
| } | |
| } | |
| return (2.0 * intersectionSize) / (first.length + second.length - 2) | |
| } | |
| } | |
| module.exports = StringUtils |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment