beam: Damerau-Levenshtein true DL distance with randomized cross-check

This commit is contained in:
beam committed 2026-10-30 23:46:08 +00:00
1 parent e094c913f2
commit 5bded8094b
3 files changed
+91

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$(readlink -f "$0")")"
python3 test_damerau_levenshtein.py
+49
View File
@@ -0,0 +1,49 @@
"""Damerau-Levenshtein edit distance (true DL, not OSA)."""
from __future__ import annotations
def damerau_levenshtein(a: str, b: str) -> int:
la, lb = len(a), len(b)
if la == 0:
return lb
if lb == 0:
return la
last = {}
INF = la + lb
d = [[0] * (lb + 2) for _ in range(la + 2)]
d[0][0] = INF
for i in range(1, la + 2):
d[i][0] = INF
d[i][1] = i - 1
for j in range(1, lb + 2):
d[0][j] = INF
d[1][j] = j - 1
for i in range(1, la + 1):
db = 0
for j in range(1, lb + 1):
i1 = last.get(b[j - 1], 0)
j1 = db
cost = 1
if a[i - 1] == b[j - 1]:
cost = 0
db = j
d[i + 1][j + 1] = min(
d[i][j] + cost,
d[i + 1][j] + 1,
d[i][j + 1] + 1,
)
if i1 > 0 and j1 > 0:
d[i + 1][j + 1] = min(
d[i + 1][j + 1],
d[i1][j1] + (i - i1 - 1) + 1 + (j - j1 - 1),
)
last[a[i - 1]] = i
return d[la + 1][lb + 1]
if __name__ == "__main__":
import sys
print(damerau_levenshtein(sys.argv[1], sys.argv[2]))
@@ -0,0 +1,38 @@
import sys
sys.path.insert(0, "/root/.hermes/wizard-outputs/beam")
from damerau_levenshtein import damerau_levenshtein as dl
# known values
assert dl("", "") == 0
assert dl("a", "") == 1
assert dl("", "a") == 1
assert dl("ca", "abc") == 2 # transpose? ca->ac is 1 transposition +?
# classic: "CA" -> "ABC": distance?
assert dl("abcd", "abdc") == 1, dl("abcd","abdc") # transposition dc
assert dl("ab", "ba") == 1
assert dl("abc", "acb") == 1 # transpose b/c
# true DL allows multiple ops on same substring: "CA" -> "ABC" = 2? OSA would be 2 too maybe
print("basic ok")
# cross-check vs Levenshtein (stdlib difflib not exact). Use simple lev.
def lev(a,b):
if not a: return len(b)
if not b: return len(a)
prev=list(range(len(b)+1))
for i,ca in enumerate(a,1):
cur=[i]
for j,cb in enumerate(b,1):
cur.append(min(prev[j]+1, cur[-1]+1, prev[j-1]+(ca!=cb)))
prev=cur
return prev[-1]
import random
random.seed(42)
for _ in range(2000):
n=random.randint(0,8)
m=random.randint(0,8)
a=''.join(random.choice('ab') for _ in range(n))
b=''.join(random.choice('ab') for _ in range(m))
# DL <= lev always (more ops allowed)
assert dl(a,b) <= lev(a,b), (a,b,dl(a,b),lev(a,b))
print("monotonic ok")