# Note: Taken from https://code.activestate.com/recipes/577197-sortedcollection/. # # Copyright 2010 Raymond Hettinger # # Permission is hereby granted, free of charge, to any person obtaining a copy of # this software and associated documentation files (the "Software"), to deal in the # Software without restriction, including without limitation the rights to use, copy, # modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, # and to permit persons to whom the Software is furnished to do so, subject to the # following conditions: # # The above copyright notice and this permission notice shall be included in all copies # or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE # FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. from bisect import bisect_left, bisect_right class SortedCollection(object): def __init__(self, iterable=(), key=None): self._given_key = key key = (lambda x: x) if key is None else key decorated = sorted((key(item), item) for item in iterable) self._keys = [k for k, item in decorated] self._items = [item for k, item in decorated] self._key = key def _getkey(self): return self._key def _setkey(self, key): if key is not self._key: self.__init__(self._items, key=key) def _delkey(self): self._setkey(None) key = property(_getkey, _setkey, _delkey, 'key function') def clear(self): self.__init__([], self._key) def copy(self): return self.__class__(self, self._key) def __len__(self): return len(self._items) def __getitem__(self, i): return self._items[i] def __iter__(self): return iter(self._items) def __reversed__(self): return reversed(self._items) def __repr__(self): return '%s(%r, key=%s)' % ( self.__class__.__name__, self._items, getattr(self._given_key, '__name__', repr(self._given_key)) ) def __reduce__(self): return self.__class__, (self._items, self._given_key) def __contains__(self, item): k = self._key(item) i = bisect_left(self._keys, k) j = bisect_right(self._keys, k) return item in self._items[i:j] def index(self, item): 'Find the position of an item. Raise ValueError if not found.' k = self._key(item) i = bisect_left(self._keys, k) j = bisect_right(self._keys, k) return self._items[i:j].index(item) + i def count(self, item): 'Return number of occurrences of item' k = self._key(item) i = bisect_left(self._keys, k) j = bisect_right(self._keys, k) return self._items[i:j].count(item) def insert(self, item): 'Insert a new item. If equal keys are found, add to the left' k = self._key(item) i = bisect_left(self._keys, k) self._keys.insert(i, k) self._items.insert(i, item) def insert_right(self, item): 'Insert a new item. If equal keys are found, add to the right' k = self._key(item) i = bisect_right(self._keys, k) self._keys.insert(i, k) self._items.insert(i, item) def remove(self, item): 'Remove first occurence of item. Raise ValueError if not found' i = self.index(item) del self._keys[i] del self._items[i] def find(self, k): 'Return first item with a key == k. Raise ValueError if not found.' i = bisect_left(self._keys, k) if i != len(self) and self._keys[i] == k: return self._items[i] raise ValueError('No item found with key equal to: %r' % (k,)) def find_le(self, k): 'Return last item with a key <= k. Raise ValueError if not found.' i = bisect_right(self._keys, k) if i: return self._items[i-1] raise ValueError('No item found with key at or below: %r' % (k,)) def find_lt(self, k): 'Return last item with a key < k. Raise ValueError if not found.' i = bisect_left(self._keys, k) if i: return self._items[i-1] raise ValueError('No item found with key below: %r' % (k,)) def find_ge(self, k): 'Return first item with a key >= equal to k. Raise ValueError if not found' i = bisect_left(self._keys, k) if i != len(self): return self._items[i] raise ValueError('No item found with key at or above: %r' % (k,)) def find_gt(self, k): 'Return first item with a key > k. Raise ValueError if not found' i = bisect_right(self._keys, k) if i != len(self): return self._items[i] raise ValueError('No item found with key above: %r' % (k,))