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.
30
31"""\
32
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"""
41
42
43from bisect import bisect_left
44from collections.abc import MutableSet
45from itertools import chain, islice
46import operator
47
48try:
49 from .typeutils import make_sentinel
50 _MISSING = make_sentinel(var_name='_MISSING')
51except ImportError:
52 _MISSING = object()
53
54
55__all__ = ['IndexedSet', 'complement']
56
57
58_COMPACTION_FACTOR = 8
59
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)
66
67
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.
73
74 Args:
75 other (iterable): An optional iterable used to initialize the set.
76
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'
87
88 Standard set operators and interoperation with :class:`set` are
89 all supported:
90
91 >>> fcr & set('cash4gold.com')
92 IndexedSet(['c', 'd', 'o', '.', 'm'])
93
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::
99
100 my_indexed_set = [A, B, C, D]
101 my_indexed_set[2] = A
102
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__()``.
107
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)
119
120 # internal functions
121 @property
122 def _dead_index_count(self):
123 return len(self.item_list) - len(self.item_index_map)
124
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[:]
137
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:]
157
158 def _get_real_index(self, index):
159 if index < 0:
160 index += len(self)
161 if not self.dead_indices:
162 return index
163 real_index = index
164 for d_start, d_stop in self.dead_indices:
165 if real_index < d_start:
166 break
167 real_index += d_stop - d_start
168 return real_index
169
170 def _get_apparent_index(self, index):
171 if index < 0:
172 index += len(self)
173 if not self.dead_indices:
174 return index
175 apparent_index = index
176 for d_start, d_stop in self.dead_indices:
177 if index < d_start:
178 break
179 apparent_index -= d_stop - d_start
180 return apparent_index
181
182 def _add_dead(self, start, stop=None):
183 # TODO: does not handle when the new interval subsumes
184 # multiple existing intervals
185 dints = self.dead_indices
186 if stop is None:
187 stop = start + 1
188 cand_int = [start, stop]
189 if not dints:
190 dints.append(cand_int)
191 return
192 int_idx = bisect_left(dints, cand_int)
193 dint = dints[int_idx - 1]
194 d_start, d_stop = dint
195 if start <= d_start <= stop:
196 dint[0] = start
197 elif start <= d_stop <= stop:
198 dint[1] = stop
199 else:
200 dints.insert(int_idx, cand_int)
201 return
202
203 # common operations (shared by set and list)
204 def __len__(self):
205 return len(self.item_index_map)
206
207 def __contains__(self, item):
208 return item in self.item_index_map
209
210 def __iter__(self):
211 return (item for item in self.item_list if item is not _MISSING)
212
213 def __reversed__(self):
214 item_list = self.item_list
215 return (item for item in reversed(item_list) if item is not _MISSING)
216
217 def __repr__(self):
218 return f'{self.__class__.__name__}({list(self)!r})'
219
220 def __eq__(self, other):
221 if isinstance(other, IndexedSet):
222 return len(self) == len(other) and list(self) == list(other)
223 try:
224 return set(self) == set(other)
225 except TypeError:
226 return False
227
228 @classmethod
229 def from_iterable(cls, it):
230 "from_iterable(it) -> create a set from an iterable"
231 return cls(it)
232
233 # set operations
234 def add(self, item):
235 "add(item) -> add item to the set"
236 if item not in self.item_index_map:
237 self.item_index_map[item] = len(self.item_list)
238 self.item_list.append(item)
239
240 def remove(self, item):
241 "remove(item) -> remove item from the set, raises if not present"
242 try:
243 didx = self.item_index_map.pop(item)
244 except KeyError:
245 raise KeyError(item)
246 self.item_list[didx] = _MISSING
247 self._add_dead(didx)
248 self._cull()
249
250 def discard(self, item):
251 "discard(item) -> discard item from the set (does not raise)"
252 try:
253 self.remove(item)
254 except KeyError:
255 pass
256
257 def clear(self):
258 "clear() -> empty the set"
259 del self.item_list[:]
260 del self.dead_indices[:]
261 self.item_index_map.clear()
262
263 def isdisjoint(self, other):
264 "isdisjoint(other) -> return True if no overlap with other"
265 iim = self.item_index_map
266 for k in other:
267 if k in iim:
268 return False
269 return True
270
271 def issubset(self, other):
272 "issubset(other) -> return True if other contains this set"
273 if len(other) < len(self):
274 return False
275 for k in self.item_index_map:
276 if k not in other:
277 return False
278 return True
279
280 def issuperset(self, other):
281 "issuperset(other) -> return True if set contains other"
282 if len(other) > len(self):
283 return False
284 iim = self.item_index_map
285 for k in other:
286 if k not in iim:
287 return False
288 return True
289
290 def union(self, *others):
291 "union(*others) -> return a new set containing this set and others"
292 return self.from_iterable(chain(self, *others))
293
294 def iter_intersection(self, *others):
295 "iter_intersection(*others) -> iterate over elements also in others"
296 for k in self:
297 for other in others:
298 if k not in other:
299 break
300 else:
301 yield k
302 return
303
304 def intersection(self, *others):
305 "intersection(*others) -> get a set with overlap of this and others"
306 if len(others) == 1:
307 other = others[0]
308 return self.from_iterable(k for k in self if k in other)
309 return self.from_iterable(self.iter_intersection(*others))
310
311 def iter_difference(self, *others):
312 "iter_difference(*others) -> iterate over elements not in others"
313 for k in self:
314 for other in others:
315 if k in other:
316 break
317 else:
318 yield k
319 return
320
321 def difference(self, *others):
322 "difference(*others) -> get a new set with elements not in others"
323 if len(others) == 1:
324 other = others[0]
325 return self.from_iterable(k for k in self if k not in other)
326 return self.from_iterable(self.iter_difference(*others))
327
328 def symmetric_difference(self, *others):
329 "symmetric_difference(*others) -> XOR set of this and others"
330 ret = self.union(*others)
331 return ret.difference(self.intersection(*others))
332
333 __or__ = __ror__ = union
334 __and__ = __rand__ = intersection
335 __sub__ = difference
336 __xor__ = __rxor__ = symmetric_difference
337
338 def __rsub__(self, other):
339 vals = [x for x in other if x not in self]
340 return type(other)(vals)
341
342 # in-place set operations
343 def update(self, *others):
344 "update(*others) -> add values from one or more iterables"
345 if not others:
346 return # raise?
347 elif len(others) == 1:
348 other = others[0]
349 else:
350 other = chain(others)
351 for o in other:
352 self.add(o)
353
354 def intersection_update(self, *others):
355 "intersection_update(*others) -> discard self.difference(*others)"
356 for val in self.difference(*others):
357 self.discard(val)
358
359 def difference_update(self, *others):
360 "difference_update(*others) -> discard self.intersection(*others)"
361 if self in others:
362 self.clear()
363 for val in self.intersection(*others):
364 self.discard(val)
365
366 def symmetric_difference_update(self, other): # note singular 'other'
367 "symmetric_difference_update(other) -> in-place XOR with other"
368 if self is other:
369 self.clear()
370 for val in other:
371 if val in self:
372 self.discard(val)
373 else:
374 self.add(val)
375
376 def __ior__(self, *others):
377 self.update(*others)
378 return self
379
380 def __iand__(self, *others):
381 self.intersection_update(*others)
382 return self
383
384 def __isub__(self, *others):
385 self.difference_update(*others)
386 return self
387
388 def __ixor__(self, *others):
389 self.symmetric_difference_update(*others)
390 return self
391
392 def iter_slice(self, start, stop, step=None):
393 "iterate over a slice of the set"
394 iterable = self
395 if start is not None:
396 start = self._get_real_index(start)
397 if stop is not None:
398 stop = self._get_real_index(stop)
399 if step is not None and step < 0:
400 step = -step
401 iterable = reversed(self)
402 return islice(iterable, start, stop, step)
403
404 # list operations
405 def __getitem__(self, index):
406 try:
407 start, stop, step = index.start, index.stop, index.step
408 except AttributeError:
409 index = operator.index(index)
410 else:
411 iter_slice = self.iter_slice(start, stop, step)
412 return self.from_iterable(iter_slice)
413 if index < 0:
414 index += len(self)
415 real_index = self._get_real_index(index)
416 try:
417 ret = self.item_list[real_index]
418 except IndexError:
419 raise IndexError('IndexedSet index out of range')
420 return ret
421
422 def pop(self, index=None):
423 "pop(index) -> remove the item at a given index (-1 by default)"
424 item_index_map = self.item_index_map
425 len_self = len(item_index_map)
426 if index is None or index == -1 or index == len_self - 1:
427 ret = self.item_list.pop()
428 del item_index_map[ret]
429 else:
430 real_index = self._get_real_index(index)
431 ret = self.item_list[real_index]
432 self.item_list[real_index] = _MISSING
433 del item_index_map[ret]
434 self._add_dead(real_index)
435 self._cull()
436 return ret
437
438 def count(self, val):
439 "count(val) -> count number of instances of value (0 or 1)"
440 if val in self.item_index_map:
441 return 1
442 return 0
443
444 def reverse(self):
445 "reverse() -> reverse the contents of the set in-place"
446 reversed_list = list(reversed(self))
447 self.item_list[:] = reversed_list
448 for i, item in enumerate(self.item_list):
449 self.item_index_map[item] = i
450 del self.dead_indices[:]
451
452 def sort(self, **kwargs):
453 "sort() -> sort the contents of the set in-place"
454 sorted_list = sorted(self, **kwargs)
455 if sorted_list == self.item_list:
456 return
457 self.item_list[:] = sorted_list
458 for i, item in enumerate(self.item_list):
459 self.item_index_map[item] = i
460 del self.dead_indices[:]
461
462 def index(self, val):
463 "index(val) -> get the index of a value, raises if not present"
464 try:
465 return self._get_apparent_index(self.item_index_map[val])
466 except KeyError:
467 cn = self.__class__.__name__
468 raise ValueError(f'{val!r} is not in {cn}')
469
470
471def complement(wrapped):
472 """Given a :class:`set`, convert it to a **complement set**.
473
474 Whereas a :class:`set` keeps track of what it contains, a
475 `complement set
476 <https://en.wikipedia.org/wiki/Complement_(set_theory)>`_ keeps
477 track of what it does *not* contain. For example, look what
478 happens when we intersect a normal set with a complement set::
479
480 >>> list(set(range(5)) & complement(set([2, 3])))
481 [0, 1, 4]
482
483 We get the everything in the left that wasn't in the right,
484 because intersecting with a complement is the same as subtracting
485 a normal set.
486
487 Args:
488 wrapped (set): A set or any other iterable which should be
489 turned into a complement set.
490
491 All set methods and operators are supported by complement sets,
492 between other :func:`complement`-wrapped sets and/or regular
493 :class:`set` objects.
494
495 Because a complement set only tracks what elements are *not* in
496 the set, functionality based on set contents is unavailable:
497 :func:`len`, :func:`iter` (and for loops), and ``.pop()``. But a
498 complement set can always be turned back into a regular set by
499 complementing it again:
500
501 >>> s = set(range(5))
502 >>> complement(complement(s)) == s
503 True
504
505 .. note::
506
507 An empty complement set corresponds to the concept of a
508 `universal set <https://en.wikipedia.org/wiki/Universal_set>`_
509 from mathematics.
510
511 Complement sets by example
512 ^^^^^^^^^^^^^^^^^^^^^^^^^^
513
514 Many uses of sets can be expressed more simply by using a
515 complement. Rather than trying to work out in your head the proper
516 way to invert an expression, you can just throw a complement on
517 the set. Consider this example of a name filter::
518
519 >>> class NamesFilter(object):
520 ... def __init__(self, allowed):
521 ... self._allowed = allowed
522 ...
523 ... def filter(self, names):
524 ... return [name for name in names if name in self._allowed]
525 >>> NamesFilter(set(['alice', 'bob'])).filter(['alice', 'bob', 'carol'])
526 ['alice', 'bob']
527
528 What if we want to just express "let all the names through"?
529
530 We could try to enumerate all of the expected names::
531
532 ``NamesFilter({'alice', 'bob', 'carol'})``
533
534 But this is very brittle -- what if at some point over this
535 object is changed to filter ``['alice', 'bob', 'carol', 'dan']``?
536
537 Even worse, what about the poor programmer who next works
538 on this piece of code? They cannot tell whether the purpose
539 of the large allowed set was "allow everything", or if 'dan'
540 was excluded for some subtle reason.
541
542 A complement set lets the programmer intention be expressed
543 succinctly and directly::
544
545 NamesFilter(complement(set()))
546
547 Not only is this code short and robust, it is easy to understand
548 the intention.
549
550 """
551 if type(wrapped) is _ComplementSet:
552 return wrapped.complemented()
553 if type(wrapped) is frozenset:
554 return _ComplementSet(excluded=wrapped)
555 return _ComplementSet(excluded=set(wrapped))
556
557
558def _norm_args_typeerror(other):
559 '''normalize args and raise type-error if there is a problem'''
560 if type(other) in (set, frozenset):
561 inc, exc = other, None
562 elif type(other) is _ComplementSet:
563 inc, exc = other._included, other._excluded
564 else:
565 raise TypeError('argument must be another set or complement(set)')
566 return inc, exc
567
568
569def _norm_args_notimplemented(other):
570 '''normalize args and return NotImplemented (for overloaded operators)'''
571 if type(other) in (set, frozenset):
572 inc, exc = other, None
573 elif type(other) is _ComplementSet:
574 inc, exc = other._included, other._excluded
575 else:
576 return NotImplemented, None
577 return inc, exc
578
579
580class _ComplementSet:
581 """
582 helper class for complement() that implements the set methods
583 """
584 __slots__ = ('_included', '_excluded')
585
586 def __init__(self, included=None, excluded=None):
587 if included is None:
588 assert type(excluded) in (set, frozenset)
589 elif excluded is None:
590 assert type(included) in (set, frozenset)
591 else:
592 raise ValueError('one of included or excluded must be a set')
593 self._included, self._excluded = included, excluded
594
595 def __repr__(self):
596 if self._included is None:
597 return f'complement({repr(self._excluded)})'
598 return f'complement(complement({repr(self._included)}))'
599
600 def complemented(self):
601 '''return a complement of the current set'''
602 if type(self._included) is frozenset or type(self._excluded) is frozenset:
603 return _ComplementSet(included=self._excluded, excluded=self._included)
604 return _ComplementSet(
605 included=None if self._excluded is None else set(self._excluded),
606 excluded=None if self._included is None else set(self._included))
607
608 __invert__ = complemented
609
610 def complement(self):
611 '''convert the current set to its complement in-place'''
612 self._included, self._excluded = self._excluded, self._included
613
614 def __contains__(self, item):
615 if self._included is None:
616 return not item in self._excluded
617 return item in self._included
618
619 def add(self, item):
620 if self._included is None:
621 if item in self._excluded:
622 self._excluded.remove(item)
623 else:
624 self._included.add(item)
625
626 def remove(self, item):
627 if self._included is None:
628 self._excluded.add(item)
629 else:
630 self._included.remove(item)
631
632 def pop(self):
633 if self._included is None:
634 raise NotImplementedError # self.missing.add(random.choice(gc.objects()))
635 return self._included.pop()
636
637 def intersection(self, other):
638 try:
639 return self & other
640 except NotImplementedError:
641 raise TypeError('argument must be another set or complement(set)')
642
643 def __and__(self, other):
644 inc, exc = _norm_args_notimplemented(other)
645 if inc is NotImplemented:
646 return NotImplemented
647 if self._included is None:
648 if exc is None: # - +
649 return _ComplementSet(included=inc - self._excluded)
650 else: # - -
651 return _ComplementSet(excluded=self._excluded.union(other._excluded))
652 else:
653 if inc is None: # + -
654 return _ComplementSet(included=exc - self._included)
655 else: # + +
656 return _ComplementSet(included=self._included.intersection(inc))
657
658 __rand__ = __and__
659
660 def __iand__(self, other):
661 inc, exc = _norm_args_notimplemented(other)
662 if inc is NotImplemented:
663 return NotImplemented
664 if self._included is None:
665 if exc is None: # - +
666 self._excluded = inc - self._excluded # TODO: do this in place?
667 else: # - -
668 self._excluded |= exc
669 else:
670 if inc is None: # + -
671 self._included -= exc
672 self._included, self._excluded = None, self._included
673 else: # + +
674 self._included &= inc
675 return self
676
677 def union(self, other):
678 try:
679 return self | other
680 except NotImplementedError:
681 raise TypeError('argument must be another set or complement(set)')
682
683 def __or__(self, other):
684 inc, exc = _norm_args_notimplemented(other)
685 if inc is NotImplemented:
686 return NotImplemented
687 if self._included is None:
688 if exc is None: # - +
689 return _ComplementSet(excluded=self._excluded - inc)
690 else: # - -
691 return _ComplementSet(excluded=self._excluded.intersection(exc))
692 else:
693 if inc is None: # + -
694 return _ComplementSet(excluded=exc - self._included)
695 else: # + +
696 return _ComplementSet(included=self._included.union(inc))
697
698 __ror__ = __or__
699
700 def __ior__(self, other):
701 inc, exc = _norm_args_notimplemented(other)
702 if inc is NotImplemented:
703 return NotImplemented
704 if self._included is None:
705 if exc is None: # - +
706 self._excluded -= inc
707 else: # - -
708 self._excluded &= exc
709 else:
710 if inc is None: # + -
711 self._included, self._excluded = None, exc - self._included # TODO: do this in place?
712 else: # + +
713 self._included |= inc
714 return self
715
716 def update(self, items):
717 if type(items) in (set, frozenset):
718 inc, exc = items, None
719 elif type(items) is _ComplementSet:
720 inc, exc = items._included, items._excluded
721 else:
722 inc, exc = frozenset(items), None
723 if self._included is None:
724 if exc is None: # - +
725 self._excluded &= inc
726 else: # - -
727 self._excluded.discard(exc)
728 else:
729 if inc is None: # + -
730 self._included &= exc
731 self._included, self._excluded = None, self._excluded
732 else: # + +
733 self._included.update(inc)
734
735 def discard(self, items):
736 if type(items) in (set, frozenset):
737 inc, exc = items, None
738 elif type(items) is _ComplementSet:
739 inc, exc = items._included, items._excluded
740 else:
741 inc, exc = frozenset(items), None
742 if self._included is None:
743 if exc is None: # - +
744 self._excluded.update(inc)
745 else: # - -
746 self._included, self._excluded = exc - self._excluded, None
747 else:
748 if inc is None: # + -
749 self._included &= exc
750 else: # + +
751 self._included.discard(inc)
752
753 def symmetric_difference(self, other):
754 try:
755 return self ^ other
756 except NotImplementedError:
757 raise TypeError('argument must be another set or complement(set)')
758
759 def __xor__(self, other):
760 inc, exc = _norm_args_notimplemented(other)
761 if inc is NotImplemented:
762 return NotImplemented
763 if inc is NotImplemented:
764 return NotImplemented
765 if self._included is None:
766 if exc is None: # - +
767 return _ComplementSet(excluded=self._excluded - inc)
768 else: # - -
769 return _ComplementSet(included=self._excluded.symmetric_difference(exc))
770 else:
771 if inc is None: # + -
772 return _ComplementSet(excluded=exc - self._included)
773 else: # + +
774 return _ComplementSet(included=self._included.symmetric_difference(inc))
775
776 __rxor__ = __xor__
777
778 def symmetric_difference_update(self, other):
779 inc, exc = _norm_args_typeerror(other)
780 if self._included is None:
781 if exc is None: # - +
782 self._excluded |= inc
783 else: # - -
784 self._excluded.symmetric_difference_update(exc)
785 self._included, self._excluded = self._excluded, None
786 else:
787 if inc is None: # + -
788 self._included |= exc
789 self._included, self._excluded = None, self._included
790 else: # + +
791 self._included.symmetric_difference_update(inc)
792
793 def isdisjoint(self, other):
794 inc, exc = _norm_args_typeerror(other)
795 if inc is NotImplemented:
796 return NotImplemented
797 if self._included is None:
798 if exc is None: # - +
799 return inc.issubset(self._excluded)
800 else: # - -
801 return False
802 else:
803 if inc is None: # + -
804 return self._included.issubset(exc)
805 else: # + +
806 return self._included.isdisjoint(inc)
807
808 def issubset(self, other):
809 '''everything missing from other is also missing from self'''
810 try:
811 return self <= other
812 except NotImplementedError:
813 raise TypeError('argument must be another set or complement(set)')
814
815 def __le__(self, other):
816 inc, exc = _norm_args_notimplemented(other)
817 if inc is NotImplemented:
818 return NotImplemented
819 if inc is NotImplemented:
820 return NotImplemented
821 if self._included is None:
822 if exc is None: # - +
823 return False
824 else: # - -
825 return self._excluded.issupserset(exc)
826 else:
827 if inc is None: # + -
828 return self._included.isdisjoint(exc)
829 else: # + +
830 return self._included.issubset(inc)
831
832 def __lt__(self, other):
833 inc, exc = _norm_args_notimplemented(other)
834 if inc is NotImplemented:
835 return NotImplemented
836 if inc is NotImplemented:
837 return NotImplemented
838 if self._included is None:
839 if exc is None: # - +
840 return False
841 else: # - -
842 return self._excluded > exc
843 else:
844 if inc is None: # + -
845 return self._included.isdisjoint(exc)
846 else: # + +
847 return self._included < inc
848
849 def issuperset(self, other):
850 '''everything missing from self is also missing from super'''
851 try:
852 return self >= other
853 except NotImplementedError:
854 raise TypeError('argument must be another set or complement(set)')
855
856 def __ge__(self, other):
857 inc, exc = _norm_args_notimplemented(other)
858 if inc is NotImplemented:
859 return NotImplemented
860 if self._included is None:
861 if exc is None: # - +
862 return not self._excluded.intersection(inc)
863 else: # - -
864 return self._excluded.issubset(exc)
865 else:
866 if inc is None: # + -
867 return False
868 else: # + +
869 return self._included.issupserset(inc)
870
871 def __gt__(self, other):
872 inc, exc = _norm_args_notimplemented(other)
873 if inc is NotImplemented:
874 return NotImplemented
875 if self._included is None:
876 if exc is None: # - +
877 return not self._excluded.intersection(inc)
878 else: # - -
879 return self._excluded < exc
880 else:
881 if inc is None: # + -
882 return False
883 else: # + +
884 return self._included > inc
885
886 def difference(self, other):
887 try:
888 return self - other
889 except NotImplementedError:
890 raise TypeError('argument must be another set or complement(set)')
891
892 def __sub__(self, other):
893 inc, exc = _norm_args_notimplemented(other)
894 if inc is NotImplemented:
895 return NotImplemented
896 if self._included is None:
897 if exc is None: # - +
898 return _ComplementSet(excluded=self._excluded | inc)
899 else: # - -
900 return _ComplementSet(included=exc - self._excluded)
901 else:
902 if inc is None: # + -
903 return _ComplementSet(included=self._included & exc)
904 else: # + +
905 return _ComplementSet(included=self._included.difference(inc))
906
907 def __rsub__(self, other):
908 inc, exc = _norm_args_notimplemented(other)
909 if inc is NotImplemented:
910 return NotImplemented
911 # rsub, so the expression being evaluated is "other - self"
912 if self._included is None:
913 if exc is None: # - +
914 return _ComplementSet(included=inc & self._excluded)
915 else: # - -
916 return _ComplementSet(included=self._excluded - exc)
917 else:
918 if inc is None: # + -
919 return _ComplementSet(excluded=exc | self._included)
920 else: # + +
921 return _ComplementSet(included=inc.difference(self._included))
922
923 def difference_update(self, other):
924 try:
925 self -= other
926 except NotImplementedError:
927 raise TypeError('argument must be another set or complement(set)')
928
929 def __isub__(self, other):
930 inc, exc = _norm_args_notimplemented(other)
931 if inc is NotImplemented:
932 return NotImplemented
933 if self._included is None:
934 if exc is None: # - +
935 self._excluded |= inc
936 else: # - -
937 self._included, self._excluded = exc - self._excluded, None
938 else:
939 if inc is None: # + -
940 self._included &= exc
941 else: # + +
942 self._included.difference_update(inc)
943 return self
944
945 def __eq__(self, other):
946 return (
947 type(self) is type(other)
948 and self._included == other._included
949 and self._excluded == other._excluded) or (
950 type(other) in (set, frozenset) and self._included == other)
951
952 def __hash__(self):
953 return hash(self._included) ^ hash(self._excluded)
954
955 def __len__(self):
956 if self._included is not None:
957 return len(self._included)
958 raise NotImplementedError('complemented sets have undefined length')
959
960 def __iter__(self):
961 if self._included is not None:
962 return iter(self._included)
963 raise NotImplementedError('complemented sets have undefined contents')
964
965 def __bool__(self):
966 if self._included is not None:
967 return bool(self._included)
968 return True
969