Edit Distance
The naive algorithm to compute the edit-distance of two strings (sequences) runs in time that is exponential in terms of the string length:
Distance = lambda A. lambda B. if null A then length B else if null B then length A else let As = tl A, Bs = tl B in if hd A = hd B then Distance As Bs else 1 + min (Distance As Bs) (min (Distance As B) (Distance A Bs))
The well-known dynamic programming algorithm (DPA) avoids doing repeated work by storing partial results in an array or other data-structure. This reduces the time complexity to O(|A|*|B|) where the two strings are A and B, e.g.:
Distance = lambda A. lambda B. let rec Rows = (0 :: count hd Rows B) {the first row } :: EachRow A hd Rows {the other rows}, EachRow = lambda A. lambda lastrow. if null A then nil else let rec Ach = hd A, DoRow = lambda B. lambda NW. lambda W. {NW N} if null B then nil {W .} else let N = tl NW in let me = if Ach = hd B then hd NW else 1 + min W (min hd N hd NW) in me :: DoRow tl B tl NW me, thisrow = (1 + hd lastrow) :: DoRow B lastrow hd thisrow in thisrow :: EachRow tl A thisrow in last (last Rows)
If the strings, A and B, have a small edit-distance and are of length ~n then we can do better [All92] with an algorithm that takes O(n*D(A,B)) time:
There are more λ-calculus examples here.
References
- [All92] Lloyd Allison,
'Lazy dynamic programming can be eager',
Information Processing Letters, 43, pp.207-212,
doi:10.1016/0020-0190(92)90202-7.
September 1992.
- And other publications.
And for the record, the last algorithm in Haskell:
module Edit_IPL_V43_N4 (d) where -- compute the edit distance of sequences a and b [All92]. d a b = let -- diagonal from the top-left element mainDiag = oneDiag a b (head uppers) ( -1 : (head lowers)) -- diagonals above the mainDiag uppers = eachDiag a b (mainDiag : uppers) -- diagonals below the mainDiag lowers = eachDiag b a (mainDiag : lowers) -- ! oneDiag a b diagAbove diagBelow = -- \ \ \ let -- \ \ \ doDiag [] b nw n w = [] -- \ nw n doDiag a [] nw n w = [] -- \ \ doDiag (a:as) (b:bs) nw n w = -- w me let me = if a==b then nw -- dynamic programming DPA else 1+min3 (head w) nw (head n) in me : (doDiag as bs me (tail n) (tail w)) firstelt = 1+(head diagBelow) thisdiag = firstelt:(doDiag a b firstelt diagAbove (tail diagBelow)) in thisdiag min3 x y z = -- see L. Allison, Lazy Dynamic-Programming can be Eager -- Inf. Proc. Letters 43(4) pp207-212, Sept' 1992 if x < y then x else min y z -- makes it O(|a|*D(a,b)) -- min x (min y z) -- makes it O(|a|*|b|) -- the fast one does not always evaluate all three values. eachDiag a [] diags = [] eachDiag a (b:bs) (lastDiag:diags) = let nextDiag = head(tail diags) in (oneDiag a bs nextDiag lastDiag):(eachDiag a bs diags) -- which is the diagonal containing the bottom R.H. elt? lab = (length a) - (length b) in last( if lab == 0 then mainDiag else if lab > 0 then lowers !! ( lab-1) else uppers !! (-lab-1) ) -- module under Gnu 'copyleft' GPL General Public Licence.