Skip to content

Commit 0209162

Browse files
authored
Add lookupMin and lookupMax for IntSet (#976)
These already exist for Set, Map, and IntMap. Additionally, * Implement lookupMin, lookupMax, findMin, findMax in a consistent manner for all structures. The rationale for this implementation is provided in the Note [Inline lookupMin] in Data.Set.Internal. * Update docs for lookupMin, lookupMax, findMin, findMax to be consistent across all structures.
1 parent fb2aa39 commit 0209162

File tree

7 files changed

+135
-51
lines changed

7 files changed

+135
-51
lines changed

containers-tests/tests/intset-properties.hs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Data.Word (Word)
55
import Data.IntSet
66
import Data.List (nub,sort)
77
import qualified Data.List as List
8+
import Data.Maybe (listToMaybe)
89
import Data.Monoid (mempty)
910
import qualified Data.Set as Set
1011
import IntSetValidity (valid)
@@ -55,6 +56,8 @@ main = defaultMain $ testGroup "intset-properties"
5556
, testProperty "prop_isSubsetOf2" prop_isSubsetOf2
5657
, testProperty "prop_disjoint" prop_disjoint
5758
, testProperty "prop_size" prop_size
59+
, testProperty "prop_lookupMin" prop_lookupMin
60+
, testProperty "prop_lookupMax" prop_lookupMax
5861
, testProperty "prop_findMax" prop_findMax
5962
, testProperty "prop_findMin" prop_findMin
6063
, testProperty "prop_ord" prop_ord
@@ -342,6 +345,12 @@ prop_size s = sz === foldl' (\i _ -> i + 1) (0 :: Int) s .&&.
342345
sz === List.length (toList s)
343346
where sz = size s
344347

348+
prop_lookupMin :: IntSet -> Property
349+
prop_lookupMin s = lookupMin s === listToMaybe (toAscList s)
350+
351+
prop_lookupMax :: IntSet -> Property
352+
prop_lookupMax s = lookupMax s === listToMaybe (toDescList s)
353+
345354
prop_findMax :: IntSet -> Property
346355
prop_findMax s = not (null s) ==> findMax s == maximum (toList s)
347356

containers/changelog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535
provided `only2` function with empty maps, contrary to documentation.
3636
(Soumik Sarkar)
3737

38+
### Additions
39+
40+
* Add `lookupMin` and `lookupMax` for `Data.IntSet`. (Soumik Sarkar)
41+
3842
## Unreleased with `@since` annotation for 0.7.1:
3943

4044
### Additions

containers/src/Data/IntMap/Internal.hs

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2301,37 +2301,55 @@ deleteFindMax = fromMaybe (error "deleteFindMax: empty map has no maximal elemen
23012301
deleteFindMin :: IntMap a -> ((Key, a), IntMap a)
23022302
deleteFindMin = fromMaybe (error "deleteFindMin: empty map has no minimal element") . minViewWithKey
23032303

2304+
-- The KeyValue type is used when returning a key-value pair and helps with
2305+
-- GHC optimizations.
2306+
--
2307+
-- For lookupMinSure, if the return type is (Int, a), GHC compiles it to a
2308+
-- worker $wlookupMinSure :: IntMap a -> (# Int, a #). If the return type is
2309+
-- KeyValue a instead, the worker does not box the int and returns
2310+
-- (# Int#, a #).
2311+
-- For a modern enough GHC (>=9.4), this measure turns out to be unnecessary in
2312+
-- this instance. We still use it for older GHCs and to make our intent clear.
2313+
2314+
data KeyValue a = KeyValue {-# UNPACK #-} !Key a
2315+
2316+
kvToTuple :: KeyValue a -> (Key, a)
2317+
kvToTuple (KeyValue k x) = (k, x)
2318+
{-# INLINE kvToTuple #-}
2319+
2320+
lookupMinSure :: IntMap a -> KeyValue a
2321+
lookupMinSure (Tip k v) = KeyValue k v
2322+
lookupMinSure (Bin _ l _) = lookupMinSure l
2323+
lookupMinSure Nil = error "lookupMinSure Nil"
2324+
23042325
-- | \(O(\min(n,W))\). The minimal key of the map. Returns 'Nothing' if the map is empty.
23052326
lookupMin :: IntMap a -> Maybe (Key, a)
2306-
lookupMin Nil = Nothing
2307-
lookupMin (Tip k v) = Just (k,v)
2308-
lookupMin (Bin p l r)
2309-
| signBranch p = go r
2310-
| otherwise = go l
2311-
where go (Tip k v) = Just (k,v)
2312-
go (Bin _ l' _) = go l'
2313-
go Nil = Nothing
2327+
lookupMin Nil = Nothing
2328+
lookupMin (Tip k v) = Just (k,v)
2329+
lookupMin (Bin p l r) =
2330+
Just $! kvToTuple (lookupMinSure (if signBranch p then r else l))
2331+
{-# INLINE lookupMin #-} -- See Note [Inline lookupMin] in Data.Set.Internal
23142332

23152333
-- | \(O(\min(n,W))\). The minimal key of the map. Calls 'error' if the map is empty.
2316-
-- Use 'minViewWithKey' if the map may be empty.
23172334
findMin :: IntMap a -> (Key, a)
23182335
findMin t
23192336
| Just r <- lookupMin t = r
23202337
| otherwise = error "findMin: empty map has no minimal element"
23212338

2339+
lookupMaxSure :: IntMap a -> KeyValue a
2340+
lookupMaxSure (Tip k v) = KeyValue k v
2341+
lookupMaxSure (Bin _ _ r) = lookupMaxSure r
2342+
lookupMaxSure Nil = error "lookupMaxSure Nil"
2343+
23222344
-- | \(O(\min(n,W))\). The maximal key of the map. Returns 'Nothing' if the map is empty.
23232345
lookupMax :: IntMap a -> Maybe (Key, a)
2324-
lookupMax Nil = Nothing
2325-
lookupMax (Tip k v) = Just (k,v)
2326-
lookupMax (Bin p l r)
2327-
| signBranch p = go l
2328-
| otherwise = go r
2329-
where go (Tip k v) = Just (k,v)
2330-
go (Bin _ _ r') = go r'
2331-
go Nil = Nothing
2346+
lookupMax Nil = Nothing
2347+
lookupMax (Tip k v) = Just (k,v)
2348+
lookupMax (Bin p l r) =
2349+
Just $! kvToTuple (lookupMaxSure (if signBranch p then l else r))
2350+
{-# INLINE lookupMax #-} -- See Note [Inline lookupMin] in Data.Set.Internal
23322351

23332352
-- | \(O(\min(n,W))\). The maximal key of the map. Calls 'error' if the map is empty.
2334-
-- Use 'maxViewWithKey' if the map may be empty.
23352353
findMax :: IntMap a -> (Key, a)
23362354
findMax t
23372355
| Just r <- lookupMax t = r

containers/src/Data/IntSet.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ module Data.IntSet (
136136
, fold
137137

138138
-- * Min\/Max
139+
, lookupMin
140+
, lookupMax
139141
, findMin
140142
, findMax
141143
, deleteMin

containers/src/Data/IntSet/Internal.hs

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ module Data.IntSet.Internal (
153153
, fold
154154

155155
-- * Min\/Max
156+
, lookupMin
157+
, lookupMax
156158
, findMin
157159
, findMax
158160
, deleteMin
@@ -1044,29 +1046,49 @@ deleteFindMin = fromMaybe (error "deleteFindMin: empty set has no minimal elemen
10441046
deleteFindMax :: IntSet -> (Key, IntSet)
10451047
deleteFindMax = fromMaybe (error "deleteFindMax: empty set has no maximal element") . maxView
10461048

1049+
lookupMinSure :: IntSet -> Key
1050+
lookupMinSure (Tip kx bm) = kx + lowestBitSet bm
1051+
lookupMinSure (Bin _ l _) = lookupMinSure l
1052+
lookupMinSure Nil = error "lookupMin Nil"
10471053

1048-
-- | \(O(\min(n,W))\). The minimal element of the set.
1054+
-- | \(O(\min(n,W))\). The minimal element of the set. Returns 'Nothing' if the
1055+
-- set is empty.
1056+
--
1057+
-- @since FIXME
1058+
lookupMin :: IntSet -> Maybe Key
1059+
lookupMin Nil = Nothing
1060+
lookupMin (Tip kx bm) = Just $! kx + lowestBitSet bm
1061+
lookupMin (Bin p l r) = Just $! lookupMinSure (if signBranch p then r else l)
1062+
{-# INLINE lookupMin #-} -- See Note [Inline lookupMin] in Data.Set.Internal
1063+
1064+
-- | \(O(\min(n,W))\). The minimal element of the set. Calls 'error' if the set
1065+
-- is empty.
10491066
findMin :: IntSet -> Key
1050-
findMin Nil = error "findMin: empty set has no minimal element"
1051-
findMin (Tip kx bm) = kx + lowestBitSet bm
1052-
findMin (Bin p l r)
1053-
| signBranch p = find r
1054-
| otherwise = find l
1055-
where find (Tip kx bm) = kx + lowestBitSet bm
1056-
find (Bin _ l' _) = find l'
1057-
find Nil = error "findMin Nil"
1058-
1059-
-- | \(O(\min(n,W))\). The maximal element of a set.
1060-
findMax :: IntSet -> Key
1061-
findMax Nil = error "findMax: empty set has no maximal element"
1062-
findMax (Tip kx bm) = kx + highestBitSet bm
1063-
findMax (Bin p l r)
1064-
| signBranch p = find l
1065-
| otherwise = find r
1066-
where find (Tip kx bm) = kx + highestBitSet bm
1067-
find (Bin _ _ r') = find r'
1068-
find Nil = error "findMax Nil"
1067+
findMin t
1068+
| Just r <- lookupMin t = r
1069+
| otherwise = error "findMin: empty set has no minimal element"
1070+
1071+
lookupMaxSure :: IntSet -> Key
1072+
lookupMaxSure (Tip kx bm) = kx + highestBitSet bm
1073+
lookupMaxSure (Bin _ _ r) = lookupMaxSure r
1074+
lookupMaxSure Nil = error "lookupMax Nil"
10691075

1076+
-- | \(O(\min(n,W))\). The maximal element of the set. Returns 'Nothing' if the
1077+
-- set is empty.
1078+
--
1079+
-- @since FIXME
1080+
lookupMax :: IntSet -> Maybe Key
1081+
lookupMax Nil = Nothing
1082+
lookupMax (Tip kx bm) = Just $! kx + highestBitSet bm
1083+
lookupMax (Bin p l r) = Just $! lookupMaxSure (if signBranch p then l else r)
1084+
{-# INLINE lookupMax #-} -- See Note [Inline lookupMin] in Data.Set.Internal
1085+
1086+
-- | \(O(\min(n,W))\). The maximal element of the set. Calls 'error' if the set
1087+
-- is empty.
1088+
findMax :: IntSet -> Key
1089+
findMax t
1090+
| Just r <- lookupMax t = r
1091+
| otherwise = error "findMax: empty set has no maximal element"
10701092

10711093
-- | \(O(\min(n,W))\). Delete the minimal element. Returns an empty set if the set is empty.
10721094
--

containers/src/Data/Map/Internal.hs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1627,8 +1627,26 @@ deleteAt !i t =
16271627
Minimal, Maximal
16281628
--------------------------------------------------------------------}
16291629

1630-
lookupMinSure :: k -> a -> Map k a -> (k, a)
1631-
lookupMinSure k a Tip = (k, a)
1630+
-- The KeyValue type is used when returning a key-value pair and helps GHC keep
1631+
-- track of the fact that key is in WHNF.
1632+
--
1633+
-- As an example, for a use case like
1634+
--
1635+
-- fmap (\(k,_) -> <strict use of k>) (lookupMin m)
1636+
--
1637+
-- on a non-empty map, GHC can decide to evaluate the usage of k if it is cheap
1638+
-- and put the result in the Just, instead of making a thunk for it.
1639+
-- If GHC does not know that k is in WHNF, it could be bottom, and so GHC must
1640+
-- always return Just with a thunk inside.
1641+
1642+
data KeyValue k a = KeyValue !k a
1643+
1644+
kvToTuple :: KeyValue k a -> (k, a)
1645+
kvToTuple (KeyValue k a) = (k, a)
1646+
{-# INLINE kvToTuple #-}
1647+
1648+
lookupMinSure :: k -> a -> Map k a -> KeyValue k a
1649+
lookupMinSure !k a Tip = KeyValue k a
16321650
lookupMinSure _ _ (Bin _ k a l _) = lookupMinSure k a l
16331651

16341652
-- | \(O(\log n)\). The minimal key of the map. Returns 'Nothing' if the map is empty.
@@ -1640,7 +1658,8 @@ lookupMinSure _ _ (Bin _ k a l _) = lookupMinSure k a l
16401658

16411659
lookupMin :: Map k a -> Maybe (k,a)
16421660
lookupMin Tip = Nothing
1643-
lookupMin (Bin _ k x l _) = Just $! lookupMinSure k x l
1661+
lookupMin (Bin _ k x l _) = Just $! kvToTuple (lookupMinSure k x l)
1662+
{-# INLINE lookupMin #-} -- See Note [Inline lookupMin] in Data.Set.Internal
16441663

16451664
-- | \(O(\log n)\). The minimal key of the map. Calls 'error' if the map is empty.
16461665
--
@@ -1652,8 +1671,8 @@ findMin t
16521671
| Just r <- lookupMin t = r
16531672
| otherwise = error "Map.findMin: empty map has no minimal element"
16541673

1655-
lookupMaxSure :: k -> a -> Map k a -> (k, a)
1656-
lookupMaxSure k a Tip = (k, a)
1674+
lookupMaxSure :: k -> a -> Map k a -> KeyValue k a
1675+
lookupMaxSure !k a Tip = KeyValue k a
16571676
lookupMaxSure _ _ (Bin _ k a _ r) = lookupMaxSure k a r
16581677

16591678
-- | \(O(\log n)\). The maximal key of the map. Returns 'Nothing' if the map is empty.
@@ -1665,7 +1684,8 @@ lookupMaxSure _ _ (Bin _ k a _ r) = lookupMaxSure k a r
16651684

16661685
lookupMax :: Map k a -> Maybe (k, a)
16671686
lookupMax Tip = Nothing
1668-
lookupMax (Bin _ k x _ r) = Just $! lookupMaxSure k x r
1687+
lookupMax (Bin _ k x _ r) = Just $! kvToTuple (lookupMaxSure k x r)
1688+
{-# INLINE lookupMax #-} -- See Note [Inline lookupMin] in Data.Set.Internal
16691689

16701690
-- | \(O(\log n)\). The maximal key of the map. Calls 'error' if the map is empty.
16711691
--

containers/src/Data/Set/Internal.hs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -754,23 +754,29 @@ disjoint (Bin _ x l r) t
754754
Minimal, Maximal
755755
--------------------------------------------------------------------}
756756

757-
-- We perform call-pattern specialization manually on lookupMin
758-
-- and lookupMax. Otherwise, GHC doesn't seem to do it, which is
759-
-- unfortunate if, for example, someone uses findMin or findMax.
757+
-- Note [Inline lookupMin]
758+
-- ~~~~~~~~~~~~~~~~~~~~~~~
759+
-- The core of lookupMin is implemented as lookupMinSure, a recursive function
760+
-- that does not involve Maybes. lookupMin wraps the result of lookupMinSure in
761+
-- a Just. We inline lookupMin so that GHC optimizations can eliminate the Maybe
762+
-- if it is matched on at the call site.
760763

761764
lookupMinSure :: a -> Set a -> a
762765
lookupMinSure x Tip = x
763766
lookupMinSure _ (Bin _ x l _) = lookupMinSure x l
764767

765-
-- | \(O(\log n)\). The minimal element of a set.
768+
-- | \(O(\log n)\). The minimal element of the set. Returns 'Nothing' if the set
769+
-- is empty.
766770
--
767771
-- @since 0.5.9
768772

769773
lookupMin :: Set a -> Maybe a
770774
lookupMin Tip = Nothing
771775
lookupMin (Bin _ x l _) = Just $! lookupMinSure x l
776+
{-# INLINE lookupMin #-} -- See Note [Inline lookupMin]
772777

773-
-- | \(O(\log n)\). The minimal element of a set.
778+
-- | \(O(\log n)\). The minimal element of the set. Calls 'error' if the set is
779+
-- empty.
774780
findMin :: Set a -> a
775781
findMin t
776782
| Just r <- lookupMin t = r
@@ -780,15 +786,18 @@ lookupMaxSure :: a -> Set a -> a
780786
lookupMaxSure x Tip = x
781787
lookupMaxSure _ (Bin _ x _ r) = lookupMaxSure x r
782788

783-
-- | \(O(\log n)\). The maximal element of a set.
789+
-- | \(O(\log n)\). The maximal element of the set. Returns 'Nothing' if the set
790+
-- is empty.
784791
--
785792
-- @since 0.5.9
786793

787794
lookupMax :: Set a -> Maybe a
788795
lookupMax Tip = Nothing
789796
lookupMax (Bin _ x _ r) = Just $! lookupMaxSure x r
797+
{-# INLINE lookupMax #-} -- See Note [Inline lookupMin]
790798

791-
-- | \(O(\log n)\). The maximal element of a set.
799+
-- | \(O(\log n)\). The maximal element of the set. Calls 'error' if the set is
800+
-- empty.
792801
findMax :: Set a -> a
793802
findMax t
794803
| Just r <- lookupMax t = r

0 commit comments

Comments
 (0)