sets.py 19.1 KB
Newer Older
1 2 3 4 5 6
"""Classes to represent arbitrary sets (including sets of sets).

This module implements sets using dictionaries whose values are
ignored.  The usual operations (union, intersection, deletion, etc.)
are provided as both methods and operators.

7 8 9 10 11 12 13 14 15 16
Important: sets are not sequences!  While they support 'x in s',
'len(s)', and 'for x in s', none of those operations are unique for
sequences; for example, mappings support all three as well.  The
characteristic operation for sequences is subscripting with small
integers: s[i], for i in range(len(s)).  Sets don't support
subscripting at all.  Also, sequences allow multiple occurrences and
their elements have a definite order; sets on the other hand don't
record multiple occurrences and don't remember the order of element
insertion (which is why they don't support s[i]).

17 18 19 20 21 22 23 24 25 26 27
The following classes are provided:

BaseSet -- All the operations common to both mutable and immutable
    sets. This is an abstract class, not meant to be directly
    instantiated.

Set -- Mutable sets, subclass of BaseSet; not hashable.

ImmutableSet -- Immutable sets, subclass of BaseSet; hashable.
    An iterable argument is mandatory to create an ImmutableSet.

28 29 30
_TemporarilyImmutableSet -- A wrapper around a Set, hashable,
    giving the same hash value as the immutable set equivalent
    would have.  Do not use this class directly.
31 32 33

Only hashable objects can be added to a Set. In particular, you cannot
really add a Set as an element to another Set; if you try, what is
Raymond Hettinger's avatar
Raymond Hettinger committed
34
actually added is an ImmutableSet built from it (it compares equal to
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
the one you tried adding).

When you ask if `x in y' where x is a Set and y is a Set or
ImmutableSet, x is wrapped into a _TemporarilyImmutableSet z, and
what's tested is actually `z in y'.

"""

# Code history:
#
# - Greg V. Wilson wrote the first version, using a different approach
#   to the mutable/immutable problem, and inheriting from dict.
#
# - Alex Martelli modified Greg's version to implement the current
#   Set/ImmutableSet approach, and make the data an attribute.
#
# - Guido van Rossum rewrote much of the code, made some API changes,
#   and cleaned up the docstrings.
53
#
54
# - Raymond Hettinger added a number of speedups and other
55
#   improvements.
56

57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
from __future__ import generators
try:
    from itertools import ifilter, ifilterfalse
except ImportError:
    # Code to make the module run under Py2.2
    def ifilter(predicate, iterable):
        if predicate is None:
            def predicate(x):
                return x
        for x in iterable:
            if predicate(x):
                yield x
    def ifilterfalse(predicate, iterable):
        if predicate is None:
            def predicate(x):
                return x
        for x in iterable:
            if not predicate(x):
                yield x
76 77 78 79
    try:
        True, False
    except NameError:
        True, False = (0==0, 0!=0)
80 81 82 83 84 85 86 87 88 89

__all__ = ['BaseSet', 'Set', 'ImmutableSet']

class BaseSet(object):
    """Common base class for mutable and immutable sets."""

    __slots__ = ['_data']

    # Constructor

90 91 92 93
    def __init__(self):
        """This is an abstract class."""
        # Don't call this from a concrete subclass!
        if self.__class__ is BaseSet:
94 95
            raise TypeError, ("BaseSet is an abstract class.  "
                              "Use Set or ImmutableSet.")
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125

    # Standard protocols: __len__, __repr__, __str__, __iter__

    def __len__(self):
        """Return the number of elements of a set."""
        return len(self._data)

    def __repr__(self):
        """Return string representation of a set.

        This looks like 'Set([<list of elements>])'.
        """
        return self._repr()

    # __str__ is the same as __repr__
    __str__ = __repr__

    def _repr(self, sorted=False):
        elements = self._data.keys()
        if sorted:
            elements.sort()
        return '%s(%r)' % (self.__class__.__name__, elements)

    def __iter__(self):
        """Return an iterator over the elements or a set.

        This is the keys iterator for the underlying dict.
        """
        return self._data.iterkeys()

126 127 128 129
    # Three-way comparison is not supported.  However, because __eq__ is
    # tried before __cmp__, if Set x == Set y, x.__eq__(y) returns True and
    # then cmp(x, y) returns 0 (Python doesn't actually call __cmp__ in this
    # case).
130 131 132 133

    def __cmp__(self, other):
        raise TypeError, "can't compare sets using cmp()"

134 135 136 137 138 139 140 141 142 143 144 145 146 147
    # Equality comparisons using the underlying dicts.  Mixed-type comparisons
    # are allowed here, where Set == z for non-Set z always returns False,
    # and Set != z always True.  This allows expressions like "x in y" to
    # give the expected result when y is a sequence of mixed types, not
    # raising a pointless TypeError just because y contains a Set, or x is
    # a Set and y contain's a non-set ("in" invokes only __eq__).
    # Subtle:  it would be nicer if __eq__ and __ne__ could return
    # NotImplemented instead of True or False.  Then the other comparand
    # would get a chance to determine the result, and if the other comparand
    # also returned NotImplemented then it would fall back to object address
    # comparison (which would always return False for __eq__ and always
    # True for __ne__).  However, that doesn't work, because this type
    # *also* implements __cmp__:  if, e.g., __eq__ returns NotImplemented,
    # Python tries __cmp__ next, and the __cmp__ here then raises TypeError.
148 149

    def __eq__(self, other):
150 151 152 153
        if isinstance(other, BaseSet):
            return self._data == other._data
        else:
            return False
154 155

    def __ne__(self, other):
156 157 158 159
        if isinstance(other, BaseSet):
            return self._data != other._data
        else:
            return True
160 161 162 163 164

    # Copying operations

    def copy(self):
        """Return a shallow copy of a set."""
165
        result = self.__class__()
166 167
        result._data.update(self._data)
        return result
168 169 170 171 172 173 174 175 176 177 178

    __copy__ = copy # For the copy module

    def __deepcopy__(self, memo):
        """Return a deep copy of a set; used by copy module."""
        # This pre-creates the result and inserts it in the memo
        # early, in case the deep copy recurses into another reference
        # to this same set.  A set can't be an element of itself, but
        # it can certainly contain an object that has a reference to
        # itself.
        from copy import deepcopy
179
        result = self.__class__()
180 181 182 183 184 185 186
        memo[id(self)] = result
        data = result._data
        value = True
        for elt in self:
            data[deepcopy(elt, memo)] = value
        return result

187 188 189
    # Standard set operations: union, intersection, both differences.
    # Each has an operator version (e.g. __or__, invoked with |) and a
    # method version (e.g. union).
190 191 192 193 194
    # Subtle:  Each pair requires distinct code so that the outcome is
    # correct when the type of other isn't suitable.  For example, if
    # we did "union = __or__" instead, then Set().union(3) would return
    # NotImplemented instead of raising TypeError (albeit that *why* it
    # raises TypeError as-is is also a bit subtle).
195

196
    def __or__(self, other):
197 198 199 200
        """Return the union of two sets as a new set.

        (I.e. all elements that are in either set.)
        """
201 202
        if not isinstance(other, BaseSet):
            return NotImplemented
203
        return self.union(other)
204

205 206
    def union(self, other):
        """Return the union of two sets as a new set.
207

208 209
        (I.e. all elements that are in either set.)
        """
210 211 212
        result = self.__class__(self)
        result._update(other)
        return result
213 214

    def __and__(self, other):
215 216 217 218
        """Return the intersection of two sets as a new set.

        (I.e. all elements that are in both sets.)
        """
219 220
        if not isinstance(other, BaseSet):
            return NotImplemented
221
        return self.intersection(other)
222

223 224
    def intersection(self, other):
        """Return the intersection of two sets as a new set.
225

226 227
        (I.e. all elements that are in both sets.)
        """
228 229 230 231 232 233 234 235
        if not isinstance(other, BaseSet):
            other = Set(other)
        if len(self) <= len(other):
            little, big = self, other
        else:
            little, big = other, self
        common = ifilter(big._data.has_key, little)
        return self.__class__(common)
236 237

    def __xor__(self, other):
238 239 240 241
        """Return the symmetric difference of two sets as a new set.

        (I.e. all elements that are in exactly one of the sets.)
        """
242 243
        if not isinstance(other, BaseSet):
            return NotImplemented
244 245 246 247 248 249 250
        return self.symmetric_difference(other)

    def symmetric_difference(self, other):
        """Return the symmetric difference of two sets as a new set.

        (I.e. all elements that are in exactly one of the sets.)
        """
251
        result = self.__class__()
252 253
        data = result._data
        value = True
254
        selfdata = self._data
255 256 257 258
        try:
            otherdata = other._data
        except AttributeError:
            otherdata = Set(other)._data
Raymond Hettinger's avatar
Raymond Hettinger committed
259
        for elt in ifilterfalse(otherdata.has_key, selfdata):
260
            data[elt] = value
Raymond Hettinger's avatar
Raymond Hettinger committed
261
        for elt in ifilterfalse(selfdata.has_key, otherdata):
262
            data[elt] = value
263 264
        return result

265
    def  __sub__(self, other):
266 267 268 269
        """Return the difference of two sets as a new Set.

        (I.e. all elements that are in this set and not in the other.)
        """
270 271
        if not isinstance(other, BaseSet):
            return NotImplemented
272
        return self.difference(other)
273

274 275 276 277 278
    def difference(self, other):
        """Return the difference of two sets as a new Set.

        (I.e. all elements that are in this set and not in the other.)
        """
279 280 281 282 283 284 285 286 287 288
        result = self.__class__()
        data = result._data
        try:
            otherdata = other._data
        except AttributeError:
            otherdata = Set(other)._data
        value = True
        for elt in ifilterfalse(otherdata.has_key, self):
            data[elt] = value
        return result
289 290 291 292 293 294 295 296 297

    # Membership test

    def __contains__(self, element):
        """Report whether an element is a member of a set.

        (Called in response to the expression `element in self'.)
        """
        try:
298 299
            return element in self._data
        except TypeError:
300
            transform = getattr(element, "__as_temporarily_immutable__", None)
301 302 303
            if transform is None:
                raise # re-raise the TypeError exception we caught
            return transform() in self._data
304 305 306 307 308 309

    # Subset and superset test

    def issubset(self, other):
        """Report whether another set contains this set."""
        self._binary_sanity_check(other)
310 311
        if len(self) > len(other):  # Fast check for obvious cases
            return False
Raymond Hettinger's avatar
Raymond Hettinger committed
312
        for elt in ifilterfalse(other._data.has_key, self):
313
            return False
314 315 316 317 318
        return True

    def issuperset(self, other):
        """Report whether this set contains another set."""
        self._binary_sanity_check(other)
319 320
        if len(self) < len(other):  # Fast check for obvious cases
            return False
Raymond Hettinger's avatar
Raymond Hettinger committed
321
        for elt in ifilterfalse(self._data.has_key, other):
Tim Peters's avatar
Tim Peters committed
322
            return False
323 324
        return True

325 326 327 328 329 330 331 332 333 334 335 336
    # Inequality comparisons using the is-subset relation.
    __le__ = issubset
    __ge__ = issuperset

    def __lt__(self, other):
        self._binary_sanity_check(other)
        return len(self) < len(other) and self.issubset(other)

    def __gt__(self, other):
        self._binary_sanity_check(other)
        return len(self) > len(other) and self.issuperset(other)

337 338 339 340 341 342 343 344 345 346
    # Assorted helpers

    def _binary_sanity_check(self, other):
        # Check that the other argument to a binary operation is also
        # a set, raising a TypeError otherwise.
        if not isinstance(other, BaseSet):
            raise TypeError, "Binary operation only permitted between sets"

    def _compute_hash(self):
        # Calculate hash code for a set by xor'ing the hash codes of
Tim Peters's avatar
Tim Peters committed
347 348 349 350
        # the elements.  This ensures that the hash code does not depend
        # on the order in which elements are added to the set.  This is
        # not called __hash__ because a BaseSet should not be hashable;
        # only an ImmutableSet is hashable.
351 352 353 354 355
        result = 0
        for elt in self:
            result ^= hash(elt)
        return result

356 357 358
    def _update(self, iterable):
        # The main loop for update() and the subclass __init__() methods.
        data = self._data
Raymond Hettinger's avatar
Raymond Hettinger committed
359 360 361 362 363 364

        # Use the fast update() method when a dictionary is available.
        if isinstance(iterable, BaseSet):
            data.update(iterable._data)
            return

365
        value = True
366 367 368 369 370 371 372 373 374 375 376

        if type(iterable) in (list, tuple, xrange):
            # Optimized: we know that __iter__() and next() can't
            # raise TypeError, so we can move 'try:' out of the loop.
            it = iter(iterable)
            while True:
                try:
                    for element in it:
                        data[element] = value
                    return
                except TypeError:
377
                    transform = getattr(element, "__as_immutable__", None)
378 379 380 381 382 383 384
                    if transform is None:
                        raise # re-raise the TypeError exception we caught
                    data[transform()] = value
        else:
            # Safe: only catch TypeError where intended
            for element in iterable:
                try:
385
                    data[element] = value
386
                except TypeError:
387
                    transform = getattr(element, "__as_immutable__", None)
388 389 390
                    if transform is None:
                        raise # re-raise the TypeError exception we caught
                    data[transform()] = value
391

392 393 394 395

class ImmutableSet(BaseSet):
    """Immutable set class."""

396
    __slots__ = ['_hashcode']
397 398 399

    # BaseSet + hashing

400 401
    def __init__(self, iterable=None):
        """Construct an immutable set from an optional iterable."""
402
        self._hashcode = None
403 404 405
        self._data = {}
        if iterable is not None:
            self._update(iterable)
406 407 408 409 410 411

    def __hash__(self):
        if self._hashcode is None:
            self._hashcode = self._compute_hash()
        return self._hashcode

412 413 414 415 416
    def __getstate__(self):
        return self._data, self._hashcode

    def __setstate__(self, state):
        self._data, self._hashcode = state
417 418 419 420 421 422 423 424

class Set(BaseSet):
    """ Mutable set class."""

    __slots__ = []

    # BaseSet + operations requiring mutability; no hashing

425 426 427 428 429 430
    def __init__(self, iterable=None):
        """Construct a set from an optional iterable."""
        self._data = {}
        if iterable is not None:
            self._update(iterable)

431 432 433 434 435 436 437
    def __getstate__(self):
        # getstate's results are ignored if it is not
        return self._data,

    def __setstate__(self, data):
        self._data, = data

438 439 440 441
    def __hash__(self):
        """A Set cannot be hashed."""
        # We inherit object.__hash__, so we must deny this explicitly
        raise TypeError, "Can't hash a Set, only an ImmutableSet."
442

443 444 445 446
    # In-place union, intersection, differences.
    # Subtle:  The xyz_update() functions deliberately return None,
    # as do all mutating operations on built-in container types.
    # The __xyz__ spellings have to return self, though.
447

448
    def __ior__(self, other):
449 450 451 452 453
        """Update a set with the union of itself and another."""
        self._binary_sanity_check(other)
        self._data.update(other._data)
        return self

454 455
    def union_update(self, other):
        """Update a set with the union of itself and another."""
456
        self._update(other)
457

458
    def __iand__(self, other):
459 460
        """Update a set with the intersection of itself and another."""
        self._binary_sanity_check(other)
461
        self._data = (self & other)._data
462 463
        return self

464 465
    def intersection_update(self, other):
        """Update a set with the intersection of itself and another."""
466 467 468 469
        if isinstance(other, BaseSet):
            self &= other
        else:
            self._data = (self.intersection(other))._data
470

471
    def __ixor__(self, other):
472 473
        """Update a set with the symmetric difference of itself and another."""
        self._binary_sanity_check(other)
474 475 476 477 478
        self.symmetric_difference_update(other)
        return self

    def symmetric_difference_update(self, other):
        """Update a set with the symmetric difference of itself and another."""
479 480
        data = self._data
        value = True
481 482
        if not isinstance(other, BaseSet):
            other = Set(other)
483 484 485 486 487 488
        for elt in other:
            if elt in data:
                del data[elt]
            else:
                data[elt] = value

489
    def __isub__(self, other):
490 491
        """Remove all elements of another set from this set."""
        self._binary_sanity_check(other)
492
        self.difference_update(other)
493 494
        return self

495 496
    def difference_update(self, other):
        """Remove all elements of another set from this set."""
497 498 499 500 501
        data = self._data
        if not isinstance(other, BaseSet):
            other = Set(other)
        for elt in ifilter(data.has_key, other):
            del data[elt]
502 503 504 505 506

    # Python dict-like mass mutations: update, clear

    def update(self, iterable):
        """Add all values from an iterable (such as a list or file)."""
507
        self._update(iterable)
508 509 510 511 512 513 514 515 516 517 518 519 520

    def clear(self):
        """Remove all elements from this set."""
        self._data.clear()

    # Single-element mutations: add, remove, discard

    def add(self, element):
        """Add an element to a set.

        This has no effect if the element is already present.
        """
        try:
521 522
            self._data[element] = True
        except TypeError:
523
            transform = getattr(element, "__as_immutable__", None)
524 525 526
            if transform is None:
                raise # re-raise the TypeError exception we caught
            self._data[transform()] = True
527 528 529 530 531 532 533

    def remove(self, element):
        """Remove an element from a set; it must be a member.

        If the element is not a member, raise a KeyError.
        """
        try:
534 535
            del self._data[element]
        except TypeError:
536
            transform = getattr(element, "__as_temporarily_immutable__", None)
537 538 539
            if transform is None:
                raise # re-raise the TypeError exception we caught
            del self._data[transform()]
540 541 542 543 544 545 546

    def discard(self, element):
        """Remove an element from a set if it is a member.

        If the element is not a member, do nothing.
        """
        try:
547
            self.remove(element)
548 549 550
        except KeyError:
            pass

551
    def pop(self):
552
        """Remove and return an arbitrary set element."""
553 554
        return self._data.popitem()[0]

555
    def __as_immutable__(self):
556 557 558
        # Return a copy of self as an immutable set
        return ImmutableSet(self)

559
    def __as_temporarily_immutable__(self):
560 561 562 563
        # Return self wrapped in a temporarily immutable set
        return _TemporarilyImmutableSet(self)


564
class _TemporarilyImmutableSet(BaseSet):
565 566 567 568 569
    # Wrap a mutable set as if it was temporarily immutable.
    # This only supplies hashing and equality comparisons.

    def __init__(self, set):
        self._set = set
570
        self._data = set._data  # Needed by ImmutableSet.__eq__()
571 572

    def __hash__(self):
573
        return self._set._compute_hash()