packages feed

FM-SBLEX-3.0: src/lib/EditDistance.hs

module EditDistance where

import Data.Array
import List
import Char
import System
import qualified Data.Set

data Position    = Int

data Operation a = Delete      a | 
		   Insert      a | 
		   Replace   a a | 
		   Untouched   a
  deriving (Show,Eq)

isUntouched :: Operation a -> Bool
isUntouched (Untouched _) = True
isUntouched _             = False

isOp :: Operation a -> Bool
isOp = not . isUntouched
                
-- Compares a list of strings, and gives a set of common substrings
commonSubsequences :: Ord a => [[a]] -> Data.Set.Set [a]
commonSubsequences      []  = Data.Set.empty
commonSubsequences     [x]  = Data.Set.empty
commonSubsequences (x:y:xs) = Data.Set.fromList $ traverse (commonSub x y) xs
 where traverse x []        = x
       traverse x (y:ys)    = traverse (commonSub (concat x) y) ys

-- Compares two strings, and gives a list of common substrings,
-- not necessary unique
commonSub :: Eq a => [a] -> [a] -> [[a]]
commonSub s1 s2 = collect $ snd $ editDistance s1 s2
 where collect [] = []
       collect (x:xs)
        | isUntouched x = case span isUntouched (x:xs) of
			   (us,rest) -> [c | (Untouched c) <- us] : collect rest
	| otherwise     = collect xs

-- Calculate the edit distance of two strings. 
-- The result is the edit distance metrics and a list of operations.
editDistance ::  Eq a => [a] -> [a] -> (Int,[Operation a])
editDistance s1 s2 = (tab ! (l1,l2), reverse (backtrack tab (l1,l2) (s1a,s2a)))
 where l1 = length s1
       l2 = length s2
       s1a = array (0,l1) (zip [1..(l1+1)] s1)
       s2a = array (0,l2) (zip [1..(l2+1)] s2)
       tab = array ((0,0),(l1,l2)) 
	      [((x,y),edit (x,y)) | x <- [0..l1], y <- [0..l2]]
       edit (x,0) = x
       edit (0,x) = x
       edit (x,y) = 
	   minimum [tab ! (x-1,y)+1,          -- insertion
		    tab ! (x,y-1)+1,          -- deletion
	            tab ! (x-1,y-1)+cost x y] -- substitution
       cost x y 
         | s1a ! x == s2a ! y = 0
	 | otherwise          = 2 -- substitution cost.

dot_it :: [String] -> String
dot_it (s:xs) = dot s $ map (snd . (editDistance s)) xs

-- Trace the edit distance algorithm
backtrack :: Array (Int,Int) Int -> (Int,Int) -> 
	     (Array Int a,Array Int a) -> [Operation a]
backtrack tab (0,0)  _ = []
backtrack tab (0,l1) (s1,s2)  
    = [Insert (s2 ! n) | n <- reverse [1 .. l1]]
backtrack tab (l1,0) (s1,s2)  
    = [Delete (s1 ! n) | n <- reverse [1 .. l1]]
backtrack tab (x,y) (s1,s2) = if a < b && a < c then
		        (Delete (s1 ! x)) : backtrack tab (x-1,y) (s1,s2)
                       else 
                        if b < c then
			 (Insert (s2 ! y)) : backtrack tab (x,y-1) (s1,s2)
			 else if c == d then
			         (Untouched (s1 ! x)): backtrack tab (x-1,y-1) (s1,s2)
			         else (Replace (s1 ! x) (s2 ! y)) : 
				      backtrack tab (x-1,y-1) (s1,s2)	       
 where a = tab ! (x-1,y)   -- insert
       b = tab ! (x,y-1)   -- delete
       c = tab ! (x-1,y-1) -- replace
       d = tab ! (x,y)     -- untouched

dot :: String -> [[Operation Char]] -> String
dot s xs = unlines $ ["digraph paradigm {", " rankdir=LR;", unlines (map dotify xs) ++ "}"]
 where dotify  ops   = " " ++ s ++ " -> " ++ ending ops ++ transform ops ++ " ;"
       ending ops    = [a | (Insert a ) <- ops]
       transform ops = case [(a:" -> ") ++ [b] | (Replace a b) <- ops] ++
                            [(a:" -> ")  | (Delete a ) <- ops] of
                         [] -> []
                         xs -> " [label=" ++ ("\"" ++ concat (intersperse ", " xs) ++ "\"") ++ "]"

test = putStrLn $ dot_it ["smula","smulan", "smulans", "smulas", "smulor", "smulors", "smulornas", "smulorna"]

test2 = putStrLn $ dot_it ["man","mannen", "mannens", "mans", "män", "mäns", "männen", "männens"]