diff --git a/README.md b/README.md index 6776ec8..a53d419 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ TBD ## TODO - remove -- union ## Limitations @@ -51,7 +50,11 @@ Returns a new KeySet that represents the inverse Set of this one. ### `intersect(other)` -Returns a new KeySet with the intersection (A ∩ B) of both Sets. +Returns a new KeySet with the intersection (A ∩ B) of both Sets: a set that contains the elements included in both sets. + +### `union(other)` + +Returns a new KeySet with the union (A ∩ B) of both Sets: a set that contains the elements in any of the sets. ### `includes(element)` diff --git a/key_set/base.py b/key_set/base.py index 3e19daa..a9b0e3e 100644 --- a/key_set/base.py +++ b/key_set/base.py @@ -59,6 +59,16 @@ def clone(self) -> KeySet: """Returns a new KeySet that represents the same Set of this one.""" pass + @abstractmethod + def intersect(self, other: KeySet) -> KeySet: + """Returns a new KeySet that represents the intersection (A ∩ B).""" + pass + + @abstractmethod + def union(self, other: KeySet) -> KeySet: + """Returns a new KeySet that contains the elements of both (A U B).""" + pass + class KeySetAll(KeySet): """Represents the ALL sets: π•Œ (the entirety of possible keys).""" @@ -99,6 +109,10 @@ def intersect(self, other: KeySet) -> KeySet: """Returns a new KeySet that represents the intersection (A ∩ B).""" return other.clone() + def union(self, _other: KeySet) -> KeySet: + """Returns a new KeySet that contains the elements of both (A U B).""" + return self.clone() + class KeySetNone(KeySet): """Represents the NONE sets: ΓΈ (empty set).""" @@ -139,6 +153,10 @@ def intersect(self, _other: KeySet) -> KeySetNone: """Returns a new KeySet that represents the intersection (A ∩ B).""" return self.clone() + def union(self, other: KeySet) -> KeySet: + """Returns a new KeySet that contains the elements of both (A U B).""" + return other.clone() + class KeySetSome(KeySet): """Represents the SOME sets: a concrete set (`A βŠ‚ π•Œ`).""" @@ -196,6 +214,20 @@ def intersect(self, other: KeySet) -> KeySet: return build_some_or_none(elems) return NotImplemented + def union(self, other: KeySet) -> KeySet: + """Returns a new KeySet that contains the elements of both (A U B).""" + if other.represents_all(): + return other.clone() + if other.represents_none(): + return self.clone() + if other.represents_some(): + elems = self._elements.union(other.elements()) + return build_some_or_none(elems) + if other.represents_all_except_some(): + elems = other.elements().difference(self._elements) + return build_all_except_some_or_all(elems) + return NotImplemented + class KeySetAllExceptSome(KeySet): """Represents the ALL_EXCEPT_SOME sets: the complementary of a concrete set. @@ -256,6 +288,20 @@ def intersect(self, other: KeySet) -> KeySet: return build_all_except_some_or_all(elems) return NotImplemented + def union(self, other: KeySet) -> KeySet: + """Returns a new KeySet that contains the elements of both (A U B).""" + if other.represents_all(): + return other.clone() + if other.represents_none(): + return self.clone() + if other.represents_some(): + elems = self._elements.difference(other.elements()) + return build_all_except_some_or_all(elems) + if other.represents_all_except_some(): + elems = other.elements().intersection(self._elements) + return build_all_except_some_or_all(elems) + return NotImplemented + TS = Union[KeySetSome, KeySetNone] TAES = Union[KeySetAllExceptSome, KeySetAll] diff --git a/tests/test_all.py b/tests/test_all.py index 74ef907..48fddf5 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -62,3 +62,27 @@ def test_intersect_all_except_some(self) -> None: def test_includes(self) -> None: ks = KeySetAll() assert ks.includes('a') + + def test_union_all(self) -> None: + ks = KeySetAll() + other = KeySetAll() + actual = ks.union(other) + assert actual.represents_all() + + def test_union_none(self) -> None: + ks = KeySetAll() + other = KeySetNone() + actual = ks.union(other) + assert actual.represents_all() + + def test_union_some(self) -> None: + ks = KeySetAll() + other = KeySetSome({'a', 'b'}) + actual = ks.union(other) + assert actual.represents_all() + + def test_union_all_except_some(self) -> None: + ks = KeySetAll() + other = KeySetAllExceptSome({'a', 'b'}) + actual = ks.union(other) + assert actual.represents_all() diff --git a/tests/test_all_except_some.py b/tests/test_all_except_some.py index efa8c39..d199805 100644 --- a/tests/test_all_except_some.py +++ b/tests/test_all_except_some.py @@ -122,3 +122,85 @@ def test_includes_included(self) -> None: def test_includes_missing(self) -> None: ks = KeySetAllExceptSome({'a', 'b'}) assert ks.includes('c') + + def test_union_all(self) -> None: + ks = KeySetAllExceptSome({'a', 'b'}) + other = KeySetAll() + actual = ks.union(other) + assert actual.represents_all() + assert actual == other + assert actual is not other + + def test_union_none(self) -> None: + ks = KeySetAllExceptSome({'a', 'b'}) + other = KeySetNone() + actual = ks.union(other) + assert actual.represents_all_except_some() + assert actual.elements() == {'a', 'b'} + + def test_union_some_same_keys(self) -> None: + ks = KeySetAllExceptSome({'a', 'b'}) + other = KeySetSome({'a', 'b'}) + actual = ks.union(other) + assert actual.represents_all() + + def test_union_some_subset_keys(self) -> None: + ks = KeySetAllExceptSome({'a', 'b'}) + other = KeySetSome({'a'}) + actual = ks.union(other) + assert actual.represents_all_except_some() + assert actual.elements() == {'b'} + + def test_union_some_superset_keys(self) -> None: + ks = KeySetAllExceptSome({'a', 'b'}) + other = KeySetSome({'a', 'b', 'c'}) + actual = ks.union(other) + assert actual.represents_all() + + def test_union_some_with_some_common_keys(self) -> None: + ks = KeySetAllExceptSome({'a', 'b'}) + other = KeySetSome({'a', 'c'}) + actual = ks.union(other) + assert actual.represents_all_except_some() + assert actual.elements() == {'b'} + + def test_union_some_without_common_keys(self) -> None: + ks = KeySetAllExceptSome({'a', 'b'}) + other = KeySetSome({'c', 'd'}) + actual = ks.union(other) + assert actual.represents_all_except_some() + assert actual.elements() == {'a', 'b'} + + def test_union_all_except_some_same_keys(self) -> None: + ks = KeySetAllExceptSome({'a', 'b'}) + other = KeySetAllExceptSome({'a', 'b'}) + actual = ks.union(other) + assert actual.represents_all_except_some() + assert actual.elements() == {'a', 'b'} + + def test_union_all_except_some_subset_keys(self) -> None: + ks = KeySetAllExceptSome({'a', 'b'}) + other = KeySetAllExceptSome({'a'}) + actual = ks.union(other) + assert actual.represents_all_except_some() + assert actual.elements() == {'a'} + + def test_union_all_except_some_superset_keys(self) -> None: + ks = KeySetAllExceptSome({'a', 'b'}) + other = KeySetAllExceptSome({'a', 'b', 'c'}) + actual = ks.union(other) + assert actual.represents_all_except_some() + assert actual.elements() == {'a', 'b'} + + def test_union_all_except_some_with_some_common_keys(self) -> None: + ks = KeySetAllExceptSome({'a', 'b'}) + other = KeySetAllExceptSome({'a', 'c'}) + actual = ks.union(other) + assert actual.represents_all_except_some() + assert actual.elements() == {'a'} + + def test_union_all_except_some_without_common_keys(self) -> None: + ks = KeySetAllExceptSome({'a', 'b'}) + other = KeySetAllExceptSome({'c', 'd'}) + actual = ks.union(other) + assert actual.represents_all() diff --git a/tests/test_none.py b/tests/test_none.py index 070ee27..03e7e79 100644 --- a/tests/test_none.py +++ b/tests/test_none.py @@ -64,3 +64,33 @@ def test_intersect_all_except_some(self) -> None: def test_includes(self) -> None: ks = KeySetNone() assert not ks.includes('a') + + def test_union_all(self) -> None: + ks = KeySetNone() + other = KeySetAll() + actual = ks.union(other) + assert actual.represents_all() + + def test_union_none(self) -> None: + ks = KeySetNone() + other = KeySetNone() + actual = ks.union(other) + assert actual.represents_none() + + def test_union_some(self) -> None: + ks = KeySetNone() + other = KeySetSome({'a', 'b'}) + actual = ks.union(other) + assert actual.represents_some() + assert actual.elements() == {'a', 'b'} + assert actual == other + assert actual is not other + + def test_union_all_except_some(self) -> None: + ks = KeySetNone() + other = KeySetAllExceptSome({'a', 'b'}) + actual = ks.union(other) + assert actual.represents_all_except_some() + assert actual.elements() == {'a', 'b'} + assert actual == other + assert actual is not other diff --git a/tests/test_some.py b/tests/test_some.py index bb99b8e..80ca9a7 100644 --- a/tests/test_some.py +++ b/tests/test_some.py @@ -122,3 +122,84 @@ def test_includes_included(self) -> None: def test_includes_missing(self) -> None: ks = KeySetSome({'a', 'b'}) assert not ks.includes('c') + + def test_union_all(self) -> None: + ks = KeySetSome({'a', 'b'}) + other = KeySetAll() + actual = ks.union(other) + assert actual.represents_all() + + def test_union_none(self) -> None: + ks = KeySetSome({'a', 'b'}) + other = KeySetNone() + actual = ks.union(other) + assert actual.represents_some() + assert actual.elements() == {'a', 'b'} + + def test_union_some_same_keys(self) -> None: + ks = KeySetSome({'a', 'b'}) + other = KeySetSome({'a', 'b'}) + actual = ks.union(other) + assert actual.represents_some() + assert actual.elements() == {'a', 'b'} + + def test_union_some_subset_keys(self) -> None: + ks = KeySetSome({'a', 'b'}) + other = KeySetSome({'a'}) + actual = ks.union(other) + assert actual.represents_some() + assert actual.elements() == {'a', 'b'} + + def test_union_some_superset_keys(self) -> None: + ks = KeySetSome({'a', 'b'}) + other = KeySetSome({'a', 'b', 'c'}) + actual = ks.union(other) + assert actual.represents_some() + assert actual.elements() == {'a', 'b', 'c'} + + def test_union_some_with_some_common_keys(self) -> None: + ks = KeySetSome({'a', 'b'}) + other = KeySetSome({'a', 'c'}) + actual = ks.union(other) + assert actual.represents_some() + assert actual.elements() == {'a', 'b', 'c'} + + def test_union_some_without_common_keys(self) -> None: + ks = KeySetSome({'a', 'b'}) + other = KeySetSome({'c', 'd'}) + actual = ks.union(other) + assert actual.represents_some() + assert actual.elements() == {'a', 'b', 'c', 'd'} + + def test_union_all_except_some_same_keys(self) -> None: + ks = KeySetSome({'a', 'b'}) + other = KeySetAllExceptSome({'a', 'b'}) + actual = ks.union(other) + assert actual.represents_all() + + def test_union_all_except_some_subset_keys(self) -> None: + ks = KeySetSome({'a', 'b'}) + other = KeySetAllExceptSome({'a'}) + actual = ks.union(other) + assert actual.represents_all() + + def test_union_all_except_some_superset_keys(self) -> None: + ks = KeySetSome({'a', 'b'}) + other = KeySetAllExceptSome({'a', 'b', 'c'}) + actual = ks.union(other) + assert actual.represents_all_except_some() + assert actual.elements() == {'c'} + + def test_union_all_except_some_with_some_common_keys(self) -> None: + ks = KeySetSome({'a', 'b'}) + other = KeySetAllExceptSome({'a', 'c'}) + actual = ks.union(other) + assert actual.represents_all_except_some() + assert actual.elements() == {'c'} + + def test_union_all_except_some_without_common_keys(self) -> None: + ks = KeySetSome({'a', 'b'}) + other = KeySetAllExceptSome({'c', 'd'}) + actual = ks.union(other) + assert actual.represents_all_except_some() + assert actual.elements() == {'c', 'd'}