beam: Damerau-Levenshtein true DL distance with randomized cross-check
This commit is contained in:
1 parent
e094c913f2
commit
5bded8094b
3 files changed
+91
No files matched your search
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
cd "$(dirname "$(readlink -f "$0")")"
|
||||
python3 test_damerau_levenshtein.py
|
||||
@@ -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")
|
||||
Reference in new issue
Block a user