Skip to content

Commit

Permalink
Merge pull request #34 from giraugh/feat/edit-distance-ops
Browse files Browse the repository at this point in the history
Add options to editDistance for operation weights
  • Loading branch information
giraugh authored Jan 12, 2024
2 parents 77198a2 + a8db3ee commit 6e718d6
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/wicked-coats-know.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@giraugh/tools": minor
---

Add weight options to `editDistance`
29 changes: 22 additions & 7 deletions lib/strings/editDistance.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
export type EditDistanceOptions = {
weights: {
insertion?: number,
deletion?: number,
substition?: number,
}
}

/**
* Compute the levenshtein edit distance between two strings.
* Equivalent to the the number of edits necessary to get from source to target.
Expand All @@ -7,20 +15,23 @@
*
* @example editDistance('snail', 'mail') === 2
*/
export const editDistance = (source: string, target: string): number => {
export const editDistance = (source: string, target: string, options?: Partial<EditDistanceOptions>): number => {
// Create dynamic programming matrix
const dynMat = Array(target.length + 1).fill(-1)
.map((_, i) => Array(source.length + 1).fill(-1)
.map((_, j) => i === 0 ? j : (j === 0 ? i : -1) ))
.map((_, j) => i === 0
? j * (options?.weights?.deletion ?? 1)
: (j === 0 ? (i * (options?.weights?.insertion ?? 1)) : -1) ))


// Compute the matrix values
for (let j = 1; j <= target.length; j++) {
for (let i = 1; i <= source.length; i++) {
const ind = source[i - 1] === target[j - 1] ? 0 : 1
dynMat[j][i] = Math.min(
dynMat[j][i - 1] + 1, // deletion
dynMat[j - 1][i] + 1, // insertion
dynMat[j - 1][i - 1] + ind, // substitution
dynMat[j][i - 1] + (options?.weights?.deletion ?? 1), // deletion
dynMat[j - 1][i] + (options?.weights?.insertion ?? 1), // insertion
dynMat[j - 1][i - 1] + (options?.weights?.substition ?? 1) * ind, // substitution
)
}
}
Expand All @@ -29,8 +40,6 @@ export const editDistance = (source: string, target: string): number => {
return dynMat[target.length][source.length]
}



// Tests
if (import.meta.vitest) {
const { it, expect } = import.meta.vitest
Expand All @@ -51,4 +60,10 @@ if (import.meta.vitest) {
it('correctly calculates the distance between a word and an empty string', () => {
expect(editDistance('', 'gracious')).toBe(8)
})

it('uses provided operation weights', () => {
expect(editDistance('', 'abc', { weights: { insertion: 2 } })).toBe(6)
expect(editDistance('abc', '', { weights: { deletion: 2 } })).toBe(6)
expect(editDistance('abc', 'defg', { weights: { substition: 2, insertion: 3 } })).toBe(9)
})
}

0 comments on commit 6e718d6

Please sign in to comment.