Skip to content

Commit 97a3d51

Browse files
committed
Merge pull request #111 from phadej/hashable-instances-fix
Hashable instances fix
2 parents 367b358 + 69151b7 commit 97a3d51

File tree

5 files changed

+75
-11
lines changed

5 files changed

+75
-11
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@
66
.hpc/
77
/benchmarks/dist/*
88
/dist/*
9+
.cabal-sandbox
10+
cabal.sandbox.config

Data/HashMap/Base.hs

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -201,9 +201,6 @@ instance Traversable (HashMap k) where
201201
instance (Eq k, Eq v) => Eq (HashMap k v) where
202202
(==) = equal
203203

204-
instance (Hashable k, Hashable v) => Hashable (HashMap k v) where
205-
hashWithSalt = foldlWithKey' (\h k v -> h `H.hashWithSalt` k `H.hashWithSalt` v)
206-
207204
equal :: (Eq k, Eq v) => HashMap k v -> HashMap k v -> Bool
208205
equal t1 t2 = go (toList' t1 []) (toList' t2 [])
209206
where
@@ -221,11 +218,39 @@ equal t1 t2 = go (toList' t1 []) (toList' t2 [])
221218
go [] [] = True
222219
go _ _ = False
223220

224-
toList' (BitmapIndexed _ ary) a = A.foldr toList' a ary
225-
toList' (Full ary) a = A.foldr toList' a ary
226-
toList' l@(Leaf _ _) a = l : a
227-
toList' c@(Collision _ _) a = c : a
228-
toList' Empty a = a
221+
instance (Hashable k, Hashable v) => Hashable (HashMap k v) where
222+
hashWithSalt salt hm = go salt (toList' hm [])
223+
where
224+
go :: Int -> [HashMap k v] -> Int
225+
go s [] = s
226+
go s (Leaf _ l : tl)
227+
= s `hashLeafWithSalt` l `go` tl
228+
-- For collisions we hashmix hash value
229+
-- and then array of values' hashes sorted
230+
go s (Collision h a : tl)
231+
= (s `H.hashWithSalt` h) `hashCollisionWithSalt` a `go` tl
232+
go s (_ : tl) = s `go` tl
233+
234+
hashLeafWithSalt :: Int -> Leaf k v -> Int
235+
hashLeafWithSalt s (L k v) = s `H.hashWithSalt` k `H.hashWithSalt` v
236+
237+
hashCollisionWithSalt :: Int -> A.Array (Leaf k v) -> Int
238+
hashCollisionWithSalt s
239+
= L.foldl' H.hashWithSalt s . arrayHashesSorted
240+
241+
arrayHashesSorted :: A.Array (Leaf k v) -> [Int]
242+
arrayHashesSorted = L.sort . L.map leafValueHash . A.toList
243+
244+
leafValueHash :: Leaf k v -> Int
245+
leafValueHash (L _ v) = H.hash v
246+
247+
-- Helper to get 'Leaf's and 'Collision's as a list.
248+
toList' :: HashMap k v -> [HashMap k v] -> [HashMap k v]
249+
toList' (BitmapIndexed _ ary) a = A.foldr toList' a ary
250+
toList' (Full ary) a = A.foldr toList' a ary
251+
toList' l@(Leaf _ _) a = l : a
252+
toList' c@(Collision _ _) a = c : a
253+
toList' Empty a = a
229254

230255
-- Helper function to detect 'Leaf's and 'Collision's.
231256
isLeafOrCollision :: HashMap k v -> Bool

Data/HashSet.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ instance (Data a, Eq a, Hashable a) => Data (HashSet a) where
137137
dataCast1 f = gcast1 f
138138

139139
instance (Hashable a) => Hashable (HashSet a) where
140-
hashWithSalt = foldl' hashWithSalt
140+
hashWithSalt salt = hashWithSalt salt . asMap
141141

142142
fromListConstr :: Constr
143143
fromListConstr = mkConstr hashSetDataType "fromList" [] Prefix

tests/HashMapProperties.hs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@ import qualified Data.Foldable as Foldable
1010
import Data.Function (on)
1111
import Data.Hashable (Hashable(hashWithSalt))
1212
import qualified Data.List as L
13+
import Data.Ord (comparing)
1314
#if defined(STRICT)
1415
import qualified Data.HashMap.Strict as HM
1516
#else
1617
import qualified Data.HashMap.Lazy as HM
1718
#endif
1819
import qualified Data.Map as M
19-
import Test.QuickCheck (Arbitrary, Property, (==>))
20+
import Test.QuickCheck (Arbitrary, Property, (==>), (===))
2021
import Test.Framework (Test, defaultMain, testGroup)
2122
import Test.Framework.Providers.QuickCheck2 (testProperty)
2223

@@ -49,6 +50,19 @@ pFoldable :: [(Int, Int)] -> Bool
4950
pFoldable = (L.sort . Foldable.foldr (:) []) `eq`
5051
(L.sort . Foldable.foldr (:) [])
5152

53+
pHashable :: [(Key, Int)] -> [Int] -> Int -> Property
54+
pHashable xs is salt =
55+
x == y ==> hashWithSalt salt x === hashWithSalt salt y
56+
where
57+
ys = shuffle is xs
58+
x = HM.fromList xs
59+
y = HM.fromList ys
60+
-- Shuffle the list using indexes in the second
61+
shuffle :: [Int] -> [a] -> [a]
62+
shuffle idxs = L.map snd
63+
. L.sortBy (comparing fst)
64+
. L.zip (idxs ++ [L.maximum (0:is) + 1 ..])
65+
5266
------------------------------------------------------------------------
5367
-- ** Basic interface
5468

@@ -229,6 +243,7 @@ tests =
229243
, testProperty "Read/Show" pReadShow
230244
, testProperty "Functor" pFunctor
231245
, testProperty "Foldable" pFoldable
246+
, testProperty "Hashable" pHashable
232247
]
233248
-- Basic interface
234249
, testGroup "basic interface"

tests/HashSetProperties.hs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import Data.Hashable (Hashable(hashWithSalt))
1010
import qualified Data.List as L
1111
import qualified Data.HashSet as S
1212
import qualified Data.Set as Set
13-
import Test.QuickCheck (Arbitrary)
13+
import Data.Ord (comparing)
14+
import Test.QuickCheck (Arbitrary, Property, (==>), (===))
1415
import Test.Framework (Test, defaultMain, testGroup)
1516
import Test.Framework.Providers.QuickCheck2 (testProperty)
1617

@@ -40,6 +41,25 @@ pFoldable :: [Int] -> Bool
4041
pFoldable = (L.sort . Foldable.foldr (:) []) `eq`
4142
(L.sort . Foldable.foldr (:) [])
4243

44+
pPermutationEq :: [Key] -> [Int] -> Bool
45+
pPermutationEq xs is = S.fromList xs == S.fromList ys
46+
where
47+
ys = shuffle is xs
48+
shuffle idxs = L.map snd
49+
. L.sortBy (comparing fst)
50+
. L.zip (idxs ++ [L.maximum (0:is) + 1 ..])
51+
52+
pHashable :: [Key] -> [Int] -> Int -> Property
53+
pHashable xs is salt =
54+
x == y ==> hashWithSalt salt x === hashWithSalt salt y
55+
where
56+
ys = shuffle is xs
57+
x = S.fromList xs
58+
y = S.fromList ys
59+
shuffle idxs = L.map snd
60+
. L.sortBy (comparing fst)
61+
. L.zip (idxs ++ [L.maximum (0:is) + 1 ..])
62+
4363
------------------------------------------------------------------------
4464
-- ** Basic interface
4565

@@ -113,9 +133,11 @@ tests =
113133
-- Instances
114134
testGroup "instances"
115135
[ testProperty "==" pEq
136+
, testProperty "Permutation ==" pPermutationEq
116137
, testProperty "/=" pNeq
117138
, testProperty "Read/Show" pReadShow
118139
, testProperty "Foldable" pFoldable
140+
, testProperty "Hashable" pHashable
119141
]
120142
-- Basic interface
121143
, testGroup "basic interface"

0 commit comments

Comments
 (0)