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

1# -*- coding: utf-8 -*- 

2 

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. 

32 

33"""\ 

34 

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

43 

44from __future__ import print_function 

45 

46from bisect import bisect_left 

47from itertools import chain, islice 

48import operator 

49 

50try: 

51 from collections.abc import MutableSet 

52except ImportError: 

53 from collections import MutableSet 

54 

55try: 

56 from .typeutils import make_sentinel 

57 _MISSING = make_sentinel(var_name='_MISSING') 

58except ImportError: 

59 _MISSING = object() 

60 

61 

62__all__ = ['IndexedSet', 'complement'] 

63 

64 

65_COMPACTION_FACTOR = 8 

66 

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) 

73 

74 

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. 

80 

81 Args: 

82 other (iterable): An optional iterable used to initialize the set. 

83 

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' 

94 

95 Standard set operators and interoperation with :class:`set` are 

96 all supported: 

97 

98 >>> fcr & set('cash4gold.com') 

99 IndexedSet(['c', 'd', 'o', '.', 'm']) 

100 

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

106 

107 my_indexed_set = [A, B, C, D] 

108 my_indexed_set[2] = A 

109 

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__()``. 

114 

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) 

126 

127 # internal functions 

128 @property 

129 def _dead_index_count(self): 

130 return len(self.item_list) - len(self.item_index_map) 

131 

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

144 

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

164 

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 

176 

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 

188 

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 

209 

210 # common operations (shared by set and list) 

211 def __len__(self): 

212 return len(self.item_index_map) 

213 

214 def __contains__(self, item): 

215 return item in self.item_index_map 

216 

217 def __iter__(self): 

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

219 

220 def __reversed__(self): 

221 item_list = self.item_list 

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

223 

224 def __repr__(self): 

225 return '%s(%r)' % (self.__class__.__name__, list(self)) 

226 

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) 

231 

232 @classmethod 

233 def from_iterable(cls, it): 

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

235 return cls(it) 

236 

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) 

243 

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

253 

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 

260 

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

266 

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 

274 

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 

283 

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 

293 

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

297 

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 

307 

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

314 

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 

324 

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

331 

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

336 

337 __or__ = __ror__ = union 

338 __and__ = __rand__ = intersection 

339 __sub__ = difference 

340 __xor__ = __rxor__ = symmetric_difference 

341 

342 def __rsub__(self, other): 

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

344 return type(other)(vals) 

345 

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) 

357 

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) 

362 

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) 

369 

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) 

379 

380 def __ior__(self, *others): 

381 self.update(*others) 

382 return self 

383 

384 def __iand__(self, *others): 

385 self.intersection_update(*others) 

386 return self 

387 

388 def __isub__(self, *others): 

389 self.difference_update(*others) 

390 return self 

391 

392 def __ixor__(self, *others): 

393 self.symmetric_difference_update(*others) 

394 return self 

395 

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) 

407 

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 

425 

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 

441 

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 

447 

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

455 

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

465 

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

473 

474 

475def complement(wrapped): 

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

477 

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

483 

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

485 [0, 1, 4] 

486 

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. 

490 

491 Args: 

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

493 turned into a complement set. 

494 

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. 

498 

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: 

504 

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

506 >>> complement(complement(s)) == s 

507 True 

508 

509 .. note:: 

510 

511 An empty complement set corresponds to the concept of a 

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

513 from mathematics. 

514 

515 Complement sets by example 

516 ^^^^^^^^^^^^^^^^^^^^^^^^^^ 

517 

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

522 

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

531 

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

533 

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

535 

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

537 

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

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

540 

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. 

545 

546 A complement set lets the programmer intention be expressed 

547 succinctly and directly:: 

548 

549 NamesFilter(complement(set())) 

550 

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

552 the intention. 

553 

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

560 

561 

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 

571 

572 

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 

582 

583 

584class _ComplementSet(object): 

585 """ 

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

587 """ 

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

589 

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 

598 

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

603 

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

611 

612 __invert__ = complemented 

613 

614 def complement(self): 

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

616 self._included, self._excluded = self._excluded, self._included 

617 

618 def __contains__(self, item): 

619 if self._included is None: 

620 return not item in self._excluded 

621 return item in self._included 

622 

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) 

629 

630 def remove(self, item): 

631 if self._included is None: 

632 self._excluded.add(item) 

633 else: 

634 self._included.remove(item) 

635 

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

640 

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

646 

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

661 

662 __rand__ = __and__ 

663 

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 

680 

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

686 

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

701 

702 __ror__ = __or__ 

703 

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 

719 

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) 

738 

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) 

756 

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

762 

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

779 

780 __rxor__ = __xor__ 

781 

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) 

796 

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) 

811 

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

818 

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) 

835 

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 

852 

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

859 

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) 

874 

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 

889 

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

895 

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

910 

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

926 

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

932 

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 

948 

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) 

955 

956 def __hash__(self): 

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

958 

959 def __len__(self): 

960 if self._included is not None: 

961 return len(self._included) 

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

963 

964 def __iter__(self): 

965 if self._included is not None: 

966 return iter(self._included) 

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

968 

969 def __bool__(self): 

970 if self._included is not None: 

971 return bool(self._included) 

972 return True 

973 

974 __nonzero__ = __bool__ # py2 compat