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