Skip to content

Instantly share code, notes, and snippets.

@loonix
Created April 18, 2019 11:52
Show Gist options
  • Save loonix/8576e960b96e6e9289820856106d5045 to your computer and use it in GitHub Desktop.
Save loonix/8576e960b96e6e9289820856106d5045 to your computer and use it in GitHub Desktop.
Find Best Match Typescript
import { TestBed, inject } from '@angular/core/testing';
import { FindMatchService } from './find-match.service';
describe('FindMatchService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [FindMatchService]
});
});
it('should be created', inject(
[FindMatchService],
(service: FindMatchService) => {
expect(service).toBeTruthy();
}
));
describe('findBestMatch', () => {
const service: FindMatchService = this.FindMatchService;
const findBestMatch = service.findBestMatch;
const badArgsErrorMsg =
'Bad arguments: First argument should be a string, second should be an array of strings';
it('is a function', () => {
expect(typeof findBestMatch).toBe('function');
});
it('accepts a string and an array of strings and returns an object', () => {
const output = findBestMatch('one', ['two', 'three']);
expect(typeof output).toBe('object');
});
it('throws a \'Bad arguments\' error if second argument is not an array with at least one element', () => {
expect(() => {
findBestMatch('hello', 'something');
}).toThrowError(badArgsErrorMsg);
expect(() => {
findBestMatch('hello', []);
}).toThrowError(badArgsErrorMsg);
});
it('throws a \'Bad arguments\' error if second argument is not an array of strings', () => {
expect(() => {
findBestMatch('hello', [2, 'something']);
}).toThrowError(badArgsErrorMsg);
});
it('assigns a similarity rating to each string passed in the array', () => {
const matches = findBestMatch('healed', [
'mailed',
'edward',
'sealed',
'theatre'
]);
expect(matches.ratings).toEqual([
{ target: 'mailed', rating: 0.4 },
{ target: 'edward', rating: 0.2 },
{ target: 'sealed', rating: 0.8 },
{ target: 'theatre', rating: 0.36363636363636365 }
]);
});
it('returns the best match and its similarity rating', () => {
const matches = findBestMatch('healed', [
'mailed',
'edward',
'sealed',
'theatre'
]);
expect(matches.bestMatch).toEqual({ target: 'sealed', rating: 0.8 });
});
it('returns the index of best match from the target strings', () => {
const matches = findBestMatch('healed', [
'mailed',
'edward',
'sealed',
'theatre'
]);
expect(matches.bestMatchIndex).toBe(2);
});
});
});
import { Injectable } from '@angular/core';
@Injectable()
export class FindMatchService {
constructor() {}
findBestMatch(mainString, targetStrings): any {
if (!this.areArgsValid(mainString, targetStrings)) {
throw new Error(
'Bad arguments: First argument should be a string, second should be an array of strings'
);
}
const ratings = [];
let bestMatchIndex = 0;
for (let i = 0; i < targetStrings.length; i++) {
const currentTargetString = targetStrings[i];
const currentRating = this.compareTwoStrings(
mainString,
currentTargetString
);
ratings.push({ target: currentTargetString, rating: currentRating });
if (currentRating > ratings[bestMatchIndex].rating) {
bestMatchIndex = i;
}
}
const bestMatch = ratings[bestMatchIndex];
// return { ratings, bestMatch, bestMatchIndex };
return ratings[bestMatchIndex].target;
}
areArgsValid(mainString, targetStrings): any {
if (typeof mainString !== 'string') return false;
if (!Array.isArray(targetStrings)) return false;
if (!targetStrings.length) return false;
if (targetStrings.find(s => typeof s !== 'string')) return false;
return true;
}
compareTwoStrings(first, second): any {
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
const 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);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment