Skip to content

Commit 97156ef

Browse files
authored
Add compareSize for IntSet and IntMap (#1135)
This function short-circuits, which can be more efficient than calculating the size. Other structures for which `length` takes O(n) provide similar `compareLength`s: list, Text, lazy Text, lazy ByteString.
1 parent aa64020 commit 97156ef

File tree

9 files changed

+74
-0
lines changed

9 files changed

+74
-0
lines changed

containers-tests/tests/intmap-properties.hs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ main = defaultMain $ testGroup "intmap-properties"
138138
, testCase "maxViewWithKey" test_maxViewWithKey
139139
, testCase "minimum" test_minimum
140140
, testCase "maximum" test_maximum
141+
, testCase "compareSize" test_compareSize
141142
, testProperty "valid" prop_valid
142143
, testProperty "hasPrefix" prop_hasPrefix
143144
, testProperty "empty valid" prop_emptyValid
@@ -254,6 +255,7 @@ main = defaultMain $ testGroup "intmap-properties"
254255
, testProperty "fromDistinctAscList" prop_fromDistinctAscList
255256
, testProperty "fromListWith" prop_fromListWith
256257
, testProperty "fromListWithKey" prop_fromListWithKey
258+
, testProperty "compareSize" prop_compareSize
257259
]
258260

259261
{--------------------------------------------------------------------
@@ -1196,6 +1198,12 @@ test_maximum = do
11961198
maximum (elems testOrdMap) @?= maximum testOrdMap
11971199
where getOW (OrdWith s _) = s
11981200

1201+
-- Check cases where there is a risk of overflow
1202+
test_compareSize :: Assertion
1203+
test_compareSize = do
1204+
compareSize (fromList [(1,'a')]) minBound @?= GT
1205+
compareSize (fromList [(1,'a')]) maxBound @?= LT
1206+
11991207
testOrdMap :: IntMap (OrdWith Int)
12001208
testOrdMap = fromList [(1,OrdWith "max" 1),(-1,OrdWith "min" 1)]
12011209

@@ -2033,3 +2041,6 @@ prop_fromListWithKey f kxs =
20332041
m === List.foldl' (\m' (k,x) -> insertWith (applyFun3 f k) k x m') empty kxs
20342042
where
20352043
m = fromListWithKey (applyFun3 f) kxs
2044+
2045+
prop_compareSize :: IntMap A -> Int -> Property
2046+
prop_compareSize t c = compareSize t c === compare (size t) c

containers-tests/tests/intset-properties.hs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ main = defaultMain $ testGroup "intset-properties"
2424
, testCase "lookupGE" test_lookupGE
2525
, testCase "split" test_split
2626
, testCase "isProperSubsetOf" test_isProperSubsetOf
27+
, testCase "compareSize" test_compareSize
2728
, testProperty "prop_Valid" prop_Valid
2829
, testProperty "prop_EmptyValid" prop_EmptyValid
2930
, testProperty "prop_SingletonValid" prop_SingletonValid
@@ -91,6 +92,7 @@ main = defaultMain $ testGroup "intset-properties"
9192
, testProperty "deleteMax" prop_deleteMax
9293
, testProperty "fromAscList" prop_fromAscList
9394
, testProperty "fromDistinctAscList" prop_fromDistinctAscList
95+
, testProperty "compareSize" prop_compareSize
9496
]
9597

9698
----------------------------------------------------------------
@@ -136,6 +138,12 @@ test_isProperSubsetOf = do
136138
-- See Github #1007
137139
isProperSubsetOf (fromList [-65,-1]) (fromList [-65,-1,0]) @?= True
138140

141+
-- Check cases where there is a risk of overflow
142+
test_compareSize :: Assertion
143+
test_compareSize = do
144+
compareSize (fromList [1]) minBound @?= GT
145+
compareSize (fromList [1]) maxBound @?= LT
146+
139147
{--------------------------------------------------------------------
140148
Arbitrary, reasonably balanced trees
141149
--------------------------------------------------------------------}
@@ -535,3 +543,6 @@ prop_fromDistinctAscList xs =
535543
where
536544
nubSortedXs = List.map NE.head $ NE.group $ sort xs
537545
t = fromDistinctAscList nubSortedXs
546+
547+
prop_compareSize :: IntSet -> Int -> Property
548+
prop_compareSize t c = compareSize t c === compare (size t) c

containers/changelog.md

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

33
## Next release
44

5+
### Additions
6+
7+
* Add `compareSize` for `IntSet` and `IntMap`. (Soumik Sarkar)
8+
([#1135](https://github.com/haskell/containers/pull/1135))
9+
510
### Performance improvements
611

712
* Improved performance for `Data.IntMap.restrictKeys` and

containers/src/Data/IntMap/Internal.hs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ module Data.IntMap.Internal (
9595
-- * Query
9696
, null
9797
, size
98+
, compareSize
9899
, member
99100
, notMember
100101
, lookup
@@ -579,13 +580,33 @@ null _ = False
579580
-- > size empty == 0
580581
-- > size (singleton 1 'a') == 1
581582
-- > size (fromList([(1,'a'), (2,'c'), (3,'b')])) == 3
583+
--
584+
-- See also: 'compareSize'
582585
size :: IntMap a -> Int
583586
size = go 0
584587
where
585588
go !acc (Bin _ l r) = go (go acc l) r
586589
go acc (Tip _ _) = 1 + acc
587590
go acc Nil = acc
588591

592+
-- | \(O(\min(n,c))\). Compare the number of entries in the map to an @Int@.
593+
--
594+
-- @compareSize m c@ returns the same result as @compare ('size' m) c@ but is
595+
-- more efficient when @c@ is smaller than the size of the map.
596+
--
597+
-- @since FIXME
598+
compareSize :: IntMap a -> Int -> Ordering
599+
compareSize !_ c0 | c0 < 0 = GT
600+
compareSize Nil c0 = compare 0 c0
601+
compareSize t c0 = compare 0 (go t c0)
602+
where
603+
go (Bin _ l r) c
604+
| c' <= 0 = -1
605+
| otherwise = go r c'
606+
where
607+
c' = go l c
608+
go _ c = c - 1 -- Must be Tip (invariant)
609+
589610
-- | \(O(\min(n,W))\). Is the key a member of the map?
590611
--
591612
-- > member 5 (fromList [(5,'a'), (3,'b')]) == True

containers/src/Data/IntMap/Lazy.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ module Data.IntMap.Lazy (
147147
-- ** Size
148148
, IM.null
149149
, size
150+
, compareSize
150151

151152
-- * Combine
152153

containers/src/Data/IntMap/Strict.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ module Data.IntMap.Strict (
165165
-- ** Size
166166
, null
167167
, size
168+
, compareSize
168169

169170
-- * Combine
170171

containers/src/Data/IntMap/Strict/Internal.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ module Data.IntMap.Strict.Internal (
108108
-- ** Size
109109
, null
110110
, size
111+
, compareSize
111112

112113
-- * Combine
113114

@@ -305,6 +306,7 @@ import Data.IntMap.Internal
305306
, spanAntitone
306307
, restrictKeys
307308
, size
309+
, compareSize
308310
, split
309311
, splitLookup
310312
, splitRoot

containers/src/Data/IntSet.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ module Data.IntSet (
122122
, lookupGE
123123
, IS.null
124124
, size
125+
, compareSize
125126
, isSubsetOf
126127
, isProperSubsetOf
127128
, disjoint

containers/src/Data/IntSet/Internal.hs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ module Data.IntSet.Internal (
9898
-- * Query
9999
, null
100100
, size
101+
, compareSize
101102
, member
102103
, notMember
103104
, lookupLT
@@ -358,13 +359,33 @@ null _ = False
358359
{-# INLINE null #-}
359360

360361
-- | \(O(n)\). Cardinality of the set.
362+
--
363+
-- See also: 'compareSize'
361364
size :: IntSet -> Int
362365
size = go 0
363366
where
364367
go !acc (Bin _ l r) = go (go acc l) r
365368
go acc (Tip _ bm) = acc + popCount bm
366369
go acc Nil = acc
367370

371+
-- | \(O(\min(n,c))\). Compare the number of elements in the set to an @Int@.
372+
--
373+
-- @compareSize m c@ returns the same result as @compare ('size' m) c@ but is
374+
-- more efficient when @c@ is smaller than the size of the set.
375+
--
376+
-- @since FIXME
377+
compareSize :: IntSet -> Int -> Ordering
378+
compareSize !_ c0 | c0 < 0 = GT
379+
compareSize t c0 = compare 0 (go t c0)
380+
where
381+
go (Bin _ l r) c
382+
| c' <= 0 = -1
383+
| otherwise = go r c'
384+
where
385+
c' = go l c
386+
go (Tip _ bm) c = c - popCount bm
387+
go Nil c = c
388+
368389
-- | \(O(\min(n,W))\). Is the value a member of the set?
369390

370391
-- See Note: Local 'go' functions and capturing.

0 commit comments

Comments
 (0)