Skip to content

Commit 9d395e4

Browse files
authored
Add Data.IntSet.fromRange (#965)
fromRange (l,h) is faster than fromRange [l..h], primarily because we can pack Tips with multiple elements at once.
1 parent b652f2f commit 9d395e4

File tree

4 files changed

+71
-5
lines changed

4 files changed

+71
-5
lines changed

containers-tests/benchmarks/IntSet.hs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ main = do
3838
, bench "difference" $ whnf (IS.difference s) s_even
3939
, bench "intersection" $ whnf (IS.intersection s) s_even
4040
, bench "fromList" $ whnf IS.fromList elems
41+
, bench "fromRange" $ whnf IS.fromRange (1,bound)
42+
, bench "fromRange:small" $ whnf IS.fromRange (-1,0)
4143
, bench "fromAscList" $ whnf IS.fromAscList elems
4244
, bench "fromDistinctAscList" $ whnf IS.fromDistinctAscList elems
4345
, bench "disjoint:false" $ whnf (IS.disjoint s) s_even
@@ -56,12 +58,13 @@ main = do
5658
, bench "splitMember:sparse" $ whnf (IS.splitMember elem_sparse_mid) s_sparse
5759
]
5860
where
59-
elems = [1..2^12]
60-
elems_even = [2,4..2^12]
61-
elems_odd = [1,3..2^12]
62-
elem_mid = 2^11 + 31 -- falls in the middle of a packed Tip bitmask (assuming 64-bit words)
61+
bound = 2^12
62+
elems = [1..bound]
63+
elems_even = [2,4..bound]
64+
elems_odd = [1,3..bound]
65+
elem_mid = bound `div` 2 + 31 -- falls in the middle of a packed Tip bitmask (assuming 64-bit words)
6366
elems_sparse = map (*64) elems -- when built into a map, each Tip is a singleton
64-
elem_sparse_mid = 2^11 * 64
67+
elem_sparse_mid = bound `div` 2 * 64
6568

6669
member :: [Int] -> IS.IntSet -> Int
6770
member xs s = foldl' (\n x -> if IS.member x s then n + 1 else n) 0 xs

containers-tests/tests/intset-properties.hs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ main = defaultMain $ testGroup "intset-properties"
4545
, testProperty "prop_DescList" prop_DescList
4646
, testProperty "prop_AscDescList" prop_AscDescList
4747
, testProperty "prop_fromList" prop_fromList
48+
, testProperty "prop_fromRange" prop_fromRange
4849
, testProperty "prop_MaskPow2" prop_MaskPow2
4950
, testProperty "prop_Prefix" prop_Prefix
5051
, testProperty "prop_LeftRight" prop_LeftRight
@@ -277,6 +278,12 @@ prop_fromList xs
277278
where sort_xs = sort xs
278279
nub_sort_xs = List.map List.head $ List.group sort_xs
279280

281+
prop_fromRange :: Property
282+
prop_fromRange = forAll (scale (*100) arbitrary) go
283+
where
284+
go (l,h) = valid t .&&. t === fromAscList [l..h]
285+
where t = fromRange (l,h)
286+
280287
{--------------------------------------------------------------------
281288
Bin invariants
282289
--------------------------------------------------------------------}

containers/src/Data/IntSet.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ module Data.IntSet (
7676
, empty
7777
, singleton
7878
, fromList
79+
, fromRange
7980
, fromAscList
8081
, fromDistinctAscList
8182

containers/src/Data/IntSet/Internal.hs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ module Data.IntSet.Internal (
123123
-- * Construction
124124
, empty
125125
, singleton
126+
, fromRange
126127
, insert
127128
, delete
128129
, alterF
@@ -1215,6 +1216,60 @@ fromList xs
12151216
where
12161217
ins t x = insert x t
12171218

1219+
-- | \(O(n / W)\). Create a set from a range of integers.
1220+
--
1221+
-- > fromRange (low, high) == fromList [low..high]
1222+
--
1223+
-- @since FIXME
1224+
fromRange :: (Key, Key) -> IntSet
1225+
fromRange (lx,rx)
1226+
| lx > rx = empty
1227+
| lp == rp = Tip lp (bitmapOf rx `shiftLL` 1 - bitmapOf lx)
1228+
| otherwise =
1229+
let m = branchMask lx rx
1230+
p = mask lx m
1231+
in if m < 0 -- handle negative numbers
1232+
then Bin 0 m (goR 0) (goL 0)
1233+
else Bin p m (goL (p .|. m)) (goR (p .|. m))
1234+
where
1235+
lp = prefixOf lx
1236+
rp = prefixOf rx
1237+
-- goL p0 = fromList [lx .. p0-1]
1238+
-- Expected: p0 is lx where one 0-bit is flipped to 1 and all bits lower than that are 0.
1239+
-- p0 can be 0 (pretend that bit WORD_SIZE is flipped to 1).
1240+
goL :: Prefix -> IntSet
1241+
goL !p0 = go (Tip lp (- bitmapOf lx)) (lp + lbm prefixBitMask)
1242+
where
1243+
go !l p | p == p0 = l
1244+
go l p =
1245+
let m = lbm p
1246+
p' = p `xor` m
1247+
l' = Bin p' m l (goFull p (shr1 m))
1248+
in go l' (p + m)
1249+
-- goR p0 = fromList [p0 .. rx]
1250+
-- Expected: p0 is a prefix of rx
1251+
goR :: Prefix -> IntSet
1252+
goR !p0 = go (Tip rp (bitmapOf rx `shiftLL` 1 - 1)) rp
1253+
where
1254+
go !r p | p == p0 = r
1255+
go r p =
1256+
let m = lbm p
1257+
p' = p `xor` m
1258+
r' = Bin p' m (goFull p' (shr1 m)) r
1259+
in go r' p'
1260+
-- goFull p m = fromList [p .. p+2*m-1]
1261+
-- Expected: popCount m == 1, p == mask p m
1262+
goFull :: Prefix -> Mask -> IntSet
1263+
goFull p m
1264+
| m < suffixBitMask = Tip p (complement 0)
1265+
| otherwise = Bin p m (goFull p (shr1 m)) (goFull (p .|. m) (shr1 m))
1266+
lbm :: Prefix -> Prefix
1267+
lbm p = intFromNat (lowestBitMask (natFromInt p))
1268+
{-# INLINE lbm #-}
1269+
shr1 :: Mask -> Mask
1270+
shr1 m = intFromNat (natFromInt m `shiftRL` 1)
1271+
{-# INLINE shr1 #-}
1272+
12181273
-- | \(O(n)\). Build a set from an ascending list of elements.
12191274
-- /The precondition (input list is ascending) is not checked./
12201275
fromAscList :: [Key] -> IntSet

0 commit comments

Comments
 (0)