Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/boltons/setutils.py: 18%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Copyright (c) 2013, Mahmoud Hashemi
2#
3# Redistribution and use in source and binary forms, with or without
4# modification, are permitted provided that the following conditions are
5# met:
6#
7# * Redistributions of source code must retain the above copyright
8# notice, this list of conditions and the following disclaimer.
9#
10# * Redistributions in binary form must reproduce the above
11# copyright notice, this list of conditions and the following
12# disclaimer in the documentation and/or other materials provided
13# with the distribution.
14#
15# * The names of the contributors may not be used to endorse or
16# promote products derived from this software without specific
17# prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31"""\
33The :class:`set` type brings the practical expressiveness of
34set theory to Python. It has a very rich API overall, but lacks a
35couple of fundamental features. For one, sets are not ordered. On top
36of this, sets are not indexable, i.e, ``my_set[8]`` will raise an
37:exc:`TypeError`. The :class:`IndexedSet` type remedies both of these
38issues without compromising on the excellent complexity
39characteristics of Python's built-in set implementation.
40"""
43from bisect import bisect_left
44from collections.abc import MutableSet
45from itertools import chain, islice
46import operator
48try:
49 from .typeutils import make_sentinel
50 _MISSING = make_sentinel(var_name='_MISSING')
51except ImportError:
52 _MISSING = object()
55__all__ = ['IndexedSet', 'complement']
58_COMPACTION_FACTOR = 8
60# TODO: inherit from set()
61# TODO: .discard_many(), .remove_many()
62# TODO: raise exception on non-set params?
63# TODO: technically reverse operators should probably reverse the
64# order of the 'other' inputs and put self last (to try and maintain
65# insertion order)
68class IndexedSet(MutableSet):
69 """``IndexedSet`` is a :class:`collections.MutableSet` that maintains
70 insertion order and uniqueness of inserted elements. It's a hybrid
71 type, mostly like an OrderedSet, but also :class:`list`-like, in
72 that it supports indexing and slicing.
74 Args:
75 other (iterable): An optional iterable used to initialize the set.
77 >>> x = IndexedSet(list(range(4)) + list(range(8)))
78 >>> x
79 IndexedSet([0, 1, 2, 3, 4, 5, 6, 7])
80 >>> x - set(range(2))
81 IndexedSet([2, 3, 4, 5, 6, 7])
82 >>> x[-1]
83 7
84 >>> fcr = IndexedSet('freecreditreport.com')
85 >>> ''.join(fcr[:fcr.index('.')])
86 'frecditpo'
88 Standard set operators and interoperation with :class:`set` are
89 all supported:
91 >>> fcr & set('cash4gold.com')
92 IndexedSet(['c', 'd', 'o', '.', 'm'])
94 As you can see, the ``IndexedSet`` is almost like a ``UniqueList``,
95 retaining only one copy of a given value, in the order it was
96 first added. For the curious, the reason why IndexedSet does not
97 support setting items based on index (i.e, ``__setitem__()``),
98 consider the following dilemma::
100 my_indexed_set = [A, B, C, D]
101 my_indexed_set[2] = A
103 At this point, a set requires only one *A*, but a :class:`list` would
104 overwrite *C*. Overwriting *C* would change the length of the list,
105 meaning that ``my_indexed_set[2]`` would not be *A*, as expected with a
106 list, but rather *D*. So, no ``__setitem__()``.
108 Otherwise, the API strives to be as complete a union of the
109 :class:`list` and :class:`set` APIs as possible.
110 """
111 def __init__(self, other=None):
112 self.item_index_map = dict()
113 self.item_list = []
114 self.dead_indices = []
115 self._compactions = 0
116 self._c_max_size = 0
117 if other:
118 self.update(other)
120 # internal functions
121 @property
122 def _dead_index_count(self):
123 return len(self.item_list) - len(self.item_index_map)
125 def _compact(self):
126 if not self.dead_indices:
127 return
128 self._compactions += 1
129 dead_index_count = self._dead_index_count
130 items, index_map = self.item_list, self.item_index_map
131 self._c_max_size = max(self._c_max_size, len(items))
132 for i, item in enumerate(self):
133 items[i] = item
134 index_map[item] = i
135 del items[-dead_index_count:]
136 del self.dead_indices[:]
138 def _cull(self):
139 ded = self.dead_indices
140 if not ded:
141 return
142 items, ii_map = self.item_list, self.item_index_map
143 if not ii_map:
144 del items[:]
145 del ded[:]
146 elif len(ded) > 384:
147 self._compact()
148 elif self._dead_index_count > (len(items) / _COMPACTION_FACTOR):
149 self._compact()
150 elif items[-1] is _MISSING: # get rid of dead right hand side
151 num_dead = 1
152 while items[-(num_dead + 1)] is _MISSING:
153 num_dead += 1
154 if ded and ded[-1][1] == len(items):
155 del ded[-1]
156 del items[-num_dead:]
158 def _get_real_index(self, index):
159 if index < 0:
160 index += len(self)
161 if index < 0 or index >= len(self):
162 raise IndexError('IndexedSet index out of range')
163 if not self.dead_indices:
164 return index
165 real_index = index
166 for d_start, d_stop in self.dead_indices:
167 if real_index < d_start:
168 break
169 real_index += d_stop - d_start
170 return real_index
172 def _get_apparent_index(self, index):
173 if index < 0:
174 index += len(self)
175 if not self.dead_indices:
176 return index
177 apparent_index = index
178 for d_start, d_stop in self.dead_indices:
179 if index < d_start:
180 break
181 apparent_index -= d_stop - d_start
182 return apparent_index
184 def _add_dead(self, start, stop=None):
185 # TODO: does not handle when the new interval subsumes
186 # multiple existing intervals
187 dints = self.dead_indices
188 if stop is None:
189 stop = start + 1
190 cand_int = [start, stop]
191 if not dints:
192 dints.append(cand_int)
193 return
194 int_idx = bisect_left(dints, cand_int)
195 dint = dints[int_idx - 1]
196 d_start, d_stop = dint
197 if start <= d_start <= stop:
198 dint[0] = start
199 elif start <= d_stop <= stop:
200 dint[1] = stop
201 else:
202 dints.insert(int_idx, cand_int)
203 return
205 # common operations (shared by set and list)
206 def __len__(self):
207 return len(self.item_index_map)
209 def __contains__(self, item):
210 return item in self.item_index_map
212 def __iter__(self):
213 return (item for item in self.item_list if item is not _MISSING)
215 def __reversed__(self):
216 item_list = self.item_list
217 return (item for item in reversed(item_list) if item is not _MISSING)
219 def __repr__(self):
220 return f'{self.__class__.__name__}({list(self)!r})'
222 def __eq__(self, other):
223 if isinstance(other, IndexedSet):
224 return len(self) == len(other) and list(self) == list(other)
225 try:
226 return set(self) == set(other)
227 except TypeError:
228 return False
230 @classmethod
231 def from_iterable(cls, it):
232 "from_iterable(it) -> create a set from an iterable"
233 return cls(it)
235 # set operations
236 def add(self, item):
237 "add(item) -> add item to the set"
238 if item not in self.item_index_map:
239 self.item_index_map[item] = len(self.item_list)
240 self.item_list.append(item)
242 def remove(self, item):
243 "remove(item) -> remove item from the set, raises if not present"
244 try:
245 didx = self.item_index_map.pop(item)
246 except KeyError:
247 raise KeyError(item)
248 self.item_list[didx] = _MISSING
249 self._add_dead(didx)
250 self._cull()
252 def discard(self, item):
253 "discard(item) -> discard item from the set (does not raise)"
254 try:
255 self.remove(item)
256 except KeyError:
257 pass
259 def clear(self):
260 "clear() -> empty the set"
261 del self.item_list[:]
262 del self.dead_indices[:]
263 self.item_index_map.clear()
265 def isdisjoint(self, other):
266 "isdisjoint(other) -> return True if no overlap with other"
267 iim = self.item_index_map
268 for k in other:
269 if k in iim:
270 return False
271 return True
273 def issubset(self, other):
274 "issubset(other) -> return True if other contains this set"
275 if len(other) < len(self):
276 return False
277 for k in self.item_index_map:
278 if k not in other:
279 return False
280 return True
282 def issuperset(self, other):
283 "issuperset(other) -> return True if set contains other"
284 if len(other) > len(self):
285 return False
286 iim = self.item_index_map
287 for k in other:
288 if k not in iim:
289 return False
290 return True
292 def union(self, *others):
293 "union(*others) -> return a new set containing this set and others"
294 return self.from_iterable(chain(self, *others))
296 def iter_intersection(self, *others):
297 "iter_intersection(*others) -> iterate over elements also in others"
298 for k in self:
299 for other in others:
300 if k not in other:
301 break
302 else:
303 yield k
304 return
306 def intersection(self, *others):
307 "intersection(*others) -> get a set with overlap of this and others"
308 if len(others) == 1:
309 other = others[0]
310 return self.from_iterable(k for k in self if k in other)
311 return self.from_iterable(self.iter_intersection(*others))
313 def iter_difference(self, *others):
314 "iter_difference(*others) -> iterate over elements not in others"
315 for k in self:
316 for other in others:
317 if k in other:
318 break
319 else:
320 yield k
321 return
323 def difference(self, *others):
324 "difference(*others) -> get a new set with elements not in others"
325 if len(others) == 1:
326 other = others[0]
327 return self.from_iterable(k for k in self if k not in other)
328 return self.from_iterable(self.iter_difference(*others))
330 def symmetric_difference(self, *others):
331 "symmetric_difference(*others) -> XOR set of this and others"
332 ret = self.union(*others)
333 return ret.difference(self.intersection(*others))
335 __or__ = __ror__ = union
336 __and__ = __rand__ = intersection
337 __sub__ = difference
338 __xor__ = __rxor__ = symmetric_difference
340 def __rsub__(self, other):
341 vals = [x for x in other if x not in self]
342 return type(other)(vals)
344 # in-place set operations
345 def update(self, *others):
346 "update(*others) -> add values from one or more iterables"
347 if not others:
348 return # raise?
349 elif len(others) == 1:
350 other = others[0]
351 else:
352 other = chain(others)
353 for o in other:
354 self.add(o)
356 def intersection_update(self, *others):
357 "intersection_update(*others) -> discard self.difference(*others)"
358 for val in self.difference(*others):
359 self.discard(val)
361 def difference_update(self, *others):
362 "difference_update(*others) -> discard self.intersection(*others)"
363 if self in others:
364 self.clear()
365 for val in self.intersection(*others):
366 self.discard(val)
368 def symmetric_difference_update(self, other): # note singular 'other'
369 "symmetric_difference_update(other) -> in-place XOR with other"
370 if self is other:
371 self.clear()
372 for val in other:
373 if val in self:
374 self.discard(val)
375 else:
376 self.add(val)
378 def __ior__(self, *others):
379 self.update(*others)
380 return self
382 def __iand__(self, *others):
383 self.intersection_update(*others)
384 return self
386 def __isub__(self, *others):
387 self.difference_update(*others)
388 return self
390 def __ixor__(self, *others):
391 self.symmetric_difference_update(*others)
392 return self
394 def iter_slice(self, start, stop, step=None):
395 "iterate over a slice of the set"
396 iterable = self
397 # start/stop are apparent (dead-slot-free) indices, the same space
398 # islice consumes; mapping them through _get_real_index() (item_list
399 # space) over-counted by the dead slots before each bound. Only
400 # negatives need normalizing, as islice rejects them.
401 # NB: a negative step slices the reversed stream with forward bounds
402 # (x[2:4:-1] == reversed(x)[2:4]), behavior since 2013.
403 if start is not None and start < 0:
404 start = max(len(self) + start, 0)
405 if stop is not None and stop < 0:
406 stop = max(len(self) + stop, 0)
407 if step is not None and step < 0:
408 step = -step
409 iterable = reversed(self)
410 return islice(iterable, start, stop, step)
412 # list operations
413 def __getitem__(self, index):
414 try:
415 start, stop, step = index.start, index.stop, index.step
416 except AttributeError:
417 index = operator.index(index)
418 else:
419 iter_slice = self.iter_slice(start, stop, step)
420 return self.from_iterable(iter_slice)
421 real_index = self._get_real_index(index)
422 return self.item_list[real_index]
424 def pop(self, index=None):
425 "pop(index) -> remove the item at a given index (-1 by default)"
426 item_index_map = self.item_index_map
427 len_self = len(item_index_map)
428 if index is None or index == -1 or index == len_self - 1:
429 ret = self.item_list.pop()
430 del item_index_map[ret]
431 else:
432 real_index = self._get_real_index(index)
433 ret = self.item_list[real_index]
434 self.item_list[real_index] = _MISSING
435 del item_index_map[ret]
436 self._add_dead(real_index)
437 self._cull()
438 return ret
440 def count(self, val):
441 "count(val) -> count number of instances of value (0 or 1)"
442 if val in self.item_index_map:
443 return 1
444 return 0
446 def reverse(self):
447 "reverse() -> reverse the contents of the set in-place"
448 reversed_list = list(reversed(self))
449 self.item_list[:] = reversed_list
450 for i, item in enumerate(self.item_list):
451 self.item_index_map[item] = i
452 del self.dead_indices[:]
454 def sort(self, **kwargs):
455 "sort() -> sort the contents of the set in-place"
456 sorted_list = sorted(self, **kwargs)
457 if sorted_list == self.item_list:
458 return
459 self.item_list[:] = sorted_list
460 for i, item in enumerate(self.item_list):
461 self.item_index_map[item] = i
462 del self.dead_indices[:]
464 def index(self, val):
465 "index(val) -> get the index of a value, raises if not present"
466 try:
467 return self._get_apparent_index(self.item_index_map[val])
468 except KeyError:
469 cn = self.__class__.__name__
470 raise ValueError(f'{val!r} is not in {cn}')
473def complement(wrapped):
474 """Given a :class:`set`, convert it to a **complement set**.
476 Whereas a :class:`set` keeps track of what it contains, a
477 `complement set
478 <https://en.wikipedia.org/wiki/Complement_(set_theory)>`_ keeps
479 track of what it does *not* contain. For example, look what
480 happens when we intersect a normal set with a complement set::
482 >>> list(set(range(5)) & complement(set([2, 3])))
483 [0, 1, 4]
485 We get the everything in the left that wasn't in the right,
486 because intersecting with a complement is the same as subtracting
487 a normal set.
489 Args:
490 wrapped (set): A set or any other iterable which should be
491 turned into a complement set.
493 All set methods and operators are supported by complement sets,
494 between other :func:`complement`-wrapped sets and/or regular
495 :class:`set` objects.
497 Because a complement set only tracks what elements are *not* in
498 the set, functionality based on set contents is unavailable:
499 :func:`len`, :func:`iter` (and for loops), and ``.pop()``. But a
500 complement set can always be turned back into a regular set by
501 complementing it again:
503 >>> s = set(range(5))
504 >>> complement(complement(s)) == s
505 True
507 .. note::
509 An empty complement set corresponds to the concept of a
510 `universal set <https://en.wikipedia.org/wiki/Universal_set>`_
511 from mathematics.
513 Complement sets by example
514 ^^^^^^^^^^^^^^^^^^^^^^^^^^
516 Many uses of sets can be expressed more simply by using a
517 complement. Rather than trying to work out in your head the proper
518 way to invert an expression, you can just throw a complement on
519 the set. Consider this example of a name filter::
521 >>> class NamesFilter(object):
522 ... def __init__(self, allowed):
523 ... self._allowed = allowed
524 ...
525 ... def filter(self, names):
526 ... return [name for name in names if name in self._allowed]
527 >>> NamesFilter(set(['alice', 'bob'])).filter(['alice', 'bob', 'carol'])
528 ['alice', 'bob']
530 What if we want to just express "let all the names through"?
532 We could try to enumerate all of the expected names::
534 ``NamesFilter({'alice', 'bob', 'carol'})``
536 But this is very brittle -- what if at some point over this
537 object is changed to filter ``['alice', 'bob', 'carol', 'dan']``?
539 Even worse, what about the poor programmer who next works
540 on this piece of code? They cannot tell whether the purpose
541 of the large allowed set was "allow everything", or if 'dan'
542 was excluded for some subtle reason.
544 A complement set lets the programmer intention be expressed
545 succinctly and directly::
547 NamesFilter(complement(set()))
549 Not only is this code short and robust, it is easy to understand
550 the intention.
552 """
553 if type(wrapped) is _ComplementSet:
554 return wrapped.complemented()
555 if type(wrapped) is frozenset:
556 return _ComplementSet(excluded=wrapped)
557 return _ComplementSet(excluded=set(wrapped))
560def _norm_args_typeerror(other):
561 '''normalize args and raise type-error if there is a problem'''
562 if type(other) in (set, frozenset):
563 inc, exc = other, None
564 elif type(other) is _ComplementSet:
565 inc, exc = other._included, other._excluded
566 else:
567 raise TypeError('argument must be another set or complement(set)')
568 return inc, exc
571def _norm_args_notimplemented(other):
572 '''normalize args and return NotImplemented (for overloaded operators)'''
573 if type(other) in (set, frozenset):
574 inc, exc = other, None
575 elif type(other) is _ComplementSet:
576 inc, exc = other._included, other._excluded
577 else:
578 return NotImplemented, None
579 return inc, exc
582class _ComplementSet:
583 """
584 helper class for complement() that implements the set methods
585 """
586 __slots__ = ('_included', '_excluded')
588 def __init__(self, included=None, excluded=None):
589 if included is None:
590 assert type(excluded) in (set, frozenset)
591 elif excluded is None:
592 assert type(included) in (set, frozenset)
593 else:
594 raise ValueError('one of included or excluded must be a set')
595 self._included, self._excluded = included, excluded
597 def __repr__(self):
598 if self._included is None:
599 return f'complement({repr(self._excluded)})'
600 return f'complement(complement({repr(self._included)}))'
602 def complemented(self):
603 '''return a complement of the current set'''
604 if type(self._included) is frozenset or type(self._excluded) is frozenset:
605 return _ComplementSet(included=self._excluded, excluded=self._included)
606 return _ComplementSet(
607 included=None if self._excluded is None else set(self._excluded),
608 excluded=None if self._included is None else set(self._included))
610 __invert__ = complemented
612 def complement(self):
613 '''convert the current set to its complement in-place'''
614 self._included, self._excluded = self._excluded, self._included
616 def __contains__(self, item):
617 if self._included is None:
618 return not item in self._excluded
619 return item in self._included
621 def add(self, item):
622 if self._included is None:
623 if item in self._excluded:
624 self._excluded.remove(item)
625 else:
626 self._included.add(item)
628 def remove(self, item):
629 if self._included is None:
630 self._excluded.add(item)
631 else:
632 self._included.remove(item)
634 def pop(self):
635 if self._included is None:
636 raise NotImplementedError # self.missing.add(random.choice(gc.objects()))
637 return self._included.pop()
639 def intersection(self, other):
640 try:
641 return self & other
642 except NotImplementedError:
643 raise TypeError('argument must be another set or complement(set)')
645 def __and__(self, other):
646 inc, exc = _norm_args_notimplemented(other)
647 if inc is NotImplemented:
648 return NotImplemented
649 if self._included is None:
650 if exc is None: # - +
651 return _ComplementSet(included=inc - self._excluded)
652 else: # - -
653 return _ComplementSet(excluded=self._excluded.union(other._excluded))
654 else:
655 if inc is None: # + -
656 return _ComplementSet(included=exc - self._included)
657 else: # + +
658 return _ComplementSet(included=self._included.intersection(inc))
660 __rand__ = __and__
662 def __iand__(self, other):
663 inc, exc = _norm_args_notimplemented(other)
664 if inc is NotImplemented:
665 return NotImplemented
666 if self._included is None:
667 if exc is None: # - +
668 self._excluded = inc - self._excluded # TODO: do this in place?
669 else: # - -
670 self._excluded |= exc
671 else:
672 if inc is None: # + -
673 self._included -= exc
674 self._included, self._excluded = None, self._included
675 else: # + +
676 self._included &= inc
677 return self
679 def union(self, other):
680 try:
681 return self | other
682 except NotImplementedError:
683 raise TypeError('argument must be another set or complement(set)')
685 def __or__(self, other):
686 inc, exc = _norm_args_notimplemented(other)
687 if inc is NotImplemented:
688 return NotImplemented
689 if self._included is None:
690 if exc is None: # - +
691 return _ComplementSet(excluded=self._excluded - inc)
692 else: # - -
693 return _ComplementSet(excluded=self._excluded.intersection(exc))
694 else:
695 if inc is None: # + -
696 return _ComplementSet(excluded=exc - self._included)
697 else: # + +
698 return _ComplementSet(included=self._included.union(inc))
700 __ror__ = __or__
702 def __ior__(self, other):
703 inc, exc = _norm_args_notimplemented(other)
704 if inc is NotImplemented:
705 return NotImplemented
706 if self._included is None:
707 if exc is None: # - +
708 self._excluded -= inc
709 else: # - -
710 self._excluded &= exc
711 else:
712 if inc is None: # + -
713 self._included, self._excluded = None, exc - self._included # TODO: do this in place?
714 else: # + +
715 self._included |= inc
716 return self
718 def update(self, items):
719 if type(items) in (set, frozenset):
720 inc, exc = items, None
721 elif type(items) is _ComplementSet:
722 inc, exc = items._included, items._excluded
723 else:
724 inc, exc = frozenset(items), None
725 if self._included is None:
726 if exc is None: # - +
727 self._excluded &= inc
728 else: # - -
729 self._excluded.discard(exc)
730 else:
731 if inc is None: # + -
732 self._included &= exc
733 self._included, self._excluded = None, self._excluded
734 else: # + +
735 self._included.update(inc)
737 def discard(self, items):
738 if type(items) in (set, frozenset):
739 inc, exc = items, None
740 elif type(items) is _ComplementSet:
741 inc, exc = items._included, items._excluded
742 else:
743 inc, exc = frozenset(items), None
744 if self._included is None:
745 if exc is None: # - +
746 self._excluded.update(inc)
747 else: # - -
748 self._included, self._excluded = exc - self._excluded, None
749 else:
750 if inc is None: # + -
751 self._included &= exc
752 else: # + +
753 self._included.discard(inc)
755 def symmetric_difference(self, other):
756 try:
757 return self ^ other
758 except NotImplementedError:
759 raise TypeError('argument must be another set or complement(set)')
761 def __xor__(self, other):
762 inc, exc = _norm_args_notimplemented(other)
763 if inc is NotImplemented:
764 return NotImplemented
765 if inc is NotImplemented:
766 return NotImplemented
767 if self._included is None:
768 if exc is None: # - +
769 return _ComplementSet(excluded=self._excluded - inc)
770 else: # - -
771 return _ComplementSet(included=self._excluded.symmetric_difference(exc))
772 else:
773 if inc is None: # + -
774 return _ComplementSet(excluded=exc - self._included)
775 else: # + +
776 return _ComplementSet(included=self._included.symmetric_difference(inc))
778 __rxor__ = __xor__
780 def symmetric_difference_update(self, other):
781 inc, exc = _norm_args_typeerror(other)
782 if self._included is None:
783 if exc is None: # - +
784 self._excluded |= inc
785 else: # - -
786 self._excluded.symmetric_difference_update(exc)
787 self._included, self._excluded = self._excluded, None
788 else:
789 if inc is None: # + -
790 self._included |= exc
791 self._included, self._excluded = None, self._included
792 else: # + +
793 self._included.symmetric_difference_update(inc)
795 def isdisjoint(self, other):
796 inc, exc = _norm_args_typeerror(other)
797 if inc is NotImplemented:
798 return NotImplemented
799 if self._included is None:
800 if exc is None: # - +
801 return inc.issubset(self._excluded)
802 else: # - -
803 return False
804 else:
805 if inc is None: # + -
806 return self._included.issubset(exc)
807 else: # + +
808 return self._included.isdisjoint(inc)
810 def issubset(self, other):
811 '''everything missing from other is also missing from self'''
812 try:
813 return self <= other
814 except NotImplementedError:
815 raise TypeError('argument must be another set or complement(set)')
817 def __le__(self, other):
818 inc, exc = _norm_args_notimplemented(other)
819 if inc is NotImplemented:
820 return NotImplemented
821 if inc is NotImplemented:
822 return NotImplemented
823 if self._included is None:
824 if exc is None: # - +
825 return False
826 else: # - -
827 return self._excluded.issupserset(exc)
828 else:
829 if inc is None: # + -
830 return self._included.isdisjoint(exc)
831 else: # + +
832 return self._included.issubset(inc)
834 def __lt__(self, other):
835 inc, exc = _norm_args_notimplemented(other)
836 if inc is NotImplemented:
837 return NotImplemented
838 if inc is NotImplemented:
839 return NotImplemented
840 if self._included is None:
841 if exc is None: # - +
842 return False
843 else: # - -
844 return self._excluded > exc
845 else:
846 if inc is None: # + -
847 return self._included.isdisjoint(exc)
848 else: # + +
849 return self._included < inc
851 def issuperset(self, other):
852 '''everything missing from self is also missing from super'''
853 try:
854 return self >= other
855 except NotImplementedError:
856 raise TypeError('argument must be another set or complement(set)')
858 def __ge__(self, other):
859 inc, exc = _norm_args_notimplemented(other)
860 if inc is NotImplemented:
861 return NotImplemented
862 if self._included is None:
863 if exc is None: # - +
864 return not self._excluded.intersection(inc)
865 else: # - -
866 return self._excluded.issubset(exc)
867 else:
868 if inc is None: # + -
869 return False
870 else: # + +
871 return self._included.issupserset(inc)
873 def __gt__(self, other):
874 inc, exc = _norm_args_notimplemented(other)
875 if inc is NotImplemented:
876 return NotImplemented
877 if self._included is None:
878 if exc is None: # - +
879 return not self._excluded.intersection(inc)
880 else: # - -
881 return self._excluded < exc
882 else:
883 if inc is None: # + -
884 return False
885 else: # + +
886 return self._included > inc
888 def difference(self, other):
889 try:
890 return self - other
891 except NotImplementedError:
892 raise TypeError('argument must be another set or complement(set)')
894 def __sub__(self, other):
895 inc, exc = _norm_args_notimplemented(other)
896 if inc is NotImplemented:
897 return NotImplemented
898 if self._included is None:
899 if exc is None: # - +
900 return _ComplementSet(excluded=self._excluded | inc)
901 else: # - -
902 return _ComplementSet(included=exc - self._excluded)
903 else:
904 if inc is None: # + -
905 return _ComplementSet(included=self._included & exc)
906 else: # + +
907 return _ComplementSet(included=self._included.difference(inc))
909 def __rsub__(self, other):
910 inc, exc = _norm_args_notimplemented(other)
911 if inc is NotImplemented:
912 return NotImplemented
913 # rsub, so the expression being evaluated is "other - self"
914 if self._included is None:
915 if exc is None: # - +
916 return _ComplementSet(included=inc & self._excluded)
917 else: # - -
918 return _ComplementSet(included=self._excluded - exc)
919 else:
920 if inc is None: # + -
921 return _ComplementSet(excluded=exc | self._included)
922 else: # + +
923 return _ComplementSet(included=inc.difference(self._included))
925 def difference_update(self, other):
926 try:
927 self -= other
928 except NotImplementedError:
929 raise TypeError('argument must be another set or complement(set)')
931 def __isub__(self, other):
932 inc, exc = _norm_args_notimplemented(other)
933 if inc is NotImplemented:
934 return NotImplemented
935 if self._included is None:
936 if exc is None: # - +
937 self._excluded |= inc
938 else: # - -
939 self._included, self._excluded = exc - self._excluded, None
940 else:
941 if inc is None: # + -
942 self._included &= exc
943 else: # + +
944 self._included.difference_update(inc)
945 return self
947 def __eq__(self, other):
948 return (
949 type(self) is type(other)
950 and self._included == other._included
951 and self._excluded == other._excluded) or (
952 type(other) in (set, frozenset) and self._included == other)
954 def __hash__(self):
955 return hash(self._included) ^ hash(self._excluded)
957 def __len__(self):
958 if self._included is not None:
959 return len(self._included)
960 raise NotImplementedError('complemented sets have undefined length')
962 def __iter__(self):
963 if self._included is not None:
964 return iter(self._included)
965 raise NotImplementedError('complemented sets have undefined contents')
967 def __bool__(self):
968 if self._included is not None:
969 return bool(self._included)
970 return True