feat(monorepo): migrate vue packages and apply oxlint refactors
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
export * from './levenshtein-distance';
|
||||
export * from './trigram-distance';
|
||||
// TODO: Template is not implemented yet
|
||||
// export * from './template';
|
||||
// export * from './template';
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {levenshteinDistance} from '.';
|
||||
|
||||
describe('levenshteinDistance', () => {
|
||||
it('calculate edit distance between two strings', () => {
|
||||
// just one substitution I at the beginning
|
||||
expect(levenshteinDistance('islander', 'slander')).toBe(1);
|
||||
|
||||
// substitution M->K, T->M and add an A to the end
|
||||
expect(levenshteinDistance('mart', 'karma')).toBe(3);
|
||||
|
||||
// substitution K->S, E->I and insert G at the end
|
||||
expect(levenshteinDistance('kitten', 'sitting')).toBe(3);
|
||||
|
||||
// should add 4 letters FOOT at the beginning
|
||||
expect(levenshteinDistance('ball', 'football')).toBe(4);
|
||||
|
||||
// should delete 4 letters FOOT at the beginning
|
||||
expect(levenshteinDistance('football', 'foot')).toBe(4);
|
||||
|
||||
// needs to substitute the first 5 chars INTEN->EXECU
|
||||
expect(levenshteinDistance('intention', 'execution')).toBe(5);
|
||||
});
|
||||
import { levenshteinDistance } from '.';
|
||||
|
||||
it('handle empty strings', () => {
|
||||
expect(levenshteinDistance('', '')).toBe(0);
|
||||
expect(levenshteinDistance('a', '')).toBe(1);
|
||||
expect(levenshteinDistance('', 'a')).toBe(1);
|
||||
expect(levenshteinDistance('abc', '')).toBe(3);
|
||||
expect(levenshteinDistance('', 'abc')).toBe(3);
|
||||
});
|
||||
});
|
||||
describe('levenshteinDistance', () => {
|
||||
it('calculate edit distance between two strings', () => {
|
||||
// just one substitution I at the beginning
|
||||
expect(levenshteinDistance('islander', 'slander')).toBe(1);
|
||||
|
||||
// substitution M->K, T->M and add an A to the end
|
||||
expect(levenshteinDistance('mart', 'karma')).toBe(3);
|
||||
|
||||
// substitution K->S, E->I and insert G at the end
|
||||
expect(levenshteinDistance('kitten', 'sitting')).toBe(3);
|
||||
|
||||
// should add 4 letters FOOT at the beginning
|
||||
expect(levenshteinDistance('ball', 'football')).toBe(4);
|
||||
|
||||
// should delete 4 letters FOOT at the beginning
|
||||
expect(levenshteinDistance('football', 'foot')).toBe(4);
|
||||
|
||||
// needs to substitute the first 5 chars INTEN->EXECU
|
||||
expect(levenshteinDistance('intention', 'execution')).toBe(5);
|
||||
});
|
||||
|
||||
it('handle empty strings', () => {
|
||||
expect(levenshteinDistance('', '')).toBe(0);
|
||||
expect(levenshteinDistance('a', '')).toBe(1);
|
||||
expect(levenshteinDistance('', 'a')).toBe(1);
|
||||
expect(levenshteinDistance('abc', '')).toBe(3);
|
||||
expect(levenshteinDistance('', 'abc')).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @name levenshteinDistance
|
||||
* @category Text
|
||||
* @description Calculate the Levenshtein distance between two strings
|
||||
*
|
||||
*
|
||||
* @param {string} left First string
|
||||
* @param {string} right Second string
|
||||
* @returns {number} The Levenshtein distance between the two strings
|
||||
@@ -10,37 +10,37 @@
|
||||
* @since 0.0.1
|
||||
*/
|
||||
export function levenshteinDistance(left: string, right: string): number {
|
||||
if (left === right) return 0;
|
||||
if (left === right) return 0;
|
||||
|
||||
if (left.length === 0) return right.length;
|
||||
if (right.length === 0) return left.length;
|
||||
if (left.length === 0) return right.length;
|
||||
if (right.length === 0) return left.length;
|
||||
|
||||
// Create empty edit distance matrix for all possible modifications of
|
||||
// substrings of left to substrings of right
|
||||
const distanceMatrix = Array(right.length + 1).fill(null).map(() => Array(left.length + 1).fill(null));
|
||||
// Create empty edit distance matrix for all possible modifications of
|
||||
// substrings of left to substrings of right
|
||||
const distanceMatrix = Array(right.length + 1).fill(null).map(() => Array(left.length + 1).fill(null));
|
||||
|
||||
// Fill the first row of the matrix
|
||||
// If this is the first row, we're transforming from an empty string to left
|
||||
// In this case, the number of operations equals the length of left substring
|
||||
for (let i = 0; i <= left.length; i++)
|
||||
distanceMatrix[0]![i]! = i;
|
||||
// Fill the first row of the matrix
|
||||
// If this is the first row, we're transforming from an empty string to left
|
||||
// In this case, the number of operations equals the length of left substring
|
||||
for (let i = 0; i <= left.length; i++)
|
||||
distanceMatrix[0]![i]! = i;
|
||||
|
||||
// Fill the first column of the matrix
|
||||
// If this is the first column, we're transforming empty string to right
|
||||
// In this case, the number of operations equals the length of right substring
|
||||
for (let j = 0; j <= right.length; j++)
|
||||
distanceMatrix[j]![0]! = j;
|
||||
// Fill the first column of the matrix
|
||||
// If this is the first column, we're transforming empty string to right
|
||||
// In this case, the number of operations equals the length of right substring
|
||||
for (let j = 0; j <= right.length; j++)
|
||||
distanceMatrix[j]![0]! = j;
|
||||
|
||||
for (let j = 1; j <= right.length; j++) {
|
||||
for (let i = 1; i <= left.length; i++) {
|
||||
const indicator = left[i - 1] === right[j - 1] ? 0 : 1;
|
||||
distanceMatrix[j]![i]! = Math.min(
|
||||
distanceMatrix[j]![i - 1]! + 1, // deletion
|
||||
distanceMatrix[j - 1]![i]! + 1, // insertion
|
||||
distanceMatrix[j - 1]![i - 1]! + indicator // substitution
|
||||
);
|
||||
}
|
||||
for (let j = 1; j <= right.length; j++) {
|
||||
for (let i = 1; i <= left.length; i++) {
|
||||
const indicator = left[i - 1] === right[j - 1] ? 0 : 1;
|
||||
distanceMatrix[j]![i]! = Math.min(
|
||||
distanceMatrix[j]![i - 1]! + 1, // deletion
|
||||
distanceMatrix[j - 1]![i]! + 1, // insertion
|
||||
distanceMatrix[j - 1]![i - 1]! + indicator, // substitution
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return distanceMatrix[right.length]![left.length]!;
|
||||
return distanceMatrix[right.length]![left.length]!;
|
||||
}
|
||||
|
||||
@@ -102,4 +102,4 @@ describe.skip('template', () => {
|
||||
expectTypeOf<actual>().toEqualTypeOf<expected>();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,16 +33,16 @@ describe.skip('templateObject', () => {
|
||||
});
|
||||
|
||||
it('replace template placeholders with nested values from args', () => {
|
||||
const result = templateObject('Hello {{user.name}, your address {user.addresses.0.street}', {
|
||||
user: {
|
||||
name: 'John Doe',
|
||||
addresses: [
|
||||
{ street: '123 Main St', city: 'Springfield'},
|
||||
{ street: '456 Elm St', city: 'Shelbyville'}
|
||||
]
|
||||
}
|
||||
});
|
||||
const result = templateObject('Hello {{user.name}, your address {user.addresses.0.street}', {
|
||||
user: {
|
||||
name: 'John Doe',
|
||||
addresses: [
|
||||
{ street: '123 Main St', city: 'Springfield' },
|
||||
{ street: '456 Elm St', city: 'Shelbyville' },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(result).toBe('Hello {John Doe, your address 123 Main St');
|
||||
expect(result).toBe('Hello {John Doe, your address 123 Main St');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,14 +19,14 @@ const TEMPLATE_PLACEHOLDER = /\{\s*([^{}]+?)\s*\}/gm;
|
||||
|
||||
/**
|
||||
* Removes the placeholder syntax from a template string.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* type Base = ClearPlaceholder<'{user.name}'>; // 'user.name'
|
||||
* type Unbalanced = ClearPlaceholder<'{user.name'>; // 'user.name'
|
||||
* type Spaces = ClearPlaceholder<'{ user.name }'>; // 'user.name'
|
||||
*/
|
||||
export type ClearPlaceholder<In extends string> =
|
||||
In extends `${string}{${infer Template}`
|
||||
export type ClearPlaceholder<In extends string>
|
||||
= In extends `${string}{${infer Template}`
|
||||
? ClearPlaceholder<Template>
|
||||
: In extends `${infer Template}}${string}`
|
||||
? ClearPlaceholder<Template>
|
||||
@@ -34,12 +34,12 @@ export type ClearPlaceholder<In extends string> =
|
||||
|
||||
/**
|
||||
* Extracts all placeholders from a template string.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* type Base = ExtractPlaceholders<'Hello {user.name}, {user.addresses.0.street}'>; // 'user.name' | 'user.addresses.0.street'
|
||||
*/
|
||||
export type ExtractPlaceholders<In extends string> =
|
||||
In extends `${infer Before}}${infer After}`
|
||||
export type ExtractPlaceholders<In extends string>
|
||||
= In extends `${infer Before}}${infer After}`
|
||||
? Before extends `${string}{${infer Placeholder}`
|
||||
? ClearPlaceholder<Placeholder> | ExtractPlaceholders<After>
|
||||
: ExtractPlaceholders<After>
|
||||
@@ -47,7 +47,7 @@ export type ExtractPlaceholders<In extends string> =
|
||||
|
||||
/**
|
||||
* Generates a type for a template string with placeholders.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* type Base = GenerateTypes<'Hello {user.name}, your address {user.addresses.0.street}'>; // { user: { name: string; addresses: { 0: { street: string; }; }; }; }
|
||||
* type WithTarget = GenerateTypes<'Hello {user.age}', number>; // { user: { age: number; }; }
|
||||
@@ -56,12 +56,12 @@ export type GenerateTypes<T extends string, Target = string> = UnionToIntersecti
|
||||
|
||||
export function templateObject<
|
||||
T extends string,
|
||||
A extends GenerateTypes<ExtractPlaceholders<T>, TemplateValue> & Collection
|
||||
A extends GenerateTypes<ExtractPlaceholders<T>, TemplateValue> & Collection,
|
||||
>(template: T, args: A, fallback?: TemplateFallback) {
|
||||
return template.replace(TEMPLATE_PLACEHOLDER, (_, key) => {
|
||||
const value = get(args, key)?.toString();
|
||||
return value !== undefined ? value : (isFunction(fallback) ? fallback(key) : '');
|
||||
});
|
||||
return template.replace(TEMPLATE_PLACEHOLDER, (_, key) => {
|
||||
const value = get(args, key)?.toString();
|
||||
return value !== undefined ? value : (isFunction(fallback) ? fallback(key) : '');
|
||||
});
|
||||
}
|
||||
|
||||
templateObject('Hello {user.name}, your address {user.addresses.0.city}', {
|
||||
|
||||
@@ -2,92 +2,92 @@ import { describe, it, expect } from 'vitest';
|
||||
import { trigramDistance, trigramProfile } from '.';
|
||||
|
||||
describe('trigramProfile', () => {
|
||||
it('trigram profile of a text with different trigrams', () => {
|
||||
const different_trigrams = 'hello world';
|
||||
const profile1 = trigramProfile(different_trigrams);
|
||||
it('trigram profile of a text with different trigrams', () => {
|
||||
const different_trigrams = 'hello world';
|
||||
const profile1 = trigramProfile(different_trigrams);
|
||||
|
||||
expect(profile1).toEqual(new Map([
|
||||
['\n\nh', 1],
|
||||
['\nhe', 1],
|
||||
['hel', 1],
|
||||
['ell', 1],
|
||||
['llo', 1],
|
||||
['lo ', 1],
|
||||
['o w', 1],
|
||||
[' wo', 1],
|
||||
['wor', 1],
|
||||
['orl', 1],
|
||||
['rld', 1],
|
||||
['ld\n', 1],
|
||||
['d\n\n', 1]
|
||||
]));
|
||||
});
|
||||
expect(profile1).toEqual(new Map([
|
||||
['\n\nh', 1],
|
||||
['\nhe', 1],
|
||||
['hel', 1],
|
||||
['ell', 1],
|
||||
['llo', 1],
|
||||
['lo ', 1],
|
||||
['o w', 1],
|
||||
[' wo', 1],
|
||||
['wor', 1],
|
||||
['orl', 1],
|
||||
['rld', 1],
|
||||
['ld\n', 1],
|
||||
['d\n\n', 1],
|
||||
]));
|
||||
});
|
||||
|
||||
it('trigram profile of a text with repeated trigrams', () => {
|
||||
const repeated_trigrams = 'hello hello';
|
||||
const profile2 = trigramProfile(repeated_trigrams);
|
||||
it('trigram profile of a text with repeated trigrams', () => {
|
||||
const repeated_trigrams = 'hello hello';
|
||||
const profile2 = trigramProfile(repeated_trigrams);
|
||||
|
||||
expect(profile2).toEqual(new Map([
|
||||
['\n\nh', 1],
|
||||
['\nhe', 1],
|
||||
['hel', 2],
|
||||
['ell', 2],
|
||||
['llo', 2],
|
||||
['lo ', 1],
|
||||
['o h', 1],
|
||||
[' he', 1],
|
||||
['lo\n', 1],
|
||||
['o\n\n', 1]
|
||||
]));
|
||||
});
|
||||
expect(profile2).toEqual(new Map([
|
||||
['\n\nh', 1],
|
||||
['\nhe', 1],
|
||||
['hel', 2],
|
||||
['ell', 2],
|
||||
['llo', 2],
|
||||
['lo ', 1],
|
||||
['o h', 1],
|
||||
[' he', 1],
|
||||
['lo\n', 1],
|
||||
['o\n\n', 1],
|
||||
]));
|
||||
});
|
||||
|
||||
it('trigram profile of an empty text', () => {
|
||||
const text = '';
|
||||
const profile = trigramProfile(text);
|
||||
it('trigram profile of an empty text', () => {
|
||||
const text = '';
|
||||
const profile = trigramProfile(text);
|
||||
|
||||
expect(profile).toEqual(new Map([
|
||||
['\n\n\n', 2],
|
||||
]));
|
||||
});
|
||||
expect(profile).toEqual(new Map([
|
||||
['\n\n\n', 2],
|
||||
]));
|
||||
});
|
||||
});
|
||||
|
||||
describe('trigramDistance', () => {
|
||||
it('zero when comparing the same text', () => {
|
||||
const profile1 = trigramProfile('hello world');
|
||||
const profile2 = trigramProfile('hello world');
|
||||
it('zero when comparing the same text', () => {
|
||||
const profile1 = trigramProfile('hello world');
|
||||
const profile2 = trigramProfile('hello world');
|
||||
|
||||
expect(trigramDistance(profile1, profile2)).toBe(0);
|
||||
});
|
||||
expect(trigramDistance(profile1, profile2)).toBe(0);
|
||||
});
|
||||
|
||||
it('one for completely different text', () => {
|
||||
const profile1 = trigramProfile('hello world');
|
||||
const profile2 = trigramProfile('lorem ipsum');
|
||||
it('one for completely different text', () => {
|
||||
const profile1 = trigramProfile('hello world');
|
||||
const profile2 = trigramProfile('lorem ipsum');
|
||||
|
||||
expect(trigramDistance(profile1, profile2)).toBe(1);
|
||||
});
|
||||
expect(trigramDistance(profile1, profile2)).toBe(1);
|
||||
});
|
||||
|
||||
it('one for empty text and non-empty text', () => {
|
||||
const profile1 = trigramProfile('hello world');
|
||||
const profile2 = trigramProfile('');
|
||||
it('one for empty text and non-empty text', () => {
|
||||
const profile1 = trigramProfile('hello world');
|
||||
const profile2 = trigramProfile('');
|
||||
|
||||
expect(trigramDistance(profile1, profile2)).toBe(1);
|
||||
});
|
||||
expect(trigramDistance(profile1, profile2)).toBe(1);
|
||||
});
|
||||
|
||||
it('approximately 0.5 for similar text', () => {
|
||||
const profile1 = trigramProfile('hello world');
|
||||
const profile2 = trigramProfile('hello lorem');
|
||||
it('approximately 0.5 for similar text', () => {
|
||||
const profile1 = trigramProfile('hello world');
|
||||
const profile2 = trigramProfile('hello lorem');
|
||||
|
||||
const approx = trigramDistance(profile1, profile2);
|
||||
const approx = trigramDistance(profile1, profile2);
|
||||
|
||||
expect(approx).toBeGreaterThan(0.45);
|
||||
expect(approx).toBeLessThan(0.55);
|
||||
});
|
||||
expect(approx).toBeGreaterThan(0.45);
|
||||
expect(approx).toBeLessThan(0.55);
|
||||
});
|
||||
|
||||
it('triangle inequality', () => {
|
||||
const A = trigramDistance(trigramProfile('metric'), trigramProfile('123ric'));
|
||||
const B = trigramDistance(trigramProfile('123ric'), trigramProfile('123456'));
|
||||
const C = trigramDistance(trigramProfile('metric'), trigramProfile('123456'));
|
||||
it('triangle inequality', () => {
|
||||
const A = trigramDistance(trigramProfile('metric'), trigramProfile('123ric'));
|
||||
const B = trigramDistance(trigramProfile('123ric'), trigramProfile('123456'));
|
||||
const C = trigramDistance(trigramProfile('metric'), trigramProfile('123456'));
|
||||
|
||||
expect(A + B).toBeGreaterThanOrEqual(C);
|
||||
});
|
||||
});
|
||||
expect(A + B).toBeGreaterThanOrEqual(C);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,31 +4,31 @@ export type Trigrams = Map<string, number>;
|
||||
* @name trigramProfile
|
||||
* @category Text
|
||||
* @description Extracts trigrams from a text and returns a map of trigram to count
|
||||
*
|
||||
*
|
||||
* @param {string} text The text to extract trigrams
|
||||
* @returns {Trigrams} A map of trigram to count
|
||||
*
|
||||
* @since 0.0.1
|
||||
*/
|
||||
export function trigramProfile(text: string): Trigrams {
|
||||
text = `\n\n${text}\n\n`;
|
||||
text = `\n\n${text}\n\n`;
|
||||
|
||||
const trigrams = new Map<string, number>();
|
||||
const trigrams = new Map<string, number>();
|
||||
|
||||
for (let i = 0; i < text.length - 2; i++) {
|
||||
const trigram = text.slice(i, i + 3);
|
||||
const count = trigrams.get(trigram) ?? 0;
|
||||
trigrams.set(trigram, count + 1);
|
||||
}
|
||||
for (let i = 0; i < text.length - 2; i++) {
|
||||
const trigram = text.slice(i, i + 3);
|
||||
const count = trigrams.get(trigram) ?? 0;
|
||||
trigrams.set(trigram, count + 1);
|
||||
}
|
||||
|
||||
return trigrams;
|
||||
return trigrams;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name trigramDistance
|
||||
* @category Text
|
||||
* @description Calculates the trigram distance between two strings
|
||||
*
|
||||
*
|
||||
* @param {Trigrams} left First text trigram profile
|
||||
* @param {Trigrams} right Second text trigram profile
|
||||
* @returns {number} The trigram distance between the two strings
|
||||
@@ -36,22 +36,22 @@ export function trigramProfile(text: string): Trigrams {
|
||||
* @since 0.0.1
|
||||
*/
|
||||
export function trigramDistance(left: Trigrams, right: Trigrams): number {
|
||||
let distance = -4;
|
||||
let total = -4;
|
||||
let distance = -4;
|
||||
let total = -4;
|
||||
|
||||
for (const [trigram, left_count] of left) {
|
||||
total += left_count;
|
||||
const right_count = right.get(trigram) ?? 0;
|
||||
distance += Math.abs(left_count - right_count);
|
||||
}
|
||||
for (const [trigram, left_count] of left) {
|
||||
total += left_count;
|
||||
const right_count = right.get(trigram) ?? 0;
|
||||
distance += Math.abs(left_count - right_count);
|
||||
}
|
||||
|
||||
for (const [trigram, right_count] of right) {
|
||||
total += right_count;
|
||||
const left_count = left.get(trigram) ?? 0;
|
||||
distance += Math.abs(left_count - right_count);
|
||||
}
|
||||
for (const [trigram, right_count] of right) {
|
||||
total += right_count;
|
||||
const left_count = left.get(trigram) ?? 0;
|
||||
distance += Math.abs(left_count - right_count);
|
||||
}
|
||||
|
||||
if (distance < 0) return 0;
|
||||
if (distance < 0) return 0;
|
||||
|
||||
return distance / total;
|
||||
}
|
||||
return distance / total;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user