packages feed

bytestring-trie 0.1.2 → 0.1.3

raw patch · 5 files changed

+670/−504 lines, 5 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Data.Trie: instance (Binary a) => Binary (Trie a)
- Data.Trie: instance (Eq a) => Eq (Trie a)
- Data.Trie: instance (Monoid a) => Monoid (Trie a)
- Data.Trie: instance Foldable Trie
- Data.Trie: instance Functor Trie
- Data.Trie: instance Monad Trie
- Data.Trie: instance Traversable Trie
- Data.Trie: lookupBy_ :: (Maybe a -> Trie a -> b) -> b -> (Trie a -> b) -> KeyString -> Trie a -> b
- Data.Trie: showTrie :: (Show a) => Trie a -> String
+ Data.Trie: keys :: Trie a -> [KeyString]
+ Data.Trie: toListBy :: (KeyString -> a -> b) -> Trie a -> [b]
+ Data.Trie.Convenience: fromListL :: [(KeyString, a)] -> Trie a
+ Data.Trie.Convenience: fromListR :: [(KeyString, a)] -> Trie a
+ Data.Trie.Convenience: fromListS :: [(KeyString, a)] -> Trie a
+ Data.Trie.Internal: alterBy :: (KeyString -> a -> Maybe a -> Maybe a) -> KeyString -> a -> Trie a -> Trie a
+ Data.Trie.Internal: data Trie a
+ Data.Trie.Internal: empty :: Trie a
+ Data.Trie.Internal: filterMap :: (a -> Maybe b) -> Trie a -> Trie b
+ Data.Trie.Internal: instance (Binary a) => Binary (Trie a)
+ Data.Trie.Internal: instance (Eq a) => Eq (Trie a)
+ Data.Trie.Internal: instance (Monoid a) => Monoid (Trie a)
+ Data.Trie.Internal: instance Foldable Trie
+ Data.Trie.Internal: instance Functor Trie
+ Data.Trie.Internal: instance Monad Trie
+ Data.Trie.Internal: instance Traversable Trie
+ Data.Trie.Internal: lookupBy_ :: (Maybe a -> Trie a -> b) -> b -> (Trie a -> b) -> KeyString -> Trie a -> b
+ Data.Trie.Internal: mapBy :: (KeyString -> a -> Maybe b) -> Trie a -> Trie b
+ Data.Trie.Internal: mergeBy :: (a -> a -> Maybe a) -> Trie a -> Trie a -> Trie a
+ Data.Trie.Internal: null :: Trie a -> Bool
+ Data.Trie.Internal: showTrie :: (Show a) => Trie a -> String
+ Data.Trie.Internal: singleton :: KeyString -> a -> Trie a
+ Data.Trie.Internal: size :: Trie a -> Int
+ Data.Trie.Internal: submap :: KeyString -> Trie a -> Trie a
+ Data.Trie.Internal: toListBy :: (KeyString -> a -> b) -> Trie a -> [b]
+ Data.Trie.Internal: type KeyElem = ByteStringElem
+ Data.Trie.Internal: type KeyString = ByteString

Files

bytestring-trie.cabal view
@@ -3,7 +3,7 @@ ----------------------------------------------------------------  Name:           bytestring-trie-Version:        0.1.2+Version:        0.1.3 Cabal-Version:  >= 1.2 Build-Type:     Simple Stability:      beta@@ -28,6 +28,7 @@ Library     Hs-Source-Dirs:  src     Exposed-Modules: Data.Trie+                   , Data.Trie.Internal                    , Data.Trie.Convenience     Other-Modules:   Data.Trie.BitTwiddle                    , Data.Trie.ByteStringInternal
src/Data/Trie.hs view
@@ -1,7 +1,7 @@ {-# OPTIONS_GHC -Wall -fwarn-tabs #-}  -------------------------------------------------------------------                                                  ~ 2009.01.04+--                                                  ~ 2009.01.05 -- | -- Module      :  Data.Trie -- Copyright   :  Copyright (c) 2008--2009 wren ng thornton@@ -10,7 +10,7 @@ -- Stability   :  beta -- Portability :  portable ----- An efficient implementation of maps from strings to values.+-- An efficient implementation of finite maps from strings to values. -- -- The implementation is based on /big-endian patricia trees/, like -- "Data.IntMap". We first trie on the elements of "Data.ByteString"@@ -21,21 +21,24 @@ --	Workshop on ML, September 1998, pages 77-86, --	<http://www.cse.ogi.edu/~andy/pub/finite.htm> -----    * D.R. Morrison, \"/PATRICIA -- Practical Algorithm To Retrieve---	Information Coded In Alphanumeric/\", Journal of the ACM, 15(4),+--    * D.R. Morrison, \"/PATRICIA -- Practical Algorithm To Retrieve/+--	/Information Coded In Alphanumeric/\", Journal of the ACM, 15(4), --	October 1968, pages 514-534. ----------------------------------------------------------------  module Data.Trie     (     -- * Data types-      Trie(), KeyString, KeyElem, showTrie+      Trie(), KeyString, KeyElem          -- * Basic functions-    , empty, null, singleton, size, toList, fromList+    , empty, null, singleton, size     +    -- * Conversion functions+    , fromList, toListBy, toList, keys+         -- * Query functions-    , lookupBy_, lookupBy, lookup, member, submap+    , lookupBy, lookup, member, submap          -- * Single-value modification     , alterBy, insert, adjust, delete@@ -50,370 +53,49 @@ import Prelude hiding (null, lookup) import qualified Prelude -import qualified Data.ByteString as S-import Data.Trie.ByteStringInternal-import Data.Trie.BitTwiddle--import Data.Maybe          (isJust)-import Control.Monad       (liftM, liftM3, liftM4)-import Control.Applicative (Applicative(..), (<$>))-import Data.Monoid         (Monoid(..))-import Data.Foldable       (Foldable(foldMap))-import Data.Traversable    (Traversable(traverse))-import Data.Binary-------------------------------------------------------------------------------------------------------------------------------------{------------------------------------------------------------------ ByteString Big-endian Patricia Trie----------------------------------------------------------------}-type KeyString = ByteString -type KeyElem   = ByteStringElem --{- Idealized:-data Node a   = Accept a (ArcSet a)-              | Reject   (Branch a)          -- Invariant: Must be Branch-data Arc a    = Arc    KeyString (Node a)    -- Invariant: never empty string-data ArcSet a = None-              | One    {KeyElem} (Arc a)-              | Branch {Prefix} {Mask} (ArcSet a) (ArcSet a)-data Trie a   = Empty-              | Start  KeyString (Node a)    -- Maybe empty string [1]--[1] If we maintain the invariants on how Nodes recurse, then we-can't simply have Start(Node a) because we may have a shared prefix-where the prefix itself is not Accept'ed.----- Squash Arc into One:--- (pure good)-data Node a   = Accept a (ArcSet a)-              | Reject   (Branch a)-data ArcSet a = None-              | Arc    KeyString (Node a)-              | Branch {Prefix} {Mask} (ArcSet a) (ArcSet a)-data Trie a   = Empty-              | Start  KeyString (Node a)----- Squash Node together:--- (most likely good)-data Node a   = Node (Maybe a) (ArcSet a)-data ArcSet a = None-              | Arc    KeyString (Node a)-              | Branch {Prefix} {Mask} (ArcSet a) (ArcSet a)-data Trie a   = Empty-              | Start  KeyString (Node a)----- Squash Empty/None and Arc/Start together:--- (Complicates invariants about non-empty strings and Node's recursion)-data Node a = Node (Maybe a) (ArcSet a)-data Trie a = Empty-            | Arc    KeyString (Node a)-            | Branch {Prefix} {Mask} (Trie a) (Trie a)----- Squash Node into Arc:--- (By this point, pure good)--- Unseen invariants:--- * KeyString non-empty, unless Arc is absolute root of tree--- * If (Maybe a) is Nothing, then (Trie a) is Branch---   * With views, we could re-expand Arc into accepting and---     nonaccepting variants------ [2] Maybe we shouldn't unpack the KeyString. We could specialize--- or inline the splitMaximalPrefix function to prevent constructing--- a new KeyString from the parts...--}---- | A map from 'ByteString's to @a@. For all the generic functions,--- note that tries are strict in the @Maybe@ but not in @a@.------ The 'Monad' instance is strange. If a key @k1@ is a prefix of--- other keys, then results from binding the value at @k1@ will--- override values from longer keys when they collide. If this is--- useful for anything, or if there's a more sensible instance, I'd--- be curious to know.--data Trie a = Empty-            | Arc    {-# UNPACK #-} !KeyString-                                    !(Maybe a)-                                    !(Trie a)-            | Branch {-# UNPACK #-} !Prefix-                     {-# UNPACK #-} !Mask-                                    !(Trie a)-                                    !(Trie a)-    deriving Eq-    -- Prefix/Mask should be deterministic regardless of insertion order-    -- TODO: verify this is so.---- | Visualization fuction for debugging.-showTrie :: (Show a) => Trie a -> String-showTrie t = shows' id t ""-    where-    spaces f = map (const ' ') (f "")-    -    shows' _  Empty            = (".\n"++)-    shows' ss (Branch p m l r) =-        let s'  = ("--"++) . shows p . (","++) . shows m . ("-+"++)-            ss' = ss . (tail (spaces s') ++)-        in s'              . shows' (ss' . ("|"++)) l-           . ss' . ("|\n"++)-           . ss' . ("`"++) . shows' (ss' . (" "++)) r-    shows' ss (Arc k mv t') =-        let s' = ("--"++) . shows k-                 . maybe id (\v -> ("-("++) . shows v . (")"++)) mv-                 . ("--"++)-        in  s' . shows' (ss . (spaces s' ++)) t'---{------------------------------------------------------------------ Trie instances----------------------------------------------------------------}--instance Functor Trie where-    fmap _ Empty              = Empty-    fmap f (Arc k Nothing  t) = Arc k Nothing      (fmap f t)-    fmap f (Arc k (Just v) t) = Arc k (Just (f v)) (fmap f t)-    fmap f (Branch p m l r)   = Branch p m (fmap f l) (fmap f r)---- Does this even make sense? It's not nondeterminism like lists--- and sets. If no keys were prefixes of other keys it'd make sense--- as a decision-tree; but since keys /can/ prefix, tries formed--- from shorter keys can shadow the results from longer keys due--- to the 'unionL'. It does seem to follow the laws though... What--- computation could this possibly represent?------  1. return x >>= f  == f x---  2. m >>= return    == m---  3. (m >>= f) >>= g == m >>= (\x -> f x >>= g)-instance Monad Trie where-    return x = singleton S.empty x-    -    (>>=) Empty              _ = empty-    (>>=) (Arc k Nothing  t) f = arc k Nothing (t >>= f)-    (>>=) (Arc k (Just v) t) f = arc k Nothing (f v `unionL` (t >>= f))-    (>>=) (Branch p m l r)   f = branch p m (l >>= f) (r >>= f)---instance Monoid a => Monoid (Trie a) where-    mempty  = empty-    mappend = mergeBy $ \x y -> Just (x `mappend` y)---instance Foldable Trie where-    foldMap _ Empty              = mempty-    foldMap f (Arc _ Nothing  t) = foldMap f t-    foldMap f (Arc _ (Just v) t) = f v `mappend` foldMap f t-    foldMap f (Branch _ _ l r)   = foldMap f l `mappend` foldMap f r---instance Traversable Trie where-    traverse _ Empty              = pure Empty-    traverse f (Arc k Nothing  t) = Arc k Nothing        <$> traverse f t-    traverse f (Arc k (Just v) t) = Arc k . Just <$> f v <*> traverse f t-    traverse f (Branch p m l r)   = Branch p m <$> traverse f l <*> traverse f r--instance Binary a => Binary (Trie a) where-    put Empty            = put (0 :: Word8)-    put (Arc k m t)      = do put (1 :: Word8)-                              put k-                              put m-                              put t-    put (Branch p m l r) = do put (2 :: Word8)-                              put p-                              put m-                              put l-                              put r-    -    get = do tag <- get :: Get Word8-             case tag of-                 0 -> return Empty-                 1 -> liftM3 Arc get get get-                 _ -> liftM4 Branch get get get get--{------------------------------------------------------------------ Smart constructors and helper functions for building tries----------------------------------------------------------------}---- | Smart constructor to prune @Empty@ from @Branch@es.-branch :: Prefix -> Mask -> Trie a -> Trie a -> Trie a-branch _ _ Empty r     = r-branch _ _ l     Empty = l-branch p m l     r     = Branch p m l r----- | Smart constructor to prune @Arc@s that lead nowhere.-arc :: KeyString -> Maybe a -> Trie a -> Trie a-arc k mv@(Just _)   t                            = Arc k mv t-arc _    Nothing    Empty                        = Empty-arc k    Nothing  t@(Branch _ _ _ _) | S.null k  = t-                                     | otherwise = Arc k Nothing t-arc k    Nothing    (Arc k' mv' t')              = Arc (S.append k k') mv' t'----- | Smart constructor to join two tries into a @Branch@ with maximal--- prefix sharing. Requires knowing the prefixes, but can combine--- either @Branch@es or @Arc@s.------ N.B. /do not/ use if prefixes could match entirely!-branchMerge :: Prefix -> Trie a -> Prefix -> Trie a -> Trie a-branchMerge _ Empty _ t2    = t2-branchMerge _  t1   _ Empty = t1-branchMerge p1 t1  p2 t2-    | zero p1 m = Branch p m t1 t2-    | otherwise = Branch p m t2 t1-    where-    m = branchMask p1 p2-    p = mask p1 m----- It would be better if Arc used--- Data.ByteString.TrieInternal.wordHead somehow, that way--- we can see 4/8/?*Word8 at a time instead of just one.--- But that makes maintaining invariants ...difficult :(-getPrefix :: Trie a -> Prefix-getPrefix (Branch p _ _ _) = p-getPrefix (Arc k _ _)      | S.null k  = 0 -- for lack of a better-                           | otherwise = S.head k-getPrefix Empty            = error "getPrefix: no Prefix of Empty"-+import Data.Trie.Internal +import Data.Maybe    (isJust)+import Control.Monad (liftM) ---------------------------------------------------------------- ----------------------------------------------------------------   {------------------------------------------------------------------ Error messages----------------------------------------------------------------}---- | Once correctness is proven, these error messages could be--- preprocessed away in order to give minor optimizations-errorInvariantBroken :: String -> String -> a-errorInvariantBroken s e =  error (s ++ ": Invariant was broken" ++ e')-    where-    e' = if Prelude.null e then e else ", found: " ++ e--errorArcAfterNothing    :: String -> a-errorArcAfterNothing   s = errorInvariantBroken s "Arc after Nothing"--errorEmptyAfterNothing  :: String -> a-errorEmptyAfterNothing s = errorInvariantBroken s "Empty after Nothing"---errorLogHead :: String -> KeyString -> KeyElem-errorLogHead s q | S.null q  = error (s ++": found null subquery")-                 | otherwise = S.head q--{------------------------------------------------------------------ Basic functions+-- Conversion functions  ---------------------------------------------------------------} --- | /O(1)/, The empty trie.-{-# INLINE empty #-}-empty :: Trie a-empty = Empty---- | /O(1)/, Is the trie empty?-{-# INLINE null #-}-null :: Trie a -> Bool-null Empty = True-null _     = False---- | /O(1)/, A singleton trie.-{-# INLINE singleton #-}-singleton :: KeyString -> a -> Trie a-singleton k v = Arc k (Just v) Empty--- For singletons, don't need to verify invariant on arc length >0---- | /O(n)/, Get count of elements in trie.-{-# INLINE size #-}-size  :: Trie a -> Int-size t = size' t id 0---- | /O(n)/, Internal CPS accumulator function for calculating--- 'size'.-size' :: Trie a -> (Int -> Int) -> Int -> Int-size' Empty              f n = f n-size' (Branch _ _ l r)   f n = size' l (size' r f) n-size' (Arc _ Nothing t)  f n = size' t f n-size' (Arc _ (Just _) t) f n = size' t f $! n + 1---- BUG: Inefficient for now--- TODO: rewrite both of these to support list fusion------ | Convert trie into association list. Keys will be in sorted order.-toList :: Trie a -> [(KeyString,a)]-toList Empty            = []-toList (Branch _ _ l r) = toList l ++ toList r-toList (Arc k mv t)     = maybe [] (\v -> [(k,v)]) mv-                          ++ map (\(q,x) -> (S.append k q, x)) (toList t)- -- | Convert association list into a trie. On key conflict, values -- earlier in the list shadow later ones.+{-# INLINE fromList #-} fromList :: [(KeyString,a)] -> Trie a fromList = foldr (uncurry insert) empty +-- | Convert trie into association list. Keys will be in sorted order.+toList :: Trie a -> [(KeyString,a)]+toList  = toListBy (,) +-- | Return all keys in the trie, in sorted order.+keys :: Trie a -> [KeyString]+keys  = toListBy const++ {--------------------------------------------------------------- -- Query functions (just recurse) ---------------------------------------------------------------}  -- | Generic function to find a value (if it exists) and the subtrie--- rooted at the prefix. The first function argument is called if and--- only if a node is exactly reachable by the query; if no node is--- exactly reachable the default value is used; if the middle of--- an arc is reached, the second function argument is used.------ This function is intended for internal use.-lookupBy_ :: (Maybe a -> Trie a -> b) -> b -> (Trie a -> b)-          -> KeyString -> Trie a -> b-lookupBy_ f z a = go-    where-    go _    Empty             = z-    -    go q   (Arc k mv t)-        | not (S.null k')     = if S.null q' then a (Arc k' mv t) else z-        | S.null q'           = f mv t-        | otherwise           = go q' t-        where-        (_,k',q') = splitMaximalPrefix k q-        -    go q t_@(Branch _ _ _ _) = findArc t_-        where-        qh = errorLogHead "lookupBy_" q-        -        -- | /O(min(m,W))/, where /m/ is number of @Arc@s in this-        -- branching, and /W/ is the word size of the Prefix,Mask type.-        findArc (Branch p m l r)-            | nomatch qh p m  = z-            | zero qh m       = findArc l-            | otherwise       = findArc r-        findArc t@(Arc _ _ _) = go q t-        findArc Empty         = z----- | Generic function to find a value (if it exists) and the subtrie--- rooted at the prefix. Intended for public consumption.+-- rooted at the prefix. {-# INLINE lookupBy #-} lookupBy :: (Maybe a -> Trie a -> b) -> KeyString -> Trie a -> b-lookupBy f = lookupBy_ f (f Nothing Empty) (f Nothing)+lookupBy f = lookupBy_ f (f Nothing empty) (f Nothing)  -- | Return the value associated with a query string if it exists. {-# INLINE lookup #-} lookup :: KeyString -> Trie a -> Maybe a lookup = lookupBy_ const Nothing (const Nothing) --- | Return the subtrie containing all keys beginning with a prefix.-submap :: KeyString -> Trie a -> Trie a-submap q = lookupBy_ submap' Empty (arc q Nothing) q-    where-    submap' Nothing Empty       = errorEmptyAfterNothing "submap"-    submap' Nothing (Arc _ _ _) = errorArcAfterNothing   "submap"-    submap' mx      t           = Arc q mx t-+-- TODO? move to "Data.Trie.Conventience"? -- | Does a string have a value in the trie? {-# INLINE member #-} member :: KeyString -> Trie a -> Bool@@ -424,53 +106,6 @@ -- Single-value modification functions (recurse and clone spine) ---------------------------------------------------------------} --- TODO: We should CPS on Empty to avoid cloning spine if no change.--- Difficulties arise with the calls to 'branch' and 'arc'. Will--- have to create a continuation chain, so no savings on memory--- allocation; but would have savings on held memory, if they're--- still holding the old one...------ | Generic function to alter a trie by one element with a function--- to resolve conflicts (or non-conflicts).-alterBy :: (KeyString -> a -> Maybe a -> Maybe a)-         -> KeyString -> a -> Trie a -> Trie a-alterBy f_ q_ x_-    | S.null q_ = mergeBy (\x y -> f_ q_ x (Just y)) (singleton q_ x_) -    | otherwise = go q_-    where-    f         = f_ q_ x_-    nothing q = arc q (f Nothing) Empty-    -    go q Empty            = nothing q-    -    go q t@(Branch p m l r)-        | nomatch qh p m  = branchMerge p t  qh (nothing q)-        | zero qh m       = branch p m (go q l) r-        | otherwise       = branch p m l (go q r)-        where-        qh = errorLogHead "alterBy" q-    -    go q t_@(Arc k mv t)-        | not (S.null k') =-            if S.null q'-            then                         -- add node to middle of arc-                arc p (f Nothing) (Arc k' mv t)-            else                         -- add branch off of middle of arc-                case nothing q' of-                Empty -> t_              -- Nothing to add, reuse old arc-                l     -> let r = Arc k' mv t-                         in (if S.null p -- inlined 'arc'-                                then id-                                else Arc p Nothing)-                            (branchMerge (getPrefix l) l-                                         (getPrefix r) r)-        -        | S.null q'       = arc k (f mv) t-        | otherwise       = arc k mv (go q' t)-        where-        (p,k',q') = splitMaximalPrefix k q-- -- | Insert a new key. If the key is already present, overrides the -- old value {-# INLINE insert #-}@@ -492,89 +127,6 @@ -- Trie-combining functions ---------------------------------------------------------------} --- TEST CASES: foldr (unionL . uncurry singleton) empty t---             foldr (uncurry insert) empty t---    where t = map (\s -> (pk s, 0))---                  ["heat","hello","hoi","apple","appa","hell","appb","appc"]------ TODO: switch to 'go', closing over @f@.------ | Combine two tries, using a function to resolve collisions.--- This can only define the space of functions between union and--- symmetric difference but, with those two, all set operations can--- be defined (albeit inefficiently).-mergeBy :: (a -> a -> Maybe a) -> Trie a -> Trie a -> Trie a-mergeBy _ Empty t1    = t1-mergeBy _ t0    Empty = t0---- /O(n+m)/ for this part where /n/ and /m/ are sizes of the branchings-mergeBy f t0@(Branch p0 m0 l0 r0) t1@(Branch p1 m1 l1 r1)-    | shorter m0 m1  = union0-    | shorter m1 m0  = union1-    | p0 == p1       = branch p0 m0 (mergeBy f l0 l1) (mergeBy f r0 r1)-    | otherwise      = branchMerge p0 t0 p1 t1-    where-    union0  | nomatch p1 p0 m0  = branchMerge p0 t0 p1 t1-            | zero p1 m0        = branch p0 m0 (mergeBy f l0 t1) r0-            | otherwise         = branch p0 m0 l0 (mergeBy f r0 t1)-    -    union1  | nomatch p0 p1 m1  = branchMerge p0 t0 p1 t1-            | zero p0 m1        = branch p1 m1 (mergeBy f t0 l1) r1-            | otherwise         = branch p1 m1 l1 (mergeBy f t0 r1)--mergeBy f t0_ t1_ =-    case (t0_,t1_) of-    (Arc k0 mv0 t0, Arc k1 mv1 t1)-        | S.null k0 -> arc k0 mv0 (mergeBy f t0 t1_)-        | S.null k1 -> arc k1 mv1 (mergeBy f t1 t0_)-        | m' == 0   ->-            let (pk,k0',k1') = splitMaximalPrefix k0 k1-            in if S.null pk-            then error "mergeBy: no mask, but no prefix string"-            else let-                 (mv',t',t'') = case (S.null k0', S.null k1') of-                     (False,False) -> ( Nothing-                                      , Arc k0' mv0 t0-                                      , Arc k1' mv1 t1-                                      )-                     (False,True)  -> ( mv1-                                      , Arc k0' mv0 t0-                                      , t1-                                      )-                     (True, False) -> ( mv0-                                      , t0-                                      , Arc k1' mv1 t1-                                      )-                     (True, True)  -> ( case (mv0,mv1) of-                                             (Nothing,Nothing) -> Nothing-                                             (Nothing,Just _)  -> mv1-                                             (Just _, Nothing) -> mv0-                                             (Just v0,Just v1) -> f v0 v1-                                      , t0-                                      , t1-                                      )-                 in arc pk mv' (mergeBy f t' t'')-    -    (Arc _ _ _, Branch _p1 m1 l r)-        | nomatch p0 p1 m1 -> branchMerge p1 t1_  p0 t0_-        | zero p0 m1       -> branch p1 m1 (mergeBy f t0_ l) r-        | otherwise        -> branch p1 m1 l (mergeBy f t0_ r)-    -    (Branch _p0 m0 l r, Arc _ _ _)-        | nomatch p1 p0 m0 -> branchMerge p0 t0_  p1 t1_-        | zero p1 m0       -> branch p0 m0 (mergeBy f t1_ l) r-        | otherwise        -> branch p0 m0 l (mergeBy f t1_ r)-    -    -- Inlined branchMerge. Both tries are disjoint @Arc@s now.-    _ | zero p0 m' -> Branch p' m' t0_ t1_-    _              -> Branch p' m' t1_ t0_-    where-    p0 = getPrefix t0_-    p1 = getPrefix t1_-    m' = branchMask p0 p1-    p' = mask p0 m'-- -- | Combine two tries, resolving conflicts by choosing the value -- from the left trie. {-# INLINE unionL #-}@@ -586,30 +138,6 @@ {-# INLINE unionR #-} unionR :: Trie a -> Trie a -> Trie a unionR = mergeBy (\_ y -> Just y)---{------------------------------------------------------------------ Mapping functions----------------------------------------------------------------}---- | Generic version of 'fmap'. This function is notably more--- expensive than 'fmap' or 'filterMap' because we have to reconstruct--- the keys.-mapBy :: (KeyString -> a -> Maybe b) -> Trie a -> Trie b-mapBy f = go S.empty-    where-    go _ Empty              = empty-    go q (Arc k Nothing  t) = arc k Nothing  (go q' t) where q' = S.append q k-    go q (Arc k (Just v) t) = arc k (f q' v) (go q' t) where q' = S.append q k-    go q (Branch p m l r)   = branch p m (go q l) (go q r)----- | Apply a function to all values, potentially removing them.-filterMap :: (a -> Maybe b) -> Trie a -> Trie b-filterMap _ Empty              = empty-filterMap f (Arc k Nothing  t) = arc k Nothing (filterMap f t)-filterMap f (Arc k (Just v) t) = arc k (f v)   (filterMap f t)-filterMap f (Branch p m l r)   = branch p m (filterMap f l) (filterMap f r)  ---------------------------------------------------------------- ----------------------------------------------------------- fin.
src/Data/Trie/BitTwiddle.hs view
@@ -2,7 +2,7 @@ {-# OPTIONS_GHC -cpp -fglasgow-exts #-}  -------------------------------------------------------------------                                                  ~ 2008.12.19+--                                                  ~ 2009.01.05 -- | -- Module      :  Data.Trie.BitTwiddle -- Copyright   :  Copyright (c) Daan Leijen 2002@@ -45,13 +45,15 @@ type Prefix  = KeyElem  type Mask    = KeyElem  -+{-# INLINE elemToNat #-} elemToNat :: KeyElem -> Word elemToNat i = fromIntegral i +{-# INLINE natToElem #-} natToElem :: Word -> KeyElem natToElem w = fromIntegral w +{-# INLINE shiftRL #-} shiftRL :: Word -> Int -> Word #if __GLASGOW_HASKELL__ -- GHC: use unboxing to get @shiftRL@ inlined.@@ -66,14 +68,17 @@ ---------------------------------------------------------------}  -- | Is the value under the mask zero?+{-# INLINE zero #-} zero :: KeyElem -> Mask -> Bool zero i m = (elemToNat i) .&. (elemToNat m) == 0  -- | Does a value /not/ match some prefix, for all the bits preceding -- a masking bit? (Hence a subtree matching the value doesn't exist.)+{-# INLINE nomatch #-} nomatch :: KeyElem -> Prefix -> Mask -> Bool nomatch i p m = mask i m /= p +{-# INLINE mask #-} mask :: KeyElem -> Mask -> Prefix mask i m = maskW (elemToNat i) (elemToNat m) @@ -84,15 +89,18 @@  -- | Get mask by setting all bits higher than the smallest bit in -- @m@. Then apply that mask to @i@.+{-# INLINE maskW #-} maskW :: Word -> Word -> Prefix maskW i m = natToElem (i .&. (complement (m-1) `xor` m))  -- | Determine whether the first mask denotes a shorter prefix than -- the second.+{-# INLINE shorter #-} shorter :: Mask -> Mask -> Bool shorter m1 m2 = elemToNat m1 > elemToNat m2  -- | Determine first differing bit of two prefixes.+{-# INLINE branchMask #-} branchMask :: Prefix -> Prefix -> Mask branchMask p1 p2     = natToElem (highestBitMask (elemToNat p1 `xor` elemToNat p2))@@ -142,6 +150,7 @@   into highly efficient machine code. The algorithm is derived from   Jorg Arndt's FXT library. ---------------------------------------------------------------}+{-# INLINE highestBitMask #-} highestBitMask :: Word -> Word highestBitMask x     = case (x .|. shiftRL x 1) of 
src/Data/Trie/Convenience.hs view
@@ -1,7 +1,7 @@ {-# OPTIONS_GHC -Wall -fwarn-tabs #-}  -------------------------------------------------------------------                                                  ~ 2008.12.19+--                                                  ~ 2009.01.05 -- | -- Module      :  Data.Trie.Convenience -- Copyright   :  Copyright (c) 2008--2009 wren ng thornton@@ -23,16 +23,52 @@     , adjustWithKey     , update, updateWithKey     +    -- ** Conversion functions+    -- $fromList+    , fromListL, fromListR, fromListS+         -- * 'mergeBy' variants     , disunion, unionWith     ) where  import Data.Trie-import Control.Monad (liftM)+import Data.Trie.Internal (lookupBy_)+import Data.List          (foldl', sortBy)+import Control.Monad      (liftM)  ---------------------------------------------------------------- ---------------------------------------------------------------- +-- $fromList+-- Just like 'fromList' both of these functions convert an association+-- list into a trie, with earlier values shadowing later ones when+-- keys conflict. Depending on the order of keys in the list, there+-- can be as much as 5x speed difference between the two. Yet,+-- performance is about the same when matching best-case to best-case+-- and worst-case to worst-case (which is which is swapped when+-- reversing the list or changing which function is used).++{-# INLINE fromListL #-}+fromListL :: [(KeyString,a)] -> Trie a+fromListL = foldl' (flip $ uncurry $ insertIfAbsent) empty++-- | This version is just an alias for 'fromList'. It is a good+-- producer for list fusion. Worst-case behavior is somewhat worse+-- than worst-case for 'fromListL'.+{-# INLINE fromListR #-}+fromListR :: [(KeyString,a)] -> Trie a+fromListR = fromList++-- | This version sorts the list before folding over it. This adds+-- /O(n log n)/ overhead and requires the whole list be in memory+-- at once, but it ensures that the list is in best-case order. The+-- benefits generally outweigh the costs.+{-# INLINE fromListS #-}+fromListS :: [(KeyString,a)] -> Trie a+fromListS = fromListR . sortBy (\(k,_) (q,_) -> k `compare` q)+++---------------------------------------------------------------- -- | Lookup a key, returning a default value if it's not found. lookupWithDefault :: a -> KeyString -> Trie a -> a lookupWithDefault x = lookupBy_ (\mv _ -> case mv of@@ -84,7 +120,7 @@ disunion :: Trie a -> Trie a -> Trie a disunion = mergeBy (\_ _ -> Nothing) --- | Combine two tries, using a function to resolve conflicts+-- | Combine two tries, using a function to resolve conflicts. unionWith :: (a -> a -> a) -> Trie a -> Trie a -> Trie a unionWith f = mergeBy (\x y -> Just (f x y)) 
+ src/Data/Trie/Internal.hs view
@@ -0,0 +1,592 @@+{-# OPTIONS_GHC -Wall -fwarn-tabs #-}++----------------------------------------------------------------+--                                                  ~ 2009.01.10+-- |+-- Module      :  Data.Trie.Internal+-- Copyright   :  Copyright (c) 2008--2009 wren ng thornton+-- License     :  BSD3+-- Maintainer  :  wren@community.haskell.org+-- Stability   :  beta+-- Portability :  portable+--+-- Internal definition of the 'Trie' data type and generic functions+-- for manipulating them. Almost everything here is re-exported+-- from "Data.Trie".+----------------------------------------------------------------++module Data.Trie.Internal+    (+    -- * Data types+      Trie(), KeyString, KeyElem, showTrie+    +    -- * Basic functions+    , empty, null, singleton, size+    +    -- * Conversion functions+    , toListBy+    +    -- * Query functions+    , lookupBy_, submap+    +    -- * Single-value modification+    , alterBy+    +    -- * Combining tries+    , mergeBy+    +    -- * Mapping functions+    , mapBy, filterMap+    ) where++import Prelude hiding (null, lookup)+import qualified Prelude++import qualified Data.ByteString as S+import Data.Trie.ByteStringInternal+import Data.Trie.BitTwiddle++import Control.Monad       (liftM3, liftM4)+import Control.Applicative (Applicative(..), (<$>))+import Data.Monoid         (Monoid(..))+import Data.Foldable       (Foldable(foldMap))+import Data.Traversable    (Traversable(traverse))+import Data.Binary+----------------------------------------------------------------+----------------------------------------------------------------+++{---------------------------------------------------------------+-- ByteString Big-endian Patricia Trie+---------------------------------------------------------------}+type KeyString = ByteString +type KeyElem   = ByteStringElem ++{- Idealized:+data Node a   = Accept a (ArcSet a)+              | Reject   (Branch a)          -- Invariant: Must be Branch+data Arc a    = Arc    KeyString (Node a)    -- Invariant: never empty string+data ArcSet a = None+              | One    {KeyElem} (Arc a)+              | Branch {Prefix} {Mask} (ArcSet a) (ArcSet a)+data Trie a   = Empty+              | Start  KeyString (Node a)    -- Maybe empty string [1]++[1] If we maintain the invariants on how Nodes recurse, then we+can't simply have Start(Node a) because we may have a shared prefix+where the prefix itself is not Accept'ed.+++-- Squash Arc into One:+-- (pure good)+data Node a   = Accept a (ArcSet a)+              | Reject   (Branch a)+data ArcSet a = None+              | Arc    KeyString (Node a)+              | Branch {Prefix} {Mask} (ArcSet a) (ArcSet a)+data Trie a   = Empty+              | Start  KeyString (Node a)+++-- Squash Node together:+-- (most likely good)+data Node a   = Node (Maybe a) (ArcSet a)+data ArcSet a = None+              | Arc    KeyString (Node a)+              | Branch {Prefix} {Mask} (ArcSet a) (ArcSet a)+data Trie a   = Empty+              | Start  KeyString (Node a)+++-- Squash Empty/None and Arc/Start together:+-- (Complicates invariants about non-empty strings and Node's recursion)+data Node a = Node (Maybe a) (ArcSet a)+data Trie a = Empty+            | Arc    KeyString (Node a)+            | Branch {Prefix} {Mask} (Trie a) (Trie a)+++-- Squash Node into Arc:+-- (By this point, pure good)+-- Unseen invariants:+-- * KeyString non-empty, unless Arc is absolute root of tree+-- * If (Maybe a) is Nothing, then (Trie a) is Branch+--   * With views, we could re-expand Arc into accepting and+--     nonaccepting variants+--+-- [2] Maybe we shouldn't unpack the KeyString. We could specialize+-- or inline the splitMaximalPrefix function to prevent constructing+-- a new KeyString from the parts...+-}++-- | A map from 'ByteString's to @a@. For all the generic functions,+-- note that tries are strict in the @Maybe@ but not in @a@.+--+-- The 'Monad' instance is strange. If a key @k1@ is a prefix of+-- other keys, then results from binding the value at @k1@ will+-- override values from longer keys when they collide. If this is+-- useful for anything, or if there's a more sensible instance, I'd+-- be curious to know.++data Trie a = Empty+            | Arc    {-# UNPACK #-} !KeyString+                                    !(Maybe a)+                                    !(Trie a)+            | Branch {-# UNPACK #-} !Prefix+                     {-# UNPACK #-} !Mask+                                    !(Trie a)+                                    !(Trie a)+    deriving Eq+    -- Prefix/Mask should be deterministic regardless of insertion order+    -- TODO: verify this is so.+++-- | Visualization fuction for debugging.+showTrie :: (Show a) => Trie a -> String+showTrie t = shows' id t ""+    where+    spaces f = map (const ' ') (f "")+    +    shows' _  Empty            = (".\n"++)+    shows' ss (Branch p m l r) =+        let s'  = ("--"++) . shows p . (","++) . shows m . ("-+"++)+            ss' = ss . (tail (spaces s') ++)+        in s'              . shows' (ss' . ("|"++)) l+           . ss' . ("|\n"++)+           . ss' . ("`"++) . shows' (ss' . (" "++)) r+    shows' ss (Arc k mv t') =+        let s' = ("--"++) . shows k+                 . maybe id (\v -> ("-("++) . shows v . (")"++)) mv+                 . ("--"++)+        in  s' . shows' (ss . (spaces s' ++)) t'+++{---------------------------------------------------------------+-- Trie instances+---------------------------------------------------------------}++instance Functor Trie where+    fmap _ Empty              = Empty+    fmap f (Arc k Nothing  t) = Arc k Nothing      (fmap f t)+    fmap f (Arc k (Just v) t) = Arc k (Just (f v)) (fmap f t)+    fmap f (Branch p m l r)   = Branch p m (fmap f l) (fmap f r)+++-- Does this even make sense? It's not nondeterminism like lists+-- and sets. If no keys were prefixes of other keys it'd make sense+-- as a decision-tree; but since keys /can/ prefix, tries formed+-- from shorter keys can shadow the results from longer keys due+-- to the 'unionL'. It does seem to follow the laws though... What+-- computation could this possibly represent?+--+--  1. return x >>= f  == f x+--  2. m >>= return    == m+--  3. (m >>= f) >>= g == m >>= (\x -> f x >>= g)+instance Monad Trie where+    return x = singleton S.empty x+    +    (>>=) Empty              _ = empty+    (>>=) (Branch p m l r)   f = branch p m (l >>= f) (r >>= f)+    (>>=) (Arc k Nothing  t) f = arc k Nothing (t >>= f)+    (>>=) (Arc k (Just v) t) f = arc k Nothing (f v `unionL` (t >>= f))+                               where+                               unionL = mergeBy (\x _ -> Just x)+++instance Monoid a => Monoid (Trie a) where+    mempty  = empty+    mappend = mergeBy $ \x y -> Just (x `mappend` y)+++instance Foldable Trie where+    foldMap _ Empty              = mempty+    foldMap f (Arc _ Nothing  t) = foldMap f t+    foldMap f (Arc _ (Just v) t) = f v `mappend` foldMap f t+    foldMap f (Branch _ _ l r)   = foldMap f l `mappend` foldMap f r+++instance Traversable Trie where+    traverse _ Empty              = pure Empty+    traverse f (Arc k Nothing  t) = Arc k Nothing        <$> traverse f t+    traverse f (Arc k (Just v) t) = Arc k . Just <$> f v <*> traverse f t+    traverse f (Branch p m l r)   = Branch p m <$> traverse f l <*> traverse f r+++instance Binary a => Binary (Trie a) where+    put Empty            = put (0 :: Word8)+    put (Arc k m t)      = do put (1 :: Word8)+                              put k+                              put m+                              put t+    put (Branch p m l r) = do put (2 :: Word8)+                              put p+                              put m+                              put l+                              put r+    +    get = do tag <- get :: Get Word8+             case tag of+                 0 -> return Empty+                 1 -> liftM3 Arc get get get+                 _ -> liftM4 Branch get get get get+++{---------------------------------------------------------------+-- Smart constructors and helper functions for building tries+---------------------------------------------------------------}++-- | Smart constructor to prune @Empty@ from @Branch@es.+branch :: Prefix -> Mask -> Trie a -> Trie a -> Trie a+branch _ _ Empty r     = r+branch _ _ l     Empty = l+branch p m l     r     = Branch p m l r+++-- | Smart constructor to prune @Arc@s that lead nowhere.+-- N.B if mv=Just then doesn't check whether t=epsilon. It's up to callers to ensure that invariant isn't broken.+arc :: KeyString -> Maybe a -> Trie a -> Trie a+arc k mv@(Just _)   t                            = Arc k mv t+arc _    Nothing    Empty                        = Empty+arc k    Nothing  t@(Branch _ _ _ _) | S.null k  = t+                                     | otherwise = Arc k Nothing t+arc k    Nothing    (Arc k' mv' t')              = Arc (S.append k k') mv' t'+++-- | Smart constructor to join two tries into a @Branch@ with maximal+-- prefix sharing. Requires knowing the prefixes, but can combine+-- either @Branch@es or @Arc@s.+--+-- N.B. /do not/ use if prefixes could match entirely!+branchMerge :: Prefix -> Trie a -> Prefix -> Trie a -> Trie a+branchMerge _ Empty _ t2    = t2+branchMerge _  t1   _ Empty = t1+branchMerge p1 t1  p2 t2+    | zero p1 m = Branch p m t1 t2+    | otherwise = Branch p m t2 t1+    where+    m = branchMask p1 p2+    p = mask p1 m+++-- It would be better if Arc used+-- Data.ByteString.TrieInternal.wordHead somehow, that way+-- we can see 4/8/?*Word8 at a time instead of just one.+-- But that makes maintaining invariants ...difficult :(+getPrefix :: Trie a -> Prefix+getPrefix (Branch p _ _ _) = p+getPrefix (Arc k _ _)      | S.null k  = 0 -- for lack of a better+                           | otherwise = S.head k+getPrefix Empty            = error "getPrefix: no Prefix of Empty"+++{---------------------------------------------------------------+-- Error messages+---------------------------------------------------------------}++errorLogHead :: String -> KeyString -> KeyElem+errorLogHead s q | S.null q  = error (s ++": found null subquery")+                 | otherwise = S.head q+++----------------------------------------------------------------+----------------------------------------------------------------++{---------------------------------------------------------------+-- Basic functions+---------------------------------------------------------------}++-- | /O(1)/, The empty trie.+{-# INLINE empty #-}+empty :: Trie a+empty = Empty++-- | /O(1)/, Is the trie empty?+{-# INLINE null #-}+null :: Trie a -> Bool+null Empty = True+null _     = False++-- | /O(1)/, A singleton trie.+{-# INLINE singleton #-}+singleton :: KeyString -> a -> Trie a+singleton k v = Arc k (Just v) Empty+-- For singletons, don't need to verify invariant on arc length >0++-- | /O(n)/, Get count of elements in trie.+{-# INLINE size #-}+size  :: Trie a -> Int+size t = size' t id 0++-- | /O(n)/, Internal CPS accumulator function for calculating+-- 'size'.+size' :: Trie a -> (Int -> Int) -> Int -> Int+size' Empty              f n = f n+size' (Branch _ _ l r)   f n = size' l (size' r f) n+size' (Arc _ Nothing t)  f n = size' t f n+size' (Arc _ (Just _) t) f n = size' t f $! n + 1+++{---------------------------------------------------------------+-- Conversion functions +---------------------------------------------------------------}++-- Still rather inefficient+-- +-- TODO: rewrite list-catenation to be lazier (real CPS instead of+-- function building? is the function building really better than+-- (++) anyways?)+-- TODO: the @q@ accumulator should be lazy ByteString and only+-- forced by @f@+--+-- | Convert a trie into a list using a function. Resulting values+-- are in sorted order according to the keys.+toListBy :: (KeyString -> a -> b) -> Trie a -> [b]+toListBy f = \t -> go S.empty t []+    where+    go _ Empty            = id+    go q (Branch _ _ l r) = go q l . go q r+    go q (Arc k mv t)     = case mv of+                            Nothing -> rest+                            Just v  -> (f k' v :) . rest+                          where+                          rest = go k' t+                          k'   = S.append q k+++{---------------------------------------------------------------+-- Query functions (just recurse)+---------------------------------------------------------------}++-- | Generic function to find a value (if it exists) and the subtrie+-- rooted at the prefix. The first function argument is called if and+-- only if a node is exactly reachable by the query; if no node is+-- exactly reachable the default value is used; if the middle of+-- an arc is reached, the second function argument is used.+--+-- This function is intended for internal use. For the public-facing+-- version, see @lookupBy@ in "Data.Trie".+lookupBy_ :: (Maybe a -> Trie a -> b) -> b -> (Trie a -> b)+          -> KeyString -> Trie a -> b+lookupBy_ f z a = let +    isBranch (Branch _ _ _ _) = True+    isBranch _                = False+    in \q t -> if S.null q && isBranch t then f Nothing t else go q t+    where+                                          +    go _    Empty             = z+    +    go q   (Arc k mv t) =+        case (not $ S.null k', S.null q') of+             (True,  True)  -> a (Arc k' mv t)+             (True,  False) -> z+             (False, True)  -> f mv t+             (False, False) -> go q' t+        where+        (_,k',q') = splitMaximalPrefix k q+        +    go q t_@(Branch _ _ _ _) = findArc t_+        where+        qh = errorLogHead "lookupBy_" q+        +        -- | /O(min(m,W))/, where /m/ is number of @Arc@s in this+        -- branching, and /W/ is the word size of the Prefix,Mask type.+        findArc (Branch p m l r)+            | nomatch qh p m  = z+            | zero qh m       = findArc l+            | otherwise       = findArc r+        findArc t@(Arc _ _ _) = go q t+        findArc Empty         = z+++-- This function needs to be here, not in "Data.Trie", because of+-- 'arc' which isn't exported. We could use the monad instance+-- instead, though it'd be far more circuitous.+--     arc k Nothing  t === singleton k () >> t+--     arc k (Just v) t === singleton k v  >>= unionR t . singleton S.empty+--         (...except 'arc' doesn't do the invariant correction+--           of (>>=) for t=epsilon)+--+-- | Return the subtrie containing all keys beginning with a prefix.+{-# INLINE submap #-}+submap :: KeyString -> Trie a -> Trie a+submap q = lookupBy_ (arc q) empty (arc q Nothing) q+{- -- Disable superfluous error checking.+   -- @submap'@ would replace the first argument to @lookupBy_@+    where+    submap' Nothing Empty       = errorEmptyAfterNothing "submap"+    submap' Nothing (Arc _ _ _) = errorArcAfterNothing   "submap"+    submap' mx      t           = Arc q mx t+    +errorInvariantBroken :: String -> String -> a+errorInvariantBroken s e =  error (s ++ ": Invariant was broken" ++ e')+    where+    e' = if Prelude.null e then e else ", found: " ++ e++errorArcAfterNothing    :: String -> a+errorArcAfterNothing   s = errorInvariantBroken s "Arc after Nothing"++errorEmptyAfterNothing  :: String -> a+errorEmptyAfterNothing s = errorInvariantBroken s "Empty after Nothing"+-}+++{---------------------------------------------------------------+-- Single-value modification functions (recurse and clone spine)+---------------------------------------------------------------}++-- TODO: We should CPS on Empty to avoid cloning spine if no change.+-- Difficulties arise with the calls to 'branch' and 'arc'. Will+-- have to create a continuation chain, so no savings on memory+-- allocation; but would have savings on held memory, if they're+-- still holding the old one...+--+-- | Generic function to alter a trie by one element with a function+-- to resolve conflicts (or non-conflicts).+alterBy :: (KeyString -> a -> Maybe a -> Maybe a)+         -> KeyString -> a -> Trie a -> Trie a+alterBy f_ q_ x_+    | S.null q_ = mergeBy (\x y -> f_ q_ x (Just y)) (singleton q_ x_) +    | otherwise = go q_+    where+    f         = f_ q_ x_+    nothing q = arc q (f Nothing) Empty+    +    go q Empty            = nothing q+    +    go q t@(Branch p m l r)+        | nomatch qh p m  = branchMerge p t  qh (nothing q)+        | zero qh m       = branch p m (go q l) r+        | otherwise       = branch p m l (go q r)+        where+        qh = errorLogHead "alterBy" q+    +    go q t_@(Arc k mv t) =+        case (not $ S.null k', S.null q') of+             (True,  True)  -> -- add node to middle of arc+                               arc p (f Nothing) (Arc k' mv t)+             (True,  False) ->+                        case nothing q' of+                        Empty -> t_ -- Nothing to add, reuse old arc+                        l     -> arc' (branchMerge (getPrefix l) l+                                                   (getPrefix r) r)+                                 where+                                 r = Arc k' mv t+                                 +                                 -- inlined version of 'arc'+                                 arc' | S.null p  = id+                                      | otherwise = Arc p Nothing+                                 +             (False, True)  -> arc k (f mv) t+             (False, False) -> arc k mv (go q' t)+        where+        (p,k',q') = splitMaximalPrefix k q+++{---------------------------------------------------------------+-- Trie-combining functions+---------------------------------------------------------------}++-- TEST CASES: foldr (unionL . uncurry singleton) empty t+--             foldr (uncurry insert) empty t+--    where t = map (\s -> (pk s, 0))+--                  ["heat","hello","hoi","apple","appa","hell","appb","appc"]+--+-- TODO: switch to 'go', closing over @f@.+--+-- | Combine two tries, using a function to resolve collisions.+-- This can only define the space of functions between union and+-- symmetric difference but, with those two, all set operations can+-- be defined (albeit inefficiently).+mergeBy :: (a -> a -> Maybe a) -> Trie a -> Trie a -> Trie a+mergeBy _ Empty t1    = t1+mergeBy _ t0    Empty = t0++-- /O(n+m)/ for this part where /n/ and /m/ are sizes of the branchings+mergeBy f t0@(Branch p0 m0 l0 r0) t1@(Branch p1 m1 l1 r1)+    | shorter m0 m1  = union0+    | shorter m1 m0  = union1+    | p0 == p1       = branch p0 m0 (mergeBy f l0 l1) (mergeBy f r0 r1)+    | otherwise      = branchMerge p0 t0 p1 t1+    where+    union0  | nomatch p1 p0 m0  = branchMerge p0 t0 p1 t1+            | zero p1 m0        = branch p0 m0 (mergeBy f l0 t1) r0+            | otherwise         = branch p0 m0 l0 (mergeBy f r0 t1)+    +    union1  | nomatch p0 p1 m1  = branchMerge p0 t0 p1 t1+            | zero p0 m1        = branch p1 m1 (mergeBy f t0 l1) r1+            | otherwise         = branch p1 m1 l1 (mergeBy f t0 r1)++mergeBy f t0_ t1_ =+    case (t0_,t1_) of+    (Arc k0 mv0 t0, Arc k1 mv1 t1)+        | S.null k0 && S.null k1 -> arc k0 (mergeMaybe f mv0 mv1)+                                               (mergeBy f t0 t1)+        | S.null k0              -> arc k0 mv0 (mergeBy f t0 t1_)+        |              S.null k1 -> arc k1 mv1 (mergeBy f t1 t0_)+        | m' == 0                ->+            let (pk,k0',k1') = splitMaximalPrefix k0 k1+            in if S.null pk+            then error "mergeBy: no mask, but no prefix string"+            else let arcMerge mv' t1' t2' = arc pk mv' (mergeBy f t1' t2')+                 in case (S.null k0', S.null k1') of+                     (True, True)  -> arcMerge (mergeMaybe f mv0 mv1) t0 t1+                     (True, False) -> arcMerge mv0 t0 (Arc k1' mv1 t1)+                     (False,True)  -> arcMerge mv1 t1 (Arc k0' mv0 t0)+                     (False,False) -> arcMerge Nothing (Arc k0' mv0 t0)+                                                       (Arc k1' mv1 t1)+    +    -- Deal with epsilons. Could be hoisted if we use @go@ style+    (Arc k0 mv0@(Just _) t0, Branch _ _ _ _)+        | S.null k0        -> arc k0 mv0 (mergeBy f t0 t1_)+    (Branch _ _ _ _, Arc k1 mv1@(Just _) t1)+        | S.null k1        -> arc k1 mv1 (mergeBy f t1 t0_)+        +    (Arc _ _ _, Branch _p1 m1 l r)+        | nomatch p0 p1 m1 -> branchMerge p1 t1_  p0 t0_+        | zero p0 m1       -> branch p1 m1 (mergeBy f t0_ l) r+        | otherwise        -> branch p1 m1 l (mergeBy f t0_ r)+    (Branch _p0 m0 l r, Arc _ _ _)+        | nomatch p1 p0 m0 -> branchMerge p0 t0_  p1 t1_+        | zero p1 m0       -> branch p0 m0 (mergeBy f t1_ l) r+        | otherwise        -> branch p0 m0 l (mergeBy f t1_ r)+    +    -- Inlined branchMerge. Both tries are disjoint @Arc@s now.+    _ | zero p0 m' -> Branch p' m' t0_ t1_+    _              -> Branch p' m' t1_ t0_+    where+    p0 = getPrefix t0_+    p1 = getPrefix t1_+    m' = branchMask p0 p1+    p' = mask p0 m'++mergeMaybe :: (a -> a -> Maybe a) -> Maybe a -> Maybe a -> Maybe a+mergeMaybe _ Nothing      Nothing  = Nothing+mergeMaybe _ Nothing mv1@(Just _)  = mv1+mergeMaybe _ mv0@(Just _) Nothing  = mv0+mergeMaybe f (Just v0)   (Just v1) = f v0 v1++{---------------------------------------------------------------+-- Mapping functions+---------------------------------------------------------------}++-- | Generic version of 'fmap'. This function is notably more+-- expensive than 'fmap' or 'filterMap' because we have to reconstruct+-- the keys.+mapBy :: (KeyString -> a -> Maybe b) -> Trie a -> Trie b+mapBy f = go S.empty+    where+    go _ Empty              = empty+    go q (Arc k Nothing  t) = arc k Nothing  (go q' t) where q' = S.append q k+    go q (Arc k (Just v) t) = arc k (f q' v) (go q' t) where q' = S.append q k+    go q (Branch p m l r)   = branch p m (go q l) (go q r)+++-- | Apply a function to all values, potentially removing them.+filterMap :: (a -> Maybe b) -> Trie a -> Trie b+filterMap _ Empty              = empty+filterMap f (Arc k Nothing  t) = arc k Nothing (filterMap f t)+filterMap f (Arc k (Just v) t) = arc k (f v)   (filterMap f t)+filterMap f (Branch p m l r)   = branch p m (filterMap f l) (filterMap f r)++----------------------------------------------------------------+----------------------------------------------------------- fin.