packages feed

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

-- A strict general trie for types with orderings
module Trie 
    (

     Trie, 

     -- * Construction
     empty, 
     fromList, fromListWith,
     insert, insertWith,

     -- * Query
     lookup, member,

     -- * Destruction
     toList

    ) where

import qualified Data.Map as Map
import Data.Maybe (fromMaybe, isJust)
import Prelude hiding (lookup)

-- The value argument couble be Maybe b, but we
-- save some memory this way
data Trie a b = Node !(Map.Map a (Trie a b))
	      | ValNode !(Map.Map a (Trie a b)) !b
-- deriving (Show)

-- | An empty trie
empty :: Trie a b
empty = Node Map.empty

-- | Construct a trie from a list of (key, value) pairs.
fromList :: Ord a => [([a], b)] -> Trie a b
fromList = fromListWith const

-- | Construct a trie from a list of (key, value) pairs
--   with a combining function for values of equals keys.
--   The order in which the elements are inserted and passed
--   to the combining function is unspecified.
fromListWith :: Ord a => (b -> b -> b) -> [([a], b)] -> Trie a b
fromListWith f = fromListWith' f id

-- | Construct a trie from a list of (key, value) pairs
--   with a combining function for values of equals keys, 
--   and a construction function used the first time a key is inserted.
--   The order in which the elements are inserted and passed
--   to the combining function is unspecified.
fromListWith' :: Ord a => (b -> c -> b) -> (c -> b) -> [([a], c)] -> Trie a b
fromListWith' f g = foldl (flip (uncurry (insertWith' f g))) empty -- foldl for strictness

-- | Insert an element into a trie.
insert ::  Ord a => [a] -> b -> Trie a b -> Trie a b
insert = insertWith const

-- | Insert an element into a trie with a combining
--   function for the value of an existing key equal to
--   the given one.
insertWith :: Ord a => (b -> b -> b) -- ^ The first argument is the existing value,
	                             --   the second is the new value.
	   -> [a] -> b -> Trie a b -> Trie a b
insertWith f = insertWith' f id


-- | Insert an element into a trie with a combining
--   function for the value of an existing key equal to
--   the given one, and a construction function used the
--   first time a key is inserted.
insertWith' :: Ord a => (b -> c -> b) -- ^ The first argument is the existing value,
	                              --   the second is the new value.
    -> (c -> b) -- ^ Used to transform the input value to a value
                --   in the map if there is no existing value.
    -> [a] -> c -> Trie a b -> Trie a b
insertWith' f g cs y = insertWith_ cs
    where 
    insertWith_ []     (Node m)      = ValNode m (g y)
    insertWith_ []     (ValNode m v) = ValNode m (f v y)
    insertWith_ (c:cs) t             = updateMap h t
	where h m = Map.insert c (insertWith_ cs t) m
		  where t = fromMaybe empty (Map.lookup c m)
    updateMap h (Node m)      = Node (h m)
    updateMap h (ValNode m v) = ValNode (h m) v

-- | Lookup a key in a trie.
lookup :: (Monad m, Ord a) => [a] -> Trie a b -> m b
lookup []     (Node _)      = fail "Not found"
lookup []     (ValNode _ v) = return v
lookup (c:cs) t             = case Map.lookup c (getMap t) of
			           Nothing -> fail "Not found"
				   Just t -> lookup cs t
    where getMap (Node m)      = m
	  getMap (ValNode m _) = m

-- | Check if a key is in a trie.
member :: Ord a => [a] -> Trie a b -> Bool
member k tr = isJust (lookup k tr)

-- | Get the (key,value) pairs from a trie.
--   The results are ordered by key.
toList :: Trie a b -> [([a],b)]
toList t = collapse t []
    where 
    collapse (Node m)      xs = rest m xs
    collapse (ValNode m v) xs = (reverse xs, v) : rest m xs
    rest m xs = concat [collapse tr (c:xs) | (c,tr) <- Map.toList m]