Skip to content

Commit 85e11c7

Browse files
m-renaudtreeowl
authored andcommitted
Add HashMap.alterF.
The implementation utilizes specialized versions of lookup, insert, and delete to guarantee that the hash is only computed once and key comparisons are done at most once. Testing: - Property tests are added to verify that the behaviour is equivalent to that of Data.Map.alterF. Benchmarking (TODO): - TODO: compare to alter - TODO: compare to similar operations (insert, delete, update) Open questions/follow-ups: - Is the use of ptrEq in alterF valid? The values must be in WHNF for it to give correct answers. - Verify that strictness is enforced properly in Data.HashMap.Strict.alterF. - Make another pass to determine if more checks can be removed from optimized insert and delete operations. - Benchmark a version of alter using specialized insert and delete operations (maybe do this as a follow-up PR). Add alter and alterF benchmarks. ------------------------------ alter benchmark results: ------------------------------ benchmarking HashMap/alterInsert/String time 1.683 ms (1.621 ms .. 1.749 ms) 0.990 R² (0.983 R² .. 0.995 R²) mean 1.479 ms (1.422 ms .. 1.528 ms) std dev 165.1 μs (139.7 μs .. 201.8 μs) variance introduced by outliers: 73% (severely inflated) benchmarking HashMap/alterInsert/ByteString time 1.786 ms (1.753 ms .. 1.818 ms) 0.997 R² (0.996 R² .. 0.998 R²) mean 1.624 ms (1.597 ms .. 1.651 ms) std dev 91.86 μs (76.01 μs .. 111.3 μs) variance introduced by outliers: 41% (moderately inflated) benchmarking HashMap/alterInsert/Int time 1.001 ms (983.6 μs .. 1.020 ms) 0.996 R² (0.992 R² .. 0.998 R²) mean 953.3 μs (938.2 μs .. 1.001 ms) std dev 72.50 μs (36.53 μs .. 132.3 μs) variance introduced by outliers: 60% (severely inflated) benchmarking HashMap/alterInsert-dup/String time 660.3 μs (651.1 μs .. 670.4 μs) 0.999 R² (0.998 R² .. 1.000 R²) mean 618.6 μs (609.6 μs .. 627.9 μs) std dev 27.19 μs (22.38 μs .. 34.47 μs) variance introduced by outliers: 35% (moderately inflated) benchmarking HashMap/alterInsert-dup/ByteString time 393.6 μs (384.3 μs .. 407.0 μs) 0.996 R² (0.992 R² .. 1.000 R²) mean 365.2 μs (359.4 μs .. 376.3 μs) std dev 23.47 μs (13.93 μs .. 36.85 μs) variance introduced by outliers: 57% (severely inflated) benchmarking HashMap/alterInsert-dup/Int time 469.0 μs (452.9 μs .. 500.1 μs) 0.975 R² (0.934 R² .. 0.999 R²) mean 442.5 μs (431.8 μs .. 480.6 μs) std dev 54.03 μs (18.24 μs .. 108.2 μs) variance introduced by outliers: 83% (severely inflated) benchmarking HashMap/alterDelete/String time 1.321 ms (1.207 ms .. 1.505 ms) 0.936 R² (0.874 R² .. 0.995 R²) mean 1.194 ms (1.153 ms .. 1.294 ms) std dev 186.0 μs (80.28 μs .. 338.8 μs) variance introduced by outliers: 86% (severely inflated) benchmarking HashMap/alterDelete/ByteString time 937.2 μs (909.4 μs .. 971.3 μs) 0.993 R² (0.986 R² .. 0.998 R²) mean 892.4 μs (880.9 μs .. 914.6 μs) std dev 48.85 μs (34.35 μs .. 78.75 μs) variance introduced by outliers: 43% (moderately inflated) benchmarking HashMap/alterDelete/Int time 521.2 μs (500.9 μs .. 555.8 μs) 0.983 R² (0.968 R² .. 0.999 R²) mean 484.0 μs (469.8 μs .. 514.1 μs) std dev 56.59 μs (34.48 μs .. 84.56 μs) variance introduced by outliers: 81% (severely inflated) benchmarking HashMap/alterDelete-miss/String time 260.2 μs (258.8 μs .. 262.0 μs) 0.999 R² (0.999 R² .. 1.000 R²) mean 243.6 μs (240.2 μs .. 246.7 μs) std dev 9.586 μs (7.859 μs .. 11.63 μs) variance introduced by outliers: 34% (moderately inflated) benchmarking HashMap/alterDelete-miss/ByteString time 271.2 μs (266.7 μs .. 276.9 μs) 0.996 R² (0.993 R² .. 0.999 R²) mean 255.1 μs (250.4 μs .. 263.0 μs) std dev 17.36 μs (11.72 μs .. 27.42 μs) variance introduced by outliers: 61% (severely inflated) benchmarking HashMap/alterDelete-miss/Int time 334.5 μs (331.7 μs .. 337.7 μs) 0.998 R² (0.996 R² .. 1.000 R²) mean 314.7 μs (310.4 μs .. 319.2 μs) std dev 12.88 μs (10.69 μs .. 15.88 μs) variance introduced by outliers: 35% (moderately inflated) ------------------------------ alterF benchmark results ------------------------------ benchmarking HashMap/alterFInsert/String time 1.853 ms (1.790 ms .. 1.911 ms) 0.991 R² (0.987 R² .. 0.994 R²) mean 1.570 ms (1.499 ms .. 1.632 ms) std dev 199.4 μs (165.5 μs .. 261.6 μs) variance introduced by outliers: 78% (severely inflated) benchmarking HashMap/alterFInsert/ByteString time 1.742 ms (1.709 ms .. 1.779 ms) 0.996 R² (0.993 R² .. 0.998 R²) mean 1.630 ms (1.605 ms .. 1.663 ms) std dev 89.00 μs (70.92 μs .. 118.1 μs) variance introduced by outliers: 39% (moderately inflated) benchmarking HashMap/alterFInsert/Int time 1.293 ms (1.263 ms .. 1.332 ms) 0.993 R² (0.986 R² .. 0.997 R²) mean 1.248 ms (1.223 ms .. 1.280 ms) std dev 83.87 μs (63.48 μs .. 124.8 μs) variance introduced by outliers: 52% (severely inflated) benchmarking HashMap/alterFInsert-dup/String time 698.0 μs (687.5 μs .. 712.2 μs) 0.998 R² (0.996 R² .. 0.999 R²) mean 656.8 μs (646.4 μs .. 668.2 μs) std dev 33.99 μs (27.76 μs .. 44.58 μs) variance introduced by outliers: 42% (moderately inflated) benchmarking HashMap/alterFInsert-dup/ByteString time 386.8 μs (380.9 μs .. 395.1 μs) 0.997 R² (0.995 R² .. 1.000 R²) mean 361.1 μs (354.3 μs .. 369.2 μs) std dev 21.50 μs (15.61 μs .. 30.72 μs) variance introduced by outliers: 53% (severely inflated) benchmarking HashMap/alterFInsert-dup/Int time 712.0 μs (695.4 μs .. 729.9 μs) 0.969 R² (0.938 R² .. 0.992 R²) mean 751.9 μs (705.2 μs .. 873.4 μs) std dev 202.2 μs (117.2 μs .. 321.1 μs) variance introduced by outliers: 95% (severely inflated) benchmarking HashMap/alterFDelete/String time 1.351 ms (1.321 ms .. 1.398 ms) 0.994 R² (0.990 R² .. 0.998 R²) mean 1.323 ms (1.303 ms .. 1.350 ms) std dev 73.00 μs (53.15 μs .. 96.86 μs) variance introduced by outliers: 41% (moderately inflated) benchmarking HashMap/alterFDelete/ByteString time 976.5 μs (968.2 μs .. 985.7 μs) 0.999 R² (0.998 R² .. 1.000 R²) mean 914.0 μs (900.8 μs .. 927.8 μs) std dev 40.05 μs (33.51 μs .. 48.09 μs) variance introduced by outliers: 33% (moderately inflated) benchmarking HashMap/alterFDelete/Int time 687.6 μs (675.4 μs .. 701.4 μs) 0.997 R² (0.995 R² .. 0.999 R²) mean 652.3 μs (642.0 μs .. 663.5 μs) std dev 33.69 μs (27.07 μs .. 45.25 μs) variance introduced by outliers: 42% (moderately inflated) benchmarking HashMap/alterFDelete-miss/String time 309.6 μs (307.2 μs .. 312.4 μs) 0.999 R² (0.998 R² .. 1.000 R²) mean 289.2 μs (284.4 μs .. 294.2 μs) std dev 15.43 μs (12.33 μs .. 23.13 μs) variance introduced by outliers: 49% (moderately inflated) benchmarking HashMap/alterFDelete-miss/ByteString time 252.8 μs (250.1 μs .. 256.7 μs) 0.998 R² (0.996 R² .. 0.999 R²) mean 239.5 μs (235.7 μs .. 244.3 μs) std dev 12.55 μs (9.774 μs .. 16.69 μs) variance introduced by outliers: 48% (moderately inflated) benchmarking HashMap/alterFDelete-miss/Int time 424.1 μs (420.4 μs .. 429.6 μs) 0.998 R² (0.997 R² .. 0.999 R²) mean 403.6 μs (398.1 μs .. 410.7 μs) std dev 19.02 μs (14.48 μs .. 27.70 μs) variance introduced by outliers: 40% (moderately inflated) ------------------------------ insert/delete benchmark results (for comparison ------------------------------ benchmarking HashMap/insert/String time 1.655 ms (1.586 ms .. 1.753 ms) 0.958 R² (0.927 R² .. 0.984 R²) mean 1.482 ms (1.402 ms .. 1.601 ms) std dev 290.7 μs (221.3 μs .. 371.7 μs) variance introduced by outliers: 91% (severely inflated) benchmarking HashMap/insert/ByteString time 1.750 ms (1.723 ms .. 1.780 ms) 0.996 R² (0.993 R² .. 0.998 R²) mean 1.586 ms (1.546 ms .. 1.624 ms) std dev 122.7 μs (100.3 μs .. 151.5 μs) variance introduced by outliers: 56% (severely inflated) benchmarking HashMap/insert/Int time 1.010 ms (992.7 μs .. 1.028 ms) 0.995 R² (0.992 R² .. 0.997 R²) mean 982.5 μs (968.5 μs .. 1.003 ms) std dev 50.67 μs (36.35 μs .. 71.95 μs) variance introduced by outliers: 39% (moderately inflated) benchmarking HashMap/insert-dup/String time 663.6 μs (656.5 μs .. 674.1 μs) 0.997 R² (0.994 R² .. 0.999 R²) mean 634.1 μs (623.2 μs .. 646.8 μs) std dev 37.31 μs (28.16 μs .. 49.15 μs) variance introduced by outliers: 48% (moderately inflated) benchmarking HashMap/insert-dup/ByteString time 400.9 μs (389.2 μs .. 419.9 μs) 0.968 R² (0.923 R² .. 0.997 R²) mean 388.8 μs (371.7 μs .. 434.5 μs) std dev 76.86 μs (23.58 μs .. 132.4 μs) variance introduced by outliers: 94% (severely inflated) benchmarking HashMap/insert-dup/Int time 444.2 μs (436.3 μs .. 451.2 μs) 0.989 R² (0.971 R² .. 0.998 R²) mean 448.7 μs (429.7 μs .. 505.9 μs) std dev 93.00 μs (29.20 μs .. 185.2 μs) variance introduced by outliers: 93% (severely inflated) benchmarking HashMap/delete/String time 1.209 ms (1.169 ms .. 1.248 ms) 0.993 R² (0.989 R² .. 0.997 R²) mean 1.170 ms (1.144 ms .. 1.237 ms) std dev 113.0 μs (57.14 μs .. 240.5 μs) variance introduced by outliers: 69% (severely inflated) benchmarking HashMap/delete/ByteString time 949.2 μs (915.7 μs .. 986.9 μs) 0.984 R² (0.975 R² .. 0.991 R²) mean 937.6 μs (902.6 μs .. 986.2 μs) std dev 132.1 μs (89.29 μs .. 188.1 μs) variance introduced by outliers: 84% (severely inflated) benchmarking HashMap/delete/Int time 496.8 μs (492.6 μs .. 501.2 μs) 0.999 R² (0.998 R² .. 0.999 R²) mean 466.4 μs (459.3 μs .. 472.4 μs) std dev 20.02 μs (16.46 μs .. 24.88 μs) variance introduced by outliers: 35% (moderately inflated) benchmarking HashMap/delete-miss/String time 261.1 μs (259.7 μs .. 262.6 μs) 0.998 R² (0.995 R² .. 1.000 R²) mean 247.0 μs (242.8 μs .. 257.8 μs) std dev 19.51 μs (7.661 μs .. 38.23 μs) variance introduced by outliers: 68% (severely inflated) benchmarking HashMap/delete-miss/ByteString time 271.7 μs (253.7 μs .. 295.2 μs) 0.973 R² (0.962 R² .. 0.986 R²) mean 262.5 μs (253.3 μs .. 272.8 μs) std dev 30.78 μs (24.10 μs .. 39.88 μs) variance introduced by outliers: 82% (severely inflated) benchmarking HashMap/delete-miss/Int time 316.5 μs (312.8 μs .. 320.4 μs) 0.999 R² (0.999 R² .. 0.999 R²) mean 295.7 μs (291.4 μs .. 299.9 μs) std dev 12.22 μs (9.975 μs .. 15.17 μs) variance introduced by outliers: 36% (moderately inflated)
1 parent 180891e commit 85e11c7

File tree

6 files changed

+433
-4
lines changed

6 files changed

+433
-4
lines changed

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## next
2+
3+
* Add `HashMap.alterF`.
4+
15
## 0.2.9.0
26

37
* Add `Ord/Ord1/Ord2` instances. (Thanks, Oleg Grenrus)

Data/HashMap/Base.hs

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ module Data.HashMap.Base
2828
, adjust
2929
, update
3030
, alter
31+
, alterF
3132

3233
-- * Combine
3334
-- ** Union
@@ -89,6 +90,12 @@ module Data.HashMap.Base
8990
, updateOrConcatWithKey
9091
, filterMapAux
9192
, equalKeys
93+
, lookupRecordCollision
94+
, LookupRes(..)
95+
, insertNewKey
96+
, insertKeyExists
97+
, deleteKeyExists
98+
, ptrEq
9299
) where
93100

94101
#if __GLASGOW_HASKELL__ < 710
@@ -448,6 +455,41 @@ lookup k0 m0 = go h0 k0 0 m0
448455
| otherwise = Nothing
449456
{-# INLINABLE lookup #-}
450457

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+
451493
-- | /O(log n)/ Return the value to which the specified key is mapped,
452494
-- or the default value if this map contains no mapping for the key.
453495
lookupDefault :: (Eq k, Hashable k)
@@ -523,6 +565,103 @@ insert k0 v0 m0 = go h0 k0 v0 0 m0
523565
| otherwise = go h k x s $ BitmapIndexed (mask hy s) (A.singleton t)
524566
{-# INLINABLE insert #-}
525567

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+
526665
-- | In-place update version of insert
527666
unsafeInsert :: (Eq k, Hashable k) => k -> v -> HashMap k v -> HashMap k v
528667
unsafeInsert k0 v0 m0 = runST (go h0 k0 v0 0 m0)
@@ -710,6 +849,65 @@ delete k0 m0 = go h0 k0 0 m0
710849
| otherwise = t
711850
{-# INLINABLE delete #-}
712851

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+
713911
-- | /O(log n)/ Adjust the value tied to a given key in this map only
714912
-- if it is present. Otherwise, leave the map alone.
715913
adjust :: (Eq k, Hashable k) => (v -> v) -> k -> HashMap k v -> HashMap k v
@@ -751,12 +949,60 @@ update f = alter (>>= f)
751949
-- absence thereof. @alter@ can be used to insert, delete, or update a value in a
752950
-- map. In short : @'lookup' k ('alter' f k m) = f ('lookup' k m)@.
753951
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.
754953
alter f k m =
755954
case f (lookup k m) of
756955
Nothing -> delete k m
757956
Just v -> insert k v m
758957
{-# INLINABLE alter #-}
759958

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+
7601006
------------------------------------------------------------------------
7611007
-- * Combine
7621008

@@ -1209,6 +1455,19 @@ lookupInArray k0 ary0 = go k0 ary0 0 (A.length ary0)
12091455
| otherwise -> go k ary (i+1) n
12101456
{-# INLINABLE lookupInArray #-}
12111457

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+
12121471
-- | /O(n)/ Lookup the value associated with the given key in this
12131472
-- array. Returns 'Nothing' if the key wasn't found.
12141473
indexOf :: Eq k => k -> A.Array (Leaf k v) -> Maybe Int

Data/HashMap/Lazy.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ module Data.HashMap.Lazy
4646
, adjust
4747
, update
4848
, alter
49+
, alterF
4950

5051
-- * Combine
5152
-- ** Union

0 commit comments

Comments
 (0)