Skip to content

Commit 65ffbad

Browse files
committed
Eq and Ord instances for Prio queues
* Make the `Eq` and `Ord` instances for `Prio` queues compare the queues in fully sorted form—that is, as key-value maps. This seems to be the only way to make these instances make any real kind of sense. * Document the "nondeterministic" nature of `Prio` queues.
1 parent 6d3577b commit 65ffbad

File tree

9 files changed

+43
-43
lines changed

9 files changed

+43
-43
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## 1.5.0
44

5+
* Make the `Eq` and `Ord` instances for key-value queues sensible.
6+
Document the nondeterministic nature of these queues.
7+
58
* Make mapping and traversal functions force the full data structure spine.
69
This should make performance more predictable, and removes the last
710
remaining reasons to use the `seqSpine` functions. As these are no longer

src/BinomialQueue/Max.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ take :: Ord a => Int -> MaxQueue a -> [a]
169169
take n = List.take n . toDescList
170170

171171
-- | \(O(k \log n)\)/. 'drop' @k@, applied to a queue @queue@, returns @queue@ with the greatest @k@ elements deleted,
172-
-- or an empty queue if @k >= size 'queue'@.
172+
-- or an empty queue if @k >= 'size' queue@.
173173
drop :: Ord a => Int -> MaxQueue a -> MaxQueue a
174174
drop n (MaxQueue queue) = MaxQueue (MinQ.drop n queue)
175175

src/BinomialQueue/Min.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ take :: Ord a => Int -> MinQueue a -> [a]
162162
take n = List.take n . toAscList
163163

164164
-- | \(O(k \log n)\)/. 'drop' @k@, applied to a queue @queue@, returns @queue@ with the smallest @k@ elements deleted,
165-
-- or an empty queue if @k >= size 'queue'@.
165+
-- or an empty queue if @k >= 'size' queue@.
166166
drop :: Ord a => Int -> MinQueue a -> MinQueue a
167167
drop n queue = n `seq` case minView queue of
168168
Just (_, queue')

src/Data/PQueue/Internals.hs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,9 @@ mapEither f (MinQueue _ x ts)
198198
Left y -> (fromBare (BQ.insert y l), fromBare r)
199199
Right z -> (fromBare l, fromBare (BQ.insert z r))
200200

201-
-- | \(O(n)\). Assumes that the function it is given is monotonic, and applies this function to every element of the priority queue,
202-
-- as in 'fmap'. If it is not, the result is undefined.
201+
-- | \(O(n)\). Assumes that the function it is given is monotonic, and applies
202+
-- this function to every element of the priority queue, as in 'fmap'. If it is
203+
-- not, the result is undefined.
203204
mapMonotonic :: (a -> b) -> MinQueue a -> MinQueue b
204205
mapMonotonic = mapU
205206

@@ -274,7 +275,7 @@ insertMinQ' x (MinQueue n x' f) = MinQueue (n + 1) x (BQ.insertMinQ' x' f)
274275

275276
-- | @insertMaxQ' x h@ assumes that @x@ compares as greater
276277
-- than or equal to every element of @h@. It also assumes,
277-
-- and preserves, an extra invariant. See 'insertMax'' for details.
278+
-- and preserves, an extra invariant. See 'BQ.insertMax'' for details.
278279
-- tldr: this function can be used safely to build a queue from an
279280
-- ascending list/array/whatever, but that's about it.
280281
insertMaxQ' :: a -> MinQueue a -> MinQueue a
@@ -289,6 +290,9 @@ fromList :: Ord a => [a] -> MinQueue a
289290
-- comparison per element.
290291
fromList xs = fromBare (BQ.fromList xs)
291292

293+
-- | \(O(n)\). Assumes that the function it is given is monotonic, and applies
294+
-- this function to every element of the priority queue, as in 'fmap'. If it is
295+
-- not, the result is undefined.
292296
mapU :: (a -> b) -> MinQueue a -> MinQueue b
293297
mapU _ Empty = Empty
294298
mapU f (MinQueue n x ts) = MinQueue n (f x) (BQ.mapU f ts)

src/Data/PQueue/Min.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ pattern Empty = Internals.Empty
128128
infixr 5 :<
129129

130130
-- | A bidirectional pattern synonym for working with the minimum view of a
131-
-- 'MinPQueue'. Using @:<@ to construct a queue performs an insertion in
131+
-- 'Prio.MinPQueue'. Using @:<@ to construct a queue performs an insertion in
132132
-- \(O(1)\) amortized time. When matching on @a :< q@, forcing @q@ takes
133133
-- \(O(\log n)\) time.
134134
--
@@ -204,7 +204,7 @@ take :: Ord a => Int -> MinQueue a -> [a]
204204
take n = List.take n . toAscList
205205

206206
-- | \(O(k \log n)\)/. 'drop' @k@, applied to a queue @queue@, returns @queue@ with the smallest @k@ elements deleted,
207-
-- or an empty queue if @k >= size 'queue'@.
207+
-- or an empty queue if @k >= 'size' queue@.
208208
drop :: Ord a => Int -> MinQueue a -> MinQueue a
209209
drop n queue = n `seq` case minView queue of
210210
Just (_, queue')

src/Data/PQueue/Prio/Internals.hs

Lines changed: 16 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ module Data.PQueue.Prio.Internals (
5252

5353
import Control.Applicative (liftA2, liftA3)
5454
import Control.DeepSeq (NFData(rnf), deepseq)
55+
import Data.Function (on)
5556
import Data.Functor.Identity (Identity(Identity, runIdentity))
5657
import qualified Data.List as List
5758
import Data.PQueue.Internals.Foldable
@@ -210,37 +211,18 @@ instance IFoldMap t => IFoldMap (BinomForest t) where
210211
foldMapWithKey_ f (Skip ts) = foldMapWithKey_ f ts
211212
foldMapWithKey_ f (Cons t ts) = foldMapWithKey_ f t `mappend` foldMapWithKey_ f ts
212213

213-
instance (Ord k, Eq a) => Eq (MinPQueue k a) where
214-
MinPQ n1 k1 a1 ts1 == MinPQ n2 k2 a2 ts2 =
215-
n1 == n2 && eqExtract k1 a1 ts1 k2 a2 ts2
216-
Empty == Empty = True
217-
_ == _ = False
218-
219-
eqExtract :: (Ord k, Eq a) => k -> a -> BinomForest rk k a -> k -> a -> BinomForest rk k a -> Bool
220-
eqExtract k10 a10 ts10 k20 a20 ts20 =
221-
k10 == k20 && a10 == a20 &&
222-
case (extract ts10, extract ts20) of
223-
(Yes (Extract k1 a1 _ ts1'), Yes (Extract k2 a2 _ ts2'))
224-
-> eqExtract k1 a1 ts1' k2 a2 ts2'
225-
(No, No) -> True
226-
_ -> False
214+
-- | @ (==) = (==) ``on`` 'List.sort' . 'toAscList' @
215+
instance (Ord k, Ord a) => Eq (MinPQueue k a) where
216+
(==) = (==) `on` toBrokenList
227217

218+
toBrokenList :: (Ord k, Ord a) => MinPQueue k a -> [[(k, a)]]
219+
-- We break up the list to avoid lots of redundant key comparisons
220+
-- in sorting.
221+
toBrokenList = List.map (List.sortBy (compare `on` snd)) . List.groupBy ((==) `on` fst) . toAscList
222+
223+
-- | @ compare = compare ``on`` 'List.sort' . 'toAscList' @
228224
instance (Ord k, Ord a) => Ord (MinPQueue k a) where
229-
MinPQ _n1 k10 a10 ts10 `compare` MinPQ _n2 k20 a20 ts20 =
230-
cmpExtract k10 a10 ts10 k20 a20 ts20
231-
Empty `compare` Empty = EQ
232-
Empty `compare` MinPQ{} = LT
233-
MinPQ{} `compare` Empty = GT
234-
235-
cmpExtract :: (Ord k, Ord a) => k -> a -> BinomForest rk k a -> k -> a -> BinomForest rk k a -> Ordering
236-
cmpExtract k10 a10 ts10 k20 a20 ts20 =
237-
k10 `compare` k20 <> a10 `compare` a20 <>
238-
case (extract ts10, extract ts20) of
239-
(Yes (Extract k1 a1 _ ts1'), Yes (Extract k2 a2 _ ts2'))
240-
-> cmpExtract k1 a1 ts1' k2 a2 ts2'
241-
(No, Yes{}) -> LT
242-
(Yes{}, No) -> GT
243-
(No, No) -> EQ
225+
compare = compare `on` toBrokenList
244226

245227
-- | \(O(1)\). Returns the empty priority queue.
246228
empty :: MinPQueue k a
@@ -343,9 +325,11 @@ minViewWithKey (MinPQ n k a ts) = Just ((k, a), extractHeap n ts)
343325
mapWithKey :: (k -> a -> b) -> MinPQueue k a -> MinPQueue k b
344326
mapWithKey f = runIdentity . traverseWithKeyU (Identity .: f)
345327

346-
-- | \(O(n)\). @'mapKeysMonotonic' f q == 'mapKeys' f q@, but only works when @f@ is strictly
347-
-- monotonic. /The precondition is not checked./ This function has better performance than
348-
-- 'mapKeys'.
328+
-- | \(O(n)\). @'mapKeysMonotonic' f q == 'Data.PQueue.Prio.Min.mapKeys' f q@,
329+
-- but only works when @f@ is strictly monotonic.
330+
-- /The precondition is not checked./
331+
-- This function has better performance than 'Data.PQueue.Prio.Min.mapKeys'.
332+
349333
mapKeysMonotonic :: (k -> k') -> MinPQueue k a -> MinPQueue k' a
350334
mapKeysMonotonic _ Empty = Empty
351335
mapKeysMonotonic f (MinPQ n k a ts) = MinPQ n (f k) a (mapKeysMonoF f (const Zero) ts)

src/Data/PQueue/Prio/Max.hs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
-- are no guarantees about the relative order in which @k1@, @k2@, and their associated
2222
-- elements are returned. (Unlike Data.Map, we allow multiple elements with the
2323
-- same key.)
24+
-- The order in which key-value pairs with the same key are extracted, folded,
25+
-- or traversed is explicitly unspecified. From a semantic standpoint, it is
26+
-- simplest to imagine that these operations are nondeterministic.
2427
--
2528
-- This implementation offers a number of methods of the form @xxxU@, where @U@ stands for
2629
-- unordered. No guarantees whatsoever are made on the execution or traversal order of

src/Data/PQueue/Prio/Max/Internals.hs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,13 @@ build f = f (:) []
138138
-- | A priority queue where values of type @a@ are annotated with keys of type @k@.
139139
-- The queue supports extracting the element with maximum key.
140140
newtype MaxPQueue k a = MaxPQ (MinPQueue (Down k) a)
141-
# if __GLASGOW_HASKELL__
142-
deriving (Eq, Ord, Data)
143-
# else
144-
deriving (Eq, Ord)
141+
deriving
142+
( Eq -- ^ @ (==) = (==) ``Data.Function.on`` 'Data.List.sortBy (flip compare)' . 'toDescList' @
143+
, Ord -- ^ @ compare = compare ``Data.Function.on`` 'Data.List.sortBy (flip compare)' . 'toDescList' @
144+
# ifdef __GLASGOW_HASKELL__
145+
, Data
145146
# endif
147+
)
146148

147149
instance (NFData k, NFData a) => NFData (MaxPQueue k a) where
148150
rnf (MaxPQ q) = rnf q

src/Data/PQueue/Prio/Min.hs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
-- elements are returned. (Unlike Data.Map, we allow multiple elements with the
2727
-- same key.)
2828
--
29+
-- The order in which key-value pairs with the same key are extracted, folded,
30+
-- or traversed is explicitly unspecified. From a semantic standpoint, it is
31+
-- simplest to imagine that these operations are nondeterministic.
32+
--
2933
-- This implementation offers a number of methods of the form @xxxU@, where @U@ stands for
3034
-- unordered. No guarantees whatsoever are made on the execution or traversal order of
3135
-- these functions.

0 commit comments

Comments
 (0)