queuelike 1.0.5 → 1.0.6
raw patch · 11 files changed
+459/−139 lines, 11 filesdep −utility-htPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies removed: utility-ht
API changes (from Hackage documentation)
- Data.Queue.Queue: instance Foldable Queue
- Data.Queue.Queue: instance Monoid (Queue e)
- Data.Queue.TrieQueue: Edge :: (Label e) -> !!Int -> (Map e (Trie e)) -> Trie e
- Data.Queue.TrieQueue: Leaf :: (Label e) -> !!Int -> Trie e
- Data.Queue.TrieQueue: TQ :: (HeapQ (Trie e)) -> TrieQueue e
- Data.Queue.TrieQueue: catTrie :: (Ord e) => Label e -> Trie e -> Trie e
- Data.Queue.TrieQueue: compactTrie :: (Ord e) => Trie e -> Trie e
- Data.Queue.TrieQueue: compactTrie' :: (Ord e) => Trie e -> MTrie e
- Data.Queue.TrieQueue: data Trie e
- Data.Queue.TrieQueue: extractMin' :: (Ord e) => Trie e -> (Label e, MTrie e)
- Data.Queue.TrieQueue: instance (Eq e) => Eq (Trie e)
- Data.Queue.TrieQueue: instance (Eq e) => Eq (TrieQueue e)
- Data.Queue.TrieQueue: merge' :: (Ord e) => Label e -> Label e -> TailMaker e -> TailMaker e -> (e -> TailMaker e) -> (e -> TailMaker e) -> Trie e -> Trie e
- Data.Queue.TrieQueue: merger :: (Ord e) => Trie e -> Trie e -> Trie e
- Data.Queue.TrieQueue: mkLab :: [e] -> Label e
- Data.Queue.TrieQueue: mkTQ :: Int -> Trie e -> TrieQueue e
- Data.Queue.TrieQueue: newtype TrieQueue e
- Data.Queue.TrieQueue: trieFromList :: (Ord e) => [[e]] -> MTrie e
- Data.Queue.TrieQueue: type Label e = Seq e
- Data.Queue.TrieQueue: type MTrie e = Maybe (Trie e)
- Data.Queue.TrieQueue: type TailMaker e = Label e -> Trie e
+ Data.Queue.PQueue: instance (Show e) => Show (PQueue e)
+ Data.Queue.Queue: cons :: e -> Queue e -> Queue e
+ Data.Queue.TrieQueue: data TrieQueue e
Files
- Data/MQueue/Heap.hs +3/−2
- Data/Queue/Class.hs +2/−0
- Data/Queue/PQueue.hs +6/−0
- Data/Queue/Queue.hs +29/−14
- Data/Queue/QueueHelpers.hs +28/−9
- Data/Queue/TrieQueue.hs +105/−108
- Data/Queue/TrieQueue/Edge.hs +14/−0
- Data/Queue/TrieQueue/MonoidQueue.hs +111/−0
- Data/Queue/TrieQueue/MonoidQueue2.hs +84/−0
- Data/Queue/TrieQueue/TrieLabel.hs +69/−0
- queuelike.cabal +8/−6
Data/MQueue/Heap.hs view
@@ -7,8 +7,9 @@ import Data.MQueue.Class import Data.MQueue.MonadHelpers +import Control.Arrow((***)) import Control.Monad.ST.Class-import Data.Tuple.HT+--import Data.Tuple.HT import Data.Array.Base @@ -42,7 +43,7 @@ newHeap = liftM H (liftM (STH 0) (newArray_ (0, 15)) >>= newSTRef) pushHeap h = onHeap_ h . pusher pushAllHeap h ks = onHeap_ h (\ h@STH{size} -> uncurry (with . flip ensureSize h) (foldr accumulator (size, \ _ -> return ()) ks))- where accumulator k = mapPair ((+1), liftM2 (>>) (unsafePusher k))+ where accumulator k = ((+1) *** liftM2 (>>) (unsafePusher k)) popHeap_ h = onHeap_ h popper peekHeap h = queryHeap h (\ STH{..} -> if size > 0 then liftM Just (unsafeRead arr 0) else return Nothing) getHeapSize h = queryHeap h (return . size)
Data/Queue/Class.hs view
@@ -3,8 +3,10 @@ -- | Abstracts the implementation details of a single-insertion, single-extraction queuelike structure. module Data.Queue.Class where +--import Data.Foldable(Foldable(..)) import Data.List(unfoldr) import Data.Maybe+import qualified Data.List as Fold import Control.Monad.Instances() import Control.Monad
Data/Queue/PQueue.hs view
@@ -22,6 +22,9 @@ data Tree e = T e [Tree e] newtype PQueue e = PQ (HeapQ (Tree e)) deriving (Monoid) +instance Show e => Show (PQueue e) where+ show = drawQueue+ drawQueue :: Show e => PQueue e -> String drawQueue (PQ (HQ _ t)) = maybe "" (T.drawTree . fmap show . T.unfoldTree (\ (T x ts) -> (x, ts))) t @@ -50,6 +53,9 @@ where extract' (T x ts) = (x, fusing ts) toList_ (PQ (HQ _ t)) = maybe [] flatten t where flatten (T x ts) = x:concatMap flatten ts++ null (PQ (HQ _ Nothing)) = True+ null _ = False size (PQ (HQ n _)) = n single :: e -> HeapQ (Tree e)
Data/Queue/Queue.hs view
@@ -2,7 +2,7 @@ {-# OPTIONS -fno-warn-name-shadowing #-} -- | A basic first-in, first-out queue implementation implementing the 'Queuelike' abstraction. Bootstrapped from "Data.Sequence".-module Data.Queue.Queue (Queue) where+module Data.Queue.Queue (Queue, cons) where import Data.Monoid import Data.Queue.Class@@ -11,23 +11,38 @@ import qualified Data.Foldable as Fold import Prelude hiding (null) -newtype Queue e = Queue (Seq e) deriving (Monoid, Fold.Foldable, Functor)+--newtype Queue e = Queue (Seq e) deriving (Monoid, Fold.Foldable, Functor)+data Queue e = Queue Int [e] [e] [e]+ -- we never actually look at the spine of a+ -- and if we're performing fmap operations, it+ -- actually helps to existentially quantify the type +instance Functor Queue where+ fmap f (Queue n l r a) = Queue n (map f l) (map f r) (map f a)+ instance IQueue (Queue e) where type QueueKey (Queue e) = e- empty = mempty- singleton = Queue . Seq.singleton- fromList = Queue . Seq.fromList+ empty = Queue 0 [] [] []+ singleton x = let l = [x] in Queue 1 l [] l+ fromList xs = Queue (length xs) xs [] xs - null (Queue q) = Seq.null q- size (Queue q) = Seq.length q+ null (Queue _ [] _ _) = True+ null _ = False+ size (Queue n _ _ _) = n - x `insert` Queue q = Queue (q |> x)- extract (Queue q) = case viewl q of- EmptyL -> Nothing- x :< q' -> Just (x, Queue q')+ x `insert` Queue n l r a = rot (n+1) l (x:r) a+ extract (Queue n (l:ls) r a) = Just (l, rot (n-1) ls r a)+ extract _ = Nothing - merge = mappend- mergeAll = mconcat+-- toList (Queue _ l r _) = l ++ reverse r - toList = Fold.toList+rot :: Int -> [e] -> [e] -> [e] -> Queue e+rot n l r (_:as) = Queue n l r as+rot n l r [] = let rot' (l:ls) (r:rs) a = l:rot' ls rs (r:a)+ rot' ls [] a = ls ++ a+ rot' [] (r:_) a = r:a+ l' = rot' l r []+ in Queue n l' [] l'++cons :: e -> Queue e -> Queue e+cons x (Queue n l r a) = Queue (n+1) (x:l) r a
Data/Queue/QueueHelpers.hs view
@@ -18,16 +18,24 @@ ... In particular, this almost immediately yields a correct pairing heap implementation (cf. PQueue)++In general, the fusing function provided by this module implements a balanced+monoid merging operation used by nearly every priority queue implementation in this package. -------------------} -module Data.Queue.QueueHelpers (MonoidQ (..), HeapQ, endoMaybe, order, fusing, fuseMerge, fuseMergeM) where+module Data.Queue.QueueHelpers (MonoidQ (..), HeapQ, fusing', endoMaybe, order, fusing, fuseMerge, fuseMergeM) where import Data.Monoid import Data.Maybe+import Data.List(unfoldr)+import GHC.Exts(build) -data MonoidQ m = HQ {elts :: {-# UNPACK #-} !Int, heap :: m} deriving (Eq, Ord, Show)+data MonoidQ m = HQ {elts :: Int, heap :: m} deriving (Eq, Ord, Show) type HeapQ m = MonoidQ (Maybe m) +instance Functor MonoidQ where+ fmap f (HQ n m) = HQ n (f m)+ instance Monoid m => Monoid (MonoidQ m) where {-# INLINE mappend #-} {-# INLINE mconcat #-}@@ -53,19 +61,21 @@ endoMaybe f (Just a) (Just b) = Just (f a b) endoMaybe _ ma mb = maybe mb Just ma -{-# INLINE fusing #-}-fusing :: Monoid m => [m] -> Maybe m-fusing = let meld = mappend+fusing' :: (m -> m -> m) -> [m] -> Maybe m+fusing' (><) = let fuser [] = Nothing fuser [t] = Just t fuser ts = fuser (fuser' ts) fuser' (t1:t2:t3:t4:ts) =- (t1 `meld` t2) `meld` (t3 `meld` t4) : fuser' ts- fuser' [t1,t2,t3] = [t1 `meld` t2 `meld` t3]- fuser' [t1,t2] = [t1 `meld` t2]+ (t1 >< t2) >< (t3 >< t4) : fuser' ts+ fuser' [t1,t2,t3] = [t1 >< t2 >< t3]+ fuser' [t1,t2] = [t1 >< t2] fuser' ts = ts- in meld `seq` fuser+ in fuser +fusing :: Monoid m => [m] -> Maybe m+fusing = fusing' mappend+ {- fusing [] = Nothing fusing [t] = Just t@@ -95,8 +105,17 @@ in case foldr merger (IA 0 []) qs of IA n ts -> HQ n (fusing ts) +{-# INLINE [0] unfoldFB #-}+unfoldFB :: (b -> Maybe (a, b)) -> b -> (a -> c -> c) -> c -> c+unfoldFB suc s0 c nil = unfold' s0 where+ unfold' s = case suc s of+ Nothing -> nil+ Just (x, s') -> x `c` unfold' s'+ {-# RULES "[] ++" forall l . [] ++ l = l; "++ []" forall l . l ++ [] = l; "fuseMerge/HeapQ" forall (qs :: Monoid m => [MonoidQ (Maybe m)]) . fuseMerge qs = fuseMergeM qs;+-- "unfold" [~1] forall suc s0 . unfoldr suc s0 = build (unfoldrFB suc s0) #-}+
Data/Queue/TrieQueue.hs view
@@ -1,135 +1,132 @@-{-# LANGUAGE PatternGuards, TypeFamilies, GeneralizedNewtypeDeriving #-}-{-# OPTIONS -fno-warn-missing-methods #-}+{-# LANGUAGE TypeFamilies, PatternGuards #-} --- | An experimental trie-based priority queue for lists.-module Data.Queue.TrieQueue where--- Data.Sequence-labelled trie implementation, bootstrapping monoid structure to achieve maximum great justice.+-- | @TrieQueue e@ is a priority queue @IQueue@ instance satisfying @QueueKey (TrieQueue e) ~ [e]@, with the property that this queue frequently performs better than any other queue+-- implementation in this package for keys of type @[e]@. +-- +-- This particular implementation is highly experimental and possibly a genuinely new data structure. See the source code for details.+-- However, for many cases this priority queue may be used for a heap sort that runs faster than the "Data.List" implementation,+-- or the vanilla "Data.Queue.PQueue" implementation. -import Data.Queue.Class-import Data.Queue.QueueHelpers+module Data.Queue.TrieQueue (TrieQueue) where import Control.Arrow((***))-import Control.Monad(liftM2)--import Data.Function-import Data.Maybe-import Data.List (sortBy, groupBy)+import Control.Monad import Data.Monoid-import Data.Ord-import Data.Sequence (Seq, viewl, ViewL(..), (><), (|>), (<|))-import qualified Data.Sequence as Seq-import Data.Map (Map, findMin, minViewWithKey, fromDistinctAscList)-import qualified Data.Map as Map-import qualified Data.Foldable as Fold (toList)+import Data.Maybe +import Data.Queue.Class+import Data.Queue.QueueHelpers(fusing')++import Data.Queue.TrieQueue.Edge+import Data.Queue.TrieQueue.MonoidQueue+import Data.Queue.TrieQueue.TrieLabel+ import GHC.Exts -type Label e = Seq e+import Prelude hiding (null) --- Type of a nonempty trie.-data Trie e = Leaf (Label e) {-# UNPACK #-} !Int -- Leaf xs n represents n occurrences of the string xs. n is always strictly positive.- | Edge (Label e) {-# UNPACK #-} !Int (Map e (Trie e)) -- Edge xs n m represents n occurrences of xs, and xs prepended to each element of the map.- deriving (Eq)-type MTrie e = Maybe (Trie e)-newtype TrieQueue e = TQ (HeapQ (Trie e)) deriving (Eq, Show, Monoid)+-- On the back end it uses something called a /monoid queue/,+-- which takes ordered keys associated with monoid values and returns (k, m') pairs where m' is the concatenation of every monoid value associated with k,+-- with no guarantees made upon the order of the concatenation. Essentially, it is a priority queue which internally "merges" values with equal keys.+-- See Data.Queue.TrieQueue.MonoidQueue for details and a list of alternative implementations. -{-# INLINE mkTQ #-}-mkTQ :: Int -> Trie e -> TrieQueue e-mkTQ n t = TQ (HQ n (Just t))+-- After some experimentation, trie edge labels are currently implemented as vanilla lists; however, the implementation is modularized in+-- Data.Queue.TrieQueue.EdgeLabel. (Other possible implementations include mergeable deques and Data.Sequence finger trees.) -mkLab :: [e] -> Label e-mkLab = Seq.fromList+-- A trie, now, consists of an edge label xs, the number of strings ending with that label, and a monoid queue associating characters in the string to+-- tries consisting of strings prefixed by that character. This is the key variation in this implementation, and it exploits the fact that+-- random-access string lookup is not required in a priority queue: only extract-min and insert, operations perfectly well suited to a monoid queue.+-- Note that the monoid values in the monoid queue are themselves tries, which get recursively merged as necessary. -instance Show e => Show (Trie e) where- show (Leaf xs xn) = "(" ++ show xn ++ "x" ++ show (Fold.toList xs) ++ ")"- show (Edge xs xn m) = "==" ++ show xn ++ "x" ++ show (Fold.toList xs) ++ "==>" ++ show m+data Trie e = Trie (Label e) {-# UNPACK #-} !Int (MQueue e (Trie e)) deriving (Show)+data TrieQueue e = TQ Int (Maybe (Trie e)) deriving (Show) +-- This monoid instance can now get exploited for great justice by the monoid queue. instance Ord e => Monoid (Trie e) where- -- not a true monoid instance; only a semigroup- mappend = merger+ mempty = Trie mempty 0 mempty+ mappend = mergeTrie+ mconcat = fromMaybe mempty . mergeTries -instance Ord e => IQueue (TrieQueue e) where- type QueueKey (TrieQueue e) = [e]- empty = mempty- merge = mappend- mergeAll = mconcat+{-# INLINE forceOrd #-}+forceOrd :: Ord e => Trie e -> x -> x+forceOrd t x = cmp t `seq` x where+ cmp :: Ord e => Trie e -> (e -> e -> Ordering)+ cmp _ = compare - singleton xs = mkTQ 1 $ Leaf (mkLab xs) 1--- fromList = TQ . liftM2 HQ length trieFromList+catTrie :: Ord e => Label e -> Trie e -> Trie e+xs `catTrie` Trie ys yn yQ = Trie (xs `mappend` ys) yn yQ - extract (TQ (HQ n t)) = fmap ((Fold.toList *** (TQ . HQ (n-1))) . extractMin') t- size (TQ (HQ n _)) = n+consTrie :: Ord e => e -> Trie e -> Trie e+x `consTrie` Trie xs xn xQ = Trie (x `cons` xs) xn xQ -catTrie :: Ord e => Label e -> Trie e -> Trie e-catTrie xs (Leaf ys n) = Leaf (xs >< ys) n-catTrie xs (Edge ys n m) = compactTrie (Edge (xs >< ys) n m)+mergeTrie :: Ord e => Trie e -> Trie e -> Trie e+xT@(Trie xs0 xn xQ) `mergeTrie` yT@(Trie ys0 yn yQ) = merging xs0 ys0 split (tail xT yT) (tail yT xT) xy where+ end (Trie _ xn xQ) x xs = x :- Trie xs xn xQ+ split pfx x xs y ys = let xEnd = end xT x xs; yEnd = end yT y ys in Trie pfx 0 (xEnd `insert` singleton yEnd)+ tail (Trie xs xn xQ) yT y ys = let yEnd = end yT y ys in Trie xs xn (yEnd `insert` xQ)+ xy = Trie xs0 (xn + yn) (xQ `merge` yQ) -compactTrie :: Ord e => Trie e -> Trie e-compactTrie = fromJust . compactTrie'+{-# INLINE compactTrie #-}+compactTrie :: Ord e => Trie e -> Maybe (Trie e)+compactTrie (Trie xs 0 xQ)+ | null xQ = Nothing+ | Just (y :- t) <- extractSingle xQ+ = Just (xs `catTrie` (y `consTrie` t))+compactTrie t = Just t -compactTrie' :: Ord e => Trie e -> MTrie e-compactTrie' t@(Edge xs 0 m)- | Map.null m = Nothing- | Map.size m == 1, (y, yT) <- findMin m- = Just $ catTrie (xs |> y) yT- | otherwise = Just t-compactTrie' (Edge xs n m)- | Map.null m = Just $ Leaf xs n-compactTrie' (Leaf _ 0) = Nothing-compactTrie' t = Just t+data Acc e = A {-# UNPACK #-} !Int e +-- Note that a monoid queue is built up and (sometimes) torn down for each character. If every label on every trie being merged matches+-- on the first character, then the monoid queue simply automatically becomes a singleton, a case handled by compactTrie with a specialized+-- implementation based on extractSingle. If the labels do not match, or there are tries being merged with empty labels,+-- then the monoid queue is exactly what we needed anyway.+mergeTries :: Ord e => [Trie e] -> Maybe (Trie e)+mergeTries ts0 = compactTrie (Trie mempty nEmpty ([x :- Trie xs xn xQ | Trie (x:xs) xn xQ <- ts0] `insertAll` mergeAll qs))+ where A nEmpty qs = foldr procEmpty (A 0 []) ts0+ procEmpty (Trie [] n q) (A nEmpty qs) = A (n + nEmpty) (q:qs)+ procEmpty _ acc = acc +--mergeTries = fusing' mergeTrie -extractMin' :: Ord e => Trie e -> (Label e, MTrie e)-extractMin' (Leaf xs n) = (xs, compactTrie' (Leaf xs (n-1)))-extractMin' (Edge xs (n+1) m) = (xs, compactTrie' (Edge xs n m))-extractMin' (Edge xs 0 m)- | Just ((y, yT), m') <- minViewWithKey m,- (ys, yT') <- extractMin' yT- = (xs >< (y <| ys), maybe (compactTrie' (Edge xs 0 m')) (\ yT' -> Just $ Edge xs 0 $ Map.insert y yT' m') yT')-extractMin' _ = error "Internal failure to note empty queue"+{-# INLINE fin #-}+fin :: Ord e => Trie e -> Maybe (Trie e)+fin (Trie _ 0 q) | null q = Nothing+fin t = Just t ---extractMin :: Ord e => Trie e -> Maybe ([e], Trie e)---extractMin = fmap (first toList) . extractMin'+-- If there are strings ending at this label, we obviously process those. Otherwise, we recurse to the first hanging trie from the monoid queue.+-- If it is not exhausted, then we can simply replace the value in the monoid queue; if it is exhausted we may possibly compact the trie+-- (e.g. if there is now only one child trie and we may in fact combine those edges).+extractTrie :: Ord e => Trie e -> (Label e, Maybe (Trie e))+extractTrie (Trie xs (n+1) xQ) = (xs, inline compactTrie (Trie xs n xQ))+extractTrie (Trie xs 0 xQ)+ | Just (y :- t, xQ') <- extract xQ, (ys, t') <- extractTrie t+ = (xs `mappend` (y `cons` ys), case t' of+ Nothing -> inline compactTrie (Trie xs 0 xQ')+ Just t' -> fin (Trie xs 0 $ replace (y :- t') xQ))+extractTrie _ = error "Failure to detect empty queue" -type TailMaker e = Label e -> Trie e+instance Ord e => Monoid (TrieQueue e) where+ mempty = TQ 0 Nothing+ TQ n1 t1 `mappend` TQ n2 t2 = TQ (n1 + n2) (t1 `mappend` t2)+ mconcat ts = TQ (sum [n | TQ n _ <- ts]) (mergeTries [t | TQ _ (Just t) <- ts]) -{-# INLINE merge' #-}-merge' :: Ord e => Label e -> Label e -> TailMaker e -> TailMaker e -> (e -> TailMaker e) -> (e -> TailMaker e) -> Trie e -> Trie e-merge' xs0 ys0 xTail yTail xCons yCons xy = merge'' 0 xs0 ys0 where- merge'' n xs ys = case (viewl xs, viewl ys) of- (x :< xs1, y :< ys1) -> let pfx = Seq.take n xs0; xT = xTail xs1; yT = yTail ys1; in case x `compare` y of- LT -> Edge pfx 0 $ fromDistinctAscList [(x, xT), (y, yT)]- EQ -> merge'' (n+1) xs1 ys1- GT -> Edge pfx 0 $ fromDistinctAscList [(y, yT), (x, xT)]- (x :< xs1, EmptyL) -> yCons x xs1- (EmptyL, y :< ys1) -> xCons y ys1- (EmptyL, EmptyL) -> xy+instance Ord e => IQueue (TrieQueue e) where+ type QueueKey (TrieQueue e) = [e]+ empty = mempty+ merge = mappend+ mergeAll = mconcat -merger :: Ord e => Trie e -> Trie e -> Trie e -Leaf xs0 xn `merger` Leaf ys0 yn =- merge' xs0 ys0 (flip Leaf xn) (flip Leaf yn) (edger xs0 xn yn) (edger ys0 yn xn) (Leaf xs0 (xn + yn))- where edger xs xn yn y ys = Edge xs xn $ Map.singleton y (Leaf ys yn)-Leaf xs0 xn `merger` Edge ys0 yn yM =- merge' xs0 ys0 (flip Leaf xn) (\ ys -> Edge ys yn yM) (\ y ys -> Edge xs0 xn $ Map.singleton y (Edge ys yn yM))- (\ x xs -> Edge ys0 yn $ Map.insertWith merger x (Leaf xs xn) yM) (Edge xs0 (xn + yn) yM)-x@Edge{} `merger` y@Leaf{} = merger y x-Edge xs0 xn xM `merger` Edge ys0 yn yM- = merge' xs0 ys0 (edger xn xM) (edger yn yM) (cons xs0 xn yn yM) (cons ys0 yn xn xM)- (Edge xs0 (xn + yn) $ Map.unionWith merger xM yM)- where edger n m l = Edge l n m- cons xs xn yn yM y = Edge xs xn . Map.singleton y . edger yn yM+ singleton = TQ 1 . Just . single+ insertAll = mappend . fromListTrie+ fromList = fromListTrie -trieFromList :: Ord e => [[e]] -> MTrie e-trieFromList = extractCommon mempty- where groupHeads = groupBy ((==) `on` listToMaybe) . sortBy (comparing listToMaybe)- extractCommon pfx xs = case groupHeads xs of- [] -> Nothing- [empties@([]:_)] -> Just $ Leaf pfx (length empties)- (empties@([]:_):xss) -> Just $ Edge pfx (length empties) (fromGroups xss)- -- even if there's only one other group, we end the edge here- -- a more optimized implementation might specialize for this case- [(y:ys):yss] -> extractCommon (pfx |> y) (ys:map tail yss)- -- if there's but a single group with a shared first character, snoc it onto the accumulated prefix- -- and recurse- xss -> Just $ Edge pfx 0 (fromGroups xss)- fromGroups xss = fromDistinctAscList [(y, fromJust $ trieFromList $ ys : map tail yss) | ((y:ys):yss) <- xss]+ extract (TQ n t) = fmap ((labelToList *** TQ (n-1)) . extractTrie) t+ null (TQ _ Nothing) = True+ null _ = False++ size (TQ n _) = n++fromListTrie :: Ord e => [[e]] -> TrieQueue e+fromListTrie = liftM2 TQ length (mergeTries . map single)++single :: Ord e => [e] -> Trie e+single xs = Trie (labelFromList xs) 1 mempty
+ Data/Queue/TrieQueue/Edge.hs view
@@ -0,0 +1,14 @@+{-# LANGUAGE TypeFamilies, TypeOperators #-}++-- | As simple as it looks: an edge type constructor that performs comparisons only on its key.+module Data.Queue.TrieQueue.Edge ((:-)(..)) where++data k :- m = k :- m deriving (Show)++instance Eq k => Eq (k :- m) where+ (x :- _) == (y :- _) = x == y++instance Ord k => Ord (k :- m) where+ (x :- _) `compare` (y :- _) = x `compare` y+ (x :- _) <= (y :- _) = x <= y+
+ Data/Queue/TrieQueue/MonoidQueue.hs view
@@ -0,0 +1,111 @@+{-# LANGUAGE TypeFamilies, TypeOperators, PatternGuards #-}+{-# OPTIONS -fno-warn-missing-methods #-}++{- |+This module implements the functionality of a /monoid queue/, which is essentially a priority queue that merges values with equal keys. Several implementations were considered:++* A pairing heap which, as part of its merging operation, merges any identical-keyed values it encounters. This may result in partial merging of equal-keyed values for several different keys+ during a single delete-min operation, decreasing the number of nodes in the queue without costing any additional comparisons. In addition, it naturally falls out that+ the values associated with the minimum key are always fully merged. Disadvantages include no control over the balancedness of complete merges, and possible extra polymorphism overhead.+* This considerably simpler bootstrap on a vanilla @PQueue@, which keeps the totally merged value associated with its very minimum key and performs no partial merging until a+ key becomes the minimum. (The fact that no partial merging is performed allows an optimized and balanced 'mconcat' to be used on all the values associated with a key at once.)++The primary difference between these two is the compromise between /performance of the heap itself/ and /performance of the merging of the monoids/:+ * The first implementation speculatively takes advantage of opportunities to merge nodes with equal keys. No actual extra work is being done -- the same number of comparisons+ is made, and the actual merging is performed lazily. As a result, truly the only disadvantages of this approach are the completely uncontrolled balance of the merges,+ and possible polymorphism overhead that can be carefully hand-removed.+ * The second implementation allows an optimized bulk merge operation, which -- in the case of tries, which is what after all the motivation for this structure --+ has extremely significant advantages.++A version of the second implementation is included with the Cabal distribution in +"Data.Queue.TrieQueue.MonoidQueue2", and can serve as a literal drop-in replacement for this module.+See its implementation notes for further details.++-}+module Data.Queue.TrieQueue.MonoidQueue (MQueue, extractSingle, replace) where++import Data.Queue.Class+import Data.Queue.QueueHelpers(fusing')+import Data.Maybe+import Data.Monoid+import qualified Data.Tree as Tree++import Data.Queue.TrieQueue.Edge++-- | A pairing heap node in the monoid queue; nonempty.+data MNode k m = Node k m (MForest k m)+type MForest k m = [MNode k m]+-- | A full-fledged priority queue, including empty queues.+newtype MQueue k m = MQ (Maybe (MNode k m)) deriving (Show)++instance (Show k, Show m) => Show (MNode k m) where+ show = Tree.drawTree . Tree.unfoldTree (\ (Node k m ts) -> (show (k :- m), ts))++-- | A @Functor@ instance exploited for great justice in @mergeNodes@.+instance Functor (MNode k) where+ fmap f (Node k m ns) = Node k (f m) (map (fmap f) ns)++instance (Ord k, Monoid m) => Monoid (MNode k m) where+ -- no mempty declaration+ mappend = mergeMNode $! mappend++instance (Ord k, Monoid m) => Monoid (MQueue k m) where+ mempty = MQ Nothing+ MQ n1 `mappend` MQ n2 = MQ (n1 `mappend` n2)+ -- This is the straightforward implementation:+ -- mconcat qs = MQ $ fusing [n | MQ (Just n) <- qs]+ -- This implementation allows optimized mass mconcats rather than just using individual mappends,+ -- and exploits the flexible monoid structure.+ mconcat qs = mergeNodes [n | MQ (Just n) <- qs]+++mergeMNode :: Ord k => (m -> m -> m) -> MNode k m -> MNode k m -> MNode k m+mergeMNode (><) n1@(Node k1 m1 ns1) n2@(Node k2 m2 ns2) = case compare k1 k2 of+ LT -> Node k1 m1 (n2:ns1)+ EQ -> Node k1 (m1 >< m2) (ns1 ++ ns2)+ GT -> Node k2 m2 (n1:ns2)++{-# INLINE mergeNodes' #-}+-- | Merges a collection of nodes by performing a balanced fuse and performing an mconcat on all blocks of equal-keyed monoid values.+mergeNodes' :: (Ord k) => (k -> k -> Ordering) -> (m -> m -> m) -> MForest k m -> MQueue k m+--mergeNodes ns = MQ (fmap (fmap mconcat) $ fusing (map (fmap $ \ m -> [m]) ns))+mergeNodes' _ (><) = MQ . fusing' (mergeMNode (><))++{-# INLINE mergeNodes #-}+mergeNodes :: (Ord k, Monoid m) => MForest k m -> MQueue k m+mergeNodes = (mergeNodes' $! compare) $! mappend++instance (Ord k, Monoid m) => IQueue (MQueue k m) where+ type QueueKey (MQueue k m) = k :- m+ empty = mempty+ merge = mappend+ mergeAll = mconcat++ singleton (k :- m) = MQ (Just (Node k m []))+ insertAll = merge . fromListMQ+ fromList = fromListMQ+ extract = extractMQ++ null (MQ Nothing) = True+ null _ = False++{-# INLINE single #-}+single :: k :- m -> MNode k m+single (k :- m) = Node k m []++extractMQ :: (Ord k, Monoid m) => MQueue k m -> Maybe (k :- m, MQueue k m)+extractMQ (MQ t) = fmap extract' t where+ extract' (Node k m ns) = (k :- m, mergeNodes ns)++fromListMQ :: (Ord k, Monoid m) => [k :- m] -> MQueue k m+fromListMQ ks = mergeNodes $ map single ks++{-# INLINE extractSingle #-}+extractSingle :: (Ord k, Monoid m) => MQueue k m -> Maybe (k :- m)+extractSingle (MQ (Just (Node k m []))) = Just (k :- m)+extractSingle _ = Nothing++-- UNSAFE. Do not use unless you know the key you're putting in is less than or equal to the minimum key.+replace :: (Ord k, Monoid m) => (k :- m) -> MQueue k m -> MQueue k m+replace km@(k :- m) (MQ t) = MQ $ Just (maybe (single km) (\ (Node _ _ ns) -> Node k m ns) t)+
+ Data/Queue/TrieQueue/MonoidQueue2.hs view
@@ -0,0 +1,84 @@+{-# LANGUAGE TypeFamilies, TypeOperators, PatternGuards #-}++module Data.Queue.TrieQueue.MonoidQueue2 (MQueue, extractSingle, replace) where++import Data.Queue.PQueue+import Data.Queue.Class+import Data.Queue.QueueHelpers (fusing, fusing')++import Data.Queue.TrieQueue.Edge++import Data.Maybe+import Data.Monoid+import Prelude hiding (null)++data MQueue k m = Nil | MQ {-# UNPACK #-} !(k :- m) {-# UNPACK #-} !(PQueue (k :- m)) deriving (Show)++instance (Ord k, Monoid m) => Monoid (MQueue k m) where+ mempty = Nil+ mappend = mergeMQ+{-+Here we organize on the queues themselves. We require that fromList not be defined in terms of mergeAll, of course -- in fact, if it is defined using toQueue as it is below,+this gives us the best of both worlds -- balanced merging both on the values associated with the minimum key (provided by fromList), and balanced merging on the remaining queues.+We lose some partial merging opportunities, but this implementation does none, so that's not an issue.+-}+ mconcat qs = case mkMQueue [k :- (m, q) | MQ (k :- m) q <- qs] of+ Nil -> Nil+ MQ (k :- (m, pq)) qOfMQs -> + let q' = pq `merge` mergeAll + [(k :- m) `insert` q | k :- (m, q) <- toList_ qOfMQs]+ in MQ (k :- m) q'+ where mkMQueue :: (Ord k, Monoid m) => [k :- m] -> MQueue k m+ mkMQueue = fromList++-- mconcat = fromMaybe Nil . fusing' mergeMQ++mergeMQ' :: Ord k => (k -> k -> Ordering) -> (m -> m -> m) -> MQueue k m -> MQueue k m -> MQueue k m+mergeMQ' cmp (><) (MQ x1@(k1 :- m1) q1) (MQ x2@(k2 :- m2) q2) = case cmp k1 k2 of+ LT -> MQ x1 (x2 `insert` q)+ EQ -> MQ (k1 :- (m1 >< m2)) q+ GT -> MQ x2 (x1 `insert` q)+ where q = q1 `merge` q2+mergeMQ' _ _ q Nil = q+mergeMQ' _ _ Nil q = q++mergeMQ :: (Ord k, Monoid m) => MQueue k m -> MQueue k m -> MQueue k m+mergeMQ = mergeMQ' compare mappend++instance (Ord k, Monoid m) => IQueue (MQueue k m) where+ type QueueKey (MQueue k m) = (k :- m)+ empty = Nil+ singleton = (`MQ` empty)+ fromList = toQueue . fromList++ merge = mappend+ mergeAll = mconcat++ null Nil = True+ null _ = False+ + extract Nil = Nothing+ extract (MQ x q) = Just (x, toQueue q)++ toList_ Nil = []+ toList_ (MQ x q) = x:toList_ q++-- This version ignores balancing and does a totally simple merge on values with the minimal key in common.+toQueue :: (Ord k, Monoid m) => PQueue (k :- m) -> MQueue k m+toQueue q = case extract q of+ Nothing -> Nil+ Just (k :- m, q') -> let toQueue' ms q = case extract q of Just (k' :- m', q')+ | k == k' -> toQueue' (m':ms) q'+-- _ -> MQ (k :- maybe m (mappend m) (fusing ms)) q+ _ | [] <- ms -> MQ (k :- m) q+ | otherwise -> MQ (k :- (m `mappend` mconcat ms)) q+ in toQueue' [] q'++extractSingle :: Ord k => MQueue k m -> Maybe (k :- m)+extractSingle (MQ x q)+ | null q = Just x+extractSingle _ = Nothing++replace :: (Ord k, Monoid m) => (k :- m) -> MQueue k m -> MQueue k m+replace (k :- m) (MQ _ q) = MQ (k :- m) q+replace _ Nil = error "Attempt to modify head of an empty queue"
+ Data/Queue/TrieQueue/TrieLabel.hs view
@@ -0,0 +1,69 @@+{-# LANGUAGE MagicHash, UnboxedTuples #-}++module Data.Queue.TrieQueue.TrieLabel where++import Data.Sequence (Seq, ViewL(..), viewl, (><), (<|), (|>))+import qualified Data.Sequence as Seq+import qualified Data.Foldable as Fold++type Split e x = Label e -- ^ common prefix+ -> e -> Label e -- ^ truncated suffix of xs+ -> e -> Label e -- ^ truncated suffix of xs+ -> x -- ^ split trie++type Tail e x = e -> Label e -- left over suffix+ -> x -- trie+++{-# INLINE merging #-}+-- | Performs partial matching of two labels and applies an appropriate function upon completing a partial match.+merging :: Eq e => Label e -- ^ A label, @xs@.+ -> Label e -- ^ A label, @ys@.+ -> Split e x -- ^ A function to be applied when the two strings share some (possibly empty) common prefix and mismatchng tails.+ -> Tail e x -- ^ A function to be applied when @xs@ is a prefix of @ys@.+ -> Tail e x -- ^ A function to be applied when @ys@ is a prefix of @xs@.+ -> x -- ^ A value to be returned when @xs == ys@.+ -> x++cons :: e -> Label e -> Label e++labelToList :: Label e -> [e]++labelFromList :: [e] -> Label e++merging xs0 ys0 split xEnd yEnd xy = merging' 0 xs0 ys0 where+ merging' n xs ys = let pfx = take n xs0 in case (xs, ys) of+ (x:xs, y:ys) | x == y -> merging' (n+1) xs ys+ | otherwise -> split pfx x xs y ys+ (x:xs, []) -> yEnd x xs+ ([], y:ys) -> xEnd y ys+ ([], []) -> xy+type Label e = [e]++cons = (:)+labelToList = id+labelFromList = id+{-++type Label e = Seq e++merging xs0 ys0 split xEnd yEnd xy = merging' 0 (Fold.toList xs0) (Fold.toList ys0) where+ merging' n xs ys = let n' = n + 1; (pfx, xT0) = Seq.splitAt n xs0; _ :< xT = viewl xT0; yT = Seq.drop n' ys0 in case (xs, ys) of+ (x:xs, y:ys) | x == y -> merging' n' xs ys+ | otherwise -> split pfx x xT y yT+ (x:xs, []) -> yEnd x xT+ ([], y:ys) -> xEnd y yT+ ([], []) -> xy++cons = (<|)++labelToList = Fold.toList++labelFromList = Seq.fromList+-}++testMerging :: (Eq e, Show e) => Label e -> Label e -> String+testMerging xs0 ys0 = merging xs0 ys0 (\ pfx x xs y ys -> "Split " ++ show pfx ++ " (" ++ show x ++ " -> " ++ show xs ++ ") (" ++ show y ++ " -> " ++ show ys ++ ")")+ (\ y ys -> "Break " ++ show xs0 ++ " = " ++ show y ++ " -> " ++ show ys)+ (\ x xs -> "Break " ++ show ys0 ++ " = " ++ show x ++ " -> " ++ show xs)+ ("Equal " ++ show xs0)
queuelike.cabal view
@@ -1,7 +1,8 @@ name: queuelike-version: 1.0.5+version: 1.0.6 synopsis: A library of queuelike data structures, both functional and stateful. description: Contains several implementations of data structures implementing a /single-in, single-out/ paradigm. Intended to be a better, more useful replacement for pqueue-mtl.+ In particular, includes an experimental and possibly genuinely new trie-based priority queue on strings. Feedback is appreciated. tested-with: GHC category: Algorithms stability: experimental@@ -10,7 +11,7 @@ license-file: LICENSE author: Louis Wasserman maintainer: wasserman.louis@gmail.com-build-Depends: base, containers, mtl, array, stateful-mtl >= 1.0.7, utility-ht+build-Depends: base, containers, mtl, array, stateful-mtl >= 1.0.7 build-type: Simple Exposed-modules: Data.Queue@@ -20,9 +21,6 @@ Data.Queue.Stack Data.Queue.Queue Data.Queue.TrieQueue--- Data.Queue.SkewQueue--- Data.Queue.SoftHeap--- Data.Queue.IntQueue Data.MQueue.Class Data.MQueue.Heap Data.MQueue.SyncQueue@@ -30,6 +28,10 @@ Data.MQueue other-modules: Data.Queue.QueueHelpers+ Data.Queue.TrieQueue.Edge+ Data.Queue.TrieQueue.MonoidQueue+ Data.Queue.TrieQueue.TrieLabel Data.MQueue.MonadHelpers--- Data.Queue.Numeric+extra-source-files:+ Data/Queue/TrieQueue/MonoidQueue2.hs ghc-options: