diff --git a/doc/api.rst b/doc/api.rst index 123cf6c..dc93279 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -2,6 +2,10 @@ General Set API =============== +Inherited methods +================= + + From the `Python documentation `__. @@ -24,3 +28,18 @@ From the `Python documentation None: - """Create a new :class:`OrderedSet`, optionally initialized with *items*.""" - if items is _NotProvided: - self._dict = {} - else: - # type-ignore-reason: - # mypy thinks 'items' can still be Type[_NotProvided] here. - self._dict = dict.fromkeys(items) # type: ignore[arg-type] - - def __eq__(self, other: object) -> bool: - """Return whether this set is equal to *other*.""" - return (isinstance(other, Set) - and len(self) == len(other) - and all(i in other for i in self)) - - def __repr__(self) -> str: - """Return a string representation of this set.""" - if len(self) == 0: - return "OrderedSet()" - return "OrderedSet({" + ", ".join([repr(k) for k in self._dict]) + "})" - - def add(self, element: T) -> None: - """Add *element* to this set.""" - self._dict = {**self._dict, **{element: None}} - - def clear(self) -> None: - """Remove all elements from this set.""" - self._dict.clear() - - def copy(self) -> OrderedSet[T]: - """Return a shallow copy of this set.""" - return OrderedSet(self._dict.copy()) - - def difference(self, *others: Iterable[T]) -> OrderedSet[T]: - """Return all elements that are in this set but not in *others*.""" - if not others: - return OrderedSet(self._dict) - other_elems = set.union(*map(set, others)) - items = {item: None for item in self if item not in other_elems} - return OrderedSet(items) - - def difference_update(self, *others: Iterable[T]) -> None: - """Update this set to remove all items that are in *others*.""" - for other in others: - for e in other: - self.discard(e) - - def discard(self, element: T) -> None: - """Remove *element* from this set, if it is present.""" - if element in self._dict: - del self._dict[element] - - def intersection(self, *others: Iterable[T]) -> OrderedSet[T]: - """Return the intersection of this set and *others*.""" - if not others: - return OrderedSet(self._dict) - - result_elements = [] - for element in self._dict.keys(): - if all(element in other for other in others): - result_elements.append(element) - - return OrderedSet(result_elements) - - def intersection_update(self, *others: Iterable[T]) -> None: - """Update this set to be the intersection of itself and *others*.""" - if not others: - return - - common_keys = list(self._dict.keys()) - for other in others: - common_keys = [key for key in common_keys if key in set(other)] - - self._dict = dict.fromkeys(common_keys) - - def isdisjoint(self, s: Iterable[T]) -> bool: - """Return whether this set is disjoint with *s*.""" - return self._dict.keys().isdisjoint(s) - - def issubset(self, s: Iterable[T]) -> bool: - """Return whether this set is a subset of *s*.""" - return all(i in s for i in self) - - def issuperset(self, s: Iterable[T]) -> bool: - """Return whether this set is a superset of *s*.""" - return set(self).issuperset(set(s)) - - def pop(self) -> T: - """Remove and return the most recently added element from this set.""" - items = list(self._dict) - result = items.pop() - self._dict = dict.fromkeys(items) - return result - - def remove(self, element: T) -> None: - """Remove *element* from this set, raising :exc:`KeyError` if not present.""" - del self._dict[element] - - def symmetric_difference(self, s: Iterable[T]) -> OrderedSet[T]: - """Return the symmetric difference of this set and *s*.""" - return OrderedSet( - dict.fromkeys([e for e in self._dict if e not in s] - + [e for e in s if e not in self._dict])) - - def symmetric_difference_update(self, s: Iterable[T]) -> None: - """Update this set to be the symmetric difference of itself and *s*.""" - self._dict = self.symmetric_difference(s)._dict - - def union(self, *others: Iterable[T]) -> OrderedSet[T]: - """Return the union of this set and *others*.""" - return OrderedSet(list(self._dict) - + [e for other in others for e in other]) - - def update(self, *others: Iterable[T]) -> None: - """Update this set to be the union of itself and *others*.""" - self._dict = self.union(*others)._dict - - def __len__(self) -> int: - """Return the number of elements in this set.""" - return len(self._dict) - - def __contains__(self, o: object) -> bool: - """Return whether *o* is in this set.""" - return o in self._dict - - def __iter__(self) -> Iterator[T]: - """Return an iterator over the elements of this set.""" - return iter(self._dict) - - def __and__(self, s: Set[T]) -> OrderedSet[T]: - """Return the intersection of this set and *s*.""" - return self.intersection(s) - - def __iand__(self, s: Set[T]) -> OrderedSet[T]: - """Update this set to be the intersection of itself and *s*.""" - result = self.intersection(s) - self._dict = result._dict - return result - - def __or__(self, s: Set[Any]) -> OrderedSet[T]: - """Return the union of this set and *s*.""" - return self.union(s) - - def __ior__(self, s: Set[Any]) -> OrderedSet[T]: - """Update this set to be the union of itself and *s*.""" - result = self.union(s) - self._dict = result._dict - return result - - def __sub__(self, s: Set[T]) -> OrderedSet[T]: - """Return the difference of this set and *s*.""" - return self.difference(s) - - def __isub__(self, s: Set[T]) -> OrderedSet[T]: - """Update this set to be the difference of itself and *s*.""" - result = self.difference(s) - self._dict = result._dict - return result - - def __xor__(self, s: Set[Any]) -> OrderedSet[T]: - """Return the symmetric difference of this set and *s*.""" - return self.symmetric_difference(s) - - def __ixor__(self, s: Set[Any]) -> OrderedSet[T]: - """Update this set to be the symmetric difference of itself and *s*.""" - result = self.symmetric_difference(s) - self._dict = result._dict - return result - - def __le__(self, s: Set[T]) -> bool: - """Return whether this set is a subset of *s*.""" - return self.issubset(s) - - def __lt__(self, s: Set[T]) -> bool: - """Return whether this set is a proper subset of *s*.""" - return len(self) < len(s) and self.issubset(s) - - def __ge__(self, s: Set[T]) -> bool: - """Return whether this set is a superset of *s*.""" - return set(self) >= set(s) - - def __gt__(self, s: Set[T]) -> bool: - """Return whether this set is a proper superset of *s*.""" - return len(self) > len(s) and set(self) > set(s) - - -class FrozenOrderedSet(AbstractSet[T]): +class FrozenOrderedSet(abc_Set[T]): """A frozen set class that preserves insertion order. It can be used as a drop-in replacement for :class:`frozenset` where @@ -277,15 +86,15 @@ def __hash__(self) -> int: def __eq__(self, other: object) -> bool: """Return whether this set is equal to *other*.""" - return (isinstance(other, Set) + return (isinstance(other, abc_Set) and len(self) == len(other) and all(i in other for i in self)) def __repr__(self) -> str: """Return a string representation of this set.""" if len(self) == 0: - return "FrozenOrderedSet()" - return "FrozenOrderedSet({" + ", ".join([repr(k) for k in self._dict]) + "})" + return f"{self.__class__.__name__}()" + return self.__class__.__name__ + "({" + ", ".join([repr(k) for k in self._dict]) + "})" def __len__(self) -> int: """Return the number of elements in this set.""" @@ -301,7 +110,7 @@ def __iter__(self) -> Iterator[T]: def copy(self) -> FrozenOrderedSet[T]: """Return a shallow copy of this set.""" - return FrozenOrderedSet(self._dict) + return self.__class__(self._dict) def difference(self, *others: Iterable[T]) -> FrozenOrderedSet[T]: """Return the difference of this set and *others*.""" @@ -339,25 +148,108 @@ def issubset(self, s: Iterable[T]) -> bool: def issuperset(self, s: Iterable[T]) -> bool: """Return whether this set is a superset of *s*.""" - return set(self).issuperset(set(s)) + return all(i in self for i in s) def union(self, *others: Iterable[T]) -> FrozenOrderedSet[T]: """Return the union of this set and *others*.""" return FrozenOrderedSet(list(self._dict) + [e for other in others for e in other]) - def __and__(self, s: Set[T]) -> FrozenOrderedSet[T]: + def __and__(self, s: abc_Set[T]) -> FrozenOrderedSet[T]: """Return the intersection of this set and *s*.""" return self.intersection(s) - def __or__(self, s: Set[Any]) -> FrozenOrderedSet[T]: + def __or__(self, s: abc_Set[Any]) -> FrozenOrderedSet[T]: """Return the union of this set and *s*.""" return self.union(s) - def __sub__(self, s: Set[T]) -> FrozenOrderedSet[T]: + def __sub__(self, s: abc_Set[T]) -> FrozenOrderedSet[T]: """Return the difference of this set and *s*.""" return self.difference(s) - def __xor__(self, s: Set[Any]) -> FrozenOrderedSet[T]: + def __xor__(self, s: abc_Set[Any]) -> FrozenOrderedSet[T]: """Return the symmetric difference of this set and *s*.""" return self.symmetric_difference(s) + + +class OrderedSet(abc_MutableSet[T], FrozenOrderedSet): + """A set class that preserves insertion order. + + It can be used as a drop-in replacement for :class:`set` where ordering is + desired. + """ + + __hash__ = None + + def add(self, element: T) -> None: + """Add *element* to this set.""" + self._dict = {**self._dict, **{element: None}} + + def clear(self) -> None: + """Remove all elements from this set.""" + self._dict.clear() + + def difference_update(self, *others: Iterable[T]) -> None: + """Update this set to remove all items that are in *others*.""" + for other in others: + for e in other: + self.discard(e) + + def discard(self, element: T) -> None: + """Remove *element* from this set, if it is present.""" + if element in self._dict: + del self._dict[element] + + def intersection_update(self, *others: Iterable[T]) -> None: + """Update this set to be the intersection of itself and *others*.""" + if not others: + return + + common_keys = list(self._dict.keys()) + for other in others: + common_keys = [key for key in common_keys if key in set(other)] + + self._dict = dict.fromkeys(common_keys) + + def pop(self) -> T: + """Remove and return the most recently added element from this set.""" + items = list(self._dict) + result = items.pop() + self._dict = dict.fromkeys(items) + return result + + def remove(self, element: T) -> None: + """Remove *element* from this set, raising :exc:`KeyError` if not present.""" + del self._dict[element] + + def symmetric_difference_update(self, s: Iterable[T]) -> None: + """Update this set to be the symmetric difference of itself and *s*.""" + self._dict = self.symmetric_difference(s)._dict + + def update(self, *others: Iterable[T]) -> None: + """Update this set to be the union of itself and *others*.""" + self._dict = self.union(*others)._dict + + def __iand__(self, s: abc_Set[T]) -> OrderedSet[T]: + """Update this set to be the intersection of itself and *s*.""" + result = self.intersection(s) + self._dict = result._dict + return result + + def __ior__(self, s: abc_Set[Any]) -> OrderedSet[T]: + """Update this set to be the union of itself and *s*.""" + result = self.union(s) + self._dict = result._dict + return result + + def __isub__(self, s: abc_Set[T]) -> OrderedSet[T]: + """Update this set to be the difference of itself and *s*.""" + result = self.difference(s) + self._dict = result._dict + return result + + def __ixor__(self, s: abc_Set[Any]) -> OrderedSet[T]: + """Update this set to be the symmetric difference of itself and *s*.""" + result = self.symmetric_difference(s) + self._dict = result._dict + return result diff --git a/test/test_orderedsets.py b/test/test_orderedsets.py index 67eeb8f..0b3aa0b 100644 --- a/test/test_orderedsets.py +++ b/test/test_orderedsets.py @@ -122,11 +122,12 @@ def test_simple_ordered(_cls: T_ordered_set[str]) -> None: @all_set_types -def test_str_repr(_cls: T_ordered_set[Any]) -> None: - cls_name = "" if issubclass(_cls, set) else _cls.__name__ + "(" - end = "" if issubclass(_cls, set) else ")" +def test_str_repr(_cls: T_set[Any]) -> None: + cls_name = "" if _cls == set else _cls.__name__ + "(" + end = "" if _cls == set else ")" s = _cls([1]) + print(repr(s), str(s), cls_name + "{'d'}" + end) assert repr(s) == str(s) == cls_name + "{1}" + end s = _cls(["d"]) @@ -134,7 +135,7 @@ def test_str_repr(_cls: T_ordered_set[Any]) -> None: s = _cls() assert repr(s) == str(s) \ - == "set()" if issubclass(_cls, set) else f"{cls_name}" + end + == "set()" if _cls == set else f"{cls_name}" + end if _cls in ordered_set_types: s = _cls([1, 4, 1, 4]) @@ -242,6 +243,7 @@ def test_clear_mutable(_cls: T_mutable_set[str]) -> None: @all_set_types def test_convert_to_set(_cls: T_set[int]) -> None: assert {1, 2, 3} == set(_cls([3, 1, 2])) + assert {1, 2, 3} == frozenset(_cls([3, 1, 2])) @all_ordered_set_types @@ -654,12 +656,15 @@ def test_issubset(_cls: T_set[int]) -> None: assert s2.issubset(s1) -@all_set_types +@all_ordered_set_types def test_issuperset(_cls: T_set[int]) -> None: s1 = _cls([3, 1, 2]) s2 = _cls([1, 7]) assert not s1.issuperset(s2) + s3 = _cls([1, 2]) + assert s1.issuperset(s3) + if _cls in mutable_set_types: s2.discard(7) # type: ignore[union-attr] assert s1.issuperset(s2) @@ -776,16 +781,94 @@ def test_ordering(_cls: T_set[int]) -> None: # assert not (oset3 >= list(oset4)) -@all_ordered_set_types -def test_isinstance(_cls: T_ordered_set[int]) -> None: - assert isinstance(_cls(), AbstractSet) - assert not isinstance(_cls(), Set) - assert not isinstance(_cls(), set) - assert not isinstance(_cls(), frozenset) +@all_set_types +def test_isinstance(_cls: T_set[int]) -> None: + # Note that some tests appear multiple times, this is intentional. + + from collections.abc import MutableSet as abc_MutableSet + from collections.abc import Set as abc_Set + + # All of the following imports from 'typing' are deprecated as of Python 3.9 + from typing import AbstractSet as tp_AbstractSet + from typing import FrozenSet as tp_FrozenSet + from typing import MutableSet as tp_MutableSet + from typing import Set as tp_Set + + # set + if _cls == set: + assert isinstance(set(), tp_AbstractSet) + assert isinstance(set(), tp_Set) + assert isinstance(set(), tp_MutableSet) + assert not isinstance(set(), tp_FrozenSet) + assert isinstance(set(), abc_Set) + assert isinstance(set(), abc_MutableSet) + + assert isinstance(_cls(), set) + assert not isinstance(_cls(), frozenset) + + assert not isinstance(_cls(), OrderedSet) + assert not isinstance(_cls(), FrozenOrderedSet) + + # OrderedSet + if _cls == OrderedSet: + assert isinstance(OrderedSet(), tp_AbstractSet) + # assert isinstance(OrderedSet(), tp_Set) + assert isinstance(OrderedSet(), tp_MutableSet) + assert not isinstance(OrderedSet(), tp_FrozenSet) + assert isinstance(OrderedSet(), abc_Set) + assert isinstance(OrderedSet(), abc_MutableSet) + + # assert isinstance(_cls(), set) + assert not isinstance(_cls(), frozenset) - if _cls in mutable_set_types: assert isinstance(_cls(), OrderedSet) + # assert not isinstance(_cls(), FrozenOrderedSet) + + # frozenset + if _cls == frozenset: + assert isinstance(frozenset(), tp_AbstractSet) + assert not isinstance(frozenset(), tp_Set) + assert not isinstance(frozenset(), tp_MutableSet) + assert isinstance(frozenset(), tp_FrozenSet) + assert isinstance(frozenset(), abc_Set) + assert not isinstance(frozenset(), abc_MutableSet) + + assert not isinstance(_cls(), set) + assert isinstance(_cls(), frozenset) + + assert not isinstance(_cls(), OrderedSet) assert not isinstance(_cls(), FrozenOrderedSet) - else: + + # FrozenOrderedSet + if _cls == FrozenOrderedSet: + assert isinstance(FrozenOrderedSet(), tp_AbstractSet) + assert not isinstance(FrozenOrderedSet(), tp_Set) + assert not isinstance(FrozenOrderedSet(), tp_MutableSet) + # assert isinstance(FrozenOrderedSet(), tp_FrozenSet) + assert isinstance(FrozenOrderedSet(), abc_Set) + assert not isinstance(FrozenOrderedSet(), abc_MutableSet) + + assert not isinstance(_cls(), set) + # assert isinstance(_cls(), frozenset) + assert not isinstance(_cls(), OrderedSet) assert isinstance(_cls(), FrozenOrderedSet) + + assert isinstance(_cls(), tp_AbstractSet) + assert isinstance(_cls(), abc_Set) + assert isinstance(_cls(), AbstractSet) + + if _cls in mutable_set_types: + # assert isinstance(_cls(), Set) + # assert isinstance(_cls(), set) + assert not isinstance(_cls(), frozenset) + if _cls in ordered_set_types: + assert isinstance(_cls(), OrderedSet) + # assert not isinstance(_cls(), FrozenOrderedSet) + else: + assert not isinstance(_cls(), Set) + assert not isinstance(_cls(), set) + # assert isinstance(_cls(), frozenset) + if _cls in ordered_set_types: + assert not isinstance(_cls(), OrderedSet) + assert isinstance(_cls(), FrozenOrderedSet)