From 03c90603a3e5e115257a69075fec384f1cd0fc40 Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Fri, 24 Oct 2025 02:44:14 +0200 Subject: [PATCH 01/24] WIP: Tree-diffing `difference` --- Data/HashMap/Internal.hs | 75 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 5 deletions(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index e5b732cd..5bdf875e 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1102,7 +1102,10 @@ delete k m = delete' (hash k) k m {-# INLINABLE delete #-} delete' :: Eq k => Hash -> k -> HashMap k v -> HashMap k v -delete' h0 k0 m0 = go h0 k0 0 m0 +delete' h0 k0 m0 = delete'' h0 k0 0 m0 + +delete'' :: Eq k => Hash -> k -> Shift -> HashMap k v -> HashMap k v +delete'' = go where go !_ !_ !_ Empty = Empty go h k _ t@(Leaf hy (L ky _)) @@ -1781,13 +1784,75 @@ mapKeys f = fromList . foldrWithKey (\k x xs -> (f k, x) : xs) [] -- | \(O(n \log m)\) Difference of two maps. Return elements of the first map -- not existing in the second. difference :: (Eq k, Hashable k) => HashMap k v -> HashMap k w -> HashMap k v -difference a b = foldlWithKey' go empty a +difference = go 0 where - go m k v = case lookup k b of - Nothing -> unsafeInsert k v m - _ -> m + go !_s Empty !_ = Empty + go s t1@(Leaf h1 (L k1 _)) t2 = lookupCont (\_ -> t1) (\_ _ -> Empty) h1 k1 s t2 + go _ t1 Empty = t1 + go s t1 (Leaf h2 (L k2 _)) = delete'' h2 k2 s t1 + + go s t1@(BitmapIndexed b1 ary1) (BitmapIndexed b2 ary2) = differenceArrays go s b1 ary1 t1 b2 ary2 + go s t1@(Full ary1) (BitmapIndexed b2 ary2) = differenceArrays go s fullBitmap ary1 t1 b2 ary2 + go s t1@(BitmapIndexed b1 ary1) (Full ary2) = differenceArrays go s b1 ary1 t1 fullBitmap ary2 + go s t1@(Full ary1) (Full ary2) = differenceArrays go s fullBitmap ary1 t1 fullBitmap ary2 + + go s t1@(Collision h1 _) (BitmapIndexed b2 ary2) + | b2 .&. m == 0 = Empty + | otherwise = go (nextShift s) t1 (A.index ary2 (sparseIndex b2 m)) + where m = mask h1 s + go s (BitmapIndexed b1 ary1) t2@(Collision h2 _) + | b1 .&. m == 0 = Empty + | otherwise = go (nextShift s) (A.index ary1 (sparseIndex b1 m)) t2 + where m = mask h2 s + go s t1@(Collision h1 _) (Full ary2) + = go (nextShift s) t1 (A.index ary2 (index h1 s)) + go s (Full ary1) t2@(Collision h2 _) + = go (nextShift s) (A.index ary1 (index h2 s)) t2 + + go _ (Collision h1 ary1) (Collision h2 ary2) = differenceCollisions h1 ary1 h2 ary2 {-# INLINABLE difference #-} +differenceArrays :: (Shift -> HashMap k1 v1 -> HashMap k1 v2 -> HashMap k1 v1) -> Shift -> Bitmap -> A.Array (HashMap k1 v1) -> HashMap k1 v1 -> Bitmap -> A.Array (HashMap k1 v2) -> HashMap k1 v1 +differenceArrays diff s b1 ary1 t1 b2 ary2 + | b1 .&. b2 == 0 = t1 + | b1 == b2 && A.unsafeSameArray ary1 ary2 = Empty + | otherwise = runST $ undefined +{- + mary <- A.new_ $ popCount bIntersect + -- iterate over nonzero bits of b1 .|. b2 + let go !i !i1 !i2 !b !bFinal + | b == 0 = pure (i, bFinal) + | testBit $ b1 .&. b2 = do + x1 <- A.indexM ary1 i1 + x2 <- A.indexM ary2 i2 + case f x1 x2 of + Empty -> go i (i1 + 1) (i2 + 1) b' (bFinal .&. complement m) + _ -> do + A.write mary i $! f x1 x2 + go (i + 1) (i1 + 1) (i2 + 1) b' bFinal + | testBit b1 = go i (i1 + 1) i2 b' bFinal + | otherwise = go i i1 (i2 + 1) b' bFinal + where + m = 1 `unsafeShiftL` countTrailingZeros b + testBit x = x .&. m /= 0 + b' = b .&. complement m + (len, bFinal) <- go 0 0 0 bCombined bIntersect + case len of + 0 -> pure Empty + 1 -> do + l <- A.read mary 0 + if isLeafOrCollision l + then pure l + else BitmapIndexed bFinal <$> (A.unsafeFreeze =<< A.shrink mary 1) + _ -> bitmapIndexedOrFull bFinal <$> (A.unsafeFreeze =<< A.shrink mary len) + where + bCombined = b1 .|. b2 + bIntersect = b1 .&. b2 +-} + +differenceCollisions :: Hash -> A.Array (Leaf k1 v1) -> Hash -> A.Array (Leaf k1 v2) -> HashMap k1 v1 +differenceCollisions = undefined + -- | \(O(n \log m)\) Difference with a combining function. When two equal keys are -- encountered, the combining function is applied to the values of these keys. -- If it returns 'Nothing', the element is discarded (proper set difference). If From a0c47c5ae0f09ca624a5eb6c270e86ba48cd501a Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Fri, 24 Oct 2025 16:15:01 +0200 Subject: [PATCH 02/24] Progress on differenceArrays --- Data/HashMap/Internal.hs | 66 +++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index 5bdf875e..f7e8dc60 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1815,40 +1815,42 @@ difference = go 0 differenceArrays :: (Shift -> HashMap k1 v1 -> HashMap k1 v2 -> HashMap k1 v1) -> Shift -> Bitmap -> A.Array (HashMap k1 v1) -> HashMap k1 v1 -> Bitmap -> A.Array (HashMap k1 v2) -> HashMap k1 v1 differenceArrays diff s b1 ary1 t1 b2 ary2 | b1 .&. b2 == 0 = t1 - | b1 == b2 && A.unsafeSameArray ary1 ary2 = Empty - | otherwise = runST $ undefined -{- - mary <- A.new_ $ popCount bIntersect - -- iterate over nonzero bits of b1 .|. b2 - let go !i !i1 !i2 !b !bFinal - | b == 0 = pure (i, bFinal) - | testBit $ b1 .&. b2 = do - x1 <- A.indexM ary1 i1 - x2 <- A.indexM ary2 i2 - case f x1 x2 of - Empty -> go i (i1 + 1) (i2 + 1) b' (bFinal .&. complement m) + | {- b1 == b2 && -} A.unsafeSameArray ary1 ary2 = Empty + | otherwise = runST $ do + mary <- A.new_ $ A.length ary1 + + let go !i !i1 !b1' !bResult !sameAs1 + | b1' == 0 = pure (bResult, sameAs1) + | otherwise = do + !st1 <- A.indexM ary1 i1 + case m .&. b2 of + 0 -> do + A.write mary i st1 + go (i + 1) (i1 + 1) nextB1' (bResult .|. m) sameAs1 _ -> do - A.write mary i $! f x1 x2 - go (i + 1) (i1 + 1) (i2 + 1) b' bFinal - | testBit b1 = go i (i1 + 1) i2 b' bFinal - | otherwise = go i i1 (i2 + 1) b' bFinal + !st2 <- A.indexM ary2 (sparseIndex b2 m) + case diff (nextShift s) st1 st2 of + Empty -> go i (i1 + 1) nextB1' bResult False + st -> do + A.write mary i st + let same = st `ptrEq` st1 + go (i + 1) (i1 + 1) nextB1' (bResult .|. m) (sameAs1 && same) where - m = 1 `unsafeShiftL` countTrailingZeros b - testBit x = x .&. m /= 0 - b' = b .&. complement m - (len, bFinal) <- go 0 0 0 bCombined bIntersect - case len of - 0 -> pure Empty - 1 -> do - l <- A.read mary 0 - if isLeafOrCollision l - then pure l - else BitmapIndexed bFinal <$> (A.unsafeFreeze =<< A.shrink mary 1) - _ -> bitmapIndexedOrFull bFinal <$> (A.unsafeFreeze =<< A.shrink mary len) - where - bCombined = b1 .|. b2 - bIntersect = b1 .&. b2 --} + m = b1' .&. negate b1' + nextB1' = b1' .&. complement m + + (bFinal, sameAs1) <- go 0 0 b1 0 True -- FIXME: Does this allocate a tuple? + if sameAs1 + then pure t1 + else case popCount bFinal of + 0 -> pure Empty + 1 -> do + l <- A.read mary 0 + if isLeafOrCollision l + then pure l + else BitmapIndexed bFinal <$> (A.unsafeFreeze =<< A.shrink mary 1) + n -> bitmapIndexedOrFull bFinal <$> (A.unsafeFreeze =<< A.shrink mary n) +{-# INLINABLE differenceArrays #-} differenceCollisions :: Hash -> A.Array (Leaf k1 v1) -> Hash -> A.Array (Leaf k1 v2) -> HashMap k1 v1 differenceCollisions = undefined From 35b4184860cc02938069a48048c27ea1205c9344 Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Fri, 24 Oct 2025 17:12:34 +0200 Subject: [PATCH 03/24] Add comment --- Data/HashMap/Internal.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index f7e8dc60..51e8faa8 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1819,6 +1819,7 @@ differenceArrays diff s b1 ary1 t1 b2 ary2 | otherwise = runST $ do mary <- A.new_ $ A.length ary1 + -- TODO: i == popCount bResult. Not sure if that would be faster. let go !i !i1 !b1' !bResult !sameAs1 | b1' == 0 = pure (bResult, sameAs1) | otherwise = do From ac613a3903b8e9761d95d0b837b4b3715246d35b Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Fri, 24 Oct 2025 17:13:11 +0200 Subject: [PATCH 04/24] Naming --- Data/HashMap/Internal.hs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index 51e8faa8..0a5be29f 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1840,17 +1840,17 @@ differenceArrays diff s b1 ary1 t1 b2 ary2 m = b1' .&. negate b1' nextB1' = b1' .&. complement m - (bFinal, sameAs1) <- go 0 0 b1 0 True -- FIXME: Does this allocate a tuple? + (bResult, sameAs1) <- go 0 0 b1 0 True -- FIXME: Does this allocate a tuple? if sameAs1 then pure t1 - else case popCount bFinal of + else case popCount bResult of 0 -> pure Empty 1 -> do l <- A.read mary 0 if isLeafOrCollision l then pure l - else BitmapIndexed bFinal <$> (A.unsafeFreeze =<< A.shrink mary 1) - n -> bitmapIndexedOrFull bFinal <$> (A.unsafeFreeze =<< A.shrink mary n) + else BitmapIndexed bResult <$> (A.unsafeFreeze =<< A.shrink mary 1) + n -> bitmapIndexedOrFull bResult <$> (A.unsafeFreeze =<< A.shrink mary n) {-# INLINABLE differenceArrays #-} differenceCollisions :: Hash -> A.Array (Leaf k1 v1) -> Hash -> A.Array (Leaf k1 v2) -> HashMap k1 v1 From b067be0284928d5d6e5a27d55c2408e729559b4d Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Fri, 24 Oct 2025 17:13:18 +0200 Subject: [PATCH 05/24] WIP: differenceCollisions --- Data/HashMap/Internal.hs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index 0a5be29f..4e5ecc6f 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1853,8 +1853,17 @@ differenceArrays diff s b1 ary1 t1 b2 ary2 n -> bitmapIndexedOrFull bResult <$> (A.unsafeFreeze =<< A.shrink mary n) {-# INLINABLE differenceArrays #-} -differenceCollisions :: Hash -> A.Array (Leaf k1 v1) -> Hash -> A.Array (Leaf k1 v2) -> HashMap k1 v1 -differenceCollisions = undefined +differenceCollisions :: Hash -> A.Array (Leaf k v1) -> HashMap k v2 -> Hash -> A.Array (Leaf k v2) -> HashMap k v1 +differenceCollisions h1 ary1 t1 h2 ary2 + | h1 == h2 = + let ary = A.filter (\(L k1 _) -> isJust (indexOf k1 ary2)) ary1 + in case A.length ary of + 0 -> Empty + 1 -> Leaf h1 (A.index 0 ary) + n | A.length ary1 == n -> t1 + | otherwise -> Collision h1 ary + | otherwise = t1 +{-# INLINABLE differenceCollisions #-} -- | \(O(n \log m)\) Difference with a combining function. When two equal keys are -- encountered, the combining function is applied to the values of these keys. From 5d54f661b96429fe15cf37905ebcbc6ffc69930e Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Fri, 24 Oct 2025 17:27:26 +0200 Subject: [PATCH 06/24] WIP --- Data/HashMap/Internal.hs | 7 ++++--- Data/HashMap/Internal/Array.hs | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index 4e5ecc6f..a4384d19 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -163,6 +163,7 @@ import Data.Functor.Identity (Identity (..)) import Data.Hashable (Hashable) import Data.Hashable.Lifted (Hashable1, Hashable2) import Data.HashMap.Internal.List (isPermutationBy, unorderedCompare) +import Data.Maybe (isJust) import Data.Semigroup (Semigroup (..), stimesIdempotentMonoid) import GHC.Exts (Int (..), Int#, TYPE, (==#)) import GHC.Stack (HasCallStack) @@ -1809,7 +1810,7 @@ difference = go 0 go s (Full ary1) t2@(Collision h2 _) = go (nextShift s) (A.index ary1 (index h2 s)) t2 - go _ (Collision h1 ary1) (Collision h2 ary2) = differenceCollisions h1 ary1 h2 ary2 + go _ t1@(Collision h1 ary1) (Collision h2 ary2) = differenceCollisions h1 ary1 t1 h2 ary2 {-# INLINABLE difference #-} differenceArrays :: (Shift -> HashMap k1 v1 -> HashMap k1 v2 -> HashMap k1 v1) -> Shift -> Bitmap -> A.Array (HashMap k1 v1) -> HashMap k1 v1 -> Bitmap -> A.Array (HashMap k1 v2) -> HashMap k1 v1 @@ -1853,13 +1854,13 @@ differenceArrays diff s b1 ary1 t1 b2 ary2 n -> bitmapIndexedOrFull bResult <$> (A.unsafeFreeze =<< A.shrink mary n) {-# INLINABLE differenceArrays #-} -differenceCollisions :: Hash -> A.Array (Leaf k v1) -> HashMap k v2 -> Hash -> A.Array (Leaf k v2) -> HashMap k v1 +differenceCollisions :: Eq k => Hash -> A.Array (Leaf k v1) -> HashMap k v1 -> Hash -> A.Array (Leaf k v2) -> HashMap k v1 differenceCollisions h1 ary1 t1 h2 ary2 | h1 == h2 = let ary = A.filter (\(L k1 _) -> isJust (indexOf k1 ary2)) ary1 in case A.length ary of 0 -> Empty - 1 -> Leaf h1 (A.index 0 ary) + 1 -> Leaf h1 (A.index ary 0) n | A.length ary1 == n -> t1 | otherwise -> Collision h1 ary | otherwise = t1 diff --git a/Data/HashMap/Internal/Array.hs b/Data/HashMap/Internal/Array.hs index 3e47b0c4..e09a5802 100644 --- a/Data/HashMap/Internal/Array.hs +++ b/Data/HashMap/Internal/Array.hs @@ -72,6 +72,7 @@ module Data.HashMap.Internal.Array , thaw , map , map' + , filter , traverse , traverse' , toList @@ -496,6 +497,24 @@ map' f = \ ary -> go ary mary (i+1) n {-# INLINE map' #-} +filter :: (a -> Bool) -> Array a -> Array a +filter f = \ ary -> + let !n = length ary + in run $ do + mary <- new_ n + len <- go ary mary 0 0 n + shrink mary len + where + go ary mary iAry iMary n + | iAry >= n = return iMary + | otherwise = do + x <- indexM ary iAry + if f x + then do + write mary iMary x + go ary mary (iAry + 1) (iMary + 1) n + else go ary mary (iAry + 1) iMary n + fromList :: Int -> [a] -> Array a fromList n xs0 = CHECK_EQ("fromList", n, Prelude.length xs0) From 894a90b0a190766481566227db977c73e72df840 Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Fri, 24 Oct 2025 17:31:01 +0200 Subject: [PATCH 07/24] Comment --- Data/HashMap/Internal/Array.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/Data/HashMap/Internal/Array.hs b/Data/HashMap/Internal/Array.hs index e09a5802..601a6937 100644 --- a/Data/HashMap/Internal/Array.hs +++ b/Data/HashMap/Internal/Array.hs @@ -497,6 +497,7 @@ map' f = \ ary -> go ary mary (i+1) n {-# INLINE map' #-} +-- TODO: Should this function return the old array when the length is unchanged? filter :: (a -> Bool) -> Array a -> Array a filter f = \ ary -> let !n = length ary From bf7c61f49614c5684c0d57bed4fdecd5f9aa60f4 Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Fri, 24 Oct 2025 18:34:45 +0200 Subject: [PATCH 08/24] Fix some bugs --- Data/HashMap/Internal.hs | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index a4384d19..b84d00df 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -163,7 +163,7 @@ import Data.Functor.Identity (Identity (..)) import Data.Hashable (Hashable) import Data.Hashable.Lifted (Hashable1, Hashable2) import Data.HashMap.Internal.List (isPermutationBy, unorderedCompare) -import Data.Maybe (isJust) +import Data.Maybe (isNothing) import Data.Semigroup (Semigroup (..), stimesIdempotentMonoid) import GHC.Exts (Int (..), Int#, TYPE, (==#)) import GHC.Stack (HasCallStack) @@ -1798,16 +1798,32 @@ difference = go 0 go s t1@(Full ary1) (Full ary2) = differenceArrays go s fullBitmap ary1 t1 fullBitmap ary2 go s t1@(Collision h1 _) (BitmapIndexed b2 ary2) - | b2 .&. m == 0 = Empty + | b2 .&. m == 0 = t1 | otherwise = go (nextShift s) t1 (A.index ary2 (sparseIndex b2 m)) where m = mask h1 s - go s (BitmapIndexed b1 ary1) t2@(Collision h2 _) - | b1 .&. m == 0 = Empty - | otherwise = go (nextShift s) (A.index ary1 (sparseIndex b1 m)) t2 - where m = mask h2 s + go s t1@(BitmapIndexed b1 ary1) t2@(Collision h2 _) + | b1 .&. m == 0 = t1 + | otherwise = + let !st = A.index ary1 i1 + in case go (nextShift s) st t2 of + Empty | A.length ary1 == 1 -> Empty -- impossible?! + | A.length ary1 == 2 -> + case (i1, A.index ary1 0, A.index ary1 1) of + (0, _, l) | isLeafOrCollision l -> l + (1, l, _) | isLeafOrCollision l -> l + _ -> bIndexed + | otherwise -> bIndexed + where + bIndexed = BitmapIndexed (b1 .&. complement m) (A.delete ary1 i1) + l | isLeafOrCollision l && A.length ary1 == 1 -> l + st' | st `ptrEq` st' -> t1 + | otherwise -> BitmapIndexed b1 (A.update ary1 i1 st') + where + m = mask h2 s + i1 = sparseIndex b1 m go s t1@(Collision h1 _) (Full ary2) = go (nextShift s) t1 (A.index ary2 (index h1 s)) - go s (Full ary1) t2@(Collision h2 _) + go s (Full ary1) t2@(Collision h2 _) -- BUG = go (nextShift s) (A.index ary1 (index h2 s)) t2 go _ t1@(Collision h1 ary1) (Collision h2 ary2) = differenceCollisions h1 ary1 t1 h2 ary2 @@ -1857,7 +1873,7 @@ differenceArrays diff s b1 ary1 t1 b2 ary2 differenceCollisions :: Eq k => Hash -> A.Array (Leaf k v1) -> HashMap k v1 -> Hash -> A.Array (Leaf k v2) -> HashMap k v1 differenceCollisions h1 ary1 t1 h2 ary2 | h1 == h2 = - let ary = A.filter (\(L k1 _) -> isJust (indexOf k1 ary2)) ary1 + let ary = A.filter (\(L k1 _) -> isNothing (indexOf k1 ary2)) ary1 in case A.length ary of 0 -> Empty 1 -> Leaf h1 (A.index ary 0) From 36453d7e5c4b2571c785f763a6ad3bf2262e268a Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Fri, 24 Oct 2025 18:53:07 +0200 Subject: [PATCH 09/24] Fix other bug --- Data/HashMap/Internal.hs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index b84d00df..b78edfb3 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1823,8 +1823,16 @@ difference = go 0 i1 = sparseIndex b1 m go s t1@(Collision h1 _) (Full ary2) = go (nextShift s) t1 (A.index ary2 (index h1 s)) - go s (Full ary1) t2@(Collision h2 _) -- BUG - = go (nextShift s) (A.index ary1 (index h2 s)) t2 + go s t1@(Full ary1) t2@(Collision h2 _) + = let !st = A.index ary1 i + in case go (nextShift s) st t2 of + Empty -> + let ary1' = A.delete ary1 i + bm = fullBitmap .&. complement (1 `unsafeShiftL` i) + in BitmapIndexed bm ary1' + st' | st `ptrEq` st' -> t1 + | otherwise -> Full (A.update ary1 i st') + where i = index h2 s go _ t1@(Collision h1 ary1) (Collision h2 ary2) = differenceCollisions h1 ary1 t1 h2 ary2 {-# INLINABLE difference #-} From 666172b615dcc53a4435db9b77511af4318d5b1e Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Fri, 24 Oct 2025 18:53:19 +0200 Subject: [PATCH 10/24] Comment out dead alternative --- Data/HashMap/Internal.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index b78edfb3..b4a73337 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1806,7 +1806,7 @@ difference = go 0 | otherwise = let !st = A.index ary1 i1 in case go (nextShift s) st t2 of - Empty | A.length ary1 == 1 -> Empty -- impossible?! + Empty {- | A.length ary1 == 1 -> Empty -- Impossible! -} | A.length ary1 == 2 -> case (i1, A.index ary1 0, A.index ary1 1) of (0, _, l) | isLeafOrCollision l -> l From 29fb432fe5db3f03232dc9db70944fc872282715 Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Fri, 24 Oct 2025 18:57:48 +0200 Subject: [PATCH 11/24] Formatting --- Data/HashMap/Internal.hs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index b4a73337..6472362f 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1792,15 +1792,22 @@ difference = go 0 go _ t1 Empty = t1 go s t1 (Leaf h2 (L k2 _)) = delete'' h2 k2 s t1 - go s t1@(BitmapIndexed b1 ary1) (BitmapIndexed b2 ary2) = differenceArrays go s b1 ary1 t1 b2 ary2 - go s t1@(Full ary1) (BitmapIndexed b2 ary2) = differenceArrays go s fullBitmap ary1 t1 b2 ary2 - go s t1@(BitmapIndexed b1 ary1) (Full ary2) = differenceArrays go s b1 ary1 t1 fullBitmap ary2 - go s t1@(Full ary1) (Full ary2) = differenceArrays go s fullBitmap ary1 t1 fullBitmap ary2 + go s t1@(BitmapIndexed b1 ary1) (BitmapIndexed b2 ary2) + = differenceArrays go s b1 ary1 t1 b2 ary2 + go s t1@(Full ary1) (BitmapIndexed b2 ary2) + = differenceArrays go s fullBitmap ary1 t1 b2 ary2 + go s t1@(BitmapIndexed b1 ary1) (Full ary2) + = differenceArrays go s b1 ary1 t1 fullBitmap ary2 + go s t1@(Full ary1) (Full ary2) + = differenceArrays go s fullBitmap ary1 t1 fullBitmap ary2 go s t1@(Collision h1 _) (BitmapIndexed b2 ary2) | b2 .&. m == 0 = t1 | otherwise = go (nextShift s) t1 (A.index ary2 (sparseIndex b2 m)) where m = mask h1 s + go s t1@(Collision h1 _) (Full ary2) + = go (nextShift s) t1 (A.index ary2 (index h1 s)) + go s t1@(BitmapIndexed b1 ary1) t2@(Collision h2 _) | b1 .&. m == 0 = t1 | otherwise = @@ -1821,8 +1828,6 @@ difference = go 0 where m = mask h2 s i1 = sparseIndex b1 m - go s t1@(Collision h1 _) (Full ary2) - = go (nextShift s) t1 (A.index ary2 (index h1 s)) go s t1@(Full ary1) t2@(Collision h2 _) = let !st = A.index ary1 i in case go (nextShift s) st t2 of From 78806a61751c1fc8265eeca7c3798a04401dce0e Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Fri, 24 Oct 2025 19:09:06 +0200 Subject: [PATCH 12/24] Wibble --- Data/HashMap/Internal.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index 6472362f..1c729956 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1822,8 +1822,8 @@ difference = go 0 | otherwise -> bIndexed where bIndexed = BitmapIndexed (b1 .&. complement m) (A.delete ary1 i1) - l | isLeafOrCollision l && A.length ary1 == 1 -> l - st' | st `ptrEq` st' -> t1 + st' | isLeafOrCollision st' && A.length ary1 == 1 -> st' + | st `ptrEq` st' -> t1 | otherwise -> BitmapIndexed b1 (A.update ary1 i1 st') where m = mask h2 s From c4fa3919326916eabed20806e873967cea4008d0 Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Fri, 24 Oct 2025 19:14:01 +0200 Subject: [PATCH 13/24] Remove Hashable constraint --- Data/HashMap/Internal.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index 1c729956..0cf8dda2 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1784,7 +1784,7 @@ mapKeys f = fromList . foldrWithKey (\k x xs -> (f k, x) : xs) [] -- | \(O(n \log m)\) Difference of two maps. Return elements of the first map -- not existing in the second. -difference :: (Eq k, Hashable k) => HashMap k v -> HashMap k w -> HashMap k v +difference :: Eq k => HashMap k v -> HashMap k w -> HashMap k v difference = go 0 where go !_s Empty !_ = Empty From bd31e60b28bda61bd9ae2b8c3a7e1942bd3af98b Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Fri, 24 Oct 2025 19:14:09 +0200 Subject: [PATCH 14/24] Add comment --- Data/HashMap/Internal.hs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index 0cf8dda2..646ac6e4 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1883,6 +1883,9 @@ differenceArrays diff s b1 ary1 t1 b2 ary2 n -> bitmapIndexedOrFull bResult <$> (A.unsafeFreeze =<< A.shrink mary n) {-# INLINABLE differenceArrays #-} +-- TODO: This could be faster if we would keep track of which elements of ary2 +-- we've already matched. Those could be skipped when we check the following +-- elements of ary1. differenceCollisions :: Eq k => Hash -> A.Array (Leaf k v1) -> HashMap k v1 -> Hash -> A.Array (Leaf k v2) -> HashMap k v1 differenceCollisions h1 ary1 t1 h2 ary2 | h1 == h2 = From 797d69943ae14f06e45ad82aca3b27ce8a3475b6 Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Fri, 24 Oct 2025 21:04:12 +0200 Subject: [PATCH 15/24] Small fixes and comments --- Data/HashMap/Internal.hs | 36 +++++++++++++++++++++++++++++++--- Data/HashMap/Internal/Array.hs | 1 + 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index 646ac6e4..12d7b1ac 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1787,6 +1787,23 @@ mapKeys f = fromList . foldrWithKey (\k x xs -> (f k, x) : xs) [] difference :: Eq k => HashMap k v -> HashMap k w -> HashMap k v difference = go 0 where +{- Somehow we get repeated "cases of" on the Hashmap arguments: + + $wgo1 + = \ (ww :: Int#) (ds :: HashMap k v) (ds1 :: HashMap k w) -> + case ds of wild { + __DEFAULT -> + case ds1 of wild1 { + __DEFAULT -> + case wild of wild2 { + BitmapIndexed bx bx1 -> + case wild1 of { + BitmapIndexed bx2 bx3 -> + +Maybe don't force the first !_?! + +Or maybe this helps avoid more evaluations later on? (Check Cmm) +-} go !_s Empty !_ = Empty go s t1@(Leaf h1 (L k1 _)) t2 = lookupCont (\_ -> t1) (\_ _ -> Empty) h1 k1 s t2 go _ t1 Empty = t1 @@ -1836,20 +1853,30 @@ difference = go 0 bm = fullBitmap .&. complement (1 `unsafeShiftL` i) in BitmapIndexed bm ary1' st' | st `ptrEq` st' -> t1 + -- TODO: Should probably use updateFullArray + -- (and in other places too!) | otherwise -> Full (A.update ary1 i st') where i = index h2 s - go _ t1@(Collision h1 ary1) (Collision h2 ary2) = differenceCollisions h1 ary1 t1 h2 ary2 + -- TODO: Why does $wdifferenceCollisions appear three times in the Core + -- for difference, and not just once? + go _ t1@(Collision h1 ary1) (Collision h2 ary2) + = differenceCollisions h1 ary1 t1 h2 ary2 {-# INLINABLE difference #-} differenceArrays :: (Shift -> HashMap k1 v1 -> HashMap k1 v2 -> HashMap k1 v1) -> Shift -> Bitmap -> A.Array (HashMap k1 v1) -> HashMap k1 v1 -> Bitmap -> A.Array (HashMap k1 v2) -> HashMap k1 v1 -differenceArrays diff s b1 ary1 t1 b2 ary2 +differenceArrays diff !s !b1 !ary1 !t1 !b2 !ary2 | b1 .&. b2 == 0 = t1 | {- b1 == b2 && -} A.unsafeSameArray ary1 ary2 = Empty | otherwise = runST $ do mary <- A.new_ $ A.length ary1 -- TODO: i == popCount bResult. Not sure if that would be faster. + -- Also i1 is in some relation with b1' + -- + -- TODO: Depending on sameAs1 the Core contains jumps to either + -- $s$wgo or $s$wgo1. Maybe it would be better to keep track of + -- the "sameness" as an Int?! let go !i !i1 !b1' !bResult !sameAs1 | b1' == 0 = pure (bResult, sameAs1) | otherwise = do @@ -1886,9 +1913,12 @@ differenceArrays diff s b1 ary1 t1 b2 ary2 -- TODO: This could be faster if we would keep track of which elements of ary2 -- we've already matched. Those could be skipped when we check the following -- elements of ary1. +-- +-- TODO: Get ary1 unboxed somehow?! differenceCollisions :: Eq k => Hash -> A.Array (Leaf k v1) -> HashMap k v1 -> Hash -> A.Array (Leaf k v2) -> HashMap k v1 -differenceCollisions h1 ary1 t1 h2 ary2 +differenceCollisions !h1 !ary1 t1 !h2 !ary2 | h1 == h2 = + -- TODO: This actually allocates Maybes! let ary = A.filter (\(L k1 _) -> isNothing (indexOf k1 ary2)) ary1 in case A.length ary of 0 -> Empty diff --git a/Data/HashMap/Internal/Array.hs b/Data/HashMap/Internal/Array.hs index 601a6937..a27dc4a4 100644 --- a/Data/HashMap/Internal/Array.hs +++ b/Data/HashMap/Internal/Array.hs @@ -515,6 +515,7 @@ filter f = \ ary -> write mary iMary x go ary mary (iAry + 1) (iMary + 1) n else go ary mary (iAry + 1) iMary n +-- TODO: This should probably be inlined fromList :: Int -> [a] -> Array a fromList n xs0 = From 5249e70573a587382d3ea46a176ce5e8c171eda4 Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Sat, 25 Oct 2025 09:35:49 +0200 Subject: [PATCH 16/24] Use updateFullArray where possible --- Data/HashMap/Internal.hs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index 12d7b1ac..49df6c3f 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1143,7 +1143,7 @@ delete'' = go let ary' = A.delete ary i bm = fullBitmap .&. complement (1 `unsafeShiftL` i) in BitmapIndexed bm ary' - _ -> Full (A.update ary i st') + _ -> Full (updateFullArray ary i st') where i = index h s go h k _ t@(Collision hy v) | h == hy = case indexOf k v of @@ -1192,7 +1192,7 @@ deleteKeyExists !collPos0 !h0 !k0 !m0 = go collPos0 h0 k0 m0 let ary' = A.delete ary i bm = fullBitmap .&. complement (1 `unsafeShiftL` i) in BitmapIndexed bm ary' - _ -> Full (A.update ary i st') + _ -> Full (updateFullArray ary i st') where i = indexSH shiftedHash go collPos _shiftedHash _k (Collision h v) | A.length v == 2 @@ -1853,9 +1853,7 @@ Or maybe this helps avoid more evaluations later on? (Check Cmm) bm = fullBitmap .&. complement (1 `unsafeShiftL` i) in BitmapIndexed bm ary1' st' | st `ptrEq` st' -> t1 - -- TODO: Should probably use updateFullArray - -- (and in other places too!) - | otherwise -> Full (A.update ary1 i st') + | otherwise -> Full (updateFullArray ary1 i st') where i = index h2 s -- TODO: Why does $wdifferenceCollisions appear three times in the Core From 88209acf5a5cc287f6bc3bb04487f78e7d5004bd Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Sat, 25 Oct 2025 09:36:02 +0200 Subject: [PATCH 17/24] Formatting --- Data/HashMap/Internal.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index 49df6c3f..9c44c2e0 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1805,7 +1805,8 @@ Maybe don't force the first !_?! Or maybe this helps avoid more evaluations later on? (Check Cmm) -} go !_s Empty !_ = Empty - go s t1@(Leaf h1 (L k1 _)) t2 = lookupCont (\_ -> t1) (\_ _ -> Empty) h1 k1 s t2 + go s t1@(Leaf h1 (L k1 _)) t2 + = lookupCont (\_ -> t1) (\_ _ -> Empty) h1 k1 s t2 go _ t1 Empty = t1 go s t1 (Leaf h2 (L k2 _)) = delete'' h2 k2 s t1 From 13cf3080dc5e96e64d2e24d9d6895ca360c9cb8d Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Sat, 25 Oct 2025 09:37:00 +0200 Subject: [PATCH 18/24] INLINE A.filter --- Data/HashMap/Internal/Array.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Data/HashMap/Internal/Array.hs b/Data/HashMap/Internal/Array.hs index a27dc4a4..a3a0cff2 100644 --- a/Data/HashMap/Internal/Array.hs +++ b/Data/HashMap/Internal/Array.hs @@ -515,7 +515,7 @@ filter f = \ ary -> write mary iMary x go ary mary (iAry + 1) (iMary + 1) n else go ary mary (iAry + 1) iMary n --- TODO: This should probably be inlined +{-# INLINE filter #-} fromList :: Int -> [a] -> Array a fromList n xs0 = From df285a44f2e71f2efc8e654cf14b8401f3890e1e Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Sat, 25 Oct 2025 19:32:32 +0200 Subject: [PATCH 19/24] Array.shrink: Allow shrinking to length 0 At least in the context of `Array.filter` this seems useful and valid. --- Data/HashMap/Internal/Array.hs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Data/HashMap/Internal/Array.hs b/Data/HashMap/Internal/Array.hs index a3a0cff2..822789a1 100644 --- a/Data/HashMap/Internal/Array.hs +++ b/Data/HashMap/Internal/Array.hs @@ -114,12 +114,14 @@ import qualified Prelude if (_k_) < 0 || (_k_) >= (_len_) then error ("Data.HashMap.Internal.Array." ++ (_func_) ++ ": bounds error, offset " ++ show (_k_) ++ ", length " ++ show (_len_)) else # define CHECK_OP(_func_,_op_,_lhs_,_rhs_) \ if not ((_lhs_) _op_ (_rhs_)) then error ("Data.HashMap.Internal.Array." ++ (_func_) ++ ": Check failed: _lhs_ _op_ _rhs_ (" ++ show (_lhs_) ++ " vs. " ++ show (_rhs_) ++ ")") else +# define CHECK_GE(_func_,_lhs_,_rhs_) CHECK_OP(_func_,>=,_lhs_,_rhs_) # define CHECK_GT(_func_,_lhs_,_rhs_) CHECK_OP(_func_,>,_lhs_,_rhs_) # define CHECK_LE(_func_,_lhs_,_rhs_) CHECK_OP(_func_,<=,_lhs_,_rhs_) # define CHECK_EQ(_func_,_lhs_,_rhs_) CHECK_OP(_func_,==,_lhs_,_rhs_) #else # define CHECK_BOUNDS(_func_,_len_,_k_) # define CHECK_OP(_func_,_op_,_lhs_,_rhs_) +# define CHECK_GE(_func_,_lhs_,_rhs_) # define CHECK_GT(_func_,_lhs_,_rhs_) # define CHECK_LE(_func_,_lhs_,_rhs_) # define CHECK_EQ(_func_,_lhs_,_rhs_) @@ -222,7 +224,7 @@ new_ n = new n undefinedElem -- | The returned array is the same as the array given, as it is shrunk in place. shrink :: MArray s a -> Int -> ST s (MArray s a) shrink mary _n@(I# n#) = - CHECK_GT("shrink", _n, (0 :: Int)) + CHECK_GE("shrink", _n, (0 :: Int)) CHECK_LE("shrink", _n, (unsafeLengthM mary)) ST $ \s -> case Exts.shrinkSmallMutableArray# (unMArray mary) n# s of s' -> (# s', mary #) From 697dffe527149a6c8fdbb5597943483f23292fdf Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Sat, 25 Oct 2025 19:35:19 +0200 Subject: [PATCH 20/24] More comment --- Data/HashMap/Internal.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index 9c44c2e0..69106012 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1913,7 +1913,8 @@ differenceArrays diff !s !b1 !ary1 !t1 !b2 !ary2 -- we've already matched. Those could be skipped when we check the following -- elements of ary1. -- --- TODO: Get ary1 unboxed somehow?! +-- TODO: Get ary1 unboxed somehow?! The reboxing is quite weird. +-- Maybe try a different order of arguments?! differenceCollisions :: Eq k => Hash -> A.Array (Leaf k v1) -> HashMap k v1 -> Hash -> A.Array (Leaf k v2) -> HashMap k v1 differenceCollisions !h1 !ary1 t1 !h2 !ary2 | h1 == h2 = From cb46d24ea04539782efdde28be7f984c652fd0d8 Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Sun, 26 Oct 2025 11:24:34 +0100 Subject: [PATCH 21/24] Remove comment --- Data/HashMap/Internal/Array.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/Data/HashMap/Internal/Array.hs b/Data/HashMap/Internal/Array.hs index 822789a1..2c023638 100644 --- a/Data/HashMap/Internal/Array.hs +++ b/Data/HashMap/Internal/Array.hs @@ -499,7 +499,6 @@ map' f = \ ary -> go ary mary (i+1) n {-# INLINE map' #-} --- TODO: Should this function return the old array when the length is unchanged? filter :: (a -> Bool) -> Array a -> Array a filter f = \ ary -> let !n = length ary From 521b5c62fd90f3034e70176d7410618180085d95 Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Sun, 26 Oct 2025 11:29:08 +0100 Subject: [PATCH 22/24] s/delete''/deleteSubTree --- Data/HashMap/Internal.hs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index 69106012..df641bba 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1103,10 +1103,12 @@ delete k m = delete' (hash k) k m {-# INLINABLE delete #-} delete' :: Eq k => Hash -> k -> HashMap k v -> HashMap k v -delete' h0 k0 m0 = delete'' h0 k0 0 m0 +delete' h0 k0 m0 = deleteSubTree h0 k0 0 m0 -delete'' :: Eq k => Hash -> k -> Shift -> HashMap k v -> HashMap k v -delete'' = go +-- | This version of 'delete' can be used on subtrees when a the +-- corresponding 'Shift' argument is supplied. +deleteSubTree :: Eq k => Hash -> k -> Shift -> HashMap k v -> HashMap k v +deleteSubTree = go where go !_ !_ !_ Empty = Empty go h k _ t@(Leaf hy (L ky _)) @@ -1808,7 +1810,7 @@ Or maybe this helps avoid more evaluations later on? (Check Cmm) go s t1@(Leaf h1 (L k1 _)) t2 = lookupCont (\_ -> t1) (\_ _ -> Empty) h1 k1 s t2 go _ t1 Empty = t1 - go s t1 (Leaf h2 (L k2 _)) = delete'' h2 k2 s t1 + go s t1 (Leaf h2 (L k2 _)) = deleteSubTree h2 k2 s t1 go s t1@(BitmapIndexed b1 ary1) (BitmapIndexed b2 ary2) = differenceArrays go s b1 ary1 t1 b2 ary2 From 4e734aada05d96e610b661042e0631900f04a064 Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Sun, 26 Oct 2025 16:39:10 +0100 Subject: [PATCH 23/24] Mark deleteSubTree INLINABLE --- Data/HashMap/Internal.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index df641bba..3ceed12f 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1104,6 +1104,7 @@ delete k m = delete' (hash k) k m delete' :: Eq k => Hash -> k -> HashMap k v -> HashMap k v delete' h0 k0 m0 = deleteSubTree h0 k0 0 m0 +{-# INLINABLE delete' #-} -- | This version of 'delete' can be used on subtrees when a the -- corresponding 'Shift' argument is supplied. @@ -1157,7 +1158,7 @@ deleteSubTree = go | otherwise -> Collision h (A.delete v i) Nothing -> t | otherwise = t -{-# INLINABLE delete' #-} +{-# INLINABLE deleteSubTree #-} -- | Delete optimized for the case when we know the key is in the map. -- From d001d13762d4ebb48f19865b1d1e0c115e70fab2 Mon Sep 17 00:00:00 2001 From: Simon Jakobi Date: Sun, 26 Oct 2025 16:42:39 +0100 Subject: [PATCH 24/24] Make deleteSubTree properly self-recursive --- Data/HashMap/Internal.hs | 90 ++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 46 deletions(-) diff --git a/Data/HashMap/Internal.hs b/Data/HashMap/Internal.hs index 3ceed12f..41f0d738 100644 --- a/Data/HashMap/Internal.hs +++ b/Data/HashMap/Internal.hs @@ -1109,55 +1109,53 @@ delete' h0 k0 m0 = deleteSubTree h0 k0 0 m0 -- | This version of 'delete' can be used on subtrees when a the -- corresponding 'Shift' argument is supplied. deleteSubTree :: Eq k => Hash -> k -> Shift -> HashMap k v -> HashMap k v -deleteSubTree = go - where - go !_ !_ !_ Empty = Empty - go h k _ t@(Leaf hy (L ky _)) - | hy == h && ky == k = Empty - | otherwise = t - go h k s t@(BitmapIndexed b ary) - | b .&. m == 0 = t - | otherwise = - let !st = A.index ary i - !st' = go h k (nextShift s) st - in if st' `ptrEq` st - then t - else case st' of - Empty | A.length ary == 1 -> Empty - | A.length ary == 2 -> - case (i, A.index ary 0, A.index ary 1) of - (0, _, l) | isLeafOrCollision l -> l - (1, l, _) | isLeafOrCollision l -> l - _ -> bIndexed - | otherwise -> bIndexed - where - bIndexed = BitmapIndexed (b .&. complement m) (A.delete ary i) - l | isLeafOrCollision l && A.length ary == 1 -> l - _ -> BitmapIndexed b (A.update ary i st') - where m = mask h s - i = sparseIndex b m - go h k s t@(Full ary) = - let !st = A.index ary i - !st' = go h k (nextShift s) st +deleteSubTree !_ !_ !_ Empty = Empty +deleteSubTree h k _ t@(Leaf hy (L ky _)) + | hy == h && ky == k = Empty + | otherwise = t +deleteSubTree h k s t@(BitmapIndexed b ary) + | b .&. m == 0 = t + | otherwise = + let !st = A.index ary i + !st' = deleteSubTree h k (nextShift s) st in if st' `ptrEq` st then t else case st' of - Empty -> - let ary' = A.delete ary i - bm = fullBitmap .&. complement (1 `unsafeShiftL` i) - in BitmapIndexed bm ary' - _ -> Full (updateFullArray ary i st') - where i = index h s - go h k _ t@(Collision hy v) - | h == hy = case indexOf k v of - Just i - | A.length v == 2 -> - if i == 0 - then Leaf h (A.index v 1) - else Leaf h (A.index v 0) - | otherwise -> Collision h (A.delete v i) - Nothing -> t - | otherwise = t + Empty | A.length ary == 1 -> Empty + | A.length ary == 2 -> + case (i, A.index ary 0, A.index ary 1) of + (0, _, l) | isLeafOrCollision l -> l + (1, l, _) | isLeafOrCollision l -> l + _ -> bIndexed + | otherwise -> bIndexed + where + bIndexed = BitmapIndexed (b .&. complement m) (A.delete ary i) + l | isLeafOrCollision l && A.length ary == 1 -> l + _ -> BitmapIndexed b (A.update ary i st') + where m = mask h s + i = sparseIndex b m +deleteSubTree h k s t@(Full ary) = + let !st = A.index ary i + !st' = deleteSubTree h k (nextShift s) st + in if st' `ptrEq` st + then t + else case st' of + Empty -> + let ary' = A.delete ary i + bm = fullBitmap .&. complement (1 `unsafeShiftL` i) + in BitmapIndexed bm ary' + _ -> Full (updateFullArray ary i st') + where i = index h s +deleteSubTree h k _ t@(Collision hy v) + | h == hy = case indexOf k v of + Just i + | A.length v == 2 -> + if i == 0 + then Leaf h (A.index v 1) + else Leaf h (A.index v 0) + | otherwise -> Collision h (A.delete v i) + Nothing -> t + | otherwise = t {-# INLINABLE deleteSubTree #-} -- | Delete optimized for the case when we know the key is in the map.