changeset-containers-0.2: src/Data/Monoid/RightAction/Sequence.hs
module Data.Monoid.RightAction.Sequence where
-- base
import Data.Foldable (toList)
import GHC.Generics (Generic)
-- transformers
import Control.Monad.Trans.State.Strict
-- containers
import Data.Map (Map)
import Data.Map qualified as M
import Data.Sequence hiding (deleteAt, empty, insertAt, zipWith)
import Data.Sequence qualified as Seq
-- indexed-traversable
import Data.Foldable.WithIndex (FoldableWithIndex (..))
import Data.Functor.WithIndex (FunctorWithIndex (..))
import Data.Traversable.WithIndex (TraversableWithIndex (..))
-- changeset
import Control.Monad.Trans.Changeset (Changes (..))
import Data.Monoid.RightAction (RightAction (..), RightTorsor (..))
{- | Insert or delete an element at either end of a 'Seq'.
To change an element in a 'Seq', see the indexed changes in [@changeset-lens@](hackage.haskell.org/package/changeset-lens).
The general purpose changes 'Control.Monad.Trans.Changeset.FilterableChange',
'Control.Monad.Trans.Changeset.FilterableChanges', 'Control.Monad.Trans.Changeset.FilterableWithIndexChanges',
and 'Control.Monad.Trans.Changeset.AlignChanges' also apply to 'Seq'uences.
-}
data SeqChange a
= -- | Prepend an element
Cons a
| -- | Append an element
Snoc a
| -- | Drop an element from the left
Uncons
| -- | Drop an element from the right
Unsnoc
deriving stock (Show, Read, Eq, Ord, Functor, Generic)
instance RightAction (SeqChange a) (Seq a) where
actRight s (Cons a) = a <| s
actRight s (Snoc a) = s |> a
actRight s Uncons = case viewl s of
EmptyL -> Seq.empty
_ :< as' -> as'
actRight s Unsnoc = case viewr s of
EmptyR -> Seq.empty
as' :> _ -> as'
{- | Change a sequence by deleting and inserting elements at a specified position.
The edit is not minimal or normalized.
For example, @'Control.Monad.Trans.Changeset.changes' ['InsertAt' 0 '()', 'DeleteAt' 0]@ performs a trivial action on a sequence,
but is not equal to the empty 'Changes'.
-}
data SeqEdit a
= DeleteAt Int
| InsertAt Int a
deriving stock (Eq, Ord, Show, Read, Generic, Functor, Foldable, Traversable)
{- | Shift the position of the edit by 1
The resulting edit applies the same change to a sequence prepended by one element:
@
a '<|' 'actRight' s edit = 'actRight' (a '<|' s) (shift edit)
@
-}
shift :: SeqEdit a -> SeqEdit a
shift (DeleteAt i) = DeleteAt $ i + 1
shift (InsertAt i a) = InsertAt (i + 1) a
instance RightAction (SeqEdit a) (Seq a) where
actRight as = \case
DeleteAt i -> Seq.deleteAt i as
InsertAt i a -> Seq.insertAt i a as
{- | An edit script is a sequence of edits to a 'Seq'.
It is implemented as a 'Seq'uence of 'SeqEdit's.
The 'RightTorsor' instance implements the shortest edit between to sequences.
__Note__: This is not always a lawful 'RightTorsor', but in practice this doesn't matter.
The first law is:
@s `differenceRight` (s `actRight` es) = es@
That is, when an edit script acts on a sequence, it is recovered by 'differenceRight'.
This laws doesn't hold though if @es@ contains positions outside the range of @s@.
In practice, this doesn't matter though, because the action on @s@ of either side of the equation is still the same:
@s `actRight` (s `differenceRight` (s `actRight` es)) = s `actRight` es@
-}
newtype EditScript a = EditScript {getEditScript :: Changes (SeqEdit a)}
deriving stock (Show, Read, Traversable, Generic, Foldable, Functor)
deriving newtype (Eq, Ord, Semigroup, Monoid)
instance FunctorWithIndex Int EditScript where
imap f (EditScript es) = EditScript $ imap (\i e -> f i <$> e) es
instance FoldableWithIndex Int EditScript
instance TraversableWithIndex Int EditScript where
itraverse handler = fmap EditScript . itraverse (traverse . handler) . getEditScript
instance RightAction (EditScript a) (Seq a) where
actRight s EditScript {getEditScript} = actRight s getEditScript
{- | Compute the minimum edit script.
Implements a memoized longest common subsequence search.
-}
instance (Ord a) => RightTorsor (EditScript a) (Seq a) where
differenceRight asOrig0 asActed0 = EditScript $ Changes $ evalState (memoized (toList asOrig0) (toList asActed0)) M.empty
where
memoized :: [a] -> [a] -> State (Map ([a], [a]) (Seq (SeqEdit a))) (Seq (SeqEdit a))
memoized [] asActed = pure $ Seq.fromList $ zipWith InsertAt [0 ..] asActed
memoized asOrig [] = pure $ Seq.fromList $ DeleteAt 0 <$ asOrig
memoized asOrig@(aOrig : asOrigTail) asActed@(aActed : asActedTail) =
if aOrig == aActed
then fmap shift <$> memoized asOrigTail asActedTail
else do
cache <- gets $ M.lookup (asOrig, asActed)
case cache of
Just edit -> pure edit
Nothing -> do
l <- memoized asOrig asActedTail
r <- memoized asOrigTail asActed
let result =
if Seq.length l < Seq.length r
then InsertAt 0 aActed <| (shift <$> l)
else DeleteAt 0 <| r
modify' $ M.insert (asOrig, asActed) result
pure result