dao-0.1.0.1: src/Dao/Tree.hs
-- "src/Dao/Tree.hs" provides a fundamental data type used in the Dao
-- System, the "Tree", which is similar to the "Data.Map" data type.
--
-- Copyright (C) 2008-2014 Ramin Honary.
-- This file is part of the Dao System.
--
-- The Dao System is free software: you can redistribute it and/or
-- modify it under the terms of the GNU General Public License as
-- published by the Free Software Foundation, either version 3 of the
-- License, or (at your option) any later version.
--
-- The Dao System is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program (see the file called "LICENSE"). If not, see
-- <http://www.gnu.org/licenses/agpl.html>.
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module Dao.Tree where
import Dao.String (HasNullValue, nullValue, testNull) -- TODO: move the HasNullValue class to it's own module.
import Control.Applicative
import Control.DeepSeq
import Control.Monad
import Control.Monad.Identity
import Control.Monad.Trans
import Control.Monad.State
import Data.Typeable
import Data.Monoid
import Data.List (intercalate)
-- import Data.Binary
import qualified Data.Map as M
import Data.Word
----------------------------------------------------------------------------------------------------
data Tree p n
= Void
| Leaf { branchData :: n }
| Branch { branchMap :: M.Map p (Tree p n) }
| LeafBranch
{ branchData :: n
, branchMap :: M.Map p (Tree p n)
}
deriving Typeable
instance (Show p, Show n) => Show (Tree p n) where
show o = case o of
Void -> "voidTree"
Leaf o -> "(leaf "++show o++")"
Branch t -> "(branch ["++branch t++"])"
LeafBranch o t -> "(leaf "++show o++" branch ["++branch t++"])"
where { branch t = intercalate ", " (M.assocs t >>= \ (nm, o) -> [show nm++"="++show o]) }
instance (Eq p, Eq n) => Eq (Tree p n) where
(==) a b = case (a, b) of
(Void , Void ) -> True
(Leaf a , Leaf b ) -> a == b
(Branch a , Branch b ) -> a == b
(LeafBranch a aa, LeafBranch b bb) -> a == b && aa == bb
_ -> False
instance (Ord p, Ord n) => Ord (Tree p n) where
compare a b = case (a, b) of
(Void , Void ) -> EQ
(Leaf a , Leaf b ) -> compare a b
(Branch aa, Branch bb) -> compare aa bb
(LeafBranch a aa, LeafBranch b bb) -> case compare a b of
EQ -> compare aa bb
e -> e
(Void , _ ) -> LT
(_ , Void ) -> GT
(Leaf _ , _ ) -> LT
(_ , Leaf _ ) -> GT
(Branch _ , _ ) -> LT
(_ , Branch _ ) -> GT
instance Ord p => Functor (Tree p) where
fmap f tree = case tree of
Void -> Void
Leaf a -> Leaf (f a)
Branch m -> Branch (fmap (fmap f) m)
LeafBranch a m -> LeafBranch (f a) (fmap (fmap f) m)
instance (Ord p, Monoid n) => Monoid (Tree p n) where
mempty = Void
mappend = unionWith mappend
instance (NFData a, NFData b) => NFData (Tree a b) where
rnf Void = ()
rnf (Leaf a ) = deepseq a ()
rnf (Branch b) = deepseq b ()
rnf (LeafBranch a b) = deepseq a $! deepseq b ()
instance HasNullValue (Tree a b) where { nullValue = Void; testNull = Dao.Tree.null; }
-- | A combinator to modify the data in the 'Leaf' and 'LeafBranch' nodes of a tree when passed to
-- one of the functions below.
type ModLeaf n = Maybe n -> Maybe n
-- | A combinator to modify the data in the 'Branch' and 'LeafBranch' nodes of a tree when passed to
-- one of the functions below.
type ModBranch p n = Maybe (M.Map p (Tree p n)) -> Maybe (M.Map p (Tree p n))
-- | If a 'Tree' is 'Void' or a contains a branch that is equivalent to 'Data.Map.empty',
-- 'Data.Maybe.Nothing' is returned.
notVoid :: Tree p n -> Maybe (Tree p n)
notVoid t = case t of
Void -> Nothing
Branch b | M.null b -> Nothing
LeafBranch a b | M.null b -> Just (Leaf a)
_ -> Just t
-- | If the given node is a 'Leaf' or 'LeafBranch', returns the Leaf portion of the node.
getLeaf :: Tree p n -> Maybe n
getLeaf t = case t of { Leaf n -> Just n; LeafBranch n _ -> Just n; _ -> Nothing }
-- | If the given node is a 'Branch' or 'LeafBranch', returns the branch portion of the node.
getBranch :: Tree p a -> Maybe (M.Map p (Tree p a))
getBranch t = case t of { Branch b -> Just b; LeafBranch _ b -> Just b; _ -> Nothing }
-- | Use a 'ModLeaf' function to insert, update, or remove 'Leaf' and 'LeafBranch' nodes.
alterLeaf :: ModLeaf n -> Tree p n -> Tree p n
alterLeaf alt t = maybe Void id $ case t of
Void -> alt Nothing >>= \o -> Just (Leaf o)
Leaf o -> alt (Just o) >>= \o -> Just (Leaf o)
Branch b -> mplus (alt Nothing >>= \o -> Just (LeafBranch o b)) (Just (Branch b))
LeafBranch o b -> mplus (alt (Just o) >>= \o -> Just (LeafBranch o b)) (Just (Branch b))
alterBranch :: (Eq p, Ord p) => ModBranch p n -> Tree p n -> Tree p n
alterBranch alt t = maybe Void id $ case t of
Void -> alt Nothing >>= \b -> Just (Branch b)
Leaf o -> mplus (alt Nothing >>= \b -> Just (LeafBranch o b)) (Just (Leaf o))
Branch b -> alt (Just b) >>= \b -> Just (Branch b)
LeafBranch o b -> mplus (alt (Just b) >>= \b -> Just (LeafBranch o b)) (Just (Leaf o))
----------------------------------------------------------------------------------------------------
data ZipTree p n = ZipTree{ focus :: Tree p n, history :: [(p, Tree p n)] }
newtype UpdateTreeT p n m a = UpdateTreeT{ getUpdateTreeStateT :: StateT (ZipTree p n) m a }
type UpdateTree p n a = UpdateTreeT p n Identity a
instance Monad m =>
Monad (UpdateTreeT p n m) where
return = UpdateTreeT . return
(UpdateTreeT a) >>= b = UpdateTreeT (a >>= getUpdateTreeStateT . b)
instance Functor m =>
Functor (UpdateTreeT p n m) where { fmap f (UpdateTreeT m) = UpdateTreeT (fmap f m); }
instance (Monad m, Functor m) =>
Applicative (UpdateTreeT p n m) where { pure = return; (<*>) = ap; }
instance Monad m =>
MonadState (ZipTree p n) (UpdateTreeT p n m) where { state = UpdateTreeT . state; }
instance MonadTrans (UpdateTreeT p n) where { lift m = UpdateTreeT (lift m); }
-- | Like 'Control.Monad.State.runState', evaluates an 'UpdateTree' monad transformer lifting the
-- 'Control.Monad.Identity.Identity' monad, removing the identity monad after evaluation to give you
-- a pure function.
runUpdateTree :: Ord p => UpdateTree p n a -> Tree p n -> (a, Tree p n)
runUpdateTree updfn = runIdentity . runUpdateTreeT updfn
-- | Like 'Control.Monad.State.execState', disgards the value returned to the 'UpdateTree' monad and
-- only returns the 'Tree'.
execUpdateTree :: Ord p => UpdateTree p n a -> Tree p n -> Tree p n
execUpdateTree updfn = snd . runUpdateTree updfn
-- | Update a 'Tree' using an 'UpdateTreeT' monad, much like how 'Control.Monad.State.runStateT'
-- works. Evaluates to a monadic computation of the lifted type @m@ that 'Control.Monad.return's a
-- pair containing the value last 'Control.Monad.return'ed to the lifted monad and the updated
-- 'Tree'.
runUpdateTreeT :: (Ord p, Functor m, Monad m) => UpdateTreeT p n m a -> Tree p n -> m (a, Tree p n)
runUpdateTreeT updfn tree = fmap (fmap focus) $
runStateT (getUpdateTreeStateT (updfn >>= \a -> home >> return a)) (ZipTree{focus=tree, history=[]})
-- | Go to the node with the given path. If the path does not exist, it is created.
goto :: (Ord p, Monad m) => [p] -> UpdateTreeT p n m (Tree p n)
goto path = case path of
[] -> gets focus
(p:path) -> do
st <- get
let step tree = put $ st{focus=tree, history=(p, focus st):history st}
case getBranch (focus st) >>= M.lookup p of
Nothing -> step Void
Just tree -> step tree
goto path
-- | Go up one level in the tree, storing the current sub-tree into the upper tree, unless the
-- current tree is 'Void', in which case it is deleted from the upper tree.
back :: (Ord p, Monad m) => UpdateTreeT p n m ()
back = modify $ \st -> case history st of
[] -> st
(p, tree):hist ->
st{ history = hist
, focus = flip alterBranch tree $ \branch -> flip mplus (fmap (M.delete p) branch) $ do
subTree <- notVoid (focus st)
fmap (M.insert p subTree) (mplus branch (return mempty))
}
-- | Returns 'Prelude.True' if we are at the top level of the tree.
atTop :: (Functor m, Monad m) => UpdateTreeT p n m Bool
atTop = fmap Prelude.null (gets history)
-- | Go back to the top level of the tree.
home :: (Ord p, Functor m, Monad m) => UpdateTreeT p n m ()
home = atTop >>= flip unless (back >> home)
-- | Return the current path.
getPath :: (Ord p, Functor m, Monad m) => UpdateTreeT p n m [p]
getPath = fmap (reverse . fmap fst) (gets history)
-- | Modify the tree node at the current 'focus'. After the update, if there is a leaf attached at
-- the focus, the value of the leaf is returned.
modifyNode :: (Ord p, Functor m, Monad m) => (Tree p n -> Tree p n) -> UpdateTreeT p n m (Maybe n)
modifyNode mod = modify (\st -> st{focus=mod(focus st)}) >> fmap getLeaf (gets focus)
-- | Modify the tree node using a 'ModBranch' function which allows you to alter the 'Data.Map.Map'
-- object containing the branches of the current node.
modifyBranch :: (Ord p, Functor m, Monad m) => ModBranch p n -> UpdateTreeT p n m ()
modifyBranch mod = modifyNode (alterBranch mod) >> return ()
-- | Modify the tree node using a 'ModLeaf' function which allows you to alter the 'Data.Map.Map'
-- object containing the current of the current node.
modifyLeaf :: (Ord p, Functor m, Monad m) => ModLeaf n -> UpdateTreeT p n m (Maybe n)
modifyLeaf mod = modifyNode (alterLeaf mod)
----------------------------------------------------------------------------------------------------
-- $MapLikeFunctions
-- In this section I have made my best effor to create API functions as similar as possible to that
-- of the "Data.Map" module.
----------------------------------------------------------------------------------------------------
alter :: Ord p => (Tree p a -> Tree p a) -> [p] -> Tree p a -> Tree p a
alter mod path = execUpdateTree (goto path >> modifyNode mod)
--alterNode alt px t = runIdentity $ alterNodeM (return . alt) px t
-- | Insert a 'Leaf' at a given address.
insert :: Ord p => [p] -> n -> Tree p n -> Tree p n
insert path n = execUpdateTree (goto path >> modifyLeaf (const (Just n)))
--insert px a = alter (const (Just a)) (flip mplus (Just Void)) px
-- | Update a 'Leaf' at a given address.
update :: Ord p => [p] -> ModLeaf a -> Tree p a -> Tree p a
update path mod = execUpdateTree (goto path >> modifyLeaf mod)
--update path mod = alter mod (flip mplus (Just Void)) path
-- | Delete a 'Leaf' or 'Branch' at a given address.
delete :: Ord p => [p] -> Tree p a -> Tree p a
delete path = execUpdateTree (goto path >> modifyLeaf (const Nothing))
--delete px = alter (const Nothing) id px
-- | Create a 'Tree' from a list of associationes, the 'Prelude.fst' element containing the branch,
-- the 'Prelude.snd' element containing the leaf value. This is the inverse operation of 'assocs'.
fromList :: Ord p => [([p], a)] -> Tree p a
fromList = foldl (\ tree (px, a) -> insert px a tree) Void
-- | Lookup a 'Tree' value (the whole node, not just the data stored in the node) at given address.
-- NOTE: this may not be what you want. If you want return the data that is stored in a 'Leaf' or
-- 'LeafBranch', use 'lookup', or just do @'lookup' atBranch inTree >>= 'getLeaf'@.
lookupNode :: Ord p => [p] -> Tree p a -> Maybe (Tree p a)
lookupNode px t = case px of
[] -> Just t
p:px -> case t of
Branch t -> next p t
LeafBranch _ t -> next p t
_ -> Nothing
where { next p t = M.lookup p t >>= Dao.Tree.lookupNode px }
-- | This function analogous to the 'Data.Map.lookup' function, which returns a value stored in a
-- leaf, or nothing if there is no leaf at the given path.
lookup :: Ord p => [p] -> Tree p a -> Maybe a
lookup px t = lookupNode px t >>= getLeaf
-- | Using @[p]@ as a path, traverse the path through the given 'Tree' as far as possible, return
-- the last node that could be reached along with the remainder of the path that was not traversed.
-- This is used to lookup whether or not a leaf has been stored into the tree at the given path, or
-- at some sub-path of the given path.
partialLookup :: Ord p => [p] -> Tree p a -> Maybe ([p], Tree p a)
partialLookup px t = case px of
[] -> Just ([], t)
p:px -> mplus (getBranch t >>= M.lookup p >>= partialLookup px) $
(getLeaf t >>= \ _ -> return (p:px, t))
-- | Using @[p]@ as a path, traverse a tree and retrieve every leaf found along the path until
-- traversal cannot continue. Evaluates to the list of leaves retrieved, the portion of the path
-- that could not be traversed, and the node at which traversal stopped.
leavesAlongPath :: Ord p => [p] -> Tree p a -> ([a], ([p], Tree p a))
leavesAlongPath px t = maybe ([], (px, t)) id $ loop [] px t where
loop ax px t = return (ax ++ maybe [] (:[]) (getLeaf t)) >>= \ax -> case px of
[] -> return (ax, ([], t))
p:px -> mplus (getBranch t >>= M.lookup p >>= loop ax px) (return (ax, (p:px, t)))
-- | There are only two kinds values defined as a 'MergeType': 'union' and 'intersection.
type MergeType p a
= (Tree p a -> Tree p a -> Tree p a)
-> M.Map p (Tree p a)
-> M.Map p (Tree p a)
-> M.Map p (Tree p a)
-- | Merge two trees together.
mergeWithKey :: Ord p
=> ([p] -> Maybe a -> Maybe b -> Maybe c)
-> (Tree p a -> Tree p c)
-> (Tree p b -> Tree p c)
-> Tree p a -> Tree p b -> Tree p c
mergeWithKey overlap leftOnly rightOnly left right = loop [] left right where
-- loop :: Ord p => [p] -> Tree p a -> Tree p b -> Tree p c
loop px left right = case left of
Void -> case right of
Void -> Void
Leaf y -> rightOnly (Leaf y )
Branch b -> rightOnly (Branch b)
LeafBranch y b -> rightOnly (LeafBranch y b)
Leaf x -> case right of
Void -> leftOnly (Leaf x )
Leaf y -> maybe Void id (fmap Leaf (overlap px (Just x) (Just y)))
Branch b -> leafbranch (Just x) Nothing M.empty b
LeafBranch y b -> leafbranch (Just x) (Just y) M.empty b
Branch a -> case right of
Void -> leftOnly (Branch a)
Leaf y -> leafbranch Nothing (Just y) a M.empty
Branch b -> leafbranch Nothing Nothing a b
LeafBranch y b -> leafbranch Nothing (Just y) a b
LeafBranch x a -> case right of
Void -> leftOnly (LeafBranch x a)
Leaf y -> leafbranch (Just x) (Just y) a M.empty
Branch b -> leafbranch (Just x) Nothing a b
LeafBranch y b -> leafbranch (Just x) (Just y) a b
where
-- leafbranch :: Ord p => M.Map p (Tree p a) -> M.Map p (Tree p b) -> Maybe a -> Maybe b -> Tree p c
leafbranch x y left right =
let c = M.mergeWithKey both (bias leftOnly) (bias rightOnly) left right -- :: M.Map p (Tree p c)
in case overlap px x y of
Nothing -> notEmpty Branch c
Just z -> notEmpty (LeafBranch z) c
-- notEmpty :: Ord p => (M.Map p (Tree p a) -> Tree p a) -> M.Map p (Tree p a) -> Tree p a
notEmpty cons c = if M.null c then Void else cons c
-- both :: Ord p => p -> Tree p a -> Tree p b -> Maybe (Tree p c)
both p left right = notVoid (loop (px++[p]) left right)
-- bias :: Ord p => (Tree p a -> Tree p b) -> M.Map p (Tree p a) -> M.Map p (Tree p b)
bias fn = M.mapMaybe (notVoid . fn)
mergeWith :: Ord p => (Maybe a -> Maybe b -> Maybe c) -> (Tree p a -> Tree p c) -> (Tree p b -> Tree p c) -> Tree p a -> Tree p b -> Tree p c
mergeWith overlap = mergeWithKey (\ _ -> overlap)
unionWithKey :: Ord p => ([p] -> a -> a -> a) -> Tree p a -> Tree p a -> Tree p a
unionWithKey overlap = mergeWithKey (\k a b -> msum [liftM2 (overlap k) a b, a, b]) id id
unionWith :: Ord p => (a -> a -> a) -> Tree p a -> Tree p a -> Tree p a
unionWith overlap = unionWithKey (\ _ -> overlap)
union :: Ord p => Tree p a -> Tree p a -> Tree p a
union = unionWith const
unionsWith :: Ord p => (a -> a -> a) -> [Tree p a] -> Tree p a
unionsWith overlap = foldl (unionWith overlap) Void
unions :: Ord p => [Tree p a] -> Tree p a
unions = unionsWith (flip const)
intersectionWithKey :: Ord p => ([p] -> a -> a -> a) -> Tree p a -> Tree p a -> Tree p a
intersectionWithKey overlap = mergeWithKey (\k -> liftM2 (overlap k)) (const Void) (const Void)
intersectionWith :: Ord p => (a -> a -> a) -> Tree p a -> Tree p a -> Tree p a
intersectionWith overlap = intersectionWithKey (\ _ -> overlap)
intersection :: Ord p => Tree p a -> Tree p a -> Tree p a
intersection = intersectionWith const
intersectionsWith :: Ord p => (a -> a -> a) -> [Tree p a] -> Tree p a
intersectionsWith overlap = foldl (intersectionWith overlap) Void
intersections :: Ord p => [Tree p a] -> Tree p a
intersections = intersectionsWith (flip const)
differenceWithKey :: Ord p => ([p] -> a -> b -> Maybe a) -> Tree p a -> Tree p b -> Tree p a
differenceWithKey overlap = mergeWithKey (\k a b -> mplus (b >>= \b -> a >>= \a -> overlap k a b) a) id (const Void)
differenceWith :: Ord p => (a -> b -> Maybe a) -> Tree p a -> Tree p b -> Tree p a
differenceWith overlap = differenceWithKey (\ _ -> overlap)
difference :: Ord p => Tree p a -> Tree p b -> Tree p a
difference = differenceWith (\ _ _ -> Nothing)
-- | Get all items and their associated path.
assocs :: Tree p a -> [([p], a)]
assocs t = loop [] t where
recurs px b = M.assocs b >>= \ (p, t) -> loop (px++[p]) t
loop px t = case t of
Void -> []
Leaf a -> [(px, a)]
Branch b -> recurs px b
LeafBranch a b -> (px, a) : recurs px b
-- | Apply @'Prelude.map' 'Prelude.snd'@ to the result of 'assocs', behaves just like how
-- 'Data.Map.elems' or 'Data.Array.IArray.elems' works.
elems :: Tree p a -> [a]
elems t = fmap snd (assocs t)
-- | Counts the number of *nodes*, which includes the number of 'Branch'es and 'Leaf's.
size :: Tree p a -> Word64
size t = case t of
Void -> 0
Leaf _ -> 1
Branch m -> 0 + f m
LeafBranch _ m -> 1 + f m
where { f m = foldl (\sz tre -> sz + size tre) (fromIntegral (M.size m)) (M.elems m) }
branchCount :: Tree p a -> Int
branchCount = maybe 0 M.size . getBranch
null :: Tree p a -> Bool
null Void = True
null _ = False
----------------------------------------------------------------------------------------------------
data TreeDiff a b
= LeftOnly a -- something exists in the "left" branch but not in the "right" branch.
| RightOnly b -- something exists in the "right" branch but not in the "left" branch.
| TreeDiff a b -- something exists in the "left" and "right" branches but they are not equal
deriving (Eq, Typeable)
-- | Produce a difference report of two trees with the given comparison predicate. If the predicate
-- returns 'Prelude.True', the node is ignored, otherwise the differences is reported.
treeDiffWith :: Ord p => (a -> b -> Bool) -> Tree p a -> Tree p b -> Tree p (TreeDiff a b)
treeDiffWith compare = mergeWithKey leaf (fmap LeftOnly) (fmap RightOnly) where
leaf _ a b = msum $
[ a >>= \a -> b >>= \b -> if compare a b then Nothing else Just (TreeDiff a b)
, fmap LeftOnly a, fmap RightOnly b
]
-- | Call 'treeDiffWith' using 'Prelude.(==)' as the comparison predicate.
treeDiff :: (Eq a, Ord p) => Tree p a -> Tree p a -> Tree p (TreeDiff a a)
treeDiff = treeDiffWith (==)