diff --git a/.gitignore b/.gitignore index 9a5aced..7d40482 100644 --- a/.gitignore +++ b/.gitignore @@ -137,3 +137,8 @@ dist # Vite logs files vite.config.js.timestamp-* vite.config.ts.timestamp-* + +# MacOS +.DS_Store +src/.DS_Store +tests/.DS_Store \ No newline at end of file diff --git a/docs/STR.md b/docs/STR.md index 99460bc..a5f8a29 100644 --- a/docs/STR.md +++ b/docs/STR.md @@ -21,6 +21,7 @@ brutils str extract "\[(.*?)\]" --text "[one] [two]" --regex brutils str base64 --text "hello" --mode encode brutils str urlencode --text "hello world" --mode encode brutils str html --text "ok" --mode encode +brutils str leven "kitten" "sitting" ``` ## Actions @@ -39,6 +40,7 @@ brutils str html --text "ok" --mode encode | `base64` | `brutils str base64 --text [--mode ]` | Encode or decode Base64. | | `urlencode` | `brutils str urlencode --text [--mode ]` | Encode or decode URL content. | | `html` | `brutils str html --text [--mode ]` | Encode or decode HTML entities. | +| `leven` | `brutils str leven ` | Calculates the Levenshtein distance between two strings. | ## Flags diff --git a/package-lock.json b/package-lock.json index b1a23c8..15a47bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@danielarndt0/brutils-cli", - "version": "1.0.1", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@danielarndt0/brutils-cli", - "version": "1.0.1", + "version": "1.1.0", "dependencies": { "archiver": "^7.0.1", "commander": "^12.1.0", diff --git a/package.json b/package.json index 63771e1..ebc8d59 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@danielarndt0/brutils-cli", - "version": "1.0.1", + "version": "1.1.0", "description": "Production-ready CLI for Brazilian and general developer utilities.", "type": "module", "main": "./dist/index.js", diff --git a/src/cli/commands/register-text-data.ts b/src/cli/commands/register-text-data.ts index b8cd5c2..65a1af6 100644 --- a/src/cli/commands/register-text-data.ts +++ b/src/cli/commands/register-text-data.ts @@ -15,6 +15,7 @@ import { import { convertStringCase, extractText, + getLevenshteinDistance, normalizeText, padText, removeAccents, @@ -58,7 +59,8 @@ export function registerTextDataCommands(program: Command): void { 'brutils str truncate --text "hello world" --max 8 --suffix "..."', 'brutils str replace --text "hello 123" --from "\\\\d+" --with "X" --regex', 'brutils str extract "\\\\[(.*?)\\\\]" --text "[one] [two]" --regex', - 'brutils str base64 --text "hello" --mode encode' + 'brutils str base64 --text "hello" --mode encode', + 'brutils str leven "kitten" "sitting"' ]) ); @@ -268,6 +270,15 @@ export function registerTextDataCommands(program: Command): void { } ); + str + .description("Calculates the Levenshtein distance between two strings") + .command("leven") + .argument("") + .argument("") + .action((a: string, b: string) => { + printValue(getLevenshteinDistance(a, b)); + }); + const jsonCommand = program .command("json") .description("Local JSON formatting, editing and diff helpers.") diff --git a/src/cli/create-program.ts b/src/cli/create-program.ts index 57bc70d..0b21e04 100644 --- a/src/cli/create-program.ts +++ b/src/cli/create-program.ts @@ -8,7 +8,7 @@ import { registerTextDataCommands } from "./commands/register-text-data.js"; import { rootFooter } from "./shared/help.js"; import { configureProgramUi } from "./ui/output.js"; -const CLI_VERSION = "1.0.1"; +const CLI_VERSION = "1.1.0"; export function buildProgram(): Command { const program = new Command(); diff --git a/src/services/str/str.service.ts b/src/services/str/str.service.ts index ddd8ca1..81462e9 100644 --- a/src/services/str/str.service.ts +++ b/src/services/str/str.service.ts @@ -251,4 +251,34 @@ export function transformHtmlEntities( } return encodeHtmlEntities(value); + } + +export function getLevenshteinDistance(a: string, b: string): number { + a = a.trim() + b = b.trim() + + const n = a.length; + const m = b.length; + + const dp: number[][] = Array.from({ length: n + 1 }, () => + new Array(m + 1).fill(0) + ); + + for (let i = 0; i <= n; i++) dp[i]![0] = i; + for (let j = 0; j <= m; j++) dp[0]![j] = j; + + for (let i = 1; i <= n; i++) { + for (let j = 1; j <= m; j++) { + const cost = a[i - 1] === b[j - 1] ? 0 : 1; + + dp[i]![j] = Math.min( + dp[i - 1]![j]! + 1, + dp[i]![j - 1]! + 1, + dp[i - 1]![j - 1]! + cost + ); + } + } + + return dp[n]![m]!; +} \ No newline at end of file diff --git a/tests/str/str.service.spec.ts b/tests/str/str.service.spec.ts index 9ca9127..ffaa094 100644 --- a/tests/str/str.service.spec.ts +++ b/tests/str/str.service.spec.ts @@ -11,10 +11,35 @@ import { transformHtmlEntities, transformUrlEncoding, trimText, - truncateText + truncateText, + getLevenshteinDistance } from "../../src/services/str/index.js"; describe("str service", () => { + it("should give me the distance between two strings", () => { + expect(getLevenshteinDistance("kitten", "sitting")).toBe(3); + }); + + it("should give me the distance between two strings", () => { + expect(getLevenshteinDistance("book", "back")).toBe(2); + }); + + it("should give me the distance between two strings", () => { + expect(getLevenshteinDistance("cat", "cut")).toBe(1); + }); + + it("should give me the distance between two strings", () => { + expect(getLevenshteinDistance("", "")).toBe(0); + }); + + it("should give me the distance between two strings", () => { + expect(getLevenshteinDistance("Hello".toLowerCase(), "hello")).toBe(0); + }); + + it("should give me the distance between two strings", () => { + expect(getLevenshteinDistance("João", "Joao")).toBe(1); + }); + it("should slugify text", () => { expect(slugifyText("Hello Cool World")).toBe("hello-cool-world"); });