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

593 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 not self.dead_indices: 

162 return index 

163 real_index = index 

164 for d_start, d_stop in self.dead_indices: 

165 if real_index < d_start: 

166 break 

167 real_index += d_stop - d_start 

168 return real_index 

169 

170 def _get_apparent_index(self, index): 

171 if index < 0: 

172 index += len(self) 

173 if not self.dead_indices: 

174 return index 

175 apparent_index = index 

176 for d_start, d_stop in self.dead_indices: 

177 if index < d_start: 

178 break 

179 apparent_index -= d_stop - d_start 

180 return apparent_index 

181 

182 def _add_dead(self, start, stop=None): 

183 # TODO: does not handle when the new interval subsumes 

184 # multiple existing intervals 

185 dints = self.dead_indices 

186 if stop is None: 

187 stop = start + 1 

188 cand_int = [start, stop] 

189 if not dints: 

190 dints.append(cand_int) 

191 return 

192 int_idx = bisect_left(dints, cand_int) 

193 dint = dints[int_idx - 1] 

194 d_start, d_stop = dint 

195 if start <= d_start <= stop: 

196 dint[0] = start 

197 elif start <= d_stop <= stop: 

198 dint[1] = stop 

199 else: 

200 dints.insert(int_idx, cand_int) 

201 return 

202 

203 # common operations (shared by set and list) 

204 def __len__(self): 

205 return len(self.item_index_map) 

206 

207 def __contains__(self, item): 

208 return item in self.item_index_map 

209 

210 def __iter__(self): 

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

212 

213 def __reversed__(self): 

214 item_list = self.item_list 

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

216 

217 def __repr__(self): 

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

219 

220 def __eq__(self, other): 

221 if isinstance(other, IndexedSet): 

222 return len(self) == len(other) and list(self) == list(other) 

223 try: 

224 return set(self) == set(other) 

225 except TypeError: 

226 return False 

227 

228 @classmethod 

229 def from_iterable(cls, it): 

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

231 return cls(it) 

232 

233 # set operations 

234 def add(self, item): 

235 "add(item) -> add item to the set" 

236 if item not in self.item_index_map: 

237 self.item_index_map[item] = len(self.item_list) 

238 self.item_list.append(item) 

239 

240 def remove(self, item): 

241 "remove(item) -> remove item from the set, raises if not present" 

242 try: 

243 didx = self.item_index_map.pop(item) 

244 except KeyError: 

245 raise KeyError(item) 

246 self.item_list[didx] = _MISSING 

247 self._add_dead(didx) 

248 self._cull() 

249 

250 def discard(self, item): 

251 "discard(item) -> discard item from the set (does not raise)" 

252 try: 

253 self.remove(item) 

254 except KeyError: 

255 pass 

256 

257 def clear(self): 

258 "clear() -> empty the set" 

259 del self.item_list[:] 

260 del self.dead_indices[:] 

261 self.item_index_map.clear() 

262 

263 def isdisjoint(self, other): 

264 "isdisjoint(other) -> return True if no overlap with other" 

265 iim = self.item_index_map 

266 for k in other: 

267 if k in iim: 

268 return False 

269 return True 

270 

271 def issubset(self, other): 

272 "issubset(other) -> return True if other contains this set" 

273 if len(other) < len(self): 

274 return False 

275 for k in self.item_index_map: 

276 if k not in other: 

277 return False 

278 return True 

279 

280 def issuperset(self, other): 

281 "issuperset(other) -> return True if set contains other" 

282 if len(other) > len(self): 

283 return False 

284 iim = self.item_index_map 

285 for k in other: 

286 if k not in iim: 

287 return False 

288 return True 

289 

290 def union(self, *others): 

291 "union(*others) -> return a new set containing this set and others" 

292 return self.from_iterable(chain(self, *others)) 

293 

294 def iter_intersection(self, *others): 

295 "iter_intersection(*others) -> iterate over elements also in others" 

296 for k in self: 

297 for other in others: 

298 if k not in other: 

299 break 

300 else: 

301 yield k 

302 return 

303 

304 def intersection(self, *others): 

305 "intersection(*others) -> get a set with overlap of this and others" 

306 if len(others) == 1: 

307 other = others[0] 

308 return self.from_iterable(k for k in self if k in other) 

309 return self.from_iterable(self.iter_intersection(*others)) 

310 

311 def iter_difference(self, *others): 

312 "iter_difference(*others) -> iterate over elements not in others" 

313 for k in self: 

314 for other in others: 

315 if k in other: 

316 break 

317 else: 

318 yield k 

319 return 

320 

321 def difference(self, *others): 

322 "difference(*others) -> get a new set with elements not in others" 

323 if len(others) == 1: 

324 other = others[0] 

325 return self.from_iterable(k for k in self if k not in other) 

326 return self.from_iterable(self.iter_difference(*others)) 

327 

328 def symmetric_difference(self, *others): 

329 "symmetric_difference(*others) -> XOR set of this and others" 

330 ret = self.union(*others) 

331 return ret.difference(self.intersection(*others)) 

332 

333 __or__ = __ror__ = union 

334 __and__ = __rand__ = intersection 

335 __sub__ = difference 

336 __xor__ = __rxor__ = symmetric_difference 

337 

338 def __rsub__(self, other): 

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

340 return type(other)(vals) 

341 

342 # in-place set operations 

343 def update(self, *others): 

344 "update(*others) -> add values from one or more iterables" 

345 if not others: 

346 return # raise? 

347 elif len(others) == 1: 

348 other = others[0] 

349 else: 

350 other = chain(others) 

351 for o in other: 

352 self.add(o) 

353 

354 def intersection_update(self, *others): 

355 "intersection_update(*others) -> discard self.difference(*others)" 

356 for val in self.difference(*others): 

357 self.discard(val) 

358 

359 def difference_update(self, *others): 

360 "difference_update(*others) -> discard self.intersection(*others)" 

361 if self in others: 

362 self.clear() 

363 for val in self.intersection(*others): 

364 self.discard(val) 

365 

366 def symmetric_difference_update(self, other): # note singular 'other' 

367 "symmetric_difference_update(other) -> in-place XOR with other" 

368 if self is other: 

369 self.clear() 

370 for val in other: 

371 if val in self: 

372 self.discard(val) 

373 else: 

374 self.add(val) 

375 

376 def __ior__(self, *others): 

377 self.update(*others) 

378 return self 

379 

380 def __iand__(self, *others): 

381 self.intersection_update(*others) 

382 return self 

383 

384 def __isub__(self, *others): 

385 self.difference_update(*others) 

386 return self 

387 

388 def __ixor__(self, *others): 

389 self.symmetric_difference_update(*others) 

390 return self 

391 

392 def iter_slice(self, start, stop, step=None): 

393 "iterate over a slice of the set" 

394 iterable = self 

395 if start is not None: 

396 start = self._get_real_index(start) 

397 if stop is not None: 

398 stop = self._get_real_index(stop) 

399 if step is not None and step < 0: 

400 step = -step 

401 iterable = reversed(self) 

402 return islice(iterable, start, stop, step) 

403 

404 # list operations 

405 def __getitem__(self, index): 

406 try: 

407 start, stop, step = index.start, index.stop, index.step 

408 except AttributeError: 

409 index = operator.index(index) 

410 else: 

411 iter_slice = self.iter_slice(start, stop, step) 

412 return self.from_iterable(iter_slice) 

413 if index < 0: 

414 index += len(self) 

415 real_index = self._get_real_index(index) 

416 try: 

417 ret = self.item_list[real_index] 

418 except IndexError: 

419 raise IndexError('IndexedSet index out of range') 

420 return ret 

421 

422 def pop(self, index=None): 

423 "pop(index) -> remove the item at a given index (-1 by default)" 

424 item_index_map = self.item_index_map 

425 len_self = len(item_index_map) 

426 if index is None or index == -1 or index == len_self - 1: 

427 ret = self.item_list.pop() 

428 del item_index_map[ret] 

429 else: 

430 real_index = self._get_real_index(index) 

431 ret = self.item_list[real_index] 

432 self.item_list[real_index] = _MISSING 

433 del item_index_map[ret] 

434 self._add_dead(real_index) 

435 self._cull() 

436 return ret 

437 

438 def count(self, val): 

439 "count(val) -> count number of instances of value (0 or 1)" 

440 if val in self.item_index_map: 

441 return 1 

442 return 0 

443 

444 def reverse(self): 

445 "reverse() -> reverse the contents of the set in-place" 

446 reversed_list = list(reversed(self)) 

447 self.item_list[:] = reversed_list 

448 for i, item in enumerate(self.item_list): 

449 self.item_index_map[item] = i 

450 del self.dead_indices[:] 

451 

452 def sort(self, **kwargs): 

453 "sort() -> sort the contents of the set in-place" 

454 sorted_list = sorted(self, **kwargs) 

455 if sorted_list == self.item_list: 

456 return 

457 self.item_list[:] = sorted_list 

458 for i, item in enumerate(self.item_list): 

459 self.item_index_map[item] = i 

460 del self.dead_indices[:] 

461 

462 def index(self, val): 

463 "index(val) -> get the index of a value, raises if not present" 

464 try: 

465 return self._get_apparent_index(self.item_index_map[val]) 

466 except KeyError: 

467 cn = self.__class__.__name__ 

468 raise ValueError(f'{val!r} is not in {cn}') 

469 

470 

471def complement(wrapped): 

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

473 

474 Whereas a :class:`set` keeps track of what it contains, a 

475 `complement set 

476 <https://en.wikipedia.org/wiki/Complement_(set_theory)>`_ keeps 

477 track of what it does *not* contain. For example, look what 

478 happens when we intersect a normal set with a complement set:: 

479 

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

481 [0, 1, 4] 

482 

483 We get the everything in the left that wasn't in the right, 

484 because intersecting with a complement is the same as subtracting 

485 a normal set. 

486 

487 Args: 

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

489 turned into a complement set. 

490 

491 All set methods and operators are supported by complement sets, 

492 between other :func:`complement`-wrapped sets and/or regular 

493 :class:`set` objects. 

494 

495 Because a complement set only tracks what elements are *not* in 

496 the set, functionality based on set contents is unavailable: 

497 :func:`len`, :func:`iter` (and for loops), and ``.pop()``. But a 

498 complement set can always be turned back into a regular set by 

499 complementing it again: 

500 

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

502 >>> complement(complement(s)) == s 

503 True 

504 

505 .. note:: 

506 

507 An empty complement set corresponds to the concept of a 

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

509 from mathematics. 

510 

511 Complement sets by example 

512 ^^^^^^^^^^^^^^^^^^^^^^^^^^ 

513 

514 Many uses of sets can be expressed more simply by using a 

515 complement. Rather than trying to work out in your head the proper 

516 way to invert an expression, you can just throw a complement on 

517 the set. Consider this example of a name filter:: 

518 

519 >>> class NamesFilter(object): 

520 ... def __init__(self, allowed): 

521 ... self._allowed = allowed 

522 ... 

523 ... def filter(self, names): 

524 ... return [name for name in names if name in self._allowed] 

525 >>> NamesFilter(set(['alice', 'bob'])).filter(['alice', 'bob', 'carol']) 

526 ['alice', 'bob'] 

527 

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

529 

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

531 

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

533 

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

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

536 

537 Even worse, what about the poor programmer who next works 

538 on this piece of code? They cannot tell whether the purpose 

539 of the large allowed set was "allow everything", or if 'dan' 

540 was excluded for some subtle reason. 

541 

542 A complement set lets the programmer intention be expressed 

543 succinctly and directly:: 

544 

545 NamesFilter(complement(set())) 

546 

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

548 the intention. 

549 

550 """ 

551 if type(wrapped) is _ComplementSet: 

552 return wrapped.complemented() 

553 if type(wrapped) is frozenset: 

554 return _ComplementSet(excluded=wrapped) 

555 return _ComplementSet(excluded=set(wrapped)) 

556 

557 

558def _norm_args_typeerror(other): 

559 '''normalize args and raise type-error if there is a problem''' 

560 if type(other) in (set, frozenset): 

561 inc, exc = other, None 

562 elif type(other) is _ComplementSet: 

563 inc, exc = other._included, other._excluded 

564 else: 

565 raise TypeError('argument must be another set or complement(set)') 

566 return inc, exc 

567 

568 

569def _norm_args_notimplemented(other): 

570 '''normalize args and return NotImplemented (for overloaded operators)''' 

571 if type(other) in (set, frozenset): 

572 inc, exc = other, None 

573 elif type(other) is _ComplementSet: 

574 inc, exc = other._included, other._excluded 

575 else: 

576 return NotImplemented, None 

577 return inc, exc 

578 

579 

580class _ComplementSet: 

581 """ 

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

583 """ 

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

585 

586 def __init__(self, included=None, excluded=None): 

587 if included is None: 

588 assert type(excluded) in (set, frozenset) 

589 elif excluded is None: 

590 assert type(included) in (set, frozenset) 

591 else: 

592 raise ValueError('one of included or excluded must be a set') 

593 self._included, self._excluded = included, excluded 

594 

595 def __repr__(self): 

596 if self._included is None: 

597 return f'complement({repr(self._excluded)})' 

598 return f'complement(complement({repr(self._included)}))' 

599 

600 def complemented(self): 

601 '''return a complement of the current set''' 

602 if type(self._included) is frozenset or type(self._excluded) is frozenset: 

603 return _ComplementSet(included=self._excluded, excluded=self._included) 

604 return _ComplementSet( 

605 included=None if self._excluded is None else set(self._excluded), 

606 excluded=None if self._included is None else set(self._included)) 

607 

608 __invert__ = complemented 

609 

610 def complement(self): 

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

612 self._included, self._excluded = self._excluded, self._included 

613 

614 def __contains__(self, item): 

615 if self._included is None: 

616 return not item in self._excluded 

617 return item in self._included 

618 

619 def add(self, item): 

620 if self._included is None: 

621 if item in self._excluded: 

622 self._excluded.remove(item) 

623 else: 

624 self._included.add(item) 

625 

626 def remove(self, item): 

627 if self._included is None: 

628 self._excluded.add(item) 

629 else: 

630 self._included.remove(item) 

631 

632 def pop(self): 

633 if self._included is None: 

634 raise NotImplementedError # self.missing.add(random.choice(gc.objects())) 

635 return self._included.pop() 

636 

637 def intersection(self, other): 

638 try: 

639 return self & other 

640 except NotImplementedError: 

641 raise TypeError('argument must be another set or complement(set)') 

642 

643 def __and__(self, other): 

644 inc, exc = _norm_args_notimplemented(other) 

645 if inc is NotImplemented: 

646 return NotImplemented 

647 if self._included is None: 

648 if exc is None: # - + 

649 return _ComplementSet(included=inc - self._excluded) 

650 else: # - - 

651 return _ComplementSet(excluded=self._excluded.union(other._excluded)) 

652 else: 

653 if inc is None: # + - 

654 return _ComplementSet(included=exc - self._included) 

655 else: # + + 

656 return _ComplementSet(included=self._included.intersection(inc)) 

657 

658 __rand__ = __and__ 

659 

660 def __iand__(self, other): 

661 inc, exc = _norm_args_notimplemented(other) 

662 if inc is NotImplemented: 

663 return NotImplemented 

664 if self._included is None: 

665 if exc is None: # - + 

666 self._excluded = inc - self._excluded # TODO: do this in place? 

667 else: # - - 

668 self._excluded |= exc 

669 else: 

670 if inc is None: # + - 

671 self._included -= exc 

672 self._included, self._excluded = None, self._included 

673 else: # + + 

674 self._included &= inc 

675 return self 

676 

677 def union(self, other): 

678 try: 

679 return self | other 

680 except NotImplementedError: 

681 raise TypeError('argument must be another set or complement(set)') 

682 

683 def __or__(self, other): 

684 inc, exc = _norm_args_notimplemented(other) 

685 if inc is NotImplemented: 

686 return NotImplemented 

687 if self._included is None: 

688 if exc is None: # - + 

689 return _ComplementSet(excluded=self._excluded - inc) 

690 else: # - - 

691 return _ComplementSet(excluded=self._excluded.intersection(exc)) 

692 else: 

693 if inc is None: # + - 

694 return _ComplementSet(excluded=exc - self._included) 

695 else: # + + 

696 return _ComplementSet(included=self._included.union(inc)) 

697 

698 __ror__ = __or__ 

699 

700 def __ior__(self, other): 

701 inc, exc = _norm_args_notimplemented(other) 

702 if inc is NotImplemented: 

703 return NotImplemented 

704 if self._included is None: 

705 if exc is None: # - + 

706 self._excluded -= inc 

707 else: # - - 

708 self._excluded &= exc 

709 else: 

710 if inc is None: # + - 

711 self._included, self._excluded = None, exc - self._included # TODO: do this in place? 

712 else: # + + 

713 self._included |= inc 

714 return self 

715 

716 def update(self, items): 

717 if type(items) in (set, frozenset): 

718 inc, exc = items, None 

719 elif type(items) is _ComplementSet: 

720 inc, exc = items._included, items._excluded 

721 else: 

722 inc, exc = frozenset(items), None 

723 if self._included is None: 

724 if exc is None: # - + 

725 self._excluded &= inc 

726 else: # - - 

727 self._excluded.discard(exc) 

728 else: 

729 if inc is None: # + - 

730 self._included &= exc 

731 self._included, self._excluded = None, self._excluded 

732 else: # + + 

733 self._included.update(inc) 

734 

735 def discard(self, items): 

736 if type(items) in (set, frozenset): 

737 inc, exc = items, None 

738 elif type(items) is _ComplementSet: 

739 inc, exc = items._included, items._excluded 

740 else: 

741 inc, exc = frozenset(items), None 

742 if self._included is None: 

743 if exc is None: # - + 

744 self._excluded.update(inc) 

745 else: # - - 

746 self._included, self._excluded = exc - self._excluded, None 

747 else: 

748 if inc is None: # + - 

749 self._included &= exc 

750 else: # + + 

751 self._included.discard(inc) 

752 

753 def symmetric_difference(self, other): 

754 try: 

755 return self ^ other 

756 except NotImplementedError: 

757 raise TypeError('argument must be another set or complement(set)') 

758 

759 def __xor__(self, other): 

760 inc, exc = _norm_args_notimplemented(other) 

761 if inc is NotImplemented: 

762 return NotImplemented 

763 if inc is NotImplemented: 

764 return NotImplemented 

765 if self._included is None: 

766 if exc is None: # - + 

767 return _ComplementSet(excluded=self._excluded - inc) 

768 else: # - - 

769 return _ComplementSet(included=self._excluded.symmetric_difference(exc)) 

770 else: 

771 if inc is None: # + - 

772 return _ComplementSet(excluded=exc - self._included) 

773 else: # + + 

774 return _ComplementSet(included=self._included.symmetric_difference(inc)) 

775 

776 __rxor__ = __xor__ 

777 

778 def symmetric_difference_update(self, other): 

779 inc, exc = _norm_args_typeerror(other) 

780 if self._included is None: 

781 if exc is None: # - + 

782 self._excluded |= inc 

783 else: # - - 

784 self._excluded.symmetric_difference_update(exc) 

785 self._included, self._excluded = self._excluded, None 

786 else: 

787 if inc is None: # + - 

788 self._included |= exc 

789 self._included, self._excluded = None, self._included 

790 else: # + + 

791 self._included.symmetric_difference_update(inc) 

792 

793 def isdisjoint(self, other): 

794 inc, exc = _norm_args_typeerror(other) 

795 if inc is NotImplemented: 

796 return NotImplemented 

797 if self._included is None: 

798 if exc is None: # - + 

799 return inc.issubset(self._excluded) 

800 else: # - - 

801 return False 

802 else: 

803 if inc is None: # + - 

804 return self._included.issubset(exc) 

805 else: # + + 

806 return self._included.isdisjoint(inc) 

807 

808 def issubset(self, other): 

809 '''everything missing from other is also missing from self''' 

810 try: 

811 return self <= other 

812 except NotImplementedError: 

813 raise TypeError('argument must be another set or complement(set)') 

814 

815 def __le__(self, other): 

816 inc, exc = _norm_args_notimplemented(other) 

817 if inc is NotImplemented: 

818 return NotImplemented 

819 if inc is NotImplemented: 

820 return NotImplemented 

821 if self._included is None: 

822 if exc is None: # - + 

823 return False 

824 else: # - - 

825 return self._excluded.issupserset(exc) 

826 else: 

827 if inc is None: # + - 

828 return self._included.isdisjoint(exc) 

829 else: # + + 

830 return self._included.issubset(inc) 

831 

832 def __lt__(self, other): 

833 inc, exc = _norm_args_notimplemented(other) 

834 if inc is NotImplemented: 

835 return NotImplemented 

836 if inc is NotImplemented: 

837 return NotImplemented 

838 if self._included is None: 

839 if exc is None: # - + 

840 return False 

841 else: # - - 

842 return self._excluded > exc 

843 else: 

844 if inc is None: # + - 

845 return self._included.isdisjoint(exc) 

846 else: # + + 

847 return self._included < inc 

848 

849 def issuperset(self, other): 

850 '''everything missing from self is also missing from super''' 

851 try: 

852 return self >= other 

853 except NotImplementedError: 

854 raise TypeError('argument must be another set or complement(set)') 

855 

856 def __ge__(self, other): 

857 inc, exc = _norm_args_notimplemented(other) 

858 if inc is NotImplemented: 

859 return NotImplemented 

860 if self._included is None: 

861 if exc is None: # - + 

862 return not self._excluded.intersection(inc) 

863 else: # - - 

864 return self._excluded.issubset(exc) 

865 else: 

866 if inc is None: # + - 

867 return False 

868 else: # + + 

869 return self._included.issupserset(inc) 

870 

871 def __gt__(self, other): 

872 inc, exc = _norm_args_notimplemented(other) 

873 if inc is NotImplemented: 

874 return NotImplemented 

875 if self._included is None: 

876 if exc is None: # - + 

877 return not self._excluded.intersection(inc) 

878 else: # - - 

879 return self._excluded < exc 

880 else: 

881 if inc is None: # + - 

882 return False 

883 else: # + + 

884 return self._included > inc 

885 

886 def difference(self, other): 

887 try: 

888 return self - other 

889 except NotImplementedError: 

890 raise TypeError('argument must be another set or complement(set)') 

891 

892 def __sub__(self, other): 

893 inc, exc = _norm_args_notimplemented(other) 

894 if inc is NotImplemented: 

895 return NotImplemented 

896 if self._included is None: 

897 if exc is None: # - + 

898 return _ComplementSet(excluded=self._excluded | inc) 

899 else: # - - 

900 return _ComplementSet(included=exc - self._excluded) 

901 else: 

902 if inc is None: # + - 

903 return _ComplementSet(included=self._included & exc) 

904 else: # + + 

905 return _ComplementSet(included=self._included.difference(inc)) 

906 

907 def __rsub__(self, other): 

908 inc, exc = _norm_args_notimplemented(other) 

909 if inc is NotImplemented: 

910 return NotImplemented 

911 # rsub, so the expression being evaluated is "other - self" 

912 if self._included is None: 

913 if exc is None: # - + 

914 return _ComplementSet(included=inc & self._excluded) 

915 else: # - - 

916 return _ComplementSet(included=self._excluded - exc) 

917 else: 

918 if inc is None: # + - 

919 return _ComplementSet(excluded=exc | self._included) 

920 else: # + + 

921 return _ComplementSet(included=inc.difference(self._included)) 

922 

923 def difference_update(self, other): 

924 try: 

925 self -= other 

926 except NotImplementedError: 

927 raise TypeError('argument must be another set or complement(set)') 

928 

929 def __isub__(self, other): 

930 inc, exc = _norm_args_notimplemented(other) 

931 if inc is NotImplemented: 

932 return NotImplemented 

933 if self._included is None: 

934 if exc is None: # - + 

935 self._excluded |= inc 

936 else: # - - 

937 self._included, self._excluded = exc - self._excluded, None 

938 else: 

939 if inc is None: # + - 

940 self._included &= exc 

941 else: # + + 

942 self._included.difference_update(inc) 

943 return self 

944 

945 def __eq__(self, other): 

946 return ( 

947 type(self) is type(other) 

948 and self._included == other._included 

949 and self._excluded == other._excluded) or ( 

950 type(other) in (set, frozenset) and self._included == other) 

951 

952 def __hash__(self): 

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

954 

955 def __len__(self): 

956 if self._included is not None: 

957 return len(self._included) 

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

959 

960 def __iter__(self): 

961 if self._included is not None: 

962 return iter(self._included) 

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

964 

965 def __bool__(self): 

966 if self._included is not None: 

967 return bool(self._included) 

968 return True 

969