@@ -28,6 +28,7 @@ module Data.HashMap.Base
28
28
, adjust
29
29
, update
30
30
, alter
31
+ , alterF
31
32
32
33
-- * Combine
33
34
-- ** Union
@@ -89,6 +90,12 @@ module Data.HashMap.Base
89
90
, updateOrConcatWithKey
90
91
, filterMapAux
91
92
, equalKeys
93
+ , lookupRecordCollision
94
+ , LookupRes (.. )
95
+ , insertNewKey
96
+ , insertKeyExists
97
+ , deleteKeyExists
98
+ , ptrEq
92
99
) where
93
100
94
101
#if __GLASGOW_HASKELL__ < 710
@@ -448,6 +455,41 @@ lookup k0 m0 = go h0 k0 0 m0
448
455
| otherwise = Nothing
449
456
{-# INLINABLE lookup #-}
450
457
458
+ -- Internal helper for lookup. This version takes the precomputed hash so
459
+ -- that functions that make multiple calls to lookup and related functions
460
+ -- (insert, delete) only need to calculate the hash once.
461
+ --
462
+ -- It is used by 'alterF' so that hash computation and key comparison only needs
463
+ -- to be performed once. With this information you can use the more optimized
464
+ -- versions of insert ('insertNewKey', 'insertKeyExists') and delete
465
+ -- ('deleteKeyExists')
466
+ --
467
+ -- Outcomes:
468
+ -- Key not in map => Absent
469
+ -- Key in map, no collision => Alone v
470
+ -- Key in map, collision => Collide v position
471
+ lookupRecordCollision :: Eq k => Hash -> k -> Int -> HashMap k v -> LookupRes v
472
+ lookupRecordCollision ! _ ! _ ! _ Empty = Absent
473
+ lookupRecordCollision h k _ (Leaf hx (L kx x))
474
+ | h == hx && k == kx = Present x (- 1 )
475
+ | otherwise = Absent
476
+ lookupRecordCollision h k s (BitmapIndexed b v)
477
+ | b .&. m == 0 = Absent
478
+ | otherwise =
479
+ lookupRecordCollision h k (s+ bitsPerSubkey) (A. index v (sparseIndex b m))
480
+ where m = mask h s
481
+ lookupRecordCollision h k s (Full v) =
482
+ lookupRecordCollision h k (s+ bitsPerSubkey) (A. index v (index h s))
483
+ lookupRecordCollision h k _ (Collision hx v)
484
+ | h == hx = lookupInArrayWithPosition k v
485
+ | otherwise = Absent
486
+ {-# INLINABLE lookupRecordCollision #-}
487
+
488
+ -- The result of a lookup, keeping track of if a hash collision occured.
489
+ -- If a collision did not occur then it will have the Int value (-1).
490
+ data LookupRes a = Absent | Present a ! Int
491
+
492
+
451
493
-- | /O(log n)/ Return the value to which the specified key is mapped,
452
494
-- or the default value if this map contains no mapping for the key.
453
495
lookupDefault :: (Eq k , Hashable k )
@@ -523,6 +565,103 @@ insert k0 v0 m0 = go h0 k0 v0 0 m0
523
565
| otherwise = go h k x s $ BitmapIndexed (mask hy s) (A. singleton t)
524
566
{-# INLINABLE insert #-}
525
567
568
+ -- Insert optimized for the case when we know the key is not in the map.
569
+ --
570
+ -- It is only valid to call this when the key does not exist in the map.
571
+ --
572
+ -- We can skip:
573
+ -- - the key equality check on a Leaf
574
+ -- - check for its existence in the array for a hash collision
575
+ insertNewKey :: Hash -> k -> v -> Int -> HashMap k v -> HashMap k v
576
+ insertNewKey ! h ! k x ! _ Empty = Leaf h (L k x)
577
+ insertNewKey h k x s (Leaf hy l@ (L ky y))
578
+ | hy == h = collision h l (L k x)
579
+ | otherwise = runST (two s h k x hy ky y)
580
+ insertNewKey h k x s t@ (BitmapIndexed b ary)
581
+ | b .&. m == 0 =
582
+ let ! ary' = A. insert ary i $! Leaf h (L k x)
583
+ in bitmapIndexedOrFull (b .|. m) ary'
584
+ | otherwise =
585
+ let ! st = A. index ary i
586
+ ! st' = insertNewKey h k x (s+ bitsPerSubkey) st
587
+ in if st' `ptrEq` st
588
+ then t
589
+ else BitmapIndexed b (A. update ary i st')
590
+ where m = mask h s
591
+ i = sparseIndex b m
592
+ insertNewKey h k x s t@ (Full ary) =
593
+ let ! st = A. index ary i
594
+ ! st' = insertNewKey h k x (s+ bitsPerSubkey) st
595
+ in if st' `ptrEq` st
596
+ then t
597
+ else Full (update16 ary i st')
598
+ where i = index h s
599
+ insertNewKey h k x s t@ (Collision hy v)
600
+ | h == hy = Collision h (snocNewLeaf (L k x) v)
601
+ | otherwise =
602
+ insertNewKey h k x s $ BitmapIndexed (mask hy s) (A. singleton t)
603
+ where
604
+ snocNewLeaf :: Leaf k v -> A. Array (Leaf k v ) -> A. Array (Leaf k v )
605
+ snocNewLeaf leaf ary = A. run $ do
606
+ let n = A. length ary
607
+ mary <- A. new_ (n + 1 )
608
+ A. copy ary 0 mary 0 n
609
+ A. write mary n leaf
610
+ return mary
611
+ {-# INLINABLE insertNewKey #-}
612
+
613
+
614
+ -- Insert optimized for the case when we know the key is in the map.
615
+ --
616
+ -- It is only valid to call this when the key exists in the map and you know the
617
+ -- hash collision position if there was one. This information can be obtained
618
+ -- from 'lookupRecordCollision'. If there is no collision pass (-1) as collPos
619
+ -- (first argument).
620
+ --
621
+ -- We can skip the key equality check on a Leaf because we know the leaf must be
622
+ -- for this key.
623
+ insertKeyExists :: Int -> Hash -> k -> v -> Int -> HashMap k v -> HashMap k v
624
+ insertKeyExists _collPos h k x s t@ (Leaf hy (L ky y))
625
+ | hy == h = if x `ptrEq` y
626
+ then t
627
+ else Leaf h (L k x)
628
+ | otherwise = runST (two s h k x hy ky y)
629
+ insertKeyExists collPos h k x s t@ (BitmapIndexed b ary)
630
+ | b .&. m == 0 =
631
+ let ! ary' = A. insert ary i $! Leaf h (L k x)
632
+ in bitmapIndexedOrFull (b .|. m) ary'
633
+ | otherwise =
634
+ let ! st = A. index ary i
635
+ ! st' = insertKeyExists collPos h k x (s+ bitsPerSubkey) st
636
+ in if st' `ptrEq` st
637
+ then t
638
+ else BitmapIndexed b (A. update ary i st')
639
+ where m = mask h s
640
+ i = sparseIndex b m
641
+ insertKeyExists collPos h k x s t@ (Full ary) =
642
+ let ! st = A. index ary i
643
+ ! st' = insertKeyExists collPos h k x (s+ bitsPerSubkey) st
644
+ in if st' `ptrEq` st
645
+ then t
646
+ else Full (update16 ary i st')
647
+ where i = index h s
648
+ insertKeyExists collPos h k x s t@ (Collision hy v)
649
+ | h == hy = if - 1 == collPos
650
+ then error " Internal error: insertKeyExists {collPos = -1}"
651
+ else Collision h (setAtPosition collPos k x v)
652
+ | otherwise = insertKeyExists collPos h k x s $ BitmapIndexed (mask hy s) (A. singleton t)
653
+ insertKeyExists _ _ _ _ ! _ Empty =
654
+ error " Internal error: insertKeyExists Empty"
655
+ {-# INLINABLE insertKeyExists #-}
656
+
657
+ -- Replace the ith Leaf with Leaf k v.
658
+ --
659
+ -- This does not check that @i@ is within bounds of the array.
660
+ setAtPosition :: Int -> k -> v -> A. Array (Leaf k v ) -> A. Array (Leaf k v )
661
+ setAtPosition i k x ary = A. update ary i (L k x)
662
+ {-# INLINABLE setAtPosition #-}
663
+
664
+
526
665
-- | In-place update version of insert
527
666
unsafeInsert :: (Eq k , Hashable k ) => k -> v -> HashMap k v -> HashMap k v
528
667
unsafeInsert k0 v0 m0 = runST (go h0 k0 v0 0 m0)
@@ -710,6 +849,65 @@ delete k0 m0 = go h0 k0 0 m0
710
849
| otherwise = t
711
850
{-# INLINABLE delete #-}
712
851
852
+ -- Delete optimized for the case when we know the key is in the map and there is
853
+ -- no collision.
854
+ --
855
+ -- It is only valid to call this when the key exists in the map and you know the
856
+ -- hash collision position if there was one. This information can be obtained
857
+ -- from 'lookupRecordCollision'. If there is no collision pass (-1) as collPos.
858
+ --
859
+ -- We can skip:
860
+ -- - the key equality check on the leaf, if we reach a leaf it must be the key
861
+ deleteKeyExists :: Int -> Hash -> k -> Int -> HashMap k v -> HashMap k v
862
+ deleteKeyExists _collPos h _ _ t@ (Leaf hy (L _ _))
863
+ -- TODO(mrenaud): Can this comparison be removed?
864
+ | hy == h = Empty
865
+ | otherwise = t
866
+ deleteKeyExists collPos h k s t@ (BitmapIndexed b ary)
867
+ | b .&. m == 0 = t
868
+ | otherwise =
869
+ let ! st = A. index ary i
870
+ ! st' = deleteKeyExists collPos h k (s+ bitsPerSubkey) st
871
+ in if st' `ptrEq` st
872
+ then t
873
+ else case st' of
874
+ Empty | A. length ary == 1 -> Empty
875
+ | A. length ary == 2 ->
876
+ case (i, A. index ary 0 , A. index ary 1 ) of
877
+ (0 , _, l) | isLeafOrCollision l -> l
878
+ (1 , l, _) | isLeafOrCollision l -> l
879
+ _ -> bIndexed
880
+ | otherwise -> bIndexed
881
+ where
882
+ bIndexed = BitmapIndexed (b .&. complement m) (A. delete ary i)
883
+ l | isLeafOrCollision l && A. length ary == 1 -> l
884
+ _ -> BitmapIndexed b (A. update ary i st')
885
+ where m = mask h s
886
+ i = sparseIndex b m
887
+ deleteKeyExists collPos h k s t@ (Full ary) =
888
+ let ! st = A. index ary i
889
+ ! st' = deleteKeyExists collPos h k (s+ bitsPerSubkey) st
890
+ in if st' `ptrEq` st
891
+ then t
892
+ else case st' of
893
+ Empty ->
894
+ let ary' = A. delete ary i
895
+ bm = fullNodeMask .&. complement (1 `unsafeShiftL` i)
896
+ in BitmapIndexed bm ary'
897
+ _ -> Full (A. update ary i st')
898
+ where i = index h s
899
+ deleteKeyExists collPos h _ _ t@ (Collision hy v)
900
+ | h == hy = case collPos of
901
+ i
902
+ | A. length v == 2 ->
903
+ if i == 0
904
+ then Leaf h (A. index v 1 )
905
+ else Leaf h (A. index v 0 )
906
+ | otherwise -> Collision h (A. delete v i)
907
+ | otherwise = t
908
+ deleteKeyExists ! _ ! _ ! _ ! _ Empty = error " Internal error: deleteKeyExists empty"
909
+ {-# INLINABLE deleteKeyExists #-}
910
+
713
911
-- | /O(log n)/ Adjust the value tied to a given key in this map only
714
912
-- if it is present. Otherwise, leave the map alone.
715
913
adjust :: (Eq k , Hashable k ) => (v -> v ) -> k -> HashMap k v -> HashMap k v
@@ -751,12 +949,60 @@ update f = alter (>>= f)
751
949
-- absence thereof. @alter@ can be used to insert, delete, or update a value in a
752
950
-- map. In short : @'lookup' k ('alter' f k m) = f ('lookup' k m)@.
753
951
alter :: (Eq k , Hashable k ) => (Maybe v -> Maybe v ) -> k -> HashMap k v -> HashMap k v
952
+ -- TODO(m-renaud): Consider using specialized insert and delete for alter.
754
953
alter f k m =
755
954
case f (lookup k m) of
756
955
Nothing -> delete k m
757
956
Just v -> insert k v m
758
957
{-# INLINABLE alter #-}
759
958
959
+ -- | /O(log n)/ The expression (@'alterF' f k map@) alters the value @x@ at
960
+ -- @k@, or absence thereof. @alterF@ can be used to insert, delete, or update
961
+ -- a value in a map.
962
+ --
963
+ -- Note: 'alterF' is a flipped version of the 'at' combinator from
964
+ -- <https://hackage.haskell.org/package/lens-4.15.4/docs/Control-Lens-At.html#v:at Control.Lens.At>.
965
+ --
966
+ -- @since 0.2.9
967
+ alterF :: (Functor f , Eq k , Hashable k )
968
+ => (Maybe v -> f (Maybe v )) -> k -> HashMap k v -> f (HashMap k v )
969
+ -- Special care is taken to only calculate the hash and only perform a key
970
+ -- comparison once.
971
+ alterF f k m = (<$> f mv) $ \ fres ->
972
+ case fres of
973
+
974
+ ------------------------------
975
+ -- Delete the key from the map.
976
+ Nothing -> case lookupRes of
977
+
978
+ -- Key did not exist in the map to begin with, no-op
979
+ Absent -> m
980
+
981
+ -- Key did exist
982
+ Present _ collPos -> deleteKeyExists collPos h k 0 m
983
+
984
+ ------------------------------
985
+ -- Update value
986
+ Just v' -> case lookupRes of
987
+
988
+ -- Key did not exist before, insert v' under a new key
989
+ Absent -> insertNewKey h k v' 0 m
990
+
991
+ -- Key existed before, no hash collision
992
+ Present v collPos ->
993
+ if v `ptrEq` v'
994
+ -- If the value is identical, no-op
995
+ then m
996
+ -- If the value changed, update the value.
997
+ else insertKeyExists collPos h k v' 0 m
998
+
999
+ where ! h = hash k
1000
+ lookupRes = lookupRecordCollision h k 0 m
1001
+ mv = case lookupRes of
1002
+ Absent -> Nothing
1003
+ Present v _ -> Just v
1004
+ {-# INLINABLE alterF #-}
1005
+
760
1006
------------------------------------------------------------------------
761
1007
-- * Combine
762
1008
@@ -1209,6 +1455,19 @@ lookupInArray k0 ary0 = go k0 ary0 0 (A.length ary0)
1209
1455
| otherwise -> go k ary (i+ 1 ) n
1210
1456
{-# INLINABLE lookupInArray #-}
1211
1457
1458
+ -- | /O(n)/ Lookup the value associated with the given key in this
1459
+ -- array. Returns 'Nothing' if the key wasn't found.
1460
+ lookupInArrayWithPosition :: Eq k => k -> A. Array (Leaf k v ) -> LookupRes v
1461
+ lookupInArrayWithPosition k0 ary0 = go k0 ary0 0 (A. length ary0)
1462
+ where
1463
+ go ! k ! ary ! i ! n
1464
+ | i >= n = Absent
1465
+ | otherwise = case A. index ary i of
1466
+ (L kx v)
1467
+ | k == kx -> Present v i
1468
+ | otherwise -> go k ary (i+ 1 ) n
1469
+ {-# INLINABLE lookupInArrayWithPosition #-}
1470
+
1212
1471
-- | /O(n)/ Lookup the value associated with the given key in this
1213
1472
-- array. Returns 'Nothing' if the key wasn't found.
1214
1473
indexOf :: Eq k => k -> A. Array (Leaf k v ) -> Maybe Int
0 commit comments