setutils
#
The set
type brings the practical expressiveness of
set theory to Python. It has a very rich API overall, but lacks a
couple of fundamental features. For one, sets are not ordered. On top
of this, sets are not indexable, i.e, my_set[8]
will raise an
TypeError
. The IndexedSet
type remedies both of these
issues without compromising on the excellent complexity
characteristics of Python's built-in set implementation.
Classes#
|
Functions#
|
Given a |
- class IndexedSet(other=None)#
Bases:
collections.abc.MutableSet
IndexedSet
is acollections.MutableSet
that maintains insertion order and uniqueness of inserted elements. It's a hybrid type, mostly like an OrderedSet, but alsolist
-like, in that it supports indexing and slicing.- Parameters:
other (iterable) -- An optional iterable used to initialize the set.
>>> x = IndexedSet(list(range(4)) + list(range(8))) >>> x IndexedSet([0, 1, 2, 3, 4, 5, 6, 7]) >>> x - set(range(2)) IndexedSet([2, 3, 4, 5, 6, 7]) >>> x[-1] 7 >>> fcr = IndexedSet('freecreditreport.com') >>> ''.join(fcr[:fcr.index('.')]) 'frecditpo'
Standard set operators and interoperation with
set
are all supported:>>> fcr & set('cash4gold.com') IndexedSet(['c', 'd', 'o', '.', 'm'])
As you can see, the
IndexedSet
is almost like aUniqueList
, retaining only one copy of a given value, in the order it was first added. For the curious, the reason why IndexedSet does not support setting items based on index (i.e,__setitem__()
), consider the following dilemma:my_indexed_set = [A, B, C, D] my_indexed_set[2] = A
At this point, a set requires only one A, but a
list
would overwrite C. Overwriting C would change the length of the list, meaning thatmy_indexed_set[2]
would not be A, as expected with a list, but rather D. So, no__setitem__()
.Otherwise, the API strives to be as complete a union of the
list
andset
APIs as possible.- property _dead_index_count#
- __sub__#
- _compact()#
- _cull()#
- _get_real_index(index)#
- _get_apparent_index(index)#
- _add_dead(start, stop=None)#
- __len__()#
- __contains__(item)#
- __iter__()#
- __reversed__()#
- __repr__()#
Return repr(self).
- __eq__(other)#
Return self==value.
- classmethod from_iterable(it)#
from_iterable(it) -> create a set from an iterable
- add(item)#
add(item) -> add item to the set
- remove(item)#
remove(item) -> remove item from the set, raises if not present
- discard(item)#
discard(item) -> discard item from the set (does not raise)
- clear()#
clear() -> empty the set
- isdisjoint(other)#
isdisjoint(other) -> return True if no overlap with other
- issubset(other)#
issubset(other) -> return True if other contains this set
- issuperset(other)#
issuperset(other) -> return True if set contains other
- __rsub__(other)#
- symmetric_difference_update(other)#
symmetric_difference_update(other) -> in-place XOR with other
- __ior__(*others)#
- __iand__(*others)#
- __isub__(*others)#
- __ixor__(*others)#
- iter_slice(start, stop, step=None)#
iterate over a slice of the set
- __getitem__(index)#
- pop(index=None)#
pop(index) -> remove the item at a given index (-1 by default)
- count(val)#
count(val) -> count number of instances of value (0 or 1)
- reverse()#
reverse() -> reverse the contents of the set in-place
- sort(**kwargs)#
sort() -> sort the contents of the set in-place
- index(val)#
index(val) -> get the index of a value, raises if not present
- complement(wrapped)#
Given a
set
, convert it to a complement set.Whereas a
set
keeps track of what it contains, a complement set keeps track of what it does not contain. For example, look what happens when we intersect a normal set with a complement set:>>> list(set(range(5)) & complement(set([2, 3])))
[0, 1, 4]
We get the everything in the left that wasn't in the right, because intersecting with a complement is the same as subtracting a normal set.
- Parameters:
wrapped (set) -- A set or any other iterable which should be turned into a complement set.
All set methods and operators are supported by complement sets, between other
complement()
-wrapped sets and/or regularset
objects.Because a complement set only tracks what elements are not in the set, functionality based on set contents is unavailable:
len()
,iter()
(and for loops), and.pop()
. But a complement set can always be turned back into a regular set by complementing it again:>>> s = set(range(5)) >>> complement(complement(s)) == s True
Note
An empty complement set corresponds to the concept of a universal set from mathematics.
Complement sets by example#
Many uses of sets can be expressed more simply by using a complement. Rather than trying to work out in your head the proper way to invert an expression, you can just throw a complement on the set. Consider this example of a name filter:
>>> class NamesFilter(object): ... def __init__(self, allowed): ... self._allowed = allowed ... ... def filter(self, names): ... return [name for name in names if name in self._allowed] >>> NamesFilter(set(['alice', 'bob'])).filter(['alice', 'bob', 'carol']) ['alice', 'bob']
What if we want to just express "let all the names through"?
We could try to enumerate all of the expected names:
``NamesFilter({'alice', 'bob', 'carol'})``
But this is very brittle -- what if at some point over this object is changed to filter
['alice', 'bob', 'carol', 'dan']
?Even worse, what about the poor programmer who next works on this piece of code? They cannot tell whether the purpose of the large allowed set was "allow everything", or if 'dan' was excluded for some subtle reason.
A complement set lets the programmer intention be expressed succinctly and directly:
NamesFilter(complement(set()))
Not only is this code short and robust, it is easy to understand the intention.