Skip to content

Commit 9be5ec2

Browse files
committed
Improve query functions of IntMap and IntSet.
As documented in the Note: Local 'go' functions and capturing, it is safe to use captured key argument in query functions. Also, in order to decrease allocation, the query functions in IntMap are manually inlined, so 'member' does not have to call 'lookup' and heap-allocate 'Just a'. Tests of query functions are much improved too.
1 parent 03a0620 commit 9be5ec2

File tree

5 files changed

+85
-40
lines changed

5 files changed

+85
-40
lines changed

Data/IntMap/Base.hs

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ type Key = Int
281281
-- > fromList [(5,'a'), (3,'b')] ! 5 == 'a'
282282

283283
(!) :: IntMap a -> Key -> a
284-
m ! k = find k m
284+
m ! k = find k m
285285

286286
-- | Same as 'difference'.
287287
(\\) :: IntMap a -> IntMap b -> IntMap a
@@ -363,11 +363,15 @@ size t
363363
-- > member 5 (fromList [(5,'a'), (3,'b')]) == True
364364
-- > member 1 (fromList [(5,'a'), (3,'b')]) == False
365365

366+
-- See Note: Local 'go' functions and capturing]
366367
member :: Key -> IntMap a -> Bool
367-
member k m
368-
= case lookup k m of
369-
Nothing -> False
370-
Just _ -> True
368+
member k = k `seq` go
369+
where
370+
go (Bin p m l r) | nomatch k p m = False
371+
| zero k m = go l
372+
| otherwise = go r
373+
go (Tip kx _) = k == kx
374+
go Nil = False
371375

372376
-- | /O(min(n,W))/. Is the key not a member of the map?
373377
--
@@ -377,28 +381,32 @@ member k m
377381
notMember :: Key -> IntMap a -> Bool
378382
notMember k m = not $ member k m
379383

380-
-- The 'go' function in the lookup causes 10% speedup, but also an increased
381-
-- memory allocation. It does not cause speedup with other methods like insert
382-
-- and delete, so it is present only in lookup.
383-
384384
-- | /O(min(n,W))/. Lookup the value at a key in the map. See also 'Data.Map.lookup'.
385+
386+
-- See Note: Local 'go' functions and capturing]
385387
lookup :: Key -> IntMap a -> Maybe a
386388
lookup k = k `seq` go
387389
where
388-
go (Bin _ m l r)
389-
| zero k m = go l
390-
| otherwise = go r
391-
go (Tip kx x)
392-
| k == kx = Just x
393-
| otherwise = Nothing
394-
go Nil = Nothing
390+
go (Bin p m l r) | nomatch k p m = Nothing
391+
| zero k m = go l
392+
| otherwise = go r
393+
go (Tip kx x) | k == kx = Just x
394+
| otherwise = Nothing
395+
go Nil = Nothing
395396

396397

398+
-- See Note: Local 'go' functions and capturing]
397399
find :: Key -> IntMap a -> a
398-
find k m
399-
= case lookup k m of
400-
Nothing -> error ("IntMap.find: key " ++ show k ++ " is not an element of the map")
401-
Just x -> x
400+
find k = k `seq` go
401+
where
402+
go (Bin p m l r) | nomatch k p m = not_found
403+
| zero k m = go l
404+
| otherwise = go r
405+
go (Tip kx x) | k == kx = x
406+
| otherwise = not_found
407+
go Nil = not_found
408+
409+
not_found = error ("IntMap.!: key" ++ show k ++ " is not an element of the map")
402410

403411
-- | /O(min(n,W))/. The expression @('findWithDefault' def k map)@
404412
-- returns the value at key @k@ or returns @def@ when the key is not an
@@ -407,11 +415,16 @@ find k m
407415
-- > findWithDefault 'x' 1 (fromList [(5,'a'), (3,'b')]) == 'x'
408416
-- > findWithDefault 'x' 5 (fromList [(5,'a'), (3,'b')]) == 'a'
409417

418+
-- See Note: Local 'go' functions and capturing]
410419
findWithDefault :: a -> Key -> IntMap a -> a
411-
findWithDefault def k m
412-
= case lookup k m of
413-
Nothing -> def
414-
Just x -> x
420+
findWithDefault def k = k `seq` go
421+
where
422+
go (Bin p m l r) | nomatch k p m = def
423+
| zero k m = go l
424+
| otherwise = go r
425+
go (Tip kx x) | k == kx = x
426+
| otherwise = def
427+
go Nil = def
415428

416429
{--------------------------------------------------------------------
417430
Construction

Data/IntMap/Strict.hs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -277,11 +277,16 @@ import Data.StrictPair
277277
-- > findWithDefault 'x' 1 (fromList [(5,'a'), (3,'b')]) == 'x'
278278
-- > findWithDefault 'x' 5 (fromList [(5,'a'), (3,'b')]) == 'a'
279279

280+
-- See IntMap.Base.Note: Local 'go' functions and capturing]
280281
findWithDefault :: a -> Key -> IntMap a -> a
281-
findWithDefault def k m
282-
= def `seq` case lookup k m of
283-
Nothing -> def
284-
Just x -> x
282+
findWithDefault def k = def `seq` k `seq` go
283+
where
284+
go (Bin p m l r) | nomatch k p m = def
285+
| zero k m = go l
286+
| otherwise = go r
287+
go (Tip kx x) | k == kx = x
288+
| otherwise = def
289+
go Nil = def
285290

286291
{--------------------------------------------------------------------
287292
Construction

Data/IntSet.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ size t
304304

305305
-- | /O(min(n,W))/. Is the value a member of the set?
306306

307+
-- See Note: Local 'go' functions and capturing]
307308
member :: Int -> IntSet -> Bool
308309
member x = x `seq` go
309310
where

tests/intmap-properties.hs

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ main = defaultMainWithOpts
115115
, testCase "minViewWithKey" test_minViewWithKey
116116
, testCase "maxViewWithKey" test_maxViewWithKey
117117
, testProperty "insert to singleton" prop_singleton
118-
, testProperty "insert then lookup" prop_lookup
118+
, testProperty "insert then lookup" prop_insertLookup
119119
, testProperty "insert then delete" prop_insertDelete
120120
, testProperty "delete non member" prop_deleteNonMember
121121
, testProperty "union model" prop_unionModel
@@ -137,6 +137,8 @@ main = defaultMainWithOpts
137137
, testProperty "null" prop_null
138138
, testProperty "member" prop_member
139139
, testProperty "notmember" prop_notmember
140+
, testProperty "lookup" prop_lookup
141+
, testProperty "find" prop_find
140142
, testProperty "findWithDefault" prop_findWithDefault
141143
, testProperty "findMin" prop_findMin
142144
, testProperty "findMax" prop_findMax
@@ -704,8 +706,8 @@ test_maxViewWithKey = do
704706
prop_singleton :: Int -> Int -> Bool
705707
prop_singleton k x = insert k x empty == singleton k x
706708

707-
prop_lookup :: Int -> UMap -> Bool
708-
prop_lookup k t = lookup k (insert k () t) /= Nothing
709+
prop_insertLookup :: Int -> UMap -> Bool
710+
prop_insertLookup k t = lookup k (insert k () t) /= Nothing
709711

710712
prop_insertDelete :: Int -> UMap -> Property
711713
prop_insertDelete k t = (lookup k t == Nothing) ==> (delete k (insert k () t) == t)
@@ -833,18 +835,30 @@ prop_null m = null m == (size m == 0)
833835
prop_member :: [Int] -> Int -> Bool
834836
prop_member xs n =
835837
let m = fromList (zip xs xs)
836-
in (n `elem` xs) == (n `member` m)
838+
in all (\k -> k `member` m == (k `elem` xs)) (n : xs)
837839

838840
prop_notmember :: [Int] -> Int -> Bool
839841
prop_notmember xs n =
840842
let m = fromList (zip xs xs)
841-
in (n `notElem` xs) == (n `notMember` m)
842-
843-
prop_findWithDefault :: [(Int, Int)] -> Property
844-
prop_findWithDefault ys = length ys > 0 ==>
845-
let xs = List.nubBy ((==) `on` fst) ys
846-
m = fromList xs
847-
in and [ findWithDefault 0 i m == j | (i,j) <- xs ]
843+
in all (\k -> k `notMember` m == (k `notElem` xs)) (n : xs)
844+
845+
prop_lookup :: [(Int, Int)] -> Int -> Bool
846+
prop_lookup xs n =
847+
let xs' = List.nubBy ((==) `on` fst) xs
848+
m = fromList xs'
849+
in all (\k -> lookup k m == List.lookup k xs') (n : List.map fst xs')
850+
851+
prop_find :: [(Int, Int)] -> Bool
852+
prop_find xs =
853+
let xs' = List.nubBy ((==) `on` fst) xs
854+
m = fromList xs'
855+
in all (\(k, v) -> m ! k == v) xs'
856+
857+
prop_findWithDefault :: [(Int, Int)] -> Int -> Int -> Bool
858+
prop_findWithDefault xs n x =
859+
let xs' = List.nubBy ((==) `on` fst) xs
860+
m = fromList xs'
861+
in all (\k -> findWithDefault x k m == maybe x id (List.lookup k xs')) (n : List.map fst xs')
848862

849863
prop_findMin :: [(Int, Int)] -> Property
850864
prop_findMin ys = length ys > 0 ==>

tests/intset-properties.hs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import Test.Framework.Providers.QuickCheck2
1111

1212
main :: IO ()
1313
main = defaultMainWithOpts [ testProperty "prop_Single" prop_Single
14+
, testProperty "prop_Member" prop_Member
15+
, testProperty "prop_NotMember" prop_NotMember
1416
, testProperty "prop_InsertDelete" prop_InsertDelete
1517
, testProperty "prop_MemberFromList" prop_MemberFromList
1618
, testProperty "prop_UnionInsert" prop_UnionInsert
@@ -63,12 +65,22 @@ instance Arbitrary IntSet where
6365

6466

6567
{--------------------------------------------------------------------
66-
Single, Insert, Delete, Member, FromList
68+
Single, Member, Insert, Delete, Member, FromList
6769
--------------------------------------------------------------------}
6870
prop_Single :: Int -> Bool
6971
prop_Single x
7072
= (insert x empty == singleton x)
7173

74+
prop_Member :: [Int] -> Int -> Bool
75+
prop_Member xs n =
76+
let m = fromList xs
77+
in all (\k -> k `member` m == (k `elem` xs)) (n : xs)
78+
79+
prop_NotMember :: [Int] -> Int -> Bool
80+
prop_NotMember xs n =
81+
let m = fromList xs
82+
in all (\k -> k `notMember` m == (k `notElem` xs)) (n : xs)
83+
7284
prop_InsertDelete :: Int -> IntSet -> Property
7385
prop_InsertDelete k t
7486
= not (member k t) ==> delete k (insert k t) == t

0 commit comments

Comments
 (0)