Skip to content

Commit 3fa0102

Browse files
authored
Add disjoint (#559)
1 parent eb955f5 commit 3fa0102

File tree

8 files changed

+128
-16
lines changed

8 files changed

+128
-16
lines changed

Data/HashMap/Internal.hs

Lines changed: 103 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ module Data.HashMap.Internal
8383
, intersectionWith
8484
, intersectionWithKey
8585
, intersectionWithKey#
86+
, disjoint
8687

8788
-- * Folds
8889
, foldr'
@@ -719,23 +720,23 @@ lookupCont ::
719720
-> k
720721
-> Shift
721722
-> HashMap k v -> r
722-
lookupCont absent present !h0 !k0 !s0 m0 = go h0 k0 s0 m0
723+
lookupCont absent present !h0 !k0 !s0 m0 = lookupCont_ h0 k0 s0 m0
723724
where
724-
go :: Eq k => Hash -> k -> Shift -> HashMap k v -> r
725-
go !_ !_ !_ Empty = absent (# #)
726-
go h k _ (Leaf hx (L kx x))
725+
lookupCont_ :: Eq k => Hash -> k -> Shift -> HashMap k v -> r
726+
lookupCont_ !_ !_ !_ Empty = absent (# #)
727+
lookupCont_ h k _ (Leaf hx (L kx x))
727728
| h == hx && k == kx = present x (-1)
728729
| otherwise = absent (# #)
729-
go h k s (BitmapIndexed b v)
730+
lookupCont_ h k s (BitmapIndexed b v)
730731
| b .&. m == 0 = absent (# #)
731732
| otherwise =
732733
case A.index# v (sparseIndex b m) of
733-
(# st #) -> go h k (nextShift s) st
734+
(# st #) -> lookupCont_ h k (nextShift s) st
734735
where m = mask h s
735-
go h k s (Full v) =
736+
lookupCont_ h k s (Full v) =
736737
case A.index# v (index h s) of
737-
(# st #) -> go h k (nextShift s) st
738-
go h k _ (Collision hx v)
738+
(# st #) -> lookupCont_ h k (nextShift s) st
739+
lookupCont_ h k _ (Collision hx v)
739740
| h == hx = lookupInArrayCont absent present k v
740741
| otherwise = absent (# #)
741742
{-# INLINE lookupCont #-}
@@ -2315,6 +2316,94 @@ searchSwap mary n toFind start = go start toFind start
23152316
else go i0 k (i + 1)
23162317
{-# INLINE searchSwap #-}
23172318

2319+
-- | \(O(n \log m)\) Check whether the key sets of two maps are disjoint
2320+
-- (i.e., their 'intersection' is empty).
2321+
--
2322+
-- @
2323+
-- xs ``disjoint`` ys = null (xs ``intersection`` ys)
2324+
-- @
2325+
--
2326+
-- @since FIXME
2327+
disjoint :: Eq k => HashMap k a -> HashMap k b -> Bool
2328+
disjoint = disjointSubtrees 0
2329+
{-# INLINE disjoint #-}
2330+
2331+
-- Note that as of GHC 9.12, SpecConstr creates a specialized worker for
2332+
-- handling the Collision vs. {BitmapIndexed,Full} and vice-versa cases,
2333+
-- but this worker fails to be properly specialized for different key
2334+
-- types. See https://gitlab.haskell.org/ghc/ghc/-/issues/26615.
2335+
disjointSubtrees :: Eq k => Shift -> HashMap k a -> HashMap k b -> Bool
2336+
disjointSubtrees !_s Empty _b = True
2337+
disjointSubtrees s (Leaf hA (L kA _)) b =
2338+
lookupCont (\_ -> True) (\_ _ -> False) hA kA s b
2339+
disjointSubtrees s (BitmapIndexed bmA aryA) (BitmapIndexed bmB aryB) =
2340+
-- We could do a pointer equality check here but it's probably not worth it
2341+
-- since it would save only O(1) extra work:
2342+
--
2343+
-- not (aryA `A.unsafeSameArray` aryB) &&
2344+
disjointArrays s bmA aryA bmB aryB
2345+
disjointSubtrees s (BitmapIndexed bmA aryA) (Full aryB) =
2346+
disjointArrays s bmA aryA fullBitmap aryB
2347+
disjointSubtrees s (Full aryA) (BitmapIndexed bmB aryB) =
2348+
disjointArrays s fullBitmap aryA bmB aryB
2349+
disjointSubtrees s (Full aryA) (Full aryB) =
2350+
-- We could do a pointer equality check here but it's probably not worth it
2351+
-- since it would save only O(1) extra work:
2352+
--
2353+
-- not (aryA `A.unsafeSameArray` aryB) &&
2354+
go (maxChildren - 1)
2355+
where
2356+
go i
2357+
| i < 0 = True
2358+
| otherwise = case A.index# aryA i of
2359+
(# stA #) -> case A.index# aryB i of
2360+
(# stB #) ->
2361+
disjointSubtrees (nextShift s) stA stB &&
2362+
go (i - 1)
2363+
disjointSubtrees s a@(Collision hA _) (BitmapIndexed bmB aryB)
2364+
| m .&. bmB == 0 = True
2365+
| otherwise = case A.index# aryB i of
2366+
(# stB #) -> disjointSubtrees (nextShift s) a stB
2367+
where
2368+
m = mask hA s
2369+
i = sparseIndex bmB m
2370+
disjointSubtrees s a@(Collision hA _) (Full aryB) =
2371+
case A.index# aryB (index hA s) of
2372+
(# stB #) -> disjointSubtrees (nextShift s) a stB
2373+
disjointSubtrees _ (Collision hA aryA) (Collision hB aryB) =
2374+
disjointCollisions hA aryA hB aryB
2375+
disjointSubtrees _s _a Empty = True
2376+
disjointSubtrees s a (Leaf hB (L kB _)) =
2377+
lookupCont (\_ -> True) (\_ _ -> False) hB kB s a
2378+
disjointSubtrees s a b@Collision{} = disjointSubtrees s b a
2379+
{-# INLINABLE disjointSubtrees #-}
2380+
2381+
disjointArrays :: Eq k => Shift -> Bitmap -> A.Array (HashMap k a) -> Bitmap -> A.Array (HashMap k b) -> Bool
2382+
disjointArrays !s !bmA !aryA !bmB !aryB = go (bmA .&. bmB)
2383+
where
2384+
go 0 = True
2385+
go bm = case A.index# aryA iA of
2386+
(# stA #) -> case A.index# aryB iB of
2387+
(# stB #) ->
2388+
disjointSubtrees (nextShift s) stA stB &&
2389+
go (bm .&. complement m)
2390+
where
2391+
m = bm .&. negate bm
2392+
iA = sparseIndex bmA m
2393+
iB = sparseIndex bmB m
2394+
{-# INLINE disjointArrays #-}
2395+
2396+
-- TODO: GHC 9.12.2 inlines disjointCollisions into `disjoint @Int`.
2397+
-- How do you prevent this while preserving specialization?
2398+
-- https://stackoverflow.com/questions/79838305/ensuring-specialization-while-preventing-inlining
2399+
disjointCollisions :: Eq k => Hash -> A.Array (Leaf k a) -> Hash -> A.Array (Leaf k b) -> Bool
2400+
disjointCollisions !hA !aryA !hB !aryB
2401+
| hA == hB = A.all predicate aryA
2402+
| otherwise = True
2403+
where
2404+
predicate (L kA _) = lookupInArrayCont (\_ -> True) (\_ _ -> False) kA aryB
2405+
{-# INLINABLE disjointCollisions #-}
2406+
23182407
------------------------------------------------------------------------
23192408
-- * Folds
23202409

@@ -2639,15 +2728,16 @@ lookupInArrayCont ::
26392728
forall r k v.
26402729
#endif
26412730
Eq k => ((# #) -> r) -> (v -> Int -> r) -> k -> A.Array (Leaf k v) -> r
2642-
lookupInArrayCont absent present k0 ary0 = go k0 ary0 0 (A.length ary0)
2731+
lookupInArrayCont absent present k0 ary0 =
2732+
lookupInArrayCont_ k0 ary0 0 (A.length ary0)
26432733
where
2644-
go :: Eq k => k -> A.Array (Leaf k v) -> Int -> Int -> r
2645-
go !k !ary !i !n
2734+
lookupInArrayCont_ :: Eq k => k -> A.Array (Leaf k v) -> Int -> Int -> r
2735+
lookupInArrayCont_ !k !ary !i !n
26462736
| i >= n = absent (# #)
26472737
| otherwise = case A.index# ary i of
26482738
(# L kx v #)
26492739
| k == kx -> present v i
2650-
| otherwise -> go k ary (i+1) n
2740+
| otherwise -> lookupInArrayCont_ k ary (i+1) n
26512741
{-# INLINE lookupInArrayCont #-}
26522742

26532743
-- | \(O(n)\) Lookup the value associated with the given key in this

Data/HashMap/Internal/Array.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -399,13 +399,13 @@ foldr' f = \ z0 ary0 -> go ary0 (length ary0 - 1) z0
399399
{-# INLINE foldr' #-}
400400

401401
foldr :: (a -> b -> b) -> b -> Array a -> b
402-
foldr f = \ z0 ary0 -> go ary0 (length ary0) 0 z0
402+
foldr f = \ z0 ary0 -> foldr_ ary0 (length ary0) 0 z0
403403
where
404-
go ary n i z
404+
foldr_ !ary n i z
405405
| i >= n = z
406406
| otherwise
407407
= case index# ary i of
408-
(# x #) -> f x (go ary n (i+1) z)
408+
(# x #) -> f x (foldr_ ary n (i+1) z)
409409
{-# INLINE foldr #-}
410410

411411
foldl :: (b -> a -> b) -> b -> Array a -> b

Data/HashMap/Internal/Strict.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ module Data.HashMap.Internal.Strict
9696
, HM.intersection
9797
, intersectionWith
9898
, intersectionWithKey
99+
, HM.disjoint
99100

100101
-- * Folds
101102
, HM.foldMapWithKey

Data/HashMap/Lazy.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ module Data.HashMap.Lazy
7676
, intersection
7777
, intersectionWith
7878
, intersectionWithKey
79+
, disjoint
7980

8081
-- * Folds
8182
, foldMapWithKey

Data/HashMap/Strict.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ module Data.HashMap.Strict
7575
, intersection
7676
, intersectionWith
7777
, intersectionWithKey
78+
, disjoint
7879

7980
-- * Folds
8081
, foldMapWithKey

Data/HashSet.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ module Data.HashSet
118118
-- * Difference and intersection
119119
, difference
120120
, intersection
121+
, disjoint
121122

122123
-- * Folds
123124
, foldl'

Data/HashSet/Internal.hs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ module Data.HashSet.Internal
6767
-- * Difference and intersection
6868
, difference
6969
, intersection
70+
, disjoint
7071

7172
-- * Folds
7273
, foldr
@@ -404,6 +405,18 @@ intersection :: Eq a => HashSet a -> HashSet a -> HashSet a
404405
intersection (HashSet a) (HashSet b) = HashSet (H.intersection a b)
405406
{-# INLINABLE intersection #-}
406407

408+
-- | \(O(n \log m)\) Check whether two sets are disjoint (i.e., their
409+
-- intersection is empty).
410+
--
411+
-- @
412+
-- xs ``disjoint`` ys = null (xs ``intersection`` ys)
413+
-- @
414+
--
415+
-- @since FIXME
416+
disjoint :: Eq k => HashSet k -> HashSet k -> Bool
417+
disjoint (HashSet a) (HashSet b) = H.disjoint a b
418+
{-# INLINE disjoint #-}
419+
407420
-- | \(O(n)\) Reduce this set by applying a binary operator to all
408421
-- elements, using the given starting value (typically the
409422
-- left-identity of the operator). Each application of the operator

tests/Properties/HashMapLazy.hs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,11 @@ tests =
357357
\(Fn3 f :: Fun (Key, A, B) C) (x :: HMK A) (y :: HMK B) ->
358358
isValid (HM.intersectionWithKey f x y)
359359
]
360+
, testGroup "disjoint"
361+
[ testProperty "model" $
362+
\(x :: HMKI) (y :: HMKI) ->
363+
HM.disjoint x y === M.disjoint (toOrdMap x) (toOrdMap y)
364+
]
360365
, testGroup "compose"
361366
[ testProperty "valid" $
362367
\(x :: HMK Int) (y :: HMK Key) -> isValid (HM.compose x y)

0 commit comments

Comments
 (0)