228 lines
5.6 KiB
Python
228 lines
5.6 KiB
Python
from collections.abc import Set, Hashable
|
|
import sys
|
|
from pyrsistent._pmap import pmap
|
|
|
|
|
|
class PSet(object):
|
|
"""
|
|
Persistent set implementation. Built on top of the persistent map. The set supports all operations
|
|
in the Set protocol and is Hashable.
|
|
|
|
Do not instantiate directly, instead use the factory functions :py:func:`s` or :py:func:`pset`
|
|
to create an instance.
|
|
|
|
Random access and insert is log32(n) where n is the size of the set.
|
|
|
|
Some examples:
|
|
|
|
>>> s = pset([1, 2, 3, 1])
|
|
>>> s2 = s.add(4)
|
|
>>> s3 = s2.remove(2)
|
|
>>> s
|
|
pset([1, 2, 3])
|
|
>>> s2
|
|
pset([1, 2, 3, 4])
|
|
>>> s3
|
|
pset([1, 3, 4])
|
|
"""
|
|
__slots__ = ('_map', '__weakref__')
|
|
|
|
def __new__(cls, m):
|
|
self = super(PSet, cls).__new__(cls)
|
|
self._map = m
|
|
return self
|
|
|
|
def __contains__(self, element):
|
|
return element in self._map
|
|
|
|
def __iter__(self):
|
|
return iter(self._map)
|
|
|
|
def __len__(self):
|
|
return len(self._map)
|
|
|
|
def __repr__(self):
|
|
if not self:
|
|
return 'p' + str(set(self))
|
|
|
|
return 'pset([{0}])'.format(str(set(self))[1:-1])
|
|
|
|
def __str__(self):
|
|
return self.__repr__()
|
|
|
|
def __hash__(self):
|
|
return hash(self._map)
|
|
|
|
def __reduce__(self):
|
|
# Pickling support
|
|
return pset, (list(self),)
|
|
|
|
@classmethod
|
|
def _from_iterable(cls, it, pre_size=8):
|
|
return PSet(pmap(dict((k, True) for k in it), pre_size=pre_size))
|
|
|
|
def add(self, element):
|
|
"""
|
|
Return a new PSet with element added
|
|
|
|
>>> s1 = s(1, 2)
|
|
>>> s1.add(3)
|
|
pset([1, 2, 3])
|
|
"""
|
|
return self.evolver().add(element).persistent()
|
|
|
|
def update(self, iterable):
|
|
"""
|
|
Return a new PSet with elements in iterable added
|
|
|
|
>>> s1 = s(1, 2)
|
|
>>> s1.update([3, 4, 4])
|
|
pset([1, 2, 3, 4])
|
|
"""
|
|
e = self.evolver()
|
|
for element in iterable:
|
|
e.add(element)
|
|
|
|
return e.persistent()
|
|
|
|
def remove(self, element):
|
|
"""
|
|
Return a new PSet with element removed. Raises KeyError if element is not present.
|
|
|
|
>>> s1 = s(1, 2)
|
|
>>> s1.remove(2)
|
|
pset([1])
|
|
"""
|
|
if element in self._map:
|
|
return self.evolver().remove(element).persistent()
|
|
|
|
raise KeyError("Element '%s' not present in PSet" % repr(element))
|
|
|
|
def discard(self, element):
|
|
"""
|
|
Return a new PSet with element removed. Returns itself if element is not present.
|
|
"""
|
|
if element in self._map:
|
|
return self.evolver().remove(element).persistent()
|
|
|
|
return self
|
|
|
|
class _Evolver(object):
|
|
__slots__ = ('_original_pset', '_pmap_evolver')
|
|
|
|
def __init__(self, original_pset):
|
|
self._original_pset = original_pset
|
|
self._pmap_evolver = original_pset._map.evolver()
|
|
|
|
def add(self, element):
|
|
self._pmap_evolver[element] = True
|
|
return self
|
|
|
|
def remove(self, element):
|
|
del self._pmap_evolver[element]
|
|
return self
|
|
|
|
def is_dirty(self):
|
|
return self._pmap_evolver.is_dirty()
|
|
|
|
def persistent(self):
|
|
if not self.is_dirty():
|
|
return self._original_pset
|
|
|
|
return PSet(self._pmap_evolver.persistent())
|
|
|
|
def __len__(self):
|
|
return len(self._pmap_evolver)
|
|
|
|
def copy(self):
|
|
return self
|
|
|
|
def evolver(self):
|
|
"""
|
|
Create a new evolver for this pset. For a discussion on evolvers in general see the
|
|
documentation for the pvector evolver.
|
|
|
|
Create the evolver and perform various mutating updates to it:
|
|
|
|
>>> s1 = s(1, 2, 3)
|
|
>>> e = s1.evolver()
|
|
>>> _ = e.add(4)
|
|
>>> len(e)
|
|
4
|
|
>>> _ = e.remove(1)
|
|
|
|
The underlying pset remains the same:
|
|
|
|
>>> s1
|
|
pset([1, 2, 3])
|
|
|
|
The changes are kept in the evolver. An updated pmap can be created using the
|
|
persistent() function on the evolver.
|
|
|
|
>>> s2 = e.persistent()
|
|
>>> s2
|
|
pset([2, 3, 4])
|
|
|
|
The new pset will share data with the original pset in the same way that would have
|
|
been done if only using operations on the pset.
|
|
"""
|
|
return PSet._Evolver(self)
|
|
|
|
# All the operations and comparisons you would expect on a set.
|
|
#
|
|
# This is not very beautiful. If we avoid inheriting from PSet we can use the
|
|
# __slots__ concepts (which requires a new style class) and hopefully save some memory.
|
|
__le__ = Set.__le__
|
|
__lt__ = Set.__lt__
|
|
__gt__ = Set.__gt__
|
|
__ge__ = Set.__ge__
|
|
__eq__ = Set.__eq__
|
|
__ne__ = Set.__ne__
|
|
|
|
__and__ = Set.__and__
|
|
__or__ = Set.__or__
|
|
__sub__ = Set.__sub__
|
|
__xor__ = Set.__xor__
|
|
|
|
issubset = __le__
|
|
issuperset = __ge__
|
|
union = __or__
|
|
intersection = __and__
|
|
difference = __sub__
|
|
symmetric_difference = __xor__
|
|
|
|
isdisjoint = Set.isdisjoint
|
|
|
|
Set.register(PSet)
|
|
Hashable.register(PSet)
|
|
|
|
_EMPTY_PSET = PSet(pmap())
|
|
|
|
|
|
def pset(iterable=(), pre_size=8):
|
|
"""
|
|
Creates a persistent set from iterable. Optionally takes a sizing parameter equivalent to that
|
|
used for :py:func:`pmap`.
|
|
|
|
>>> s1 = pset([1, 2, 3, 2])
|
|
>>> s1
|
|
pset([1, 2, 3])
|
|
"""
|
|
if not iterable:
|
|
return _EMPTY_PSET
|
|
|
|
return PSet._from_iterable(iterable, pre_size=pre_size)
|
|
|
|
|
|
def s(*elements):
|
|
"""
|
|
Create a persistent set.
|
|
|
|
Takes an arbitrary number of arguments to insert into the new set.
|
|
|
|
>>> s1 = s(1, 2, 3, 2)
|
|
>>> s1
|
|
pset([1, 2, 3])
|
|
"""
|
|
return pset(elements)
|