comparison env/lib/python3.7/site-packages/boltons/setutils.py @ 0:26e78fe6e8c4 draft

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