tree-diff-0.2: src/Data/TreeDiff/List.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | A list diff.
module Data.TreeDiff.List (
diffBy,
Edit (..),
) where
import Control.DeepSeq (NFData (..))
import Control.Monad.ST (ST, runST)
import qualified Data.Primitive as P
-- import Debug.Trace
-- | List edit operations
--
-- The 'Swp' constructor is redundant, but it let us spot
-- a recursion point when performing tree diffs.
data Edit a
= Ins a -- ^ insert
| Del a -- ^ delete
| Cpy a -- ^ copy unchanged
| Swp a a -- ^ swap, i.e. delete + insert
deriving (Eq, Show)
instance NFData a => NFData (Edit a) where
rnf (Ins x) = rnf x
rnf (Del x) = rnf x
rnf (Cpy x) = rnf x
rnf (Swp x y) = rnf x `seq` rnf y
-- | List difference.
--
-- >>> diffBy (==) "hello" "world"
-- [Swp 'h' 'w',Swp 'e' 'o',Swp 'l' 'r',Cpy 'l',Swp 'o' 'd']
--
-- >>> diffBy (==) "kitten" "sitting"
-- [Swp 'k' 's',Cpy 'i',Cpy 't',Cpy 't',Swp 'e' 'i',Cpy 'n',Ins 'g']
--
-- prop> \xs ys -> length (diffBy (==) xs ys) >= max (length xs) (length (ys :: String))
-- prop> \xs ys -> length (diffBy (==) xs ys) <= length xs + length (ys :: String)
--
diffBy :: forall a. Show a => (a -> a -> Bool) -> [a] -> [a] -> [Edit a]
diffBy _ [] [] = []
diffBy _ [] ys' = map Ins ys'
diffBy _ xs' [] = map Del xs'
diffBy eq xs' ys'
| otherwise = reverse (getCell lcs)
where
xn = length xs'
yn = length ys'
xs = P.arrayFromListN xn xs'
ys = P.arrayFromListN yn ys'
lcs :: Cell [Edit a]
lcs = runST $ do
-- traceShowM ("sizes", xn, yn)
-- create two buffers.
buf1 <- P.newArray yn (Cell 0 [])
buf2 <- P.newArray yn (Cell 0 [])
-- fill the first row
-- 0,0 case is filled already
yLoop (Cell 0 []) $ \m (Cell w edit) -> do
let cell = Cell (w + 1) (Ins (P.indexArray ys m) : edit)
P.writeArray buf1 m cell
P.writeArray buf2 m cell
-- traceShowM ("init", m, cell)
return cell
-- following rows
--
-- cellC cellT
-- cellL cellX
(buf1final, _, _) <- xLoop (buf1, buf2, Cell 0 []) $ \n (prev, curr, cellC) -> do
-- prevZ <- P.unsafeFreezeArray prev
-- currZ <- P.unsafeFreezeArray prev
-- traceShowM ("prev", n, prevZ)
-- traceShowM ("curr", n, currZ)
let cellL :: Cell [Edit a]
cellL = case cellC of (Cell w edit) -> Cell (w + 1) (Del (P.indexArray xs n) : edit)
-- traceShowM ("cellC, cellL", n, cellC, cellL)
yLoop (cellC, cellL) $ \m (cellC', cellL') -> do
-- traceShowM ("inner loop", n, m)
cellT <- P.readArray prev m
-- traceShowM ("cellT", n, m, cellT)
let x, y :: a
x = P.indexArray xs n
y = P.indexArray ys m
-- from diagonal
let cellX1 :: Cell [Edit a]
cellX1
| eq x y = bimap id (Cpy x :) cellC'
| otherwise = bimap (+1) (Swp x y :) cellC'
-- from left
let cellX2 :: Cell [Edit a]
cellX2 = bimap (+1) (Ins y :) cellL'
-- from top
let cellX3 :: Cell [Edit a]
cellX3 = bimap (+1) (Del x :) cellT
-- the actual cell is best of three
let cellX :: Cell [Edit a]
cellX = bestOfThree cellX1 cellX2 cellX3
-- traceShowM ("cellX", n, m, cellX)
-- memoize
P.writeArray curr m cellX
return (cellT, cellX)
return (curr, prev, cellL)
P.readArray buf1final (yn - 1)
xLoop :: acc -> (Int -> acc -> ST s acc) -> ST s acc
xLoop !acc0 f = go acc0 0 where
go !acc !n | n < xn = do
acc' <- f n acc
go acc' (n + 1)
go !acc _ = return acc
yLoop :: acc -> (Int -> acc -> ST s acc) -> ST s ()
yLoop !acc0 f = go acc0 0 where
go !acc !m | m < yn = do
acc' <- f m acc
go acc' (m + 1)
go _ _ = return ()
data Cell a = Cell !Int !a deriving Show
getCell :: Cell a -> a
getCell (Cell _ x) = x
bestOfThree :: Cell a -> Cell a -> Cell a -> Cell a
bestOfThree a@(Cell i _x) b@(Cell j _y) c@(Cell k _z)
| i <= j
= if i <= k then a else c
| otherwise
= if j <= k then b else c
bimap :: (Int -> Int) -> (a -> b) -> Cell a -> Cell b
bimap f g (Cell i x) = Cell (f i) (g x)