Skip to content

Commit 76f3b97

Browse files
authored
Merge pull request #141 from phadej/ord
Ord instances
2 parents 581eda8 + ceeeb25 commit 76f3b97

File tree

8 files changed

+233
-19
lines changed

8 files changed

+233
-19
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 `Ord/Ord1/Ord2` instances.
4+
15
## 0.2.8.0
26

37
* Add `Eq1/2`, `Show1/2`, `Read1` instances with `base-4.9`

Data/HashMap/Base.hs

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ import Data.Hashable (Hashable)
118118
import Data.HashMap.PopCount (popCount)
119119
import Data.HashMap.Unsafe (runST)
120120
import Data.HashMap.UnsafeShift (unsafeShiftL, unsafeShiftR)
121+
import Data.HashMap.List (isPermutationBy, unorderedCompare)
121122
import Data.Typeable (Typeable)
122123

123124
#if __GLASGOW_HASKELL__ >= 707
@@ -278,26 +279,34 @@ equal eqk eqv t1 t2 = go (toList' t1 []) (toList' t2 [])
278279

279280
leafEq (L k v) (L k' v') = eqk k k' && eqv v v'
280281

281-
-- Note: previous implemenation isPermutation = null (as // bs)
282-
-- was O(n^2) too.
283-
--
284-
-- This assumes lists are of equal length
285-
isPermutationBy :: (a -> b -> Bool) -> [a] -> [b] -> Bool
286-
isPermutationBy f = go
287-
where
288-
f' = flip f
282+
#if MIN_VERSION_base(4,9,0)
283+
instance Ord2 HashMap where
284+
liftCompare2 = cmp
289285

290-
go [] [] = True
291-
go (x : xs) (y : ys)
292-
| f x y = go xs ys
293-
| otherwise = go (deleteBy f' y xs) (deleteBy f x ys)
294-
go [] (_ : _) = False
295-
go (_ : _) [] = False
296-
297-
-- Data.List.deleteBy :: (a -> a -> Bool) -> a -> [a] -> [a]
298-
deleteBy :: (a -> b -> Bool) -> a -> [b] -> [b]
299-
deleteBy _ _ [] = []
300-
deleteBy eq x (y:ys) = if x `eq` y then ys else y : deleteBy eq x ys
286+
instance Ord k => Ord1 (HashMap k) where
287+
liftCompare = cmp compare
288+
#endif
289+
290+
instance (Ord k, Ord v) => Ord (HashMap k v) where
291+
compare = cmp compare compare
292+
293+
cmp :: (k -> k' -> Ordering) -> (v -> v' -> Ordering)
294+
-> HashMap k v -> HashMap k' v' -> Ordering
295+
cmp cmpk cmpv t1 t2 = go (toList' t1 []) (toList' t2 [])
296+
where
297+
go (Leaf k1 l1 : tl1) (Leaf k2 l2 : tl2)
298+
= compare k1 k2 `mappend` leafCompare l1 l2 `mappend` go tl1 tl2
299+
go (Collision k1 ary1 : tl1) (Collision k2 ary2 : tl2)
300+
= compare k1 k2 `mappend` compare (A.length ary1) (A.length ary2) `mappend`
301+
unorderedCompare leafCompare (A.toList ary1) (A.toList ary2)
302+
go (Leaf _ _ : _) (Collision _ _ : _) = LT
303+
go (Collision _ _ : _) (Leaf _ _ : _) = GT
304+
go [] [] = EQ
305+
go [] _ = LT
306+
go _ [] = GT
307+
go _ _ = error "cmp: Should never happend, toList' includes non Leaf / Collision"
308+
309+
leafCompare (L k v) (L k' v') = cmpk k k' `mappend` cmpv v v'
301310

302311
-- Same as 'equal' but doesn't compare the values.
303312
equalKeys :: (k -> k' -> Bool) -> HashMap k v -> HashMap k' v' -> Bool

Data/HashMap/List.hs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
{-# LANGUAGE ScopedTypeVariables #-}
2+
{-# OPTIONS_GHC -fno-full-laziness -funbox-strict-fields #-}
3+
-- | Extra list functions
4+
--
5+
-- In separate module to aid testing.
6+
module Data.HashMap.List
7+
( isPermutationBy
8+
, deleteBy
9+
, unorderedCompare
10+
) where
11+
12+
import Data.Maybe (fromMaybe)
13+
import Data.List (sortBy)
14+
import Data.Monoid
15+
import Prelude
16+
17+
-- Note: previous implemenation isPermutation = null (as // bs)
18+
-- was O(n^2) too.
19+
--
20+
-- This assumes lists are of equal length
21+
isPermutationBy :: (a -> b -> Bool) -> [a] -> [b] -> Bool
22+
isPermutationBy f = go
23+
where
24+
f' = flip f
25+
26+
go [] [] = True
27+
go (x : xs) (y : ys)
28+
| f x y = go xs ys
29+
| otherwise = fromMaybe False $ do
30+
xs' <- deleteBy f' y xs
31+
ys' <- deleteBy f x ys
32+
return (go xs' ys')
33+
go [] (_ : _) = False
34+
go (_ : _) [] = False
35+
36+
-- The idea:
37+
--
38+
-- Homogeonous version
39+
--
40+
-- uc :: (a -> a -> Ordering) -> [a] -> [a] -> Ordering
41+
-- uc c as bs = compare (sortBy c as) (sortBy c bs)
42+
--
43+
-- But as we have only (a -> b -> Ordering), we cannot directly compare
44+
-- elements from the same list.
45+
--
46+
-- So when comparing elements from the list, we count how many elements are
47+
-- "less and greater" in the other list, and use the count as a metric.
48+
--
49+
unorderedCompare :: (a -> b -> Ordering) -> [a] -> [b] -> Ordering
50+
unorderedCompare c as bs = go (sortBy cmpA as) (sortBy cmpB bs)
51+
where
52+
go [] [] = EQ
53+
go [] (_ : _) = LT
54+
go (_ : _) [] = GT
55+
go (x : xs) (y : ys) = c x y `mappend` go xs ys
56+
57+
cmpA a a' = compare (inB a) (inB a')
58+
cmpB b b' = compare (inA b) (inA b')
59+
60+
inB a = (length $ filter (\b -> c a b == GT) bs, negate $ length $ filter (\b -> c a b == LT) bs)
61+
inA b = (length $ filter (\a -> c a b == LT) as, negate $ length $ filter (\a -> c a b == GT) as)
62+
63+
-- Returns Nothing is nothing deleted
64+
deleteBy :: (a -> b -> Bool) -> a -> [b] -> Maybe [b]
65+
deleteBy _ _ [] = Nothing
66+
deleteBy eq x (y:ys) = if x `eq` y then Just ys else fmap (y :) (deleteBy eq x ys)

Data/HashSet.hs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,15 @@ instance Eq1 HashSet where
123123
liftEq eq (HashSet a) (HashSet b) = equalKeys eq a b
124124
#endif
125125

126+
instance (Ord a) => Ord (HashSet a) where
127+
compare (HashSet a) (HashSet b) = compare a b
128+
{-# INLINE compare #-}
129+
130+
#if MIN_VERSION_base(4,9,0)
131+
instance Ord1 HashSet where
132+
liftCompare c (HashSet a) (HashSet b) = liftCompare2 c compare a b
133+
#endif
134+
126135
instance Foldable.Foldable HashSet where
127136
foldr = Data.HashSet.foldr
128137
{-# INLINE foldr #-}

tests/HashMapProperties.hs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,25 @@ pEq xs = (M.fromList xs ==) `eq` (HM.fromList xs ==)
4040
pNeq :: [(Key, Int)] -> [(Key, Int)] -> Bool
4141
pNeq xs = (M.fromList xs /=) `eq` (HM.fromList xs /=)
4242

43+
-- We cannot compare to `Data.Map` as ordering is different.
44+
pOrd1 :: [(Key, Int)] -> Bool
45+
pOrd1 xs = compare x x == EQ
46+
where
47+
x = HM.fromList xs
48+
49+
pOrd2 :: [(Key, Int)] -> [(Key, Int)] -> [(Key, Int)] -> Bool
50+
pOrd2 xs ys zs = case (compare x y, compare y z) of
51+
(EQ, o) -> compare x z == o
52+
(o, EQ) -> compare x z == o
53+
(LT, LT) -> compare x z == LT
54+
(GT, GT) -> compare x z == GT
55+
(LT, GT) -> True -- ys greater than xs and zs.
56+
(GT, LT) -> True
57+
where
58+
x = HM.fromList xs
59+
y = HM.fromList ys
60+
z = HM.fromList zs
61+
4362
pReadShow :: [(Key, Int)] -> Bool
4463
pReadShow xs = M.fromList xs == read (show (M.fromList xs))
4564

@@ -254,6 +273,8 @@ tests =
254273
testGroup "instances"
255274
[ testProperty "==" pEq
256275
, testProperty "/=" pNeq
276+
, testProperty "compare reflexive" pOrd1
277+
, testProperty "compare transitive" pOrd2
257278
, testProperty "Read/Show" pReadShow
258279
, testProperty "Functor" pFunctor
259280
, testProperty "Foldable" pFoldable

tests/HashSetProperties.hs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,25 @@ pEq xs = (Set.fromList xs ==) `eq` (S.fromList xs ==)
3434
pNeq :: [Key] -> [Key] -> Bool
3535
pNeq xs = (Set.fromList xs /=) `eq` (S.fromList xs /=)
3636

37+
-- We cannot compare to `Data.Map` as ordering is different.
38+
pOrd1 :: [Key] -> Bool
39+
pOrd1 xs = compare x x == EQ
40+
where
41+
x = S.fromList xs
42+
43+
pOrd2 :: [Key] -> [Key] -> [Key] -> Bool
44+
pOrd2 xs ys zs = case (compare x y, compare y z) of
45+
(EQ, o) -> compare x z == o
46+
(o, EQ) -> compare x z == o
47+
(LT, LT) -> compare x z == LT
48+
(GT, GT) -> compare x z == GT
49+
(LT, GT) -> True -- ys greater than xs and zs.
50+
(GT, LT) -> True
51+
where
52+
x = S.fromList xs
53+
y = S.fromList ys
54+
z = S.fromList zs
55+
3756
pReadShow :: [Key] -> Bool
3857
pReadShow xs = Set.fromList xs == read (show (Set.fromList xs))
3958

tests/List.hs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
module Main (main) where
2+
3+
import Data.HashMap.List
4+
import Data.List (nub, sort, sortBy)
5+
import Data.Ord (comparing)
6+
7+
import Test.Framework (Test, defaultMain, testGroup)
8+
import Test.Framework.Providers.QuickCheck2 (testProperty)
9+
import Test.QuickCheck ((==>), (===), property, Property)
10+
11+
tests :: Test
12+
tests = testGroup "Data.HashMap.List"
13+
[ testProperty "isPermutationBy" pIsPermutation
14+
, testProperty "isPermutationBy of different length" pIsPermutationDiffLength
15+
, testProperty "pUnorderedCompare" pUnorderedCompare
16+
, testGroup "modelUnorderedCompare"
17+
[ testProperty "reflexive" modelUnorderedCompareRefl
18+
, testProperty "anti-symmetric" modelUnorderedCompareAntiSymm
19+
, testProperty "transitive" modelUnorderedCompareTrans
20+
]
21+
]
22+
23+
pIsPermutation :: [Char] -> [Int] -> Bool
24+
pIsPermutation xs is = isPermutationBy (==) xs xs'
25+
where
26+
is' = nub is ++ [maximum (0:is) + 1 ..]
27+
xs' = map fst . sortBy (comparing snd) $ zip xs is'
28+
29+
pIsPermutationDiffLength :: [Int] -> [Int] -> Property
30+
pIsPermutationDiffLength xs ys =
31+
length xs /= length ys ==> isPermutationBy (==) xs ys === False
32+
33+
-- | Homogenous version of 'unorderedCompare'
34+
--
35+
-- *Compare smallest non-equal elements of the two lists*.
36+
modelUnorderedCompare :: Ord a => [a] -> [a] -> Ordering
37+
modelUnorderedCompare as bs = compare (sort as) (sort bs)
38+
39+
modelUnorderedCompareRefl :: [Int] -> Property
40+
modelUnorderedCompareRefl xs = modelUnorderedCompare xs xs === EQ
41+
42+
modelUnorderedCompareAntiSymm :: [Int] -> [Int] -> Property
43+
modelUnorderedCompareAntiSymm xs ys = case a of
44+
EQ -> b === EQ
45+
LT -> b === GT
46+
GT -> b === LT
47+
where
48+
a = modelUnorderedCompare xs ys
49+
b = modelUnorderedCompare ys xs
50+
51+
modelUnorderedCompareTrans :: [Int] -> [Int] -> [Int] -> Property
52+
modelUnorderedCompareTrans xs ys zs =
53+
case (modelUnorderedCompare xs ys, modelUnorderedCompare ys zs) of
54+
(EQ, yz) -> xz === yz
55+
(xy, EQ) -> xz === xy
56+
(LT, LT) -> xz === LT
57+
(GT, GT) -> xz === GT
58+
(LT, GT) -> property True
59+
(GT, LT) -> property True
60+
where
61+
xz = modelUnorderedCompare xs zs
62+
63+
pUnorderedCompare :: [Int] -> [Int] -> Property
64+
pUnorderedCompare xs ys =
65+
unorderedCompare compare xs ys === modelUnorderedCompare xs ys
66+
67+
main :: IO ()
68+
main = defaultMain [tests]

unordered-containers.cabal

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ library
3434
other-modules:
3535
Data.HashMap.Array
3636
Data.HashMap.Base
37+
Data.HashMap.List
3738
Data.HashMap.PopCount
3839
Data.HashMap.Unsafe
3940
Data.HashMap.UnsafeShift
@@ -103,6 +104,23 @@ test-suite hashset-properties
103104
ghc-options: -Wall
104105
cpp-options: -DASSERTS
105106

107+
test-suite list-tests
108+
hs-source-dirs: tests .
109+
main-is: List.hs
110+
other-modules:
111+
Data.HashMap.List
112+
type: exitcode-stdio-1.0
113+
114+
build-depends:
115+
base,
116+
containers >= 0.4,
117+
QuickCheck >= 2.4.0.1,
118+
test-framework >= 0.3.3,
119+
test-framework-quickcheck2 >= 0.2.9
120+
121+
ghc-options: -Wall
122+
cpp-options: -DASSERTS
123+
106124
test-suite regressions
107125
hs-source-dirs: tests
108126
main-is: Regressions.hs

0 commit comments

Comments
 (0)