Skip to content

Commit 5190223

Browse files
authored
Add a merge API for Set (#1169)
Add Data.Set.Merge to allow merging sets, similar to Data.Map.Merge.Lazy and Data.Map.Merge.Strict. To avoid the naming conflict, rename Set's `merge` to `link2` just like we have for Map.
1 parent 2582800 commit 5190223

File tree

5 files changed

+459
-30
lines changed

5 files changed

+459
-30
lines changed

containers-tests/containers-tests.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ library
113113
Data.Sequence.Internal.Sorting
114114
Data.Set
115115
Data.Set.Internal
116+
Data.Set.Merge
116117
Data.Tree
117118
Utils.Containers.Internal.BitQueue
118119
Utils.Containers.Internal.BitUtil

containers-tests/tests/set-properties.hs

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
{-# LANGUAGE CPP #-}
2+
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
23
import qualified Data.IntSet as IntSet
34
import Data.List (nub, sort, sortBy)
45
import qualified Data.List as List
56
import Data.Maybe (isJust, fromJust)
67
import qualified Data.Maybe as Maybe
78
import Data.Set
8-
import Data.Set.Internal (link, merge)
9+
import Data.Set.Internal (link, link2)
10+
import Data.Set.Merge
911
import Prelude hiding (lookup, null, map, filter, foldr, foldl, foldl', all, take, drop, splitAt)
1012
import Test.Tasty
1113
import Test.Tasty.HUnit
@@ -51,7 +53,7 @@ main = defaultMain $ testGroup "set-properties"
5153
, testProperty "alterF/four" prop_alterF_four
5254
, testProperty "alterF/valid" prop_alterF_valid
5355
, testProperty "prop_Link" prop_Link
54-
, testProperty "prop_Merge" prop_Merge
56+
, testProperty "prop_link2" prop_link2
5557
, testProperty "prop_UnionValid" prop_UnionValid
5658
, testProperty "prop_UnionInsert" prop_UnionInsert
5759
, testProperty "prop_UnionAssoc" prop_UnionAssoc
@@ -122,6 +124,8 @@ main = defaultMain $ testGroup "set-properties"
122124
, testProperty "lookupIndex" prop_lookupIndex
123125
, testProperty "elemAt" prop_elemAt
124126
, testProperty "deleteAt" prop_deleteAt
127+
, testProperty "merge" prop_merge
128+
, testProperty "mergeA" prop_mergeA
125129
]
126130

127131
-- A type with a peculiar Eq instance designed to make sure keys
@@ -391,10 +395,10 @@ prop_Link x = forValidUnitTree $ \t ->
391395
let (l,r) = split x t
392396
in valid (link x l r)
393397

394-
prop_Merge :: Int -> Property
395-
prop_Merge x = forValidUnitTree $ \t ->
398+
prop_link2 :: Int -> Property
399+
prop_link2 x = forValidUnitTree $ \t ->
396400
let (l,r) = split x t
397-
in valid (merge l r)
401+
in valid (link2 l r)
398402

399403
{--------------------------------------------------------------------
400404
Union
@@ -752,3 +756,85 @@ prop_deleteAt i s = 0 <= i && i < size s ==>
752756
toList s' === [x | (j, x) <- zip [0..] (toList s), i /= j]
753757
where
754758
s' = deleteAt i s
759+
760+
prop_merge
761+
:: WhenMissingSpec Int
762+
-> WhenMissingSpec Int
763+
-> WhenMatchedSpec Int
764+
-> Set Int
765+
-> Set Int
766+
-> Property
767+
prop_merge miss1 miss2 match s1 s2 =
768+
valid s .&&.
769+
s === (filter (runIdentity . runWhenMissing miss1') s1Only `union`
770+
filter (runIdentity . runWhenMissing miss2') s2Only `union`
771+
filter (runIdentity . runWhenMatched match') s12Both)
772+
where
773+
miss1' = toSimpleWhenMissing miss1
774+
miss2' = toSimpleWhenMissing miss2
775+
match' = toSimpleWhenMatched match
776+
s = merge miss1' miss2' match' s1 s2
777+
778+
s1Only = difference s1 s2
779+
s2Only = difference s2 s1
780+
s12Both = intersection s1 s2
781+
782+
toSimpleWhenMissing s = case s of
783+
DropMissing -> dropMissing
784+
PreserveMissing -> preserveMissing
785+
FilterMissing f -> filterMissing (applyFun f)
786+
787+
toSimpleWhenMatched (FilterMatched f) = filterMatched (applyFun f)
788+
789+
prop_mergeA
790+
:: WhenMissingSpec Int
791+
-> WhenMissingSpec Int
792+
-> WhenMatchedSpec Int
793+
-> Set Int
794+
-> Set Int
795+
-> Property
796+
prop_mergeA miss1 miss2 match s1 s2 =
797+
valid s .&&.
798+
s === (filter (snd . runWhenMissing miss1') s1Only `union`
799+
filter (snd . runWhenMissing miss2') s2Only `union`
800+
filter (snd . runWhenMatched match') s12Both) .&&.
801+
xs === sort (concat (fmap (fst . runWhenMissing miss1') (toList s1Only) ++
802+
fmap (fst . runWhenMissing miss2') (toList s2Only) ++
803+
fmap (fst . runWhenMatched match') (toList s12Both)))
804+
where
805+
miss1' = toWhenMissing miss1
806+
miss2' = toWhenMissing miss2
807+
match' = toWhenMatched match
808+
(xs, s) = mergeA miss1' miss2' match' s1 s2
809+
810+
s1Only = difference s1 s2
811+
s2Only = difference s2 s1
812+
s12Both = intersection s1 s2
813+
814+
toWhenMissing s = case s of
815+
DropMissing -> dropMissing
816+
PreserveMissing -> preserveMissing
817+
FilterMissing f -> filterAMissing (\x -> ([x], applyFun f x))
818+
819+
toWhenMatched (FilterMatched f) = filterAMatched (\x -> ([x], applyFun f x))
820+
821+
data WhenMissingSpec a
822+
= DropMissing
823+
| PreserveMissing
824+
| FilterMissing (Fun a Bool)
825+
deriving Show
826+
827+
instance (Arbitrary a, CoArbitrary a, Function a)
828+
=> Arbitrary (WhenMissingSpec a) where
829+
arbitrary = oneof
830+
[ pure DropMissing
831+
, pure PreserveMissing
832+
, FilterMissing <$> arbitrary
833+
]
834+
shrink s = case s of
835+
DropMissing -> []
836+
PreserveMissing -> []
837+
FilterMissing f -> FilterMissing <$> shrink f
838+
839+
newtype WhenMatchedSpec a = FilterMatched (Fun a Bool)
840+
deriving (Show, Arbitrary)

containers/containers.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ Library
7171
Data.Map.Internal
7272
Data.Map.Internal.Debug
7373
Data.Set.Internal
74+
Data.Set.Merge
7475
Data.Set
7576
Data.Graph
7677
Data.Sequence

0 commit comments

Comments
 (0)