packages feed

imbib-1.2.5: Diff.hs

module Diff where

-- A diff algorithm adapted from a edit-distance algorithm.

import Data.List
import Data.Function
data Source = L | R | B
              deriving (Show, Eq)

type Choice a = (Source, a)

type Diff a = [Choice a]

weight0 :: Num t => Source -> t
weight0 B = 0
weight0 _ = 1

weight :: [(Source, b)] -> Int
weight = sum . map (weight0 . fst)

diff' :: Eq a => [a] -> [a] -> Diff a
diff' = diff (==) (const)

diff :: (a -> a -> Bool) -- ^ compare two values
     -> (a -> a -> a)    -- ^ merge two values in the same equivalent class
     -> [a] -> [a] -> Diff a
diff (=?=) (=+=) a b = 
    tail $ reverse $ 
             last (if lab == 0 then mainDiag
                   else if lab > 0 then lowers !! (lab - 1)
                        else{- < 0 -}   uppers !! (-1 - lab))
    where mainDiag = oneDiag L R (error "diff: head element should not be forced") a b (head uppers) ([] : head lowers)
          uppers = eachDiag L R a b (mainDiag : uppers) -- upper diagonals
          lowers = eachDiag R L b a (mainDiag : lowers) -- lower diagonals
          eachDiag centerSrc edgeSrc a [] diags = []
          eachDiag centerSrc edgeSrc a (bch0:bs) (lastDiag:diags) = oneDiag centerSrc edgeSrc bch0 a bs nextDiag lastDiag 
                                                                    : eachDiag centerSrc edgeSrc a bs diags
              where nextDiag = head (tail diags)
          oneDiag centerSrc edgeSrc f a b diagAbove diagBelow = thisdiag
              where doDiag [] b nw n w = []
                    doDiag a [] nw n w = []
                    doDiag (ach:as) (bch:bs) nw n w = me : (doDiag as bs me (tail n) (tail w))
                        where me = if ach =?= bch then (B,ach =+= bch) : nw
                                                  else minBy weight ((edgeSrc,bch) : head w) ((centerSrc,ach) : head n)
                    firstelt = (edgeSrc,f) : head diagBelow
                    thisdiag = firstelt : doDiag a b firstelt diagAbove (tail diagBelow)
          lab = length a - length b

minBy :: Ord a => (t -> a) -> t -> t -> t
minBy f x y = if (f x) < (f y) then x else y

simplify :: Diff a -> Diff [a]
simplify = map simplOne . groupBy ((==) `on` fst)
           where simplOne l@((src,_):_) = (src,map snd l)

diffS :: Eq a => [a] -> [a] -> Diff a
diffS = diff (==) (const)

-- >>> simplify (diffS "test me" "tes me")
-- [(B,"tes"),(L,"t"),(B," me")]