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

589 statements  

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 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 

171 

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 

183 

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 

204 

205 # common operations (shared by set and list) 

206 def __len__(self): 

207 return len(self.item_index_map) 

208 

209 def __contains__(self, item): 

210 return item in self.item_index_map 

211 

212 def __iter__(self): 

213 return (item for item in self.item_list if item is not _MISSING) 

214 

215 def __reversed__(self): 

216 item_list = self.item_list 

217 return (item for item in reversed(item_list) if item is not _MISSING) 

218 

219 def __repr__(self): 

220 return f'{self.__class__.__name__}({list(self)!r})' 

221 

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 

229 

230 @classmethod 

231 def from_iterable(cls, it): 

232 "from_iterable(it) -> create a set from an iterable" 

233 return cls(it) 

234 

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) 

241 

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() 

251 

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 

258 

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() 

264 

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 

272 

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 

281 

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 

291 

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)) 

295 

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 

305 

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)) 

312 

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 

322 

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)) 

329 

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)) 

334 

335 __or__ = __ror__ = union 

336 __and__ = __rand__ = intersection 

337 __sub__ = difference 

338 __xor__ = __rxor__ = symmetric_difference 

339 

340 def __rsub__(self, other): 

341 vals = [x for x in other if x not in self] 

342 return type(other)(vals) 

343 

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) 

355 

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) 

360 

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) 

367 

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) 

377 

378 def __ior__(self, *others): 

379 self.update(*others) 

380 return self 

381 

382 def __iand__(self, *others): 

383 self.intersection_update(*others) 

384 return self 

385 

386 def __isub__(self, *others): 

387 self.difference_update(*others) 

388 return self 

389 

390 def __ixor__(self, *others): 

391 self.symmetric_difference_update(*others) 

392 return self 

393 

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) 

411 

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] 

423 

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 

439 

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 

445 

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[:] 

453 

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[:] 

463 

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}') 

471 

472 

473def complement(wrapped): 

474 """Given a :class:`set`, convert it to a **complement set**. 

475 

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:: 

481 

482 >>> list(set(range(5)) & complement(set([2, 3]))) 

483 [0, 1, 4] 

484 

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. 

488 

489 Args: 

490 wrapped (set): A set or any other iterable which should be 

491 turned into a complement set. 

492 

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. 

496 

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: 

502 

503 >>> s = set(range(5)) 

504 >>> complement(complement(s)) == s 

505 True 

506 

507 .. note:: 

508 

509 An empty complement set corresponds to the concept of a 

510 `universal set <https://en.wikipedia.org/wiki/Universal_set>`_ 

511 from mathematics. 

512 

513 Complement sets by example 

514 ^^^^^^^^^^^^^^^^^^^^^^^^^^ 

515 

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:: 

520 

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'] 

529 

530 What if we want to just express "let all the names through"? 

531 

532 We could try to enumerate all of the expected names:: 

533 

534 ``NamesFilter({'alice', 'bob', 'carol'})`` 

535 

536 But this is very brittle -- what if at some point over this 

537 object is changed to filter ``['alice', 'bob', 'carol', 'dan']``? 

538 

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. 

543 

544 A complement set lets the programmer intention be expressed 

545 succinctly and directly:: 

546 

547 NamesFilter(complement(set())) 

548 

549 Not only is this code short and robust, it is easy to understand 

550 the intention. 

551 

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)) 

558 

559 

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 

569 

570 

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 

580 

581 

582class _ComplementSet: 

583 """ 

584 helper class for complement() that implements the set methods 

585 """ 

586 __slots__ = ('_included', '_excluded') 

587 

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 

596 

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)}))' 

601 

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)) 

609 

610 __invert__ = complemented 

611 

612 def complement(self): 

613 '''convert the current set to its complement in-place''' 

614 self._included, self._excluded = self._excluded, self._included 

615 

616 def __contains__(self, item): 

617 if self._included is None: 

618 return not item in self._excluded 

619 return item in self._included 

620 

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) 

627 

628 def remove(self, item): 

629 if self._included is None: 

630 self._excluded.add(item) 

631 else: 

632 self._included.remove(item) 

633 

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() 

638 

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)') 

644 

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)) 

659 

660 __rand__ = __and__ 

661 

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 

678 

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)') 

684 

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)) 

699 

700 __ror__ = __or__ 

701 

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 

717 

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) 

736 

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) 

754 

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)') 

760 

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)) 

777 

778 __rxor__ = __xor__ 

779 

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) 

794 

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) 

809 

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)') 

816 

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) 

833 

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 

850 

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)') 

857 

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) 

872 

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 

887 

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)') 

893 

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)) 

908 

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)) 

924 

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)') 

930 

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 

946 

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) 

953 

954 def __hash__(self): 

955 return hash(self._included) ^ hash(self._excluded) 

956 

957 def __len__(self): 

958 if self._included is not None: 

959 return len(self._included) 

960 raise NotImplementedError('complemented sets have undefined length') 

961 

962 def __iter__(self): 

963 if self._included is not None: 

964 return iter(self._included) 

965 raise NotImplementedError('complemented sets have undefined contents') 

966 

967 def __bool__(self): 

968 if self._included is not None: 

969 return bool(self._included) 

970 return True 

971