Skip to content

Commit e9d5a0e

Browse files
committed
Add symmetric difference ops
...for Set, Map, IntSet, IntMap. This joins the set operations already implemented: union, intersection, difference.
1 parent b2a54c8 commit e9d5a0e

20 files changed

+247
-4
lines changed

containers-tests/benchmarks/SetOperations/SetOperations-IntMap.hs

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,10 @@ module Main where
33
import Data.IntMap as C
44
import SetOperations
55

6-
main = benchmark (\xs -> fromList [(x, x) | x <- xs]) True [("union", C.union), ("difference", C.difference), ("intersection", C.intersection)]
6+
main :: IO ()
7+
main = benchmark (\xs -> fromList [(x, x) | x <- xs]) True
8+
[ ("union", C.union)
9+
, ("difference", C.difference)
10+
, ("intersection", C.intersection)
11+
, ("symmetricDifference", C.symmetricDifference)
12+
]

containers-tests/benchmarks/SetOperations/SetOperations-IntSet.hs

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,10 @@ module Main where
33
import Data.IntSet as C
44
import SetOperations
55

6-
main = benchmark fromList True [("union", C.union), ("difference", C.difference), ("intersection", C.intersection)]
6+
main :: IO ()
7+
main = benchmark fromList True
8+
[ ("union", C.union)
9+
, ("difference", C.difference)
10+
, ("intersection", C.intersection)
11+
, ("symmetricDifference", C.symmetricDifference)
12+
]

containers-tests/benchmarks/SetOperations/SetOperations-Map.hs

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,10 @@ module Main where
33
import Data.Map as C
44
import SetOperations
55

6-
main = benchmark (\xs -> fromList [(x, x) | x <- xs]) True [("union", C.union), ("difference", C.difference), ("intersection", C.intersection)]
6+
main :: IO ()
7+
main = benchmark (\xs -> fromList [(x, x) | x <- xs]) True
8+
[ ("union", C.union)
9+
, ("difference", C.difference)
10+
, ("intersection", C.intersection)
11+
, ("symmetricDifference", C.symmetricDifference)
12+
]

containers-tests/benchmarks/SetOperations/SetOperations-Set.hs

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,10 @@ module Main where
33
import Data.Set as C
44
import SetOperations
55

6-
main = benchmark fromList True [("union", C.union), ("difference", C.difference), ("intersection", C.intersection)]
6+
main :: IO ()
7+
main = benchmark fromList True
8+
[ ("union", C.union)
9+
, ("difference", C.difference)
10+
, ("intersection", C.intersection)
11+
, ("symmetricDifference", C.symmetricDifference)
12+
]

containers-tests/tests/intmap-properties.hs

+12
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ main = defaultMain $ testGroup "intmap-properties"
150150
, testProperty "intersection model" prop_intersectionModel
151151
, testProperty "intersectionWith model" prop_intersectionWithModel
152152
, testProperty "intersectionWithKey model" prop_intersectionWithKeyModel
153+
, testProperty "symmetricDifference" prop_symmetricDifference
153154
, testProperty "mergeWithKey model" prop_mergeWithKeyModel
154155
, testProperty "merge valid" prop_merge_valid
155156
, testProperty "mergeA effects" prop_mergeA_effects
@@ -1258,6 +1259,17 @@ prop_intersectionWithKeyModel xs ys
12581259
ys' = List.nubBy ((==) `on` fst) ys
12591260
f k l r = k + 2 * l + 3 * r
12601261

1262+
prop_symmetricDifference :: IMap -> IMap -> Property
1263+
prop_symmetricDifference m1 m2 =
1264+
valid m3 .&&.
1265+
toAscList m3 ===
1266+
List.sort ( List.filter ((`notElem` fmap fst kys) . fst) kxs
1267+
++ List.filter ((`notElem` fmap fst kxs) . fst) kys)
1268+
where
1269+
m3 = symmetricDifference m1 m2
1270+
kxs = toAscList m1
1271+
kys = toAscList m2
1272+
12611273
prop_disjoint :: UMap -> UMap -> Property
12621274
prop_disjoint m1 m2 = disjoint m1 m2 === null (intersection m1 m2)
12631275

containers-tests/tests/intset-properties.hs

+12
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ main = defaultMain $ testGroup "intset-properties"
4242
, testProperty "prop_union" prop_union
4343
, testProperty "prop_difference" prop_difference
4444
, testProperty "prop_intersection" prop_intersection
45+
, testProperty "prop_symmetricDifference" prop_symmetricDifference
4546
, testProperty "prop_Ordered" prop_Ordered
4647
, testProperty "prop_List" prop_List
4748
, testProperty "prop_DescList" prop_DescList
@@ -264,6 +265,17 @@ prop_intersection xs ys =
264265
valid t .&&.
265266
toAscList t === (toAscList xs `List.intersect` toAscList ys)
266267

268+
prop_symmetricDifference :: IntSet -> IntSet -> Property
269+
prop_symmetricDifference xs ys =
270+
case symmetricDifference xs ys of
271+
t ->
272+
valid t .&&.
273+
toAscList t ===
274+
List.sort (List.filter (`notElem` xs') ys' ++ List.filter (`notElem` ys') xs')
275+
where
276+
xs' = toAscList xs
277+
ys' = toAscList ys
278+
267279
prop_disjoint :: IntSet -> IntSet -> Bool
268280
prop_disjoint a b = a `disjoint` b == null (a `intersection` b)
269281

containers-tests/tests/map-properties.hs

+12
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ main = defaultMain $ testGroup "map-properties"
178178
, testProperty "intersectionWithModel" prop_intersectionWithModel
179179
, testProperty "intersectionWithKey" prop_intersectionWithKey
180180
, testProperty "intersectionWithKeyModel" prop_intersectionWithKeyModel
181+
, testProperty "symmetricDifference" prop_symmetricDifference
181182
, testProperty "disjoint" prop_disjoint
182183
, testProperty "compose" prop_compose
183184
, testProperty "differenceMerge" prop_differenceMerge
@@ -1168,6 +1169,17 @@ prop_intersectionWithKeyModel xs ys
11681169
ys' = List.nubBy ((==) `on` fst) ys
11691170
f k l r = k + 2 * l + 3 * r
11701171

1172+
prop_symmetricDifference :: IMap -> IMap -> Property
1173+
prop_symmetricDifference m1 m2 =
1174+
valid m3 .&&.
1175+
toAscList m3 ===
1176+
List.sort ( List.filter ((`notElem` fmap fst kys) . fst) kxs
1177+
++ List.filter ((`notElem` fmap fst kxs) . fst) kys)
1178+
where
1179+
m3 = symmetricDifference m1 m2
1180+
kxs = toAscList m1
1181+
kys = toAscList m2
1182+
11711183
prop_disjoint :: UMap -> UMap -> Property
11721184
prop_disjoint m1 m2 = disjoint m1 m2 === null (intersection m1 m2)
11731185

containers-tests/tests/set-properties.hs

+11
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ main = defaultMain $ testGroup "set-properties"
7474
, testProperty "prop_isProperSubsetOf2" prop_isProperSubsetOf2
7575
, testProperty "prop_isSubsetOf" prop_isSubsetOf
7676
, testProperty "prop_isSubsetOf2" prop_isSubsetOf2
77+
, testProperty "prop_symmetricDifference" prop_symmetricDifference
7778
, testProperty "prop_disjoint" prop_disjoint
7879
, testProperty "prop_size" prop_size
7980
, testProperty "prop_lookupMax" prop_lookupMax
@@ -487,6 +488,16 @@ prop_Int :: [Int] -> [Int] -> Bool
487488
prop_Int xs ys = toAscList (intersection (fromList xs) (fromList ys))
488489
== List.sort (nub ((List.intersect) (xs) (ys)))
489490

491+
prop_symmetricDifference :: Set Int -> Set Int -> Property
492+
prop_symmetricDifference xs ys =
493+
valid zs .&&.
494+
toAscList zs ===
495+
List.sort (List.filter (`notElem` xs') ys' ++ List.filter (`notElem` ys') xs')
496+
where
497+
zs = symmetricDifference xs ys
498+
xs' = toAscList xs
499+
ys' = toAscList ys
500+
490501
prop_disjoint :: Set Int -> Set Int -> Bool
491502
prop_disjoint a b = a `disjoint` b == null (a `intersection` b)
492503

containers/src/Data/IntMap/Internal.hs

+46
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ module Data.IntMap.Internal (
125125
, intersectionWith
126126
, intersectionWithKey
127127

128+
-- ** Symmetric difference
129+
, symmetricDifference
130+
128131
-- ** Compose
129132
, compose
130133

@@ -1304,6 +1307,49 @@ intersectionWithKey :: (Key -> a -> b -> c) -> IntMap a -> IntMap b -> IntMap c
13041307
intersectionWithKey f m1 m2
13051308
= mergeWithKey' bin (\(Tip k1 x1) (Tip _k2 x2) -> Tip k1 (f k1 x1 x2)) (const Nil) (const Nil) m1 m2
13061309

1310+
{--------------------------------------------------------------------
1311+
Symmetric difference
1312+
--------------------------------------------------------------------}
1313+
1314+
-- | \(O(n+m)\). The symmetric difference of two maps.
1315+
--
1316+
-- The result contains entries whose keys appear in exactly one of the two maps.
1317+
--
1318+
-- @
1319+
-- symmetricDifference
1320+
-- (fromList [(0,\'q\'),(2,\'b\'),(4,\'w\'),(6,\'o\')])
1321+
-- (fromList [(0,\'e\'),(3,\'r\'),(6,\'t\'),(9,\'s\')])
1322+
-- ==
1323+
-- fromList [(2,\'b\'),(3,\'r\'),(4,\'w\'),(9,\'s\')]
1324+
-- @
1325+
--
1326+
-- @since FIXME
1327+
symmetricDifference :: IntMap a -> IntMap a -> IntMap a
1328+
symmetricDifference t1@(Bin p1 l1 r1) t2@(Bin p2 l2 r2) =
1329+
case treeTreeBranch p1 p2 of
1330+
ABL -> bin p1 (symmetricDifference l1 t2) r1
1331+
ABR -> bin p1 l1 (symmetricDifference r1 t2)
1332+
BAL -> bin p2 (symmetricDifference t1 l2) r2
1333+
BAR -> bin p2 l2 (symmetricDifference t1 r2)
1334+
EQL -> bin p1 (symmetricDifference l1 l2) (symmetricDifference r1 r2)
1335+
NOM -> link (unPrefix p1) t1 (unPrefix p2) t2
1336+
symmetricDifference t1@(Bin _ _ _) t2@(Tip k2 _) = symDiffTip t2 k2 t1
1337+
symmetricDifference t1@(Bin _ _ _) Nil = t1
1338+
symmetricDifference t1@(Tip k1 _) t2 = symDiffTip t1 k1 t2
1339+
symmetricDifference Nil t2 = t2
1340+
1341+
symDiffTip :: IntMap a -> Int -> IntMap a -> IntMap a
1342+
symDiffTip !t1 !k1 = go
1343+
where
1344+
go t2@(Bin p2 l2 r2)
1345+
| nomatch k1 p2 = linkKey k1 t1 p2 t2
1346+
| left k1 p2 = bin p2 (go l2) r2
1347+
| otherwise = bin p2 l2 (go r2)
1348+
go t2@(Tip k2 _)
1349+
| k1 == k2 = Nil
1350+
| otherwise = link k1 t1 k2 t2
1351+
go Nil = t1
1352+
13071353
{--------------------------------------------------------------------
13081354
MergeWithKey
13091355
--------------------------------------------------------------------}

containers/src/Data/IntMap/Lazy.hs

+3
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@ module Data.IntMap.Lazy (
142142
, intersectionWith
143143
, intersectionWithKey
144144

145+
-- ** Symmetric difference
146+
, symmetricDifference
147+
145148
-- ** Disjoint
146149
, disjoint
147150

containers/src/Data/IntMap/Strict.hs

+3
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ module Data.IntMap.Strict (
161161
, intersectionWith
162162
, intersectionWithKey
163163

164+
-- ** Symmetric difference
165+
, symmetricDifference
166+
164167
-- ** Disjoint
165168
, disjoint
166169

containers/src/Data/IntMap/Strict/Internal.hs

+4
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ module Data.IntMap.Strict.Internal (
161161
, intersectionWith
162162
, intersectionWithKey
163163

164+
-- ** Symmetric difference
165+
, symmetricDifference
166+
164167
-- ** Disjoint
165168
, disjoint
166169

@@ -339,6 +342,7 @@ import Data.IntMap.Internal
339342
, split
340343
, splitLookup
341344
, splitRoot
345+
, symmetricDifference
342346
, toAscList
343347
, toDescList
344348
, toList

containers/src/Data/IntSet.hs

+1
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ module Data.IntSet (
108108
, difference
109109
, (\\)
110110
, intersection
111+
, symmetricDifference
111112

112113
-- * Filter
113114
, IS.filter

containers/src/Data/IntSet/Internal.hs

+40
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ module Data.IntSet.Internal (
125125
, unions
126126
, difference
127127
, intersection
128+
, symmetricDifference
128129

129130
-- * Filter
130131
, filter
@@ -656,6 +657,45 @@ intersection (Tip kx1 bm1) t2 = intersectBM t2
656657

657658
intersection Nil _ = Nil
658659

660+
{--------------------------------------------------------------------
661+
Symmetric difference
662+
--------------------------------------------------------------------}
663+
664+
-- | \(O(n+m)\). The symmetric difference of two sets.
665+
--
666+
-- The result contains elements that appear in exactly one of the two sets.
667+
--
668+
-- @
669+
-- symmetricDifference (fromList [0,2,4,6]) (fromList [0,3,6,9]) == fromList [2,3,4,9]
670+
-- @
671+
--
672+
-- @since FIXME
673+
symmetricDifference :: IntSet -> IntSet -> IntSet
674+
symmetricDifference t1@(Bin p1 l1 r1) t2@(Bin p2 l2 r2) =
675+
case treeTreeBranch p1 p2 of
676+
ABL -> bin p1 (symmetricDifference l1 t2) r1
677+
ABR -> bin p1 l1 (symmetricDifference r1 t2)
678+
BAL -> bin p2 (symmetricDifference t1 l2) r2
679+
BAR -> bin p2 l2 (symmetricDifference t1 r2)
680+
EQL -> bin p1 (symmetricDifference l1 l2) (symmetricDifference r1 r2)
681+
NOM -> link (unPrefix p1) t1 (unPrefix p2) t2
682+
symmetricDifference t1@(Bin _ _ _) t2@(Tip kx2 bm2) = symDiffTip t2 kx2 bm2 t1
683+
symmetricDifference t1@(Bin _ _ _) Nil = t1
684+
symmetricDifference t1@(Tip kx1 bm1) t2 = symDiffTip t1 kx1 bm1 t2
685+
symmetricDifference Nil t2 = t2
686+
687+
symDiffTip :: IntSet -> Int -> BitMap -> IntSet -> IntSet
688+
symDiffTip !t1 !kx1 !bm1 = go
689+
where
690+
go t2@(Bin p2 l2 r2)
691+
| nomatch kx1 p2 = linkKey kx1 t1 p2 t2
692+
| left kx1 p2 = bin p2 (go l2) r2
693+
| otherwise = bin p2 l2 (go r2)
694+
go t2@(Tip kx2 bm2)
695+
| kx1 == kx2 = tip kx1 (bm1 `xor` bm2)
696+
| otherwise = link kx1 t1 kx2 t2
697+
go Nil = t1
698+
659699
{--------------------------------------------------------------------
660700
Subset
661701
--------------------------------------------------------------------}

containers/src/Data/Map/Internal.hs

+35
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,9 @@ module Data.Map.Internal (
186186
, intersectionWith
187187
, intersectionWithKey
188188

189+
-- ** Symmetric difference
190+
, symmetricDifference
191+
189192
-- ** Disjoint
190193
, disjoint
191194

@@ -2065,6 +2068,38 @@ intersectionWithKey f (Bin _ k x1 l1 r1) t2 = case mb of
20652068
{-# INLINABLE intersectionWithKey #-}
20662069
#endif
20672070

2071+
{--------------------------------------------------------------------
2072+
Symmetric difference
2073+
--------------------------------------------------------------------}
2074+
2075+
-- | \(O\bigl(m \log\bigl(\frac{n}{m}+1\bigr)\bigr), \; 0 < m \leq n\).
2076+
-- The symmetric difference of two maps.
2077+
--
2078+
-- The result contains entries whose keys appear in exactly one of the two maps.
2079+
--
2080+
-- @
2081+
-- symmetricDifference
2082+
-- (fromList [(0,\'q\'),(2,\'b\'),(4,\'w\'),(6,\'o\')])
2083+
-- (fromList [(0,\'e\'),(3,\'r\'),(6,\'t\'),(9,\'s\')])
2084+
-- ==
2085+
-- fromList [(2,\'b\'),(3,\'r\'),(4,\'w\'),(9,\'s\')]
2086+
-- @
2087+
--
2088+
-- @since FIXME
2089+
symmetricDifference :: Ord k => Map k a -> Map k a -> Map k a
2090+
symmetricDifference Tip t2 = t2
2091+
symmetricDifference t1 Tip = t1
2092+
symmetricDifference (Bin _ k x l1 r1) t2
2093+
| found = link2 l1l2 r1r2
2094+
| otherwise = link k x l1l2 r1r2
2095+
where
2096+
!(l2, found, r2) = splitMember k t2
2097+
!l1l2 = symmetricDifference l1 l2
2098+
!r1r2 = symmetricDifference r1 r2
2099+
#if __GLASGOW_HASKELL__
2100+
{-# INLINABLE symmetricDifference #-}
2101+
#endif
2102+
20682103
{--------------------------------------------------------------------
20692104
Disjoint
20702105
--------------------------------------------------------------------}

containers/src/Data/Map/Lazy.hs

+3
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,9 @@ module Data.Map.Lazy (
164164
, intersectionWith
165165
, intersectionWithKey
166166

167+
-- ** Symmetric difference
168+
, symmetricDifference
169+
167170
-- ** Disjoint
168171
, disjoint
169172

containers/src/Data/Map/Strict.hs

+3
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ module Data.Map.Strict
180180
, intersectionWith
181181
, intersectionWithKey
182182

183+
-- ** Symmetric difference
184+
, symmetricDifference
185+
183186
-- ** Disjoint
184187
, disjoint
185188

containers/src/Data/Map/Strict/Internal.hs

+4
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@ module Data.Map.Strict.Internal
142142
, intersectionWith
143143
, intersectionWithKey
144144

145+
-- ** Symmetric difference
146+
, symmetricDifference
147+
145148
-- ** Disjoint
146149
, disjoint
147150

@@ -409,6 +412,7 @@ import Data.Map.Internal
409412
, splitAt
410413
, splitLookup
411414
, splitRoot
415+
, symmetricDifference
412416
, take
413417
, takeWhileAntitone
414418
, toList

0 commit comments

Comments
 (0)