summaryrefslogtreecommitdiff
path: root/blist/_sortedlist.py
diff options
context:
space:
mode:
Diffstat (limited to 'blist/_sortedlist.py')
-rw-r--r--blist/_sortedlist.py647
1 files changed, 647 insertions, 0 deletions
diff --git a/blist/_sortedlist.py b/blist/_sortedlist.py
new file mode 100644
index 0000000..b34f69e
--- /dev/null
+++ b/blist/_sortedlist.py
@@ -0,0 +1,647 @@
+from blist._blist import blist
+import collections, bisect, weakref, operator, itertools, sys, threading
+try: # pragma: no cover
+ izip = itertools.izip
+except AttributeError: # pragma: no cover
+ izip = zip
+
+__all__ = ['sortedlist', 'weaksortedlist', 'sortedset', 'weaksortedset']
+
+class ReprRecursion(object):
+ local = threading.local()
+ def __init__(self, ob):
+ if not hasattr(self.local, 'repr_count'):
+ self.local.repr_count = collections.defaultdict(int)
+ self.ob_id = id(ob)
+ self.value = self.ob_id in self.local.repr_count
+
+ def __enter__(self):
+ self.local.repr_count[self.ob_id] += 1
+ return self.value
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self.local.repr_count[self.ob_id] -= 1
+ if self.local.repr_count[self.ob_id] == 0:
+ del self.local.repr_count[self.ob_id]
+ return False
+
+class _sortedbase(collections.Sequence):
+ def __init__(self, iterable=(), key=None):
+ self._key = key
+ if key is not None and not hasattr(key, '__call__'):
+ raise TypeError("'%s' object is not callable" % str(type(key)))
+ if ((isinstance(iterable,type(self))
+ or isinstance(self,type(iterable)))
+ and iterable._key is key):
+ self._blist = blist(iterable._blist)
+ else:
+ self._blist = blist()
+ for v in iterable:
+ self.add(v)
+
+ def _from_iterable(self, iterable):
+ return self.__class__(iterable, self._key)
+
+ def _u2key(self, value):
+ "Convert a user-object to the key"
+ if self._key is None:
+ return value
+ else:
+ return self._key(value)
+
+ def _u2i(self, value):
+ "Convert a user-object to the internal representation"
+ if self._key is None:
+ return value
+ else:
+ return (self._key(value), value)
+
+ def _i2u(self, value):
+ "Convert an internal object to a user-object"
+ if self._key is None:
+ return value
+ else:
+ return value[1]
+
+ def _i2key(self, value):
+ "Convert an internal object to the key"
+ if self._key is None:
+ return value
+ else:
+ return value[0]
+
+ def _bisect_left(self, v):
+ """Locate the point in the list where v would be inserted.
+
+ Returns an (i, value) tuple:
+ - i is the position where v would be inserted
+ - value is the current user-object at position i
+
+ This is the key function to override in subclasses. They must
+ accept a user-object v and return a user-object value.
+ """
+
+ key = self._u2key(v)
+ lo = 0
+ hi = len(self._blist)
+ while lo < hi:
+ mid = (lo+hi)//2
+ v = self._i2key(self._blist[mid])
+ if v < key: lo = mid + 1
+ else: hi = mid
+ if lo < len(self._blist):
+ return lo, self._i2u(self._blist[lo])
+ return lo, None
+
+ def _bisect_right(self, v):
+ """Same as _bisect_left, but go to the right of equal values"""
+
+ key = self._u2key(v)
+ lo = 0
+ hi = len(self._blist)
+ while lo < hi:
+ mid = (lo+hi)//2
+ v = self._i2key(self._blist[mid])
+ if key < v: hi = mid
+ else: lo = mid + 1
+ if lo < len(self._blist):
+ return lo, self._i2u(self._blist[lo])
+ return lo, None
+
+ def bisect_left(self, v):
+ """L.bisect_left(v) -> index
+
+ The return value i is such that all e in L[:i] have e < v, and
+ all e in a[i:] have e >= v. So if v already appears in the
+ list, i points just before the leftmost v already there.
+ """
+ return self._bisect_left(v)[0]
+
+ def bisect_right(self, v):
+ """L.bisect_right(v) -> index
+
+ Return the index where to insert item v in the list.
+
+ The return value i is such that all e in a[:i] have e <= v,
+ and all e in a[i:] have e > v. So if v already appears in the
+ list, i points just beyond the rightmost v already there.
+ """
+ return self._bisect_right(v)[0]
+
+ bisect = bisect_right
+
+ def add(self, value):
+ """Add an element."""
+ # Will throw a TypeError when trying to add an object that
+ # cannot be compared to objects already in the list.
+ i, _ = self._bisect_right(value)
+ self._blist.insert(i, self._u2i(value))
+
+ def discard(self, value):
+ """Remove an element if it is a member.
+
+ If the element is not a member, do nothing.
+
+ """
+
+ try:
+ i, v = self._bisect_left(value)
+ except TypeError:
+ # Value cannot be compared with values already in the list.
+ # Ergo, value isn't in the list.
+ return
+ i = self._advance(i, value)
+ if i >= 0:
+ del self._blist[i]
+
+ def __contains__(self, value):
+ """x.__contains__(y) <==> y in x"""
+ try:
+ i, v = self._bisect_left(value)
+ except TypeError:
+ # Value cannot be compared with values already in the list.
+ # Ergo, value isn't in the list.
+ return False
+ i = self._advance(i, value)
+ return i >= 0
+
+ def __len__(self):
+ """x.__len__() <==> len(x)"""
+ return len(self._blist)
+
+ def __iter__(self):
+ """ x.__iter__() <==> iter(x)"""
+ return (self._i2u(v) for v in self._blist)
+
+ def __getitem__(self, index):
+ """x.__getitem__(y) <==> x[y]"""
+ if isinstance(index, slice):
+ rv = self.__class__()
+ rv._blist = self._blist[index]
+ rv._key = self._key
+ return rv
+ return self._i2u(self._blist[index])
+
+ def _advance(self, i, value):
+ "Do a linear search through all items with the same key"
+ key = self._u2key(value)
+ while i < len(self._blist):
+ if self._i2u(self._blist[i]) == value:
+ return i
+ elif key < self._i2key(self._blist[i]):
+ break
+ i += 1
+ return -1
+
+ def __reversed__(self):
+ """L.__reversed__() -- return a reverse iterator over the list"""
+ return (self._i2u(v) for v in reversed(self._blist))
+
+ def index(self, value):
+ """L.index(value) -> integer -- return first index of value.
+
+ Raises ValueError if the value is not present.
+
+ """
+
+ try:
+ i, v = self._bisect_left(value)
+ except TypeError:
+ raise ValueError
+ i = self._advance(i, value)
+ if i >= 0:
+ return i
+ raise ValueError
+
+ def count(self, value):
+ """L.count(value) -> integer -- return number of occurrences of value"""
+ try:
+ i, _ = self._bisect_left(value)
+ except TypeError:
+ return 0
+ key = self._u2key(value)
+ count = 0
+ while True:
+ i = self._advance(i, value)
+ if i == -1:
+ return count
+ count += 1
+ i += 1
+
+ def pop(self, index=-1):
+ """L.pop([index]) -> item -- remove and return item at index (default last).
+
+ Raises IndexError if list is empty or index is out of range.
+
+ """
+
+ rv = self[index]
+ del self[index]
+ return rv
+
+ def __delslice__(self, i, j):
+ """x.__delslice__(i, j) <==> del x[i:j]
+
+ Use of negative indices is not supported.
+
+ """
+ del self._blist[i:j]
+
+ def __delitem__(self, i):
+ """x.__delitem__(y) <==> del x[y]"""
+ del self._blist[i]
+
+class _weaksortedbase(_sortedbase):
+ def _bisect_left(self, value):
+ key = self._u2key(value)
+ lo = 0
+ hi = len(self._blist)
+ while lo < hi:
+ mid = (lo+hi)//2
+ n, v = self._squeeze(mid)
+ hi -= n
+ if n and hi == len(self._blist):
+ continue
+ if self._i2key(self._blist[mid]) < key: lo = mid+1
+ else: hi = mid
+ n, v = self._squeeze(lo)
+ return lo, v
+
+ def _bisect_right(self, value):
+ key = self._u2key(value)
+ lo = 0
+ hi = len(self._blist)
+ while lo < hi:
+ mid = (lo+hi)//2
+ n, v = self._squeeze(mid)
+ hi -= n
+ if n and hi == len(self._blist):
+ continue
+ if key < self._i2key(self._blist[mid]): hi = mid
+ else: lo = mid+1
+ n, v = self._squeeze(lo)
+ return lo, v
+
+ _bisect = _bisect_right
+
+ def _u2i(self, value):
+ if self._key is None:
+ return weakref.ref(value)
+ else:
+ return (self._key(value), weakref.ref(value))
+
+ def _i2u(self, value):
+ if self._key is None:
+ return value()
+ else:
+ return value[1]()
+
+ def _i2key(self, value):
+ if self._key is None:
+ return value()
+ else:
+ return value[0]
+
+ def __iter__(self):
+ """ x.__iter__() <==> iter(x)"""
+ i = 0
+ while i < len(self._blist):
+ n, v = self._squeeze(i)
+ if v is None: break
+ yield v
+ i += 1
+
+ def _squeeze(self, i):
+ n = 0
+ while i < len(self._blist):
+ v = self._i2u(self._blist[i])
+ if v is None:
+ del self._blist[i]
+ n += 1
+ else:
+ return n, v
+ return n, None
+
+ def __getitem__(self, index):
+ """x.__getitem__(y) <==> x[y]"""
+ if isinstance(index, slice):
+ return _sortedbase.__getitem__(self, index)
+ n, v = self._squeeze(index)
+ if v is None:
+ raise IndexError('list index out of range')
+ return v
+
+ def __reversed__(self):
+ """L.__reversed__() -- return a reverse iterator over the list"""
+ i = len(self._blist)-1
+ while i >= 0:
+ n, v = self._squeeze(i)
+ if not n:
+ yield v
+ i -= 1
+
+ def _advance(self, i, value):
+ "Do a linear search through all items with the same key"
+ key = self._u2key(value)
+ while i < len(self._blist):
+ n, v = self._squeeze(i)
+ if v is None:
+ break
+ if v == value:
+ return i
+ elif key < self._i2key(self._blist[i]):
+ break
+ i += 1
+ return -1
+
+class _listmixin(object):
+ def remove(self, value):
+ """L.remove(value) -- remove first occurrence of value.
+
+ Raises ValueError if the value is not present.
+ """
+
+ del self[self.index(value)]
+
+ def update(self, iterable):
+ """L.update(iterable) -- add all elements from iterable into the list"""
+
+ for item in iterable:
+ self.add(item)
+
+ def __mul__(self, k):
+ if not isinstance(k, int):
+ raise TypeError("can't multiply sequence by non-int of type '%s'"
+ % str(type(int)))
+ rv = self.__class__()
+ rv._key = self._key
+ rv._blist = sum((blist([x])*k for x in self._blist), blist())
+ return rv
+ __rmul__ = __mul__
+
+ def __imul__(self, k):
+ if not isinstance(k, int):
+ raise TypeError("can't multiply sequence by non-int of type '%s'"
+ % str(type(int)))
+ self._blist = sum((blist([x])*k for x in self._blist), blist())
+ return self
+
+ def __eq__(self, other):
+ """x.__eq__(y) <==> x==y"""
+ return self._cmp_op(other, operator.eq)
+ def __ne__(self, other):
+ """x.__ne__(y) <==> x!=y"""
+ return self._cmp_op(other, operator.ne)
+ def __lt__(self, other):
+ """x.__lt__(y) <==> x<y"""
+ return self._cmp_op(other, operator.lt)
+ def __gt__(self, other):
+ """x.__gt__(y) <==> x>y"""
+ return self._cmp_op(other, operator.gt)
+ def __le__(self, other):
+ """x.__le__(y) <==> x<=y"""
+ return self._cmp_op(other, operator.le)
+ def __ge__(self, other):
+ """x.__ge__(y) <==> x>=y"""
+ return self._cmp_op(other, operator.ge)
+
+class _setmixin(object):
+ "Methods that override our base class"
+
+ def add(self, value):
+ """Add an element to the set.
+
+ This has no effect if the element is already present.
+
+ """
+ if value in self: return
+ super(_setmixin, self).add(value)
+
+ def __iter__(self):
+ it = super(_setmixin, self).__iter__()
+ while True:
+ item = next(it)
+ n = len(self)
+ yield item
+ if n != len(self):
+ raise RuntimeError('Set changed size during iteration')
+
+def safe_cmp(f):
+ def g(self, other):
+ if not isinstance(other, collections.Set):
+ raise TypeError("can only compare to a set")
+ return f(self, other)
+ return g
+
+class _setmixin2(collections.MutableSet):
+ "methods that override or supplement the collections.MutableSet methods"
+
+ __ror__ = collections.MutableSet.__or__
+ __rand__ = collections.MutableSet.__and__
+ __rxor__ = collections.MutableSet.__xor__
+
+ if sys.version_info[0] < 3: # pragma: no cover
+ __lt__ = safe_cmp(collections.MutableSet.__lt__)
+ __gt__ = safe_cmp(collections.MutableSet.__gt__)
+ __le__ = safe_cmp(collections.MutableSet.__le__)
+ __ge__ = safe_cmp(collections.MutableSet.__ge__)
+
+ def __ior__(self, it):
+ if self is it:
+ return self
+ for value in it:
+ self.add(value)
+ return self
+
+ def __isub__(self, it):
+ if self is it:
+ self.clear()
+ return self
+ for value in it:
+ self.discard(value)
+ return self
+
+ def __ixor__(self, it):
+ if self is it:
+ self.clear()
+ return self
+ for value in it:
+ if value in self:
+ self.discard(value)
+ else:
+ self.add(value)
+ return self
+
+ def __rsub__(self, other):
+ return self._from_iterable(other) - self
+
+ def _make_set(self, iterable):
+ if isinstance(iterable, collections.Set):
+ return iterable
+ return self._from_iterable(iterable)
+
+ def difference(self, *args):
+ """Return a new set with elements in the set that are not in the others."""
+ rv = self.copy()
+ rv.difference_update(*args)
+ return rv
+
+ def intersection(self, *args):
+ """Return a new set with elements common to the set and all others."""
+ rv = self.copy()
+ rv.intersection_update(*args)
+ return rv
+
+ def issubset(self, other):
+ """Test whether every element in the set is in *other*."""
+ return self <= self._make_set(other)
+
+ def issuperset(self, other):
+ """Test whether every element in *other* is in the set."""
+ return self >= self._make_set(other)
+
+ def symmetric_difference(self, other):
+ """Return a new set with elements in either the set or *other*
+ but not both."""
+
+ return self ^ self._make_set(other)
+
+ def union(self, *args):
+ """Return the union of sets as a new set.
+
+ (i.e. all elements that are in either set.)
+
+ """
+ rv = self.copy()
+ for arg in args:
+ rv |= self._make_set(arg)
+ return rv
+
+ def update(self, *args):
+ """Update the set, adding elements from all others."""
+ for arg in args:
+ self |= self._make_set(arg)
+
+ def difference_update(self, *args):
+ """Update the set, removing elements found in others."""
+ for arg in args:
+ self -= self._make_set(arg)
+
+ def intersection_update(self, *args):
+ """Update the set, keeping only elements found in it and all others."""
+ for arg in args:
+ self &= self._make_set(arg)
+
+ def symmetric_difference_update(self, other):
+ """Update the set, keeping only elements found in either set,
+ but not in both."""
+
+ self ^= self._make_set(other)
+
+ def clear(self):
+ """Remove all elements"""
+ del self._blist[:]
+
+ def copy(self):
+ return self[:]
+
+class sortedlist(_sortedbase, _listmixin):
+ """sortedlist(iterable=(), key=None) -> new sorted list
+
+ Keyword arguments:
+ iterable -- items used to initially populate the sorted list
+ key -- a function to return the sort key of an item
+
+ A sortedlist is indexable like a list, but always keeps its
+ members in sorted order.
+
+ """
+
+ def __repr__(self):
+ """x.__repr__() <==> repr(x)"""
+ if not self: return 'sortedlist()'
+ with ReprRecursion(self) as r:
+ if r: return 'sortedlist(...)'
+ return ('sortedlist(%s)' % repr(list(self)))
+
+ def _cmp_op(self, other, op):
+ if not (isinstance(other,type(self)) or isinstance(self,type(other))):
+ return NotImplemented
+ if len(self) != len(other):
+ if op is operator.eq:
+ return False
+ if op is operator.ne:
+ return True
+ for x, y in izip(self, other):
+ if x != y:
+ return op(x, y)
+ return op in (operator.eq, operator.le, operator.ge)
+
+class weaksortedlist(_listmixin, _weaksortedbase):
+ """weaksortedlist(iterable=(), key=None) -> new sorted weak list
+
+ Keyword arguments:
+ iterable -- items used to initially populate the sorted list
+ key -- a function to return the sort key of an item
+
+ A weaksortedlist is indexable like a list, but always keeps its
+ items in sorted order. The weaksortedlist weakly references its
+ members, so items will be discarded after there is no longer a
+ strong reference to the item.
+
+ """
+
+ def __repr__(self):
+ """x.__repr__() <==> repr(x)"""
+ if not self: return 'weaksortedlist()'
+ with ReprRecursion(self) as r:
+ if r: return 'weaksortedlist(...)'
+ return 'weaksortedlist(%s)' % repr(list(self))
+
+ def _cmp_op(self, other, op):
+ if not (isinstance(other,type(self)) or isinstance(self,type(other))):
+ return NotImplemented
+ for x, y in izip(self, other):
+ if x != y:
+ return op(x, y)
+ return op in (operator.eq, operator.le, operator.ge)
+
+class sortedset(_setmixin, _sortedbase, _setmixin2):
+ """sortedset(iterable=(), key=None) -> new sorted set
+
+ Keyword arguments:
+ iterable -- items used to initially populate the sorted set
+ key -- a function to return the sort key of an item
+
+ A sortedset is similar to a set but is also indexable like a list.
+ Items are maintained in sorted order.
+
+ """
+
+ def __repr__(self):
+ """x.__repr__() <==> repr(x)"""
+ if not self: return 'sortedset()'
+ with ReprRecursion(self) as r:
+ if r: return 'sortedset(...)'
+ return ('sortedset(%s)' % repr(list(self)))
+
+class weaksortedset(_setmixin, _weaksortedbase, _setmixin2):
+ """weaksortedset(iterable=(), key=None) -> new sorted weak set
+
+ Keyword arguments:
+ iterable -- items used to initially populate the sorted set
+ key -- a function to return the sort key of an item
+
+ A weaksortedset is similar to a set but is also indexable like a
+ list. Items are maintained in sorted order. The weaksortedset
+ weakly references its members, so items will be discarded after
+ there is no longer a strong reference to the item.
+
+ """
+
+ def __repr__(self):
+ """x.__repr__() <==> repr(x)"""
+ if not self: return 'weaksortedset()'
+ with ReprRecursion(self) as r:
+ if r: return 'weaksortedset(...)'
+ return 'weaksortedset(%s)' % repr(list(self))