add new operation: Levenshtein Distance
This commit is contained in:
parent
ba12ad8e7c
commit
2c822314df
@ -269,6 +269,7 @@
|
|||||||
"Fuzzy Match",
|
"Fuzzy Match",
|
||||||
"Offset checker",
|
"Offset checker",
|
||||||
"Hamming Distance",
|
"Hamming Distance",
|
||||||
|
"Levenshtein Distance",
|
||||||
"Convert distance",
|
"Convert distance",
|
||||||
"Convert area",
|
"Convert area",
|
||||||
"Convert mass",
|
"Convert mass",
|
||||||
|
98
src/core/operations/LevenshteinDistance.mjs
Normal file
98
src/core/operations/LevenshteinDistance.mjs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
/**
|
||||||
|
* @author mikecat
|
||||||
|
* @copyright Crown Copyright 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Levenshtein Distance operation
|
||||||
|
*/
|
||||||
|
class LevenshteinDistance extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LevenshteinDistance constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Levenshtein Distance";
|
||||||
|
this.module = "Default";
|
||||||
|
this.description = "Levenshtein Distance (also known as Edit Distance) is a string metric to measure a difference between two strings that counts operations (insertions, deletions, and substitutions) on single character that are required to change one string to another.";
|
||||||
|
this.infoURL = "https://wikipedia.org/wiki/Levenshtein_distance";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "number";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
name: "Sample delimiter",
|
||||||
|
type: "binaryString",
|
||||||
|
value: "\\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Insertion cost",
|
||||||
|
type: "number",
|
||||||
|
value: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Deletion cost",
|
||||||
|
type: "number",
|
||||||
|
value: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Substitution cost",
|
||||||
|
type: "number",
|
||||||
|
value: 1
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const [delim, insCost, delCost, subCost] = args;
|
||||||
|
const samples = input.split(delim);
|
||||||
|
if (samples.length !== 2) {
|
||||||
|
throw new OperationError("Incorrect number of samples. Check your input and/or delimiter.");
|
||||||
|
}
|
||||||
|
if (insCost < 0 || delCost < 0 || subCost < 0) {
|
||||||
|
throw new OperationError("Negative costs are not allowed.");
|
||||||
|
}
|
||||||
|
const src = samples[0], dest = samples[1];
|
||||||
|
let currentCost = new Array(src.length + 1);
|
||||||
|
let nextCost = new Array(src.length + 1);
|
||||||
|
for (let i = 0; i < currentCost.length; i++) {
|
||||||
|
currentCost[i] = delCost * i;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < dest.length; i++) {
|
||||||
|
const destc = dest.charAt(i);
|
||||||
|
nextCost[0] = currentCost[0] + insCost;
|
||||||
|
for (let j = 0; j < src.length; j++) {
|
||||||
|
let candidate;
|
||||||
|
// insertion
|
||||||
|
let optCost = currentCost[j + 1] + insCost;
|
||||||
|
// deletion
|
||||||
|
candidate = nextCost[j] + delCost;
|
||||||
|
if (candidate < optCost) optCost = candidate;
|
||||||
|
// substitution or matched character
|
||||||
|
candidate = currentCost[j];
|
||||||
|
if (src.charAt(j) !== destc) candidate += subCost;
|
||||||
|
if (candidate < optCost) optCost = candidate;
|
||||||
|
// store calculated cost
|
||||||
|
nextCost[j + 1] = optCost;
|
||||||
|
}
|
||||||
|
const tempCost = nextCost;
|
||||||
|
nextCost = currentCost;
|
||||||
|
currentCost = tempCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentCost[currentCost.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LevenshteinDistance;
|
@ -130,6 +130,7 @@ import "./tests/FletcherChecksum.mjs";
|
|||||||
import "./tests/CMAC.mjs";
|
import "./tests/CMAC.mjs";
|
||||||
import "./tests/AESKeyWrap.mjs";
|
import "./tests/AESKeyWrap.mjs";
|
||||||
import "./tests/Rabbit.mjs";
|
import "./tests/Rabbit.mjs";
|
||||||
|
import "./tests/LevenshteinDistance.mjs";
|
||||||
|
|
||||||
// Cannot test operations that use the File type yet
|
// Cannot test operations that use the File type yet
|
||||||
// import "./tests/SplitColourChannels.mjs";
|
// import "./tests/SplitColourChannels.mjs";
|
||||||
|
165
tests/operations/tests/LevenshteinDistance.mjs
Normal file
165
tests/operations/tests/LevenshteinDistance.mjs
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
/**
|
||||||
|
* @author mikecat
|
||||||
|
* @copyright Crown Copyright 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
"name": "Levenshtein Distance: Wikipedia example 1",
|
||||||
|
"input": "kitten\nsitting",
|
||||||
|
"expectedOutput": "3",
|
||||||
|
"recipeConfig": [
|
||||||
|
{
|
||||||
|
"op": "Levenshtein Distance",
|
||||||
|
"args": [
|
||||||
|
"\\n", 1, 1, 1,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Levenshtein Distance: Wikipedia example 2",
|
||||||
|
"input": "saturday\nsunday",
|
||||||
|
"expectedOutput": "3",
|
||||||
|
"recipeConfig": [
|
||||||
|
{
|
||||||
|
"op": "Levenshtein Distance",
|
||||||
|
"args": [
|
||||||
|
"\\n", 1, 1, 1,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Levenshtein Distance: Wikipedia example 1 with substitution cost 2",
|
||||||
|
"input": "kitten\nsitting",
|
||||||
|
"expectedOutput": "5",
|
||||||
|
"recipeConfig": [
|
||||||
|
{
|
||||||
|
"op": "Levenshtein Distance",
|
||||||
|
"args": [
|
||||||
|
"\\n", 1, 1, 2,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Levenshtein Distance: varied costs 1",
|
||||||
|
"input": "kitten\nsitting",
|
||||||
|
"expectedOutput": "230",
|
||||||
|
"recipeConfig": [
|
||||||
|
{
|
||||||
|
"op": "Levenshtein Distance",
|
||||||
|
"args": [
|
||||||
|
"\\n", 10, 100, 1000,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Levenshtein Distance: varied costs 2",
|
||||||
|
"input": "kitten\nsitting",
|
||||||
|
"expectedOutput": "1020",
|
||||||
|
"recipeConfig": [
|
||||||
|
{
|
||||||
|
"op": "Levenshtein Distance",
|
||||||
|
"args": [
|
||||||
|
"\\n", 1000, 100, 10,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Levenshtein Distance: another delimiter",
|
||||||
|
"input": "kitten sitting",
|
||||||
|
"expectedOutput": "3",
|
||||||
|
"recipeConfig": [
|
||||||
|
{
|
||||||
|
"op": "Levenshtein Distance",
|
||||||
|
"args": [
|
||||||
|
" ", 1, 1, 1,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Levenshtein Distance: too few samples",
|
||||||
|
"input": "kitten",
|
||||||
|
"expectedOutput": "Incorrect number of samples. Check your input and/or delimiter.",
|
||||||
|
"recipeConfig": [
|
||||||
|
{
|
||||||
|
"op": "Levenshtein Distance",
|
||||||
|
"args": [
|
||||||
|
"\\n", 1, 1, 1,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Levenshtein Distance: too many samples",
|
||||||
|
"input": "kitten\nsitting\nkitchen",
|
||||||
|
"expectedOutput": "Incorrect number of samples. Check your input and/or delimiter.",
|
||||||
|
"recipeConfig": [
|
||||||
|
{
|
||||||
|
"op": "Levenshtein Distance",
|
||||||
|
"args": [
|
||||||
|
"\\n", 1, 1, 1,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Levenshtein Distance: negative insertion cost",
|
||||||
|
"input": "kitten\nsitting",
|
||||||
|
"expectedOutput": "Negative costs are not allowed.",
|
||||||
|
"recipeConfig": [
|
||||||
|
{
|
||||||
|
"op": "Levenshtein Distance",
|
||||||
|
"args": [
|
||||||
|
"\\n", -1, 1, 1,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Levenshtein Distance: negative deletion cost",
|
||||||
|
"input": "kitten\nsitting",
|
||||||
|
"expectedOutput": "Negative costs are not allowed.",
|
||||||
|
"recipeConfig": [
|
||||||
|
{
|
||||||
|
"op": "Levenshtein Distance",
|
||||||
|
"args": [
|
||||||
|
"\\n", 1, -1, 1,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Levenshtein Distance: negative substitution cost",
|
||||||
|
"input": "kitten\nsitting",
|
||||||
|
"expectedOutput": "Negative costs are not allowed.",
|
||||||
|
"recipeConfig": [
|
||||||
|
{
|
||||||
|
"op": "Levenshtein Distance",
|
||||||
|
"args": [
|
||||||
|
"\\n", 1, 1, -1,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Levenshtein Distance: cost zero",
|
||||||
|
"input": "kitten\nsitting",
|
||||||
|
"expectedOutput": "0",
|
||||||
|
"recipeConfig": [
|
||||||
|
{
|
||||||
|
"op": "Levenshtein Distance",
|
||||||
|
"args": [
|
||||||
|
"\\n", 0, 0, 0,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
Loading…
Reference in New Issue
Block a user