Message ID | 20210702101721.34838-1-tredaelli@redhat.com |
---|---|
State | Accepted |
Headers | show |
Series | [ovs-dev] Update bundled sortedcontainers to 2.4.0 | expand |
Context | Check | Description |
---|---|---|
ovsrobot/apply-robot | success | apply and check: success |
ovsrobot/github-robot | success | github build: passed |
On 7/2/21 12:17 PM, Timothy Redaelli wrote: > This is needed since the current bundled version doesn't work on Python > 3.10+. Make sense. Terry, could you, please, take a look? I also wonder if we need to apply this patch to stable branches. Thoughts? Best regards, Ilya Maximets. > > Signed-off-by: Timothy Redaelli <tredaelli@redhat.com> > --- > python/ovs/compat/sortedcontainers/LICENSE | 2 +- > .../ovs/compat/sortedcontainers/__init__.py | 70 +- > .../ovs/compat/sortedcontainers/sorteddict.py | 1233 +++++---- > .../ovs/compat/sortedcontainers/sortedlist.py | 2409 +++++++++-------- > .../ovs/compat/sortedcontainers/sortedset.py | 636 ++++- > 5 files changed, 2494 insertions(+), 1856 deletions(-) > > diff --git a/python/ovs/compat/sortedcontainers/LICENSE b/python/ovs/compat/sortedcontainers/LICENSE > index 8794014e0..668d8ecd6 100644 > --- a/python/ovs/compat/sortedcontainers/LICENSE > +++ b/python/ovs/compat/sortedcontainers/LICENSE > @@ -1,4 +1,4 @@ > -Copyright 2014-2016 Grant Jenks > +Copyright 2014-2019 Grant Jenks > > Licensed under the Apache License, Version 2.0 (the "License"); > you may not use this file except in compliance with the License. > diff --git a/python/ovs/compat/sortedcontainers/__init__.py b/python/ovs/compat/sortedcontainers/__init__.py > index 392adfad6..a141dd1de 100644 > --- a/python/ovs/compat/sortedcontainers/__init__.py > +++ b/python/ovs/compat/sortedcontainers/__init__.py > @@ -1,9 +1,8 @@ > -"""Sorted Container Types: SortedList, SortedDict, SortedSet > +"""Sorted Containers -- Sorted List, Sorted Dict, Sorted Set > > -SortedContainers is an Apache2 licensed containers library, written in > +Sorted Containers is an Apache2 licensed containers library, written in > pure-Python, and fast as C-extensions. > > - > Python's standard library is great until you need a sorted collections > type. Many will attest that you can get really far without one, but the moment > you **really need** a sorted list, dict, or set, you're faced with a dozen > @@ -14,39 +13,62 @@ In Python, we can do better. And we can do it in pure-Python! > > :: > > - >>> from sortedcontainers import SortedList, SortedDict, SortedSet > - >>> sl = SortedList(xrange(10000000)) > - >>> 1234567 in sl > - True > - >>> sl[7654321] > - 7654321 > - >>> sl.add(1234567) > - >>> sl.count(1234567) > + >>> from sortedcontainers import SortedList > + >>> sl = SortedList(['e', 'a', 'c', 'd', 'b']) > + >>> sl > + SortedList(['a', 'b', 'c', 'd', 'e']) > + >>> sl *= 1000000 > + >>> sl.count('c') > + 1000000 > + >>> sl[-3:] > + ['e', 'e', 'e'] > + >>> from sortedcontainers import SortedDict > + >>> sd = SortedDict({'c': 3, 'a': 1, 'b': 2}) > + >>> sd > + SortedDict({'a': 1, 'b': 2, 'c': 3}) > + >>> sd.popitem(index=-1) > + ('c', 3) > + >>> from sortedcontainers import SortedSet > + >>> ss = SortedSet('abracadabra') > + >>> ss > + SortedSet(['a', 'b', 'c', 'd', 'r']) > + >>> ss.bisect_left('c') > 2 > - >>> sl *= 3 > - >>> len(sl) > - 30000003 > > -SortedContainers takes all of the work out of Python sorted types - making your > -deployment and use of Python easy. There's no need to install a C compiler or > -pre-build and distribute custom extensions. Performance is a feature and > +Sorted Containers takes all of the work out of Python sorted types - making > +your deployment and use of Python easy. There's no need to install a C compiler > +or pre-build and distribute custom extensions. Performance is a feature and > testing has 100% coverage with unit tests and hours of stress. > > -:copyright: (c) 2016 by Grant Jenks. > +:copyright: (c) 2014-2019 by Grant Jenks. > :license: Apache 2.0, see LICENSE for more details. > > """ > > > -from .sortedlist import SortedList, SortedListWithKey > +from .sortedlist import SortedList, SortedKeyList, SortedListWithKey > from .sortedset import SortedSet > -from .sorteddict import SortedDict > +from .sorteddict import ( > + SortedDict, > + SortedKeysView, > + SortedItemsView, > + SortedValuesView, > +) > > -__all__ = ['SortedList', 'SortedSet', 'SortedDict', 'SortedListWithKey'] > +__all__ = [ > + 'SortedList', > + 'SortedKeyList', > + 'SortedListWithKey', > + 'SortedDict', > + 'SortedKeysView', > + 'SortedItemsView', > + 'SortedValuesView', > + 'SortedSet', > +] > > __title__ = 'sortedcontainers' > -__version__ = '1.5.9' > -__build__ = 0x010509 > +__version__ = '2.4.0' > +__build__ = 0x020400 > __author__ = 'Grant Jenks' > __license__ = 'Apache 2.0' > -__copyright__ = 'Copyright 2016 Grant Jenks' > +__copyright__ = '2014-2019, Grant Jenks' > diff --git a/python/ovs/compat/sortedcontainers/sorteddict.py b/python/ovs/compat/sortedcontainers/sorteddict.py > index 5d425fee6..910f26088 100644 > --- a/python/ovs/compat/sortedcontainers/sorteddict.py > +++ b/python/ovs/compat/sortedcontainers/sorteddict.py > @@ -1,342 +1,563 @@ > -"""Sorted dictionary implementation. > +"""Sorted Dict > +============== > + > +:doc:`Sorted Containers<index>` is an Apache2 licensed Python sorted > +collections library, written in pure-Python, and fast as C-extensions. The > +:doc:`introduction<introduction>` is the best way to get started. > + > +Sorted dict implementations: > + > +.. currentmodule:: sortedcontainers > + > +* :class:`SortedDict` > +* :class:`SortedKeysView` > +* :class:`SortedItemsView` > +* :class:`SortedValuesView` > > """ > > -from collections import Set, Sequence > -from collections import KeysView as AbstractKeysView > -from collections import ValuesView as AbstractValuesView > -from collections import ItemsView as AbstractItemsView > -from sys import hexversion > +import sys > +import warnings > + > +from itertools import chain > > -from .sortedlist import SortedList, recursive_repr, SortedListWithKey > +from .sortedlist import SortedList, recursive_repr > from .sortedset import SortedSet > > -NONE = object() > +############################################################################### > +# BEGIN Python 2/3 Shims > +############################################################################### > > +try: > + from collections.abc import ( > + ItemsView, KeysView, Mapping, ValuesView, Sequence > + ) > +except ImportError: > + from collections import ItemsView, KeysView, Mapping, ValuesView, Sequence > > -class _IlocWrapper(object): > - "Positional indexing support for sorted dictionary objects." > - # pylint: disable=protected-access, too-few-public-methods > - def __init__(self, _dict): > - self._dict = _dict > - def __len__(self): > - return len(self._dict) > - def __getitem__(self, index): > - """ > - Very efficiently return the key at index *index* in iteration. Supports > - negative indices and slice notation. Raises IndexError on invalid > - *index*. > - """ > - return self._dict._list[index] > - def __delitem__(self, index): > - """ > - Remove the ``sdict[sdict.iloc[index]]`` from *sdict*. Supports negative > - indices and slice notation. Raises IndexError on invalid *index*. > - """ > - _dict = self._dict > - _list = _dict._list > - _delitem = _dict._delitem > - > - if isinstance(index, slice): > - keys = _list[index] > - del _list[index] > - for key in keys: > - _delitem(key) > - else: > - key = _list[index] > - del _list[index] > - _delitem(key) > +############################################################################### > +# END Python 2/3 Shims > +############################################################################### > > > class SortedDict(dict): > - """SortedDict provides the same methods as a dict. Additionally, SortedDict > - efficiently maintains its keys in sorted order. Consequently, the keys > - method will return the keys in sorted order, the popitem method will remove > - the item with the highest key, etc. > + """Sorted dict is a sorted mutable mapping. > + > + Sorted dict keys are maintained in sorted order. The design of sorted dict > + is simple: sorted dict inherits from dict to store items and maintains a > + sorted list of keys. > + > + Sorted dict keys must be hashable and comparable. The hash and total > + ordering of keys must not change while they are stored in the sorted dict. > + > + Mutable mapping methods: > + > + * :func:`SortedDict.__getitem__` (inherited from dict) > + * :func:`SortedDict.__setitem__` > + * :func:`SortedDict.__delitem__` > + * :func:`SortedDict.__iter__` > + * :func:`SortedDict.__len__` (inherited from dict) > + > + Methods for adding items: > + > + * :func:`SortedDict.setdefault` > + * :func:`SortedDict.update` > + > + Methods for removing items: > + > + * :func:`SortedDict.clear` > + * :func:`SortedDict.pop` > + * :func:`SortedDict.popitem` > + > + Methods for looking up items: > + > + * :func:`SortedDict.__contains__` (inherited from dict) > + * :func:`SortedDict.get` (inherited from dict) > + * :func:`SortedDict.peekitem` > + > + Methods for views: > + > + * :func:`SortedDict.keys` > + * :func:`SortedDict.items` > + * :func:`SortedDict.values` > + > + Methods for miscellany: > + > + * :func:`SortedDict.copy` > + * :func:`SortedDict.fromkeys` > + * :func:`SortedDict.__reversed__` > + * :func:`SortedDict.__eq__` (inherited from dict) > + * :func:`SortedDict.__ne__` (inherited from dict) > + * :func:`SortedDict.__repr__` > + * :func:`SortedDict._check` > + > + Sorted list methods available (applies to keys): > + > + * :func:`SortedList.bisect_left` > + * :func:`SortedList.bisect_right` > + * :func:`SortedList.count` > + * :func:`SortedList.index` > + * :func:`SortedList.irange` > + * :func:`SortedList.islice` > + * :func:`SortedList._reset` > + > + Additional sorted list methods available, if key-function used: > + > + * :func:`SortedKeyList.bisect_key_left` > + * :func:`SortedKeyList.bisect_key_right` > + * :func:`SortedKeyList.irange_key` > + > + Sorted dicts may only be compared for equality and inequality. > > """ > def __init__(self, *args, **kwargs): > - """SortedDict provides the same methods as a dict. Additionally, SortedDict > - efficiently maintains its keys in sorted order. Consequently, the keys > - method will return the keys in sorted order, the popitem method will > - remove the item with the highest key, etc. > - > - An optional *key* argument defines a callable that, like the `key` > - argument to Python's `sorted` function, extracts a comparison key from > - each dict key. If no function is specified, the default compares the > - dict keys directly. The `key` argument must be provided as a positional > - argument and must come before all other arguments. > - > - An optional *iterable* argument provides an initial series of items to > - populate the SortedDict. Each item in the series must itself contain > - two items. The first is used as a key in the new dictionary, and the > - second as the key's value. If a given key is seen more than once, the > - last value associated with it is retained in the new dictionary. > - > - If keyword arguments are given, the keywords themselves with their > - associated values are added as items to the dictionary. If a key is > - specified both in the positional argument and as a keyword argument, the > - value associated with the keyword is retained in the dictionary. For > - example, these all return a dictionary equal to ``{"one": 2, "two": > - 3}``: > - > - * ``SortedDict(one=2, two=3)`` > - * ``SortedDict({'one': 2, 'two': 3})`` > - * ``SortedDict(zip(('one', 'two'), (2, 3)))`` > - * ``SortedDict([['two', 3], ['one', 2]])`` > - > - The first example only works for keys that are valid Python > - identifiers; the others work with any valid keys. > + """Initialize sorted dict instance. > + > + Optional key-function argument defines a callable that, like the `key` > + argument to the built-in `sorted` function, extracts a comparison key > + from each dictionary key. If no function is specified, the default > + compares the dictionary keys directly. The key-function argument must > + be provided as a positional argument and must come before all other > + arguments. > + > + Optional iterable argument provides an initial sequence of pairs to > + initialize the sorted dict. Each pair in the sequence defines the key > + and corresponding value. If a key is seen more than once, the last > + value associated with it is stored in the new sorted dict. > + > + Optional mapping argument provides an initial mapping of items to > + initialize the sorted dict. > + > + If keyword arguments are given, the keywords themselves, with their > + associated values, are added as items to the dictionary. If a key is > + specified both in the positional argument and as a keyword argument, > + the value associated with the keyword is stored in the > + sorted dict. > + > + Sorted dict keys must be hashable, per the requirement for Python's > + dictionaries. Keys (or the result of the key-function) must also be > + comparable, per the requirement for sorted lists. > + > + >>> d = {'alpha': 1, 'beta': 2} > + >>> SortedDict([('alpha', 1), ('beta', 2)]) == d > + True > + >>> SortedDict({'alpha': 1, 'beta': 2}) == d > + True > + >>> SortedDict(alpha=1, beta=2) == d > + True > > """ > - # pylint: disable=super-init-not-called > if args and (args[0] is None or callable(args[0])): > - self._key = args[0] > + _key = self._key = args[0] > args = args[1:] > else: > - self._key = None > + _key = self._key = None > > - if self._key is None: > - self._list = SortedList() > - else: > - self._list = SortedListWithKey(key=self._key) > + self._list = SortedList(key=_key) > > - # Cache function pointers to dict methods. > - > - _dict = super(SortedDict, self) > - self._dict = _dict > - self._clear = _dict.clear > - self._delitem = _dict.__delitem__ > - self._iter = _dict.__iter__ > - self._pop = _dict.pop > - self._setdefault = _dict.setdefault > - self._setitem = _dict.__setitem__ > - self._dict_update = _dict.update > - > - # Cache function pointers to SortedList methods. > + # Reaching through ``self._list`` repeatedly adds unnecessary overhead > + # so cache references to sorted list methods. > > _list = self._list > self._list_add = _list.add > - self.bisect_left = _list.bisect_left > - self.bisect = _list.bisect_right > - self.bisect_right = _list.bisect_right > self._list_clear = _list.clear > - self.index = _list.index > + self._list_iter = _list.__iter__ > + self._list_reversed = _list.__reversed__ > self._list_pop = _list.pop > self._list_remove = _list.remove > self._list_update = _list.update > + > + # Expose some sorted list methods publicly. > + > + self.bisect_left = _list.bisect_left > + self.bisect = _list.bisect_right > + self.bisect_right = _list.bisect_right > + self.index = _list.index > self.irange = _list.irange > self.islice = _list.islice > - self._reset = _list._reset # pylint: disable=protected-access > + self._reset = _list._reset > > - if self._key is not None: > + if _key is not None: > self.bisect_key_left = _list.bisect_key_left > self.bisect_key_right = _list.bisect_key_right > self.bisect_key = _list.bisect_key > self.irange_key = _list.irange_key > > - self.iloc = _IlocWrapper(self) > - > self._update(*args, **kwargs) > > + > @property > def key(self): > - """Key function used to extract comparison key for sorting.""" > + """Function used to extract comparison key from keys. > + > + Sorted dict compares keys directly when the key function is none. > + > + """ > return self._key > > + > + @property > + def iloc(self): > + """Cached reference of sorted keys view. > + > + Deprecated in version 2 of Sorted Containers. Use > + :func:`SortedDict.keys` instead. > + > + """ > + # pylint: disable=attribute-defined-outside-init > + try: > + return self._iloc > + except AttributeError: > + warnings.warn( > + 'sorted_dict.iloc is deprecated.' > + ' Use SortedDict.keys() instead.', > + DeprecationWarning, > + stacklevel=2, > + ) > + _iloc = self._iloc = SortedKeysView(self) > + return _iloc > + > + > def clear(self): > - """Remove all elements from the dictionary.""" > - self._clear() > + > + """Remove all items from sorted dict. > + > + Runtime complexity: `O(n)` > + > + """ > + dict.clear(self) > self._list_clear() > > + > def __delitem__(self, key): > + """Remove item from sorted dict identified by `key`. > + > + ``sd.__delitem__(key)`` <==> ``del sd[key]`` > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3}) > + >>> del sd['b'] > + >>> sd > + SortedDict({'a': 1, 'c': 3}) > + >>> del sd['z'] > + Traceback (most recent call last): > + ... > + KeyError: 'z' > + > + :param key: `key` for item lookup > + :raises KeyError: if key not found > + > """ > - Remove ``d[key]`` from *d*. Raises a KeyError if *key* is not in the > - dictionary. > - """ > - self._delitem(key) > + dict.__delitem__(self, key) > self._list_remove(key) > > + > def __iter__(self): > - """ > - Return an iterator over the sorted keys of the dictionary. > + """Return an iterator over the keys of the sorted dict. > + > + ``sd.__iter__()`` <==> ``iter(sd)`` > + > + Iterating the sorted dict while adding or deleting items may raise a > + :exc:`RuntimeError` or fail to iterate over all keys. > > - Iterating the Mapping while adding or deleting keys may raise a > - `RuntimeError` or fail to iterate over all entries. > """ > - return iter(self._list) > + return self._list_iter() > + > > def __reversed__(self): > - """ > - Return a reversed iterator over the sorted keys of the dictionary. > + """Return a reverse iterator over the keys of the sorted dict. > + > + ``sd.__reversed__()`` <==> ``reversed(sd)`` > + > + Iterating the sorted dict while adding or deleting items may raise a > + :exc:`RuntimeError` or fail to iterate over all keys. > > - Iterating the Mapping while adding or deleting keys may raise a > - `RuntimeError` or fail to iterate over all entries. > """ > - return reversed(self._list) > + return self._list_reversed() > + > > def __setitem__(self, key, value): > - """Set `d[key]` to *value*.""" > + """Store item in sorted dict with `key` and corresponding `value`. > + > + ``sd.__setitem__(key, value)`` <==> ``sd[key] = value`` > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> sd = SortedDict() > + >>> sd['c'] = 3 > + >>> sd['a'] = 1 > + >>> sd['b'] = 2 > + >>> sd > + SortedDict({'a': 1, 'b': 2, 'c': 3}) > + > + :param key: key for item > + :param value: value for item > + > + """ > if key not in self: > self._list_add(key) > - self._setitem(key, value) > + dict.__setitem__(self, key, value) > + > + _setitem = __setitem__ > + > + > + def __or__(self, other): > + if not isinstance(other, Mapping): > + return NotImplemented > + items = chain(self.items(), other.items()) > + return self.__class__(self._key, items) > + > + > + def __ror__(self, other): > + if not isinstance(other, Mapping): > + return NotImplemented > + items = chain(other.items(), self.items()) > + return self.__class__(self._key, items) > + > + > + def __ior__(self, other): > + self._update(other) > + return self > + > > def copy(self): > - """Return a shallow copy of the sorted dictionary.""" > - return self.__class__(self._key, self._iteritems()) > + """Return a shallow copy of the sorted dict. > + > + Runtime complexity: `O(n)` > + > + :return: new sorted dict > + > + """ > + return self.__class__(self._key, self.items()) > > __copy__ = copy > > + > @classmethod > - def fromkeys(cls, seq, value=None): > - """ > - Create a new dictionary with keys from *seq* and values set to *value*. > - """ > - return cls((key, value) for key in seq) > - > - if hexversion < 0x03000000: > - def items(self): > - """ > - Return a list of the dictionary's items (``(key, value)`` pairs). > - """ > - return list(self._iteritems()) > - else: > - def items(self): > - """ > - Return a new ItemsView of the dictionary's items. In addition to > - the methods provided by the built-in `view` the ItemsView is > - indexable (e.g. ``d.items()[5]``). > - """ > - return ItemsView(self) > - > - def iteritems(self): > - """ > - Return an iterator over the items (``(key, value)`` pairs). > + def fromkeys(cls, iterable, value=None): > + """Return a new sorted dict initailized from `iterable` and `value`. > > - Iterating the Mapping while adding or deleting keys may raise a > - `RuntimeError` or fail to iterate over all entries. > - """ > - return iter((key, self[key]) for key in self._list) > + Items in the sorted dict have keys from `iterable` and values equal to > + `value`. > > - _iteritems = iteritems > + Runtime complexity: `O(n*log(n))` > > - if hexversion < 0x03000000: > - def keys(self): > - """Return a SortedSet of the dictionary's keys.""" > - return SortedSet(self._list, key=self._key) > - else: > - def keys(self): > - """ > - Return a new KeysView of the dictionary's keys. In addition to the > - methods provided by the built-in `view` the KeysView is indexable > - (e.g. ``d.keys()[5]``). > - """ > - return KeysView(self) > - > - def iterkeys(self): > - """ > - Return an iterator over the sorted keys of the Mapping. > + :return: new sorted dict > > - Iterating the Mapping while adding or deleting keys may raise a > - `RuntimeError` or fail to iterate over all entries. > """ > - return iter(self._list) > + return cls((key, value) for key in iterable) > + > + > + def keys(self): > + """Return new sorted keys view of the sorted dict's keys. > + > + See :class:`SortedKeysView` for details. > + > + :return: new sorted keys view > > - if hexversion < 0x03000000: > - def values(self): > - """Return a list of the dictionary's values.""" > - return list(self._itervalues()) > - else: > - def values(self): > - """ > - Return a new :class:`ValuesView` of the dictionary's values. > - In addition to the methods provided by the built-in `view` the > - ValuesView is indexable (e.g., ``d.values()[5]``). > - """ > - return ValuesView(self) > - > - def itervalues(self): > """ > - Return an iterator over the values of the Mapping. > + return SortedKeysView(self) > + > + > + def items(self): > + """Return new sorted items view of the sorted dict's items. > + > + See :class:`SortedItemsView` for details. > + > + :return: new sorted items view > > - Iterating the Mapping while adding or deleting keys may raise a > - `RuntimeError` or fail to iterate over all entries. > """ > - return iter(self[key] for key in self._list) > + return SortedItemsView(self) > + > > - _itervalues = itervalues > + def values(self): > + """Return new sorted values view of the sorted dict's values. > + > + See :class:`SortedValuesView` for details. > + > + :return: new sorted values view > > - def pop(self, key, default=NONE): > """ > - If *key* is in the dictionary, remove it and return its value, > - else return *default*. If *default* is not given and *key* is not in > - the dictionary, a KeyError is raised. > + return SortedValuesView(self) > + > + > + if sys.hexversion < 0x03000000: > + def __make_raise_attributeerror(original, alternate): > + # pylint: disable=no-self-argument > + message = ( > + 'SortedDict.{original}() is not implemented.' > + ' Use SortedDict.{alternate}() instead.' > + ).format(original=original, alternate=alternate) > + def method(self): > + # pylint: disable=missing-docstring,unused-argument > + raise AttributeError(message) > + method.__name__ = original # pylint: disable=non-str-assignment-to-dunder-name > + method.__doc__ = message > + return property(method) > + > + iteritems = __make_raise_attributeerror('iteritems', 'items') > + iterkeys = __make_raise_attributeerror('iterkeys', 'keys') > + itervalues = __make_raise_attributeerror('itervalues', 'values') > + viewitems = __make_raise_attributeerror('viewitems', 'items') > + viewkeys = __make_raise_attributeerror('viewkeys', 'keys') > + viewvalues = __make_raise_attributeerror('viewvalues', 'values') > + > + > + class _NotGiven(object): > + # pylint: disable=too-few-public-methods > + def __repr__(self): > + return '<not-given>' > + > + __not_given = _NotGiven() > + > + def pop(self, key, default=__not_given): > + """Remove and return value for item identified by `key`. > + > + If the `key` is not found then return `default` if given. If `default` > + is not given then raise :exc:`KeyError`. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3}) > + >>> sd.pop('c') > + 3 > + >>> sd.pop('z', 26) > + 26 > + >>> sd.pop('y') > + Traceback (most recent call last): > + ... > + KeyError: 'y' > + > + :param key: `key` for item > + :param default: `default` value if key not found (optional) > + :return: value for item > + :raises KeyError: if `key` not found and `default` not given > + > """ > if key in self: > self._list_remove(key) > - return self._pop(key) > + return dict.pop(self, key) > else: > - if default is NONE: > + if default is self.__not_given: > raise KeyError(key) > - else: > - return default > + return default > > - def popitem(self, last=True): > - """ > - Remove and return a ``(key, value)`` pair from the dictionary. If > - last=True (default) then remove the *greatest* `key` from the > - diciontary. Else, remove the *least* key from the dictionary. > > - If the dictionary is empty, calling `popitem` raises a > - KeyError`. > + def popitem(self, index=-1): > + """Remove and return ``(key, value)`` pair at `index` from sorted dict. > + > + Optional argument `index` defaults to -1, the last item in the sorted > + dict. Specify ``index=0`` for the first item in the sorted dict. > + > + If the sorted dict is empty, raises :exc:`KeyError`. > + > + If the `index` is out of range, raises :exc:`IndexError`. > + > + Runtime complexity: `O(log(n))` > + > + >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3}) > + >>> sd.popitem() > + ('c', 3) > + >>> sd.popitem(0) > + ('a', 1) > + >>> sd.popitem(100) > + Traceback (most recent call last): > + ... > + IndexError: list index out of range > + > + :param int index: `index` of item (default -1) > + :return: key and value pair > + :raises KeyError: if sorted dict is empty > + :raises IndexError: if `index` out of range > + > """ > if not self: > raise KeyError('popitem(): dictionary is empty') > > - key = self._list_pop(-1 if last else 0) > - value = self._pop(key) > - > + key = self._list_pop(index) > + value = dict.pop(self, key) > return (key, value) > > + > def peekitem(self, index=-1): > - """Return (key, value) item pair at index. > + """Return ``(key, value)`` pair at `index` in sorted dict. > + > + Optional argument `index` defaults to -1, the last item in the sorted > + dict. Specify ``index=0`` for the first item in the sorted dict. > > - Unlike ``popitem``, the sorted dictionary is not modified. Index > - defaults to -1, the last/greatest key in the dictionary. Specify > - ``index=0`` to lookup the first/least key in the dictiony. > + Unlike :func:`SortedDict.popitem`, the sorted dict is not modified. > > - If index is out of range, raise IndexError. > + If the `index` is out of range, raises :exc:`IndexError`. > + > + Runtime complexity: `O(log(n))` > + > + >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3}) > + >>> sd.peekitem() > + ('c', 3) > + >>> sd.peekitem(0) > + ('a', 1) > + >>> sd.peekitem(100) > + Traceback (most recent call last): > + ... > + IndexError: list index out of range > + > + :param int index: index of item (default -1) > + :return: key and value pair > + :raises IndexError: if `index` out of range > > """ > key = self._list[index] > return key, self[key] > > + > def setdefault(self, key, default=None): > - """ > - If *key* is in the dictionary, return its value. If not, insert *key* > - with a value of *default* and return *default*. *default* defaults to > - ``None``. > + """Return value for item identified by `key` in sorted dict. > + > + If `key` is in the sorted dict then return its value. If `key` is not > + in the sorted dict then insert `key` with value `default` and return > + `default`. > + > + Optional argument `default` defaults to none. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> sd = SortedDict() > + >>> sd.setdefault('a', 1) > + 1 > + >>> sd.setdefault('a', 10) > + 1 > + >>> sd > + SortedDict({'a': 1}) > + > + :param key: key for item > + :param default: value for item (default None) > + :return: value for item identified by `key` > + > """ > if key in self: > return self[key] > - > - self._setitem(key, default) > + dict.__setitem__(self, key, default) > self._list_add(key) > return default > > + > def update(self, *args, **kwargs): > - """ > - Update the dictionary with the key/value pairs from *other*, overwriting > - existing keys. > + """Update sorted dict with items from `args` and `kwargs`. > + > + Overwrites existing items. > + > + Optional arguments `args` and `kwargs` may be a mapping, an iterable of > + pairs or keyword arguments. See :func:`SortedDict.__init__` for > + details. > + > + :param args: mapping or iterable of pairs > + :param kwargs: keyword arguments mapping > > - *update* accepts either another dictionary object or an iterable of > - key/value pairs (as a tuple or other iterable of length two). If > - keyword arguments are specified, the dictionary is then updated with > - those key/value pairs: ``d.update(red=1, blue=2)``. > """ > if not self: > - self._dict_update(*args, **kwargs) > - self._list_update(self._iter()) > + dict.update(self, *args, **kwargs) > + self._list_update(dict.__iter__(self)) > return > > if not kwargs and len(args) == 1 and isinstance(args[0], dict): > @@ -345,397 +566,247 @@ class SortedDict(dict): > pairs = dict(*args, **kwargs) > > if (10 * len(pairs)) > len(self): > - self._dict_update(pairs) > + dict.update(self, pairs) > self._list_clear() > - self._list_update(self._iter()) > + self._list_update(dict.__iter__(self)) > else: > for key in pairs: > - self[key] = pairs[key] > + self._setitem(key, pairs[key]) > > _update = update > > - if hexversion >= 0x02070000: > - def viewkeys(self): > - "Return ``KeysView`` of dictionary keys." > - return KeysView(self) > > - def viewvalues(self): > - "Return ``ValuesView`` of dictionary values." > - return ValuesView(self) > + def __reduce__(self): > + """Support for pickle. > > - def viewitems(self): > - "Return ``ItemsView`` of dictionary (key, value) item pairs." > - return ItemsView(self) > + The tricks played with caching references in > + :func:`SortedDict.__init__` confuse pickle so customize the reducer. > > - def __reduce__(self): > - return (self.__class__, (self._key, list(self._iteritems()))) > + """ > + items = dict.copy(self) > + return (type(self), (self._key, items)) > > - @recursive_repr > + > + @recursive_repr() > def __repr__(self): > + """Return string representation of sorted dict. > + > + ``sd.__repr__()`` <==> ``repr(sd)`` > + > + :return: string representation > + > + """ > _key = self._key > - name = type(self).__name__ > - key = '' if _key is None else '{0!r}, '.format(_key) > - func = '{0!r}: {1!r}'.format > - items = ', '.join(func(key, self[key]) for key in self._list) > - return '{0}({1}{{{2}}})'.format(name, key, items) > + type_name = type(self).__name__ > + key_arg = '' if _key is None else '{0!r}, '.format(_key) > + item_format = '{0!r}: {1!r}'.format > + items = ', '.join(item_format(key, self[key]) for key in self._list) > + return '{0}({1}{{{2}}})'.format(type_name, key_arg, items) > + > > def _check(self): > - # pylint: disable=protected-access > - self._list._check() > - assert len(self) == len(self._list) > - assert all(key in self for key in self._list) > + """Check invariants of sorted dict. > > + Runtime complexity: `O(n)` > > -class KeysView(AbstractKeysView, Set, Sequence): > - """ > - A KeysView object is a dynamic view of the dictionary's keys, which > - means that when the dictionary's keys change, the view reflects > - those changes. > + """ > + _list = self._list > + _list._check() > + assert len(self) == len(_list) > + assert all(key in self for key in _list) > + > + > +def _view_delitem(self, index): > + """Remove item at `index` from sorted dict. > + > + ``view.__delitem__(index)`` <==> ``del view[index]`` > + > + Supports slicing. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3}) > + >>> view = sd.keys() > + >>> del view[0] > + >>> sd > + SortedDict({'b': 2, 'c': 3}) > + >>> del view[-1] > + >>> sd > + SortedDict({'b': 2}) > + >>> del view[:] > + >>> sd > + SortedDict({}) > + > + :param index: integer or slice for indexing > + :raises IndexError: if index out of range > > - The KeysView class implements the Set and Sequence Abstract Base Classes. > """ > - # pylint: disable=too-many-ancestors > - if hexversion < 0x03000000: > - def __init__(self, sorted_dict): > - """ > - Initialize a KeysView from a SortedDict container as *sorted_dict*. > - """ > - # pylint: disable=super-init-not-called, protected-access > - self._list = sorted_dict._list > - self._view = sorted_dict._dict.viewkeys() > + _mapping = self._mapping > + _list = _mapping._list > + dict_delitem = dict.__delitem__ > + if isinstance(index, slice): > + keys = _list[index] > + del _list[index] > + for key in keys: > + dict_delitem(_mapping, key) > else: > - def __init__(self, sorted_dict): > - """ > - Initialize a KeysView from a SortedDict container as *sorted_dict*. > - """ > - # pylint: disable=super-init-not-called, protected-access > - self._list = sorted_dict._list > - self._view = sorted_dict._dict.keys() > - def __len__(self): > - """Return the number of entries in the dictionary.""" > - return len(self._view) > - def __contains__(self, key): > - """ > - Return True if and only if *key* is one of the underlying dictionary's > - keys. > - """ > - return key in self._view > - def __iter__(self): > - """ > - Return an iterable over the keys in the dictionary. Keys are iterated > - over in their sorted order. > + key = _list.pop(index) > + dict_delitem(_mapping, key) > + > + > +class SortedKeysView(KeysView, Sequence): > + """Sorted keys view is a dynamic view of the sorted dict's keys. > + > + When the sorted dict's keys change, the view reflects those changes. > + > + The keys view implements the set and sequence abstract base classes. > + > + """ > + __slots__ = () > + > + > + @classmethod > + def _from_iterable(cls, it): > + return SortedSet(it) > + > > - Iterating views while adding or deleting entries in the dictionary may > - raise a `RuntimeError` or fail to iterate over all entries. > - """ > - return iter(self._list) > def __getitem__(self, index): > - """Return the key at position *index*.""" > - return self._list[index] > - def __reversed__(self): > - """ > - Return a reversed iterable over the keys in the dictionary. Keys are > - iterated over in their reverse sort order. > + """Lookup key at `index` in sorted keys views. > + > + ``skv.__getitem__(index)`` <==> ``skv[index]`` > + > + Supports slicing. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3}) > + >>> skv = sd.keys() > + >>> skv[0] > + 'a' > + >>> skv[-1] > + 'c' > + >>> skv[:] > + ['a', 'b', 'c'] > + >>> skv[100] > + Traceback (most recent call last): > + ... > + IndexError: list index out of range > + > + :param index: integer or slice for indexing > + :return: key or list of keys > + :raises IndexError: if index out of range > > - Iterating views while adding or deleting entries in the dictionary may > - raise a RuntimeError or fail to iterate over all entries. > - """ > - return reversed(self._list) > - def index(self, value, start=None, stop=None): > - """ > - Return the smallest *k* such that `keysview[k] == value` and `start <= k > - < end`. Raises `KeyError` if *value* is not present. *stop* defaults > - to the end of the set. *start* defaults to the beginning. Negative > - indexes are supported, as for slice indices. > """ > - # pylint: disable=arguments-differ > - return self._list.index(value, start, stop) > - def count(self, value): > - """Return the number of occurrences of *value* in the set.""" > - return 1 if value in self._view else 0 > - def __eq__(self, that): > - """Test set-like equality with *that*.""" > - return self._view == that > - def __ne__(self, that): > - """Test set-like inequality with *that*.""" > - return self._view != that > - def __lt__(self, that): > - """Test whether self is a proper subset of *that*.""" > - return self._view < that > - def __gt__(self, that): > - """Test whether self is a proper superset of *that*.""" > - return self._view > that > - def __le__(self, that): > - """Test whether self is contained within *that*.""" > - return self._view <= that > - def __ge__(self, that): > - """Test whether *that* is contained within self.""" > - return self._view >= that > - def __and__(self, that): > - """Return a SortedSet of the intersection of self and *that*.""" > - return SortedSet(self._view & that) > - def __or__(self, that): > - """Return a SortedSet of the union of self and *that*.""" > - return SortedSet(self._view | that) > - def __sub__(self, that): > - """Return a SortedSet of the difference of self and *that*.""" > - return SortedSet(self._view - that) > - def __xor__(self, that): > - """Return a SortedSet of the symmetric difference of self and *that*.""" > - return SortedSet(self._view ^ that) > - if hexversion < 0x03000000: > - def isdisjoint(self, that): > - """Return True if and only if *that* is disjoint with self.""" > - # pylint: disable=arguments-differ > - return not any(key in self._list for key in that) > - else: > - def isdisjoint(self, that): > - """Return True if and only if *that* is disjoint with self.""" > - # pylint: disable=arguments-differ > - return self._view.isdisjoint(that) > - @recursive_repr > - def __repr__(self): > - return 'SortedDict_keys({0!r})'.format(list(self)) > + return self._mapping._list[index] > > > -class ValuesView(AbstractValuesView, Sequence): > - """ > - A ValuesView object is a dynamic view of the dictionary's values, which > - means that when the dictionary's values change, the view reflects those > - changes. > + __delitem__ = _view_delitem > + > + > +class SortedItemsView(ItemsView, Sequence): > + """Sorted items view is a dynamic view of the sorted dict's items. > + > + When the sorted dict's items change, the view reflects those changes. > + > + The items view implements the set and sequence abstract base classes. > > - The ValuesView class implements the Sequence Abstract Base Class. > """ > - # pylint: disable=too-many-ancestors > - if hexversion < 0x03000000: > - def __init__(self, sorted_dict): > - """ > - Initialize a ValuesView from a SortedDict container as > - *sorted_dict*. > - """ > - # pylint: disable=super-init-not-called, protected-access > - self._dict = sorted_dict > - self._list = sorted_dict._list > - self._view = sorted_dict._dict.viewvalues() > - else: > - def __init__(self, sorted_dict): > - """ > - Initialize a ValuesView from a SortedDict container as > - *sorted_dict*. > - """ > - # pylint: disable=super-init-not-called, protected-access > - self._dict = sorted_dict > - self._list = sorted_dict._list > - self._view = sorted_dict._dict.values() > - def __len__(self): > - """Return the number of entries in the dictionary.""" > - return len(self._dict) > - def __contains__(self, value): > - """ > - Return True if and only if *value* is in the underlying Mapping's > - values. > - """ > - return value in self._view > - def __iter__(self): > - """ > - Return an iterator over the values in the dictionary. Values are > - iterated over in sorted order of the keys. > + __slots__ = () > + > + > + @classmethod > + def _from_iterable(cls, it): > + return SortedSet(it) > + > > - Iterating views while adding or deleting entries in the dictionary may > - raise a `RuntimeError` or fail to iterate over all entries. > - """ > - _dict = self._dict > - return iter(_dict[key] for key in self._list) > def __getitem__(self, index): > - """ > - Efficiently return value at *index* in iteration. > + """Lookup item at `index` in sorted items view. > + > + ``siv.__getitem__(index)`` <==> ``siv[index]`` > + > + Supports slicing. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3}) > + >>> siv = sd.items() > + >>> siv[0] > + ('a', 1) > + >>> siv[-1] > + ('c', 3) > + >>> siv[:] > + [('a', 1), ('b', 2), ('c', 3)] > + >>> siv[100] > + Traceback (most recent call last): > + ... > + IndexError: list index out of range > + > + :param index: integer or slice for indexing > + :return: item or list of items > + :raises IndexError: if index out of range > > - Supports slice notation and negative indexes. > """ > - _dict, _list = self._dict, self._list > + _mapping = self._mapping > + _mapping_list = _mapping._list > + > if isinstance(index, slice): > - return [_dict[key] for key in _list[index]] > - return _dict[_list[index]] > - def __reversed__(self): > - """ > - Return a reverse iterator over the values in the dictionary. Values are > - iterated over in reverse sort order of the keys. > + keys = _mapping_list[index] > + return [(key, _mapping[key]) for key in keys] > > - Iterating views while adding or deleting entries in the dictionary may > - raise a `RuntimeError` or fail to iterate over all entries. > - """ > - _dict = self._dict > - return iter(_dict[key] for key in reversed(self._list)) > - def index(self, value): > - """ > - Return index of *value* in self. > + key = _mapping_list[index] > + return key, _mapping[key] > > - Raises ValueError if *value* is not found. > - """ > - # pylint: disable=arguments-differ > - for idx, val in enumerate(self): > - if value == val: > - return idx > - raise ValueError('{0!r} is not in dict'.format(value)) > - if hexversion < 0x03000000: > - def count(self, value): > - """Return the number of occurrences of *value* in self.""" > - return sum(1 for val in self._dict.itervalues() if val == value) > - else: > - def count(self, value): > - """Return the number of occurrences of *value* in self.""" > - return sum(1 for val in self._dict.values() if val == value) > - def __lt__(self, that): > - raise TypeError > - def __gt__(self, that): > - raise TypeError > - def __le__(self, that): > - raise TypeError > - def __ge__(self, that): > - raise TypeError > - def __and__(self, that): > - raise TypeError > - def __or__(self, that): > - raise TypeError > - def __sub__(self, that): > - raise TypeError > - def __xor__(self, that): > - raise TypeError > - @recursive_repr > - def __repr__(self): > - return 'SortedDict_values({0!r})'.format(list(self)) > > + __delitem__ = _view_delitem > > -class ItemsView(AbstractItemsView, Set, Sequence): > - """ > - An ItemsView object is a dynamic view of the dictionary's ``(key, > - value)`` pairs, which means that when the dictionary changes, the > - view reflects those changes. > > - The ItemsView class implements the Set and Sequence Abstract Base Classes. > - However, the set-like operations (``&``, ``|``, ``-``, ``^``) will only > - operate correctly if all of the dictionary's values are hashable. > +class SortedValuesView(ValuesView, Sequence): > + """Sorted values view is a dynamic view of the sorted dict's values. > + > + When the sorted dict's values change, the view reflects those changes. > + > + The values view implements the sequence abstract base class. > + > """ > - # pylint: disable=too-many-ancestors > - if hexversion < 0x03000000: > - def __init__(self, sorted_dict): > - """ > - Initialize an ItemsView from a SortedDict container as > - *sorted_dict*. > - """ > - # pylint: disable=super-init-not-called, protected-access > - self._dict = sorted_dict > - self._list = sorted_dict._list > - self._view = sorted_dict._dict.viewitems() > - else: > - def __init__(self, sorted_dict): > - """ > - Initialize an ItemsView from a SortedDict container as > - *sorted_dict*. > - """ > - # pylint: disable=super-init-not-called, protected-access > - self._dict = sorted_dict > - self._list = sorted_dict._list > - self._view = sorted_dict._dict.items() > - def __len__(self): > - """Return the number of entries in the dictionary.""" > - return len(self._view) > - def __contains__(self, key): > - """ > - Return True if and only if *key* is one of the underlying dictionary's > - items. > - """ > - return key in self._view > - def __iter__(self): > - """ > - Return an iterable over the items in the dictionary. Items are iterated > - over in their sorted order. > + __slots__ = () > + > > - Iterating views while adding or deleting entries in the dictionary may > - raise a `RuntimeError` or fail to iterate over all entries. > - """ > - _dict = self._dict > - return iter((key, _dict[key]) for key in self._list) > def __getitem__(self, index): > - """Return the item as position *index*.""" > - _dict, _list = self._dict, self._list > - if isinstance(index, slice): > - return [(key, _dict[key]) for key in _list[index]] > - key = _list[index] > - return (key, _dict[key]) > - def __reversed__(self): > - """ > - Return a reversed iterable over the items in the dictionary. Items are > - iterated over in their reverse sort order. > + """Lookup value at `index` in sorted values view. > + > + ``siv.__getitem__(index)`` <==> ``siv[index]`` > + > + Supports slicing. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3}) > + >>> svv = sd.values() > + >>> svv[0] > + 1 > + >>> svv[-1] > + 3 > + >>> svv[:] > + [1, 2, 3] > + >>> svv[100] > + Traceback (most recent call last): > + ... > + IndexError: list index out of range > + > + :param index: integer or slice for indexing > + :return: value or list of values > + :raises IndexError: if index out of range > > - Iterating views while adding or deleting entries in the dictionary may > - raise a RuntimeError or fail to iterate over all entries. > - """ > - _dict = self._dict > - return iter((key, _dict[key]) for key in reversed(self._list)) > - def index(self, key, start=None, stop=None): > - """ > - Return the smallest *k* such that `itemssview[k] == key` and `start <= k > - < end`. Raises `KeyError` if *key* is not present. *stop* defaults > - to the end of the set. *start* defaults to the beginning. Negative > - indexes are supported, as for slice indices. > """ > - # pylint: disable=arguments-differ > - temp, value = key > - pos = self._list.index(temp, start, stop) > - if value == self._dict[temp]: > - return pos > - else: > - raise ValueError('{0!r} is not in dict'.format(key)) > - def count(self, item): > - """Return the number of occurrences of *item* in the set.""" > - # pylint: disable=arguments-differ > - key, value = item > - return 1 if key in self._dict and self._dict[key] == value else 0 > - def __eq__(self, that): > - """Test set-like equality with *that*.""" > - return self._view == that > - def __ne__(self, that): > - """Test set-like inequality with *that*.""" > - return self._view != that > - def __lt__(self, that): > - """Test whether self is a proper subset of *that*.""" > - return self._view < that > - def __gt__(self, that): > - """Test whether self is a proper superset of *that*.""" > - return self._view > that > - def __le__(self, that): > - """Test whether self is contained within *that*.""" > - return self._view <= that > - def __ge__(self, that): > - """Test whether *that* is contained within self.""" > - return self._view >= that > - def __and__(self, that): > - """Return a SortedSet of the intersection of self and *that*.""" > - return SortedSet(self._view & that) > - def __or__(self, that): > - """Return a SortedSet of the union of self and *that*.""" > - return SortedSet(self._view | that) > - def __sub__(self, that): > - """Return a SortedSet of the difference of self and *that*.""" > - return SortedSet(self._view - that) > - def __xor__(self, that): > - """Return a SortedSet of the symmetric difference of self and *that*.""" > - return SortedSet(self._view ^ that) > - if hexversion < 0x03000000: > - def isdisjoint(self, that): > - """Return True if and only if *that* is disjoint with self.""" > - # pylint: disable=arguments-differ > - _dict = self._dict > - for key, value in that: > - if key in _dict and _dict[key] == value: > - return False > - return True > - else: > - def isdisjoint(self, that): > - """Return True if and only if *that* is disjoint with self.""" > - # pylint: disable=arguments-differ > - return self._view.isdisjoint(that) > - @recursive_repr > - def __repr__(self): > - return 'SortedDict_items({0!r})'.format(list(self)) > + _mapping = self._mapping > + _mapping_list = _mapping._list > + > + if isinstance(index, slice): > + keys = _mapping_list[index] > + return [_mapping[key] for key in keys] > + > + key = _mapping_list[index] > + return _mapping[key] > + > + > + __delitem__ = _view_delitem > diff --git a/python/ovs/compat/sortedcontainers/sortedlist.py b/python/ovs/compat/sortedcontainers/sortedlist.py > index ba5556692..5f283fbb0 100644 > --- a/python/ovs/compat/sortedcontainers/sortedlist.py > +++ b/python/ovs/compat/sortedcontainers/sortedlist.py > @@ -1,20 +1,44 @@ > -"""Sorted list implementation. > +"""Sorted List > +============== > + > +:doc:`Sorted Containers<index>` is an Apache2 licensed Python sorted > +collections library, written in pure-Python, and fast as C-extensions. The > +:doc:`introduction<introduction>` is the best way to get started. > + > +Sorted list implementations: > + > +.. currentmodule:: sortedcontainers > + > +* :class:`SortedList` > +* :class:`SortedKeyList` > > """ > -# pylint: disable=redefined-builtin, ungrouped-imports > +# pylint: disable=too-many-lines > + > +import sys > +import traceback > > from bisect import bisect_left, bisect_right, insort > -from collections import Sequence, MutableSequence > -from functools import wraps > from itertools import chain, repeat, starmap > -from math import log as log_e > -import operator as op > -from operator import iadd, add > +from math import log > +from operator import add, eq, ne, gt, ge, lt, le, iadd > +from textwrap import dedent > + > +############################################################################### > +# BEGIN Python 2/3 Shims > +############################################################################### > + > +try: > + from collections.abc import Sequence, MutableSequence > +except ImportError: > + from collections import Sequence, MutableSequence > + > +from functools import wraps > from sys import hexversion > > if hexversion < 0x03000000: > - from itertools import izip as zip # pylint: disable=no-name-in-module > - from itertools import imap as map # pylint: disable=no-name-in-module > + from itertools import imap as map # pylint: disable=redefined-builtin > + from itertools import izip as zip # pylint: disable=redefined-builtin > try: > from thread import get_ident > except ImportError: > @@ -24,145 +48,258 @@ else: > try: > from _thread import get_ident > except ImportError: > - from _dummy_thread import get_ident # pylint: disable=import-error > + from _dummy_thread import get_ident > > -LOAD = 1000 > > -def recursive_repr(func): > - """Decorator to prevent infinite repr recursion.""" > - repr_running = set() > +def recursive_repr(fillvalue='...'): > + "Decorator to make a repr function return fillvalue for a recursive call." > + # pylint: disable=missing-docstring > + # Copied from reprlib in Python 3 > + # https://hg.python.org/cpython/file/3.6/Lib/reprlib.py > > - @wraps(func) > - def wrapper(self): > - "Return ellipsis on recursive re-entry to function." > - key = id(self), get_ident() > + def decorating_function(user_function): > + repr_running = set() > > - if key in repr_running: > - return '...' > + @wraps(user_function) > + def wrapper(self): > + key = id(self), get_ident() > + if key in repr_running: > + return fillvalue > + repr_running.add(key) > + try: > + result = user_function(self) > + finally: > + repr_running.discard(key) > + return result > > - repr_running.add(key) > + return wrapper > > - try: > - return func(self) > - finally: > - repr_running.discard(key) > + return decorating_function > + > +############################################################################### > +# END Python 2/3 Shims > +############################################################################### > > - return wrapper > > class SortedList(MutableSequence): > + """Sorted list is a sorted mutable sequence. > + > + Sorted list values are maintained in sorted order. > + > + Sorted list values must be comparable. The total ordering of values must > + not change while they are stored in the sorted list. > + > + Methods for adding values: > + > + * :func:`SortedList.add` > + * :func:`SortedList.update` > + * :func:`SortedList.__add__` > + * :func:`SortedList.__iadd__` > + * :func:`SortedList.__mul__` > + * :func:`SortedList.__imul__` > + > + Methods for removing values: > + > + * :func:`SortedList.clear` > + * :func:`SortedList.discard` > + * :func:`SortedList.remove` > + * :func:`SortedList.pop` > + * :func:`SortedList.__delitem__` > + > + Methods for looking up values: > + > + * :func:`SortedList.bisect_left` > + * :func:`SortedList.bisect_right` > + * :func:`SortedList.count` > + * :func:`SortedList.index` > + * :func:`SortedList.__contains__` > + * :func:`SortedList.__getitem__` > + > + Methods for iterating values: > + > + * :func:`SortedList.irange` > + * :func:`SortedList.islice` > + * :func:`SortedList.__iter__` > + * :func:`SortedList.__reversed__` > + > + Methods for miscellany: > + > + * :func:`SortedList.copy` > + * :func:`SortedList.__len__` > + * :func:`SortedList.__repr__` > + * :func:`SortedList._check` > + * :func:`SortedList._reset` > + > + Sorted lists use lexicographical ordering semantics when compared to other > + sequences. > + > + Some methods of mutable sequences are not supported and will raise > + not-implemented error. > + > """ > - SortedList provides most of the same methods as a list but keeps the items > - in sorted order. > - """ > - # pylint: disable=too-many-ancestors > - def __init__(self, iterable=None): > - """ > - SortedList provides most of the same methods as a list but keeps the > - items in sorted order. > + DEFAULT_LOAD_FACTOR = 1000 > + > + > + def __init__(self, iterable=None, key=None): > + """Initialize sorted list instance. > + > + Optional `iterable` argument provides an initial iterable of values to > + initialize the sorted list. > + > + Runtime complexity: `O(n*log(n))` > + > + >>> sl = SortedList() > + >>> sl > + SortedList([]) > + >>> sl = SortedList([3, 1, 2, 5, 4]) > + >>> sl > + SortedList([1, 2, 3, 4, 5]) > + > + :param iterable: initial values (optional) > > - An optional *iterable* provides an initial series of items to populate > - the SortedList. > """ > + assert key is None > self._len = 0 > + self._load = self.DEFAULT_LOAD_FACTOR > self._lists = [] > self._maxes = [] > self._index = [] > - self._load = LOAD > - self._half = LOAD >> 1 > - self._dual = LOAD << 1 > self._offset = 0 > > if iterable is not None: > self._update(iterable) > > + > def __new__(cls, iterable=None, key=None): > - """ > - SortedList provides most of the same methods as a list but keeps the > - items in sorted order. > + """Create new sorted list or sorted-key list instance. > + > + Optional `key`-function argument will return an instance of subtype > + :class:`SortedKeyList`. > > - An optional *iterable* provides an initial series of items to populate > - the SortedList. > + >>> sl = SortedList() > + >>> isinstance(sl, SortedList) > + True > + >>> sl = SortedList(key=lambda x: -x) > + >>> isinstance(sl, SortedList) > + True > + >>> isinstance(sl, SortedKeyList) > + True > + > + :param iterable: initial values (optional) > + :param key: function used to extract comparison key (optional) > + :return: sorted list or sorted-key list instance > > - An optional *key* argument will return an instance of subtype > - SortedListWithKey. > """ > # pylint: disable=unused-argument > if key is None: > return object.__new__(cls) > else: > if cls is SortedList: > - return object.__new__(SortedListWithKey) > + return object.__new__(SortedKeyList) > else: > - raise TypeError('inherit SortedListWithKey for key argument') > + raise TypeError('inherit SortedKeyList for key argument') > + > > @property > - def key(self): > - """Key function used to extract comparison key for sorting.""" > + def key(self): # pylint: disable=useless-return > + """Function used to extract comparison key from values. > + > + Sorted list compares values directly so the key function is none. > + > + """ > return None > > + > def _reset(self, load): > - """ > - Reset sorted list load. > - > - The *load* specifies the load-factor of the list. The default load > - factor of '1000' works well for lists from tens to tens of millions of > - elements. Good practice is to use a value that is the cube root of the > - list size. With billions of elements, the best load factor depends on > - your usage. It's best to leave the load factor at the default until > - you start benchmarking. > + """Reset sorted list load factor. > + > + The `load` specifies the load-factor of the list. The default load > + factor of 1000 works well for lists from tens to tens-of-millions of > + values. Good practice is to use a value that is the cube root of the > + list size. With billions of elements, the best load factor depends on > + your usage. It's best to leave the load factor at the default until you > + start benchmarking. > + > + See :doc:`implementation` and :doc:`performance-scale` for more > + information. > + > + Runtime complexity: `O(n)` > + > + :param int load: load-factor for sorted list sublists > + > """ > values = reduce(iadd, self._lists, []) > self._clear() > self._load = load > - self._half = load >> 1 > - self._dual = load << 1 > self._update(values) > > + > def clear(self): > - """Remove all the elements from the list.""" > + """Remove all values from sorted list. > + > + Runtime complexity: `O(n)` > + > + """ > self._len = 0 > del self._lists[:] > del self._maxes[:] > del self._index[:] > + self._offset = 0 > > _clear = clear > > - def add(self, val): > - """Add the element *val* to the list.""" > + > + def add(self, value): > + """Add `value` to sorted list. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> sl = SortedList() > + >>> sl.add(3) > + >>> sl.add(1) > + >>> sl.add(2) > + >>> sl > + SortedList([1, 2, 3]) > + > + :param value: value to add to sorted list > + > + """ > _lists = self._lists > _maxes = self._maxes > > if _maxes: > - pos = bisect_right(_maxes, val) > + pos = bisect_right(_maxes, value) > > if pos == len(_maxes): > pos -= 1 > - _lists[pos].append(val) > - _maxes[pos] = val > + _lists[pos].append(value) > + _maxes[pos] = value > else: > - insort(_lists[pos], val) > + insort(_lists[pos], value) > > self._expand(pos) > else: > - _lists.append([val]) > - _maxes.append(val) > + _lists.append([value]) > + _maxes.append(value) > > self._len += 1 > > + > def _expand(self, pos): > - """Splits sublists that are more than double the load level. > + """Split sublists with length greater than double the load-factor. > > Updates the index when the sublist length is less than double the load > level. This requires incrementing the nodes in a traversal from the > - leaf node to the root. For an example traversal see self._loc. > + leaf node to the root. For an example traversal see > + ``SortedList._loc``. > > """ > + _load = self._load > _lists = self._lists > _index = self._index > > - if len(_lists[pos]) > self._dual: > + if len(_lists[pos]) > (_load << 1): > _maxes = self._maxes > - _load = self._load > > _lists_pos = _lists[pos] > half = _lists_pos[_load:] > @@ -181,15 +318,28 @@ class SortedList(MutableSequence): > child = (child - 1) >> 1 > _index[0] += 1 > > + > def update(self, iterable): > - """Update the list by adding all elements from *iterable*.""" > + """Update sorted list by adding all values from `iterable`. > + > + Runtime complexity: `O(k*log(n))` -- approximate. > + > + >>> sl = SortedList() > + >>> sl.update([3, 1, 2]) > + >>> sl > + SortedList([1, 2, 3]) > + > + :param iterable: iterable of values to add > + > + """ > _lists = self._lists > _maxes = self._maxes > values = sorted(iterable) > > if _maxes: > if len(values) * 4 >= self._len: > - values.extend(chain.from_iterable(_lists)) > + _lists.append(values) > + values = reduce(iadd, _lists, []) > values.sort() > self._clear() > else: > @@ -207,78 +357,123 @@ class SortedList(MutableSequence): > > _update = update > > - def __contains__(self, val): > - """Return True if and only if *val* is an element in the list.""" > + > + def __contains__(self, value): > + """Return true if `value` is an element of the sorted list. > + > + ``sl.__contains__(value)`` <==> ``value in sl`` > + > + Runtime complexity: `O(log(n))` > + > + >>> sl = SortedList([1, 2, 3, 4, 5]) > + >>> 3 in sl > + True > + > + :param value: search for value in sorted list > + :return: true if `value` in sorted list > + > + """ > _maxes = self._maxes > > if not _maxes: > return False > > - pos = bisect_left(_maxes, val) > + pos = bisect_left(_maxes, value) > > if pos == len(_maxes): > return False > > _lists = self._lists > - idx = bisect_left(_lists[pos], val) > + idx = bisect_left(_lists[pos], value) > > - return _lists[pos][idx] == val > + return _lists[pos][idx] == value > > - def discard(self, val): > - """ > - Remove the first occurrence of *val*. > > - If *val* is not a member, does nothing. > + def discard(self, value): > + """Remove `value` from sorted list if it is a member. > + > + If `value` is not a member, do nothing. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> sl = SortedList([1, 2, 3, 4, 5]) > + >>> sl.discard(5) > + >>> sl.discard(0) > + >>> sl == [1, 2, 3, 4] > + True > + > + :param value: `value` to discard from sorted list > + > """ > _maxes = self._maxes > > if not _maxes: > return > > - pos = bisect_left(_maxes, val) > + pos = bisect_left(_maxes, value) > > if pos == len(_maxes): > return > > _lists = self._lists > - idx = bisect_left(_lists[pos], val) > + idx = bisect_left(_lists[pos], value) > > - if _lists[pos][idx] == val: > + if _lists[pos][idx] == value: > self._delete(pos, idx) > > - def remove(self, val): > - """ > - Remove first occurrence of *val*. > > - Raises ValueError if *val* is not present. > + def remove(self, value): > + """Remove `value` from sorted list; `value` must be a member. > + > + If `value` is not a member, raise ValueError. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> sl = SortedList([1, 2, 3, 4, 5]) > + >>> sl.remove(5) > + >>> sl == [1, 2, 3, 4] > + True > + >>> sl.remove(0) > + Traceback (most recent call last): > + ... > + ValueError: 0 not in list > + > + :param value: `value` to remove from sorted list > + :raises ValueError: if `value` is not in sorted list > + > """ > - # pylint: disable=arguments-differ > _maxes = self._maxes > > if not _maxes: > - raise ValueError('{0!r} not in list'.format(val)) > + raise ValueError('{0!r} not in list'.format(value)) > > - pos = bisect_left(_maxes, val) > + pos = bisect_left(_maxes, value) > > if pos == len(_maxes): > - raise ValueError('{0!r} not in list'.format(val)) > + raise ValueError('{0!r} not in list'.format(value)) > > _lists = self._lists > - idx = bisect_left(_lists[pos], val) > + idx = bisect_left(_lists[pos], value) > > - if _lists[pos][idx] == val: > + if _lists[pos][idx] == value: > self._delete(pos, idx) > else: > - raise ValueError('{0!r} not in list'.format(val)) > + raise ValueError('{0!r} not in list'.format(value)) > + > > def _delete(self, pos, idx): > - """Delete the item at the given (pos, idx). > + """Delete value at the given `(pos, idx)`. > > Combines lists that are less than half the load level. > > Updates the index when the sublist length is more than half the load > - level. This requires decrementing the nodes in a traversal from the leaf > - node to the root. For an example traversal see self._loc. > + level. This requires decrementing the nodes in a traversal from the > + leaf node to the root. For an example traversal see > + ``SortedList._loc``. > + > + :param int pos: lists index > + :param int idx: sublist index > + > """ > _lists = self._lists > _maxes = self._maxes > @@ -291,8 +486,7 @@ class SortedList(MutableSequence): > > len_lists_pos = len(_lists_pos) > > - if len_lists_pos > self._half: > - > + if len_lists_pos > (self._load >> 1): > _maxes[pos] = _lists_pos[-1] > > if _index: > @@ -301,9 +495,7 @@ class SortedList(MutableSequence): > _index[child] -= 1 > child = (child - 1) >> 1 > _index[0] -= 1 > - > elif len(_lists) > 1: > - > if not pos: > pos += 1 > > @@ -316,26 +508,24 @@ class SortedList(MutableSequence): > del _index[:] > > self._expand(prev) > - > elif len_lists_pos: > - > _maxes[pos] = _lists_pos[-1] > - > else: > - > del _lists[pos] > del _maxes[pos] > del _index[:] > > + > def _loc(self, pos, idx): > - """Convert an index pair (alpha, beta) into a single index that corresponds to > - the position of the value in the sorted list. > + """Convert an index pair (lists index, sublist index) into a single > + index number that corresponds to the position of the value in the > + sorted list. > > - Most queries require the index be built. Details of the index are > - described in self._build_index. > + Many queries require the index be built. Details of the index are > + described in ``SortedList._build_index``. > > Indexing requires traversing the tree from a leaf node to the root. The > - parent of each node is easily computable at (pos - 1) // 2. > + parent of each node is easily computable at ``(pos - 1) // 2``. > > Left-child nodes are always at odd indices and right-child nodes are > always at even indices. > @@ -345,19 +535,19 @@ class SortedList(MutableSequence): > > The final index is the sum from traversal and the index in the sublist. > > - For example, using the index from self._build_index: > + For example, using the index from ``SortedList._build_index``:: > > - _index = 14 5 9 3 2 4 5 > - _offset = 3 > + _index = 14 5 9 3 2 4 5 > + _offset = 3 > > - Tree: > + Tree:: > > 14 > 5 9 > 3 2 4 5 > > - Converting index pair (2, 3) into a single index involves iterating like > - so: > + Converting an index pair (2, 3) into a single index involves iterating > + like so: > > 1. Starting at the leaf node: offset + alpha = 3 + 2 = 5. We identify > the node as a left-child node. At such nodes, we simply traverse to > @@ -369,7 +559,12 @@ class SortedList(MutableSequence): > > 3. Iteration ends at the root. > > - Computing the index is the sum of the total and beta: 5 + 3 = 8. > + The index is then the sum of the total and sublist index: 5 + 3 = 8. > + > + :param int pos: lists index > + :param int idx: sublist index > + :return: index in sorted list > + > """ > if not pos: > return idx > @@ -401,16 +596,18 @@ class SortedList(MutableSequence): > > return total + idx > > + > def _pos(self, idx): > - """Convert an index into a pair (alpha, beta) that can be used to access > - the corresponding _lists[alpha][beta] position. > + """Convert an index into an index pair (lists index, sublist index) > + that can be used to access the corresponding lists position. > > - Most queries require the index be built. Details of the index are > - described in self._build_index. > + Many queries require the index be built. Details of the index are > + described in ``SortedList._build_index``. > > - Indexing requires traversing the tree to a leaf node. Each node has > - two children which are easily computable. Given an index, pos, the > - left-child is at pos * 2 + 1 and the right-child is at pos * 2 + 2. > + Indexing requires traversing the tree to a leaf node. Each node has two > + children which are easily computable. Given an index, pos, the > + left-child is at ``pos * 2 + 1`` and the right-child is at ``pos * 2 + > + 2``. > > When the index is less than the left-child, traversal moves to the > left sub-tree. Otherwise, the index is decremented by the left-child > @@ -420,12 +617,12 @@ class SortedList(MutableSequence): > position of the child node as compared with the offset and the remaining > index. > > - For example, using the index from self._build_index: > + For example, using the index from ``SortedList._build_index``:: > > - _index = 14 5 9 3 2 4 5 > - _offset = 3 > + _index = 14 5 9 3 2 4 5 > + _offset = 3 > > - Tree: > + Tree:: > > 14 > 5 9 > @@ -450,6 +647,10 @@ class SortedList(MutableSequence): > > The final index pair from our example is (2, 3) which corresponds to > index 8 in the sorted list. > + > + :param int idx: index in sorted list > + :return: (lists index, sublist index) pair > + > """ > if idx < 0: > last_len = len(self._lists[-1]) > @@ -489,39 +690,42 @@ class SortedList(MutableSequence): > > return (pos - self._offset, idx) > > + > def _build_index(self): > - """Build an index for indexing the sorted list. > + """Build a positional index for indexing the sorted list. > > Indexes are represented as binary trees in a dense array notation > similar to a binary heap. > > - For example, given a _lists representation storing integers: > + For example, given a lists representation storing integers:: > > - [0]: 1 2 3 > - [1]: 4 5 > - [2]: 6 7 8 9 > - [3]: 10 11 12 13 14 > + 0: [1, 2, 3] > + 1: [4, 5] > + 2: [6, 7, 8, 9] > + 3: [10, 11, 12, 13, 14] > > The first transformation maps the sub-lists by their length. The > - first row of the index is the length of the sub-lists. > + first row of the index is the length of the sub-lists:: > > - [0]: 3 2 4 5 > + 0: [3, 2, 4, 5] > > - Each row after that is the sum of consecutive pairs of the previous row: > + Each row after that is the sum of consecutive pairs of the previous > + row:: > > - [1]: 5 9 > - [2]: 14 > + 1: [5, 9] > + 2: [14] > > - Finally, the index is built by concatenating these lists together: > + Finally, the index is built by concatenating these lists together:: > > - _index = 14 5 9 3 2 4 5 > + _index = [14, 5, 9, 3, 2, 4, 5] > > - An offset storing the start of the first row is also stored: > + An offset storing the start of the first row is also stored:: > > - _offset = 3 > + _offset = 3 > > When built, the index can be used for efficient indexing into the list. > - See the comment and notes on self._pos for details. > + See the comment and notes on ``SortedList._pos`` for details. > + > """ > row0 = list(map(len, self._lists)) > > @@ -542,7 +746,7 @@ class SortedList(MutableSequence): > self._offset = 1 > return > > - size = 2 ** (int(log_e(len(row1) - 1, 2)) + 1) > + size = 2 ** (int(log(len(row1) - 1, 2)) + 1) > row1.extend(repeat(0, size - len(row1))) > tree = [row0, row1] > > @@ -555,10 +759,30 @@ class SortedList(MutableSequence): > reduce(iadd, reversed(tree), self._index) > self._offset = size * 2 - 1 > > - def __delitem__(self, idx): > - """Remove the element at *idx*. Supports slicing.""" > - if isinstance(idx, slice): > - start, stop, step = idx.indices(self._len) > + > + def __delitem__(self, index): > + """Remove value at `index` from sorted list. > + > + ``sl.__delitem__(index)`` <==> ``del sl[index]`` > + > + Supports slicing. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> sl = SortedList('abcde') > + >>> del sl[2] > + >>> sl > + SortedList(['a', 'b', 'd', 'e']) > + >>> del sl[:2] > + >>> sl > + SortedList(['d', 'e']) > + > + :param index: integer or slice for indexing > + :raises IndexError: if index out of range > + > + """ > + if isinstance(index, slice): > + start, stop, step = index.indices(self._len) > > if step == 1 and start < stop: > if start == 0 and stop == self._len: > @@ -584,23 +808,53 @@ class SortedList(MutableSequence): > pos, idx = _pos(index) > _delete(pos, idx) > else: > - pos, idx = self._pos(idx) > + pos, idx = self._pos(index) > self._delete(pos, idx) > > - _delitem = __delitem__ > > - def __getitem__(self, idx): > - """Return the element at *idx*. Supports slicing.""" > + def __getitem__(self, index): > + """Lookup value at `index` in sorted list. > + > + ``sl.__getitem__(index)`` <==> ``sl[index]`` > + > + Supports slicing. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> sl = SortedList('abcde') > + >>> sl[1] > + 'b' > + >>> sl[-1] > + 'e' > + >>> sl[2:5] > + ['c', 'd', 'e'] > + > + :param index: integer or slice for indexing > + :return: value or list of values > + :raises IndexError: if index out of range > + > + """ > _lists = self._lists > > - if isinstance(idx, slice): > - start, stop, step = idx.indices(self._len) > + if isinstance(index, slice): > + start, stop, step = index.indices(self._len) > > if step == 1 and start < stop: > + # Whole slice optimization: start to stop slices the whole > + # sorted list. > + > if start == 0 and stop == self._len: > return reduce(iadd, self._lists, []) > > start_pos, start_idx = self._pos(start) > + start_list = _lists[start_pos] > + stop_idx = start_idx + stop - start > + > + # Small slice optimization: start index and stop index are > + # within the start list. > + > + if len(start_list) >= stop_idx: > + return start_list[start_idx:stop_idx] > > if stop == self._len: > stop_pos = len(_lists) - 1 > @@ -608,9 +862,6 @@ class SortedList(MutableSequence): > else: > stop_pos, stop_idx = self._pos(stop) > > - if start_pos == stop_pos: > - return _lists[start_pos][start_idx:stop_idx] > - > prefix = _lists[start_pos][start_idx:] > middle = _lists[(start_pos + 1):stop_pos] > result = reduce(iadd, middle, prefix) > @@ -631,221 +882,104 @@ class SortedList(MutableSequence): > return list(self._getitem(index) for index in indices) > else: > if self._len: > - if idx == 0: > + if index == 0: > return _lists[0][0] > - elif idx == -1: > + elif index == -1: > return _lists[-1][-1] > else: > raise IndexError('list index out of range') > > - if 0 <= idx < len(_lists[0]): > - return _lists[0][idx] > + if 0 <= index < len(_lists[0]): > + return _lists[0][index] > > len_last = len(_lists[-1]) > > - if -len_last < idx < 0: > - return _lists[-1][len_last + idx] > + if -len_last < index < 0: > + return _lists[-1][len_last + index] > > - pos, idx = self._pos(idx) > + pos, idx = self._pos(index) > return _lists[pos][idx] > > _getitem = __getitem__ > > - def _check_order(self, idx, val): > - _len = self._len > - _lists = self._lists > - > - pos, loc = self._pos(idx) > - > - if idx < 0: > - idx += _len > - > - # Check that the inserted value is not less than the > - # previous value. > - > - if idx > 0: > - idx_prev = loc - 1 > - pos_prev = pos > - > - if idx_prev < 0: > - pos_prev -= 1 > - idx_prev = len(_lists[pos_prev]) - 1 > - > - if _lists[pos_prev][idx_prev] > val: > - msg = '{0!r} not in sort order at index {1}'.format(val, idx) > - raise ValueError(msg) > - > - # Check that the inserted value is not greater than > - # the previous value. > - > - if idx < (_len - 1): > - idx_next = loc + 1 > - pos_next = pos > - > - if idx_next == len(_lists[pos_next]): > - pos_next += 1 > - idx_next = 0 > - > - if _lists[pos_next][idx_next] < val: > - msg = '{0!r} not in sort order at index {1}'.format(val, idx) > - raise ValueError(msg) > > def __setitem__(self, index, value): > - """Replace item at position *index* with *value*. > - > - Supports slice notation. Raises :exc:`ValueError` if the sort order > - would be violated. When used with a slice and iterable, the > - :exc:`ValueError` is raised before the list is mutated if the sort > - order would be violated by the operation. > - > - """ > - _lists = self._lists > - _maxes = self._maxes > - _check_order = self._check_order > - _pos = self._pos > - > - if isinstance(index, slice): > - _len = self._len > - start, stop, step = index.indices(_len) > - indices = range(start, stop, step) > - > - # Copy value to avoid aliasing issues with self and cases where an > - # iterator is given. > - > - values = tuple(value) > - > - if step != 1: > - if len(values) != len(indices): > - raise ValueError( > - 'attempt to assign sequence of size %s' > - ' to extended slice of size %s' > - % (len(values), len(indices))) > - > - # Keep a log of values that are set so that we can > - # roll back changes if ordering is violated. > - > - log = [] > - _append = log.append > - > - for idx, val in zip(indices, values): > - pos, loc = _pos(idx) > - _append((idx, _lists[pos][loc], val)) > - _lists[pos][loc] = val > - if len(_lists[pos]) == (loc + 1): > - _maxes[pos] = val > - > - try: > - # Validate ordering of new values. > - > - for idx, _, newval in log: > - _check_order(idx, newval) > + """Raise not-implemented error. > > - except ValueError: > + ``sl.__setitem__(index, value)`` <==> ``sl[index] = value`` > > - # Roll back changes from log. > + :raises NotImplementedError: use ``del sl[index]`` and > + ``sl.add(value)`` instead > > - for idx, oldval, _ in log: > - pos, loc = _pos(idx) > - _lists[pos][loc] = oldval > - if len(_lists[pos]) == (loc + 1): > - _maxes[pos] = oldval > - > - raise > - else: > - if start == 0 and stop == _len: > - self._clear() > - return self._update(values) > - > - if stop < start: > - # When calculating indices, stop may be less than start. > - # For example: ...[5:3:1] results in slice(5, 3, 1) which > - # is a valid but not useful stop index. > - stop = start > - > - if values: > - > - # Check that given values are ordered properly. > - > - alphas = iter(values) > - betas = iter(values) > - next(betas) > - pairs = zip(alphas, betas) > - > - if not all(alpha <= beta for alpha, beta in pairs): > - raise ValueError('given values not in sort order') > - > - # Check ordering in context of sorted list. > - > - if start and self._getitem(start - 1) > values[0]: > - message = '{0!r} not in sort order at index {1}'.format( > - values[0], start) > - raise ValueError(message) > - > - if stop != _len and self._getitem(stop) < values[-1]: > - message = '{0!r} not in sort order at index {1}'.format( > - values[-1], stop) > - raise ValueError(message) > - > - # Delete the existing values. > + """ > + message = 'use ``del sl[index]`` and ``sl.add(value)`` instead' > + raise NotImplementedError(message) > > - self._delitem(index) > > - # Insert the new values. > + def __iter__(self): > + """Return an iterator over the sorted list. > > - _insert = self.insert > - for idx, val in enumerate(values): > - _insert(start + idx, val) > - else: > - pos, loc = _pos(index) > - _check_order(index, value) > - _lists[pos][loc] = value > - if len(_lists[pos]) == (loc + 1): > - _maxes[pos] = value > + ``sl.__iter__()`` <==> ``iter(sl)`` > > - def __iter__(self): > - """ > - Return an iterator over the Sequence. > + Iterating the sorted list while adding or deleting values may raise a > + :exc:`RuntimeError` or fail to iterate over all values. > > - Iterating the Sequence while adding or deleting values may raise a > - `RuntimeError` or fail to iterate over all entries. > """ > return chain.from_iterable(self._lists) > > + > def __reversed__(self): > - """ > - Return an iterator to traverse the Sequence in reverse. > + """Return a reverse iterator over the sorted list. > + > + ``sl.__reversed__()`` <==> ``reversed(sl)`` > + > + Iterating the sorted list while adding or deleting values may raise a > + :exc:`RuntimeError` or fail to iterate over all values. > > - Iterating the Sequence while adding or deleting values may raise a > - `RuntimeError` or fail to iterate over all entries. > """ > return chain.from_iterable(map(reversed, reversed(self._lists))) > > + > def reverse(self): > - """Raise NotImplementedError > + """Raise not-implemented error. > > - SortedList maintains values in ascending sort order. Values may not be > + Sorted list maintains values in ascending sort order. Values may not be > reversed in-place. > > - Use ``reversed(sorted_list)`` for a reverse iterator over values in > - descending sort order. > + Use ``reversed(sl)`` for an iterator over values in descending sort > + order. > > - Implemented to override MutableSequence.reverse which provides an > + Implemented to override `MutableSequence.reverse` which provides an > erroneous default implementation. > > + :raises NotImplementedError: use ``reversed(sl)`` instead > + > """ > - raise NotImplementedError('.reverse() not defined') > + raise NotImplementedError('use ``reversed(sl)`` instead') > > - def islice(self, start=None, stop=None, reverse=False): > > - """ > - Returns an iterator that slices `self` from `start` to `stop` index, > - inclusive and exclusive respectively. > + def islice(self, start=None, stop=None, reverse=False): > + """Return an iterator that slices sorted list from `start` to `stop`. > > - When `reverse` is `True`, values are yielded from the iterator in > - reverse order. > + The `start` and `stop` index are treated inclusive and exclusive, > + respectively. > > Both `start` and `stop` default to `None` which is automatically > - inclusive of the beginning and end. > + inclusive of the beginning and end of the sorted list. > + > + When `reverse` is `True` the values are yielded from the iterator in > + reverse order; `reverse` defaults to `False`. > + > + >>> sl = SortedList('abcdefghij') > + >>> it = sl.islice(2, 6) > + >>> list(it) > + ['c', 'd', 'e', 'f'] > + > + :param int start: start index (inclusive) > + :param int stop: stop index (exclusive) > + :param bool reverse: yield values in reverse order > + :return: iterator > + > """ > _len = self._len > > @@ -869,60 +1003,97 @@ class SortedList(MutableSequence): > > return self._islice(min_pos, min_idx, max_pos, max_idx, reverse) > > + > def _islice(self, min_pos, min_idx, max_pos, max_idx, reverse): > - """ > - Returns an iterator that slices `self` using two index pairs, > - `(min_pos, min_idx)` and `(max_pos, max_idx)`; the first inclusive > - and the latter exclusive. See `_pos` for details on how an index > - is converted to an index pair. > + """Return an iterator that slices sorted list using two index pairs. > + > + The index pairs are (min_pos, min_idx) and (max_pos, max_idx), the > + first inclusive and the latter exclusive. See `_pos` for details on how > + an index is converted to an index pair. > > When `reverse` is `True`, values are yielded from the iterator in > reverse order. > + > """ > _lists = self._lists > > if min_pos > max_pos: > return iter(()) > - elif min_pos == max_pos and not reverse: > - return iter(_lists[min_pos][min_idx:max_idx]) > - elif min_pos == max_pos and reverse: > - return reversed(_lists[min_pos][min_idx:max_idx]) > - elif min_pos + 1 == max_pos and not reverse: > - return chain(_lists[min_pos][min_idx:], _lists[max_pos][:max_idx]) > - elif min_pos + 1 == max_pos and reverse: > + > + if min_pos == max_pos: > + if reverse: > + indices = reversed(range(min_idx, max_idx)) > + return map(_lists[min_pos].__getitem__, indices) > + > + indices = range(min_idx, max_idx) > + return map(_lists[min_pos].__getitem__, indices) > + > + next_pos = min_pos + 1 > + > + if next_pos == max_pos: > + if reverse: > + min_indices = range(min_idx, len(_lists[min_pos])) > + max_indices = range(max_idx) > + return chain( > + map(_lists[max_pos].__getitem__, reversed(max_indices)), > + map(_lists[min_pos].__getitem__, reversed(min_indices)), > + ) > + > + min_indices = range(min_idx, len(_lists[min_pos])) > + max_indices = range(max_idx) > return chain( > - reversed(_lists[max_pos][:max_idx]), > - reversed(_lists[min_pos][min_idx:]), > + map(_lists[min_pos].__getitem__, min_indices), > + map(_lists[max_pos].__getitem__, max_indices), > ) > - elif not reverse: > + > + if reverse: > + min_indices = range(min_idx, len(_lists[min_pos])) > + sublist_indices = range(next_pos, max_pos) > + sublists = map(_lists.__getitem__, reversed(sublist_indices)) > + max_indices = range(max_idx) > return chain( > - _lists[min_pos][min_idx:], > - chain.from_iterable(_lists[(min_pos + 1):max_pos]), > - _lists[max_pos][:max_idx], > + map(_lists[max_pos].__getitem__, reversed(max_indices)), > + chain.from_iterable(map(reversed, sublists)), > + map(_lists[min_pos].__getitem__, reversed(min_indices)), > ) > > - temp = map(reversed, reversed(_lists[(min_pos + 1):max_pos])) > + min_indices = range(min_idx, len(_lists[min_pos])) > + sublist_indices = range(next_pos, max_pos) > + sublists = map(_lists.__getitem__, sublist_indices) > + max_indices = range(max_idx) > return chain( > - reversed(_lists[max_pos][:max_idx]), > - chain.from_iterable(temp), > - reversed(_lists[min_pos][min_idx:]), > + map(_lists[min_pos].__getitem__, min_indices), > + chain.from_iterable(sublists), > + map(_lists[max_pos].__getitem__, max_indices), > ) > > + > def irange(self, minimum=None, maximum=None, inclusive=(True, True), > reverse=False): > - """ > - Create an iterator of values between `minimum` and `maximum`. > - > - `inclusive` is a pair of booleans that indicates whether the minimum > - and maximum ought to be included in the range, respectively. The > - default is (True, True) such that the range is inclusive of both > - minimum and maximum. > + """Create an iterator of values between `minimum` and `maximum`. > > Both `minimum` and `maximum` default to `None` which is automatically > - inclusive of the start and end of the list, respectively. > + inclusive of the beginning and end of the sorted list. > + > + The argument `inclusive` is a pair of booleans that indicates whether > + the minimum and maximum ought to be included in the range, > + respectively. The default is ``(True, True)`` such that the range is > + inclusive of both minimum and maximum. > > When `reverse` is `True` the values are yielded from the iterator in > reverse order; `reverse` defaults to `False`. > + > + >>> sl = SortedList('abcdefghij') > + >>> it = sl.irange('c', 'f') > + >>> list(it) > + ['c', 'd', 'e', 'f'] > + > + :param minimum: minimum value to start iterating > + :param maximum: maximum value to stop iterating > + :param inclusive: pair of booleans > + :param bool reverse: yield values in reverse order > + :return: iterator > + > """ > _maxes = self._maxes > > @@ -979,286 +1150,263 @@ class SortedList(MutableSequence): > > return self._islice(min_pos, min_idx, max_pos, max_idx, reverse) > > + > def __len__(self): > - """Return the number of elements in the list.""" > - return self._len > + """Return the size of the sorted list. > + > + ``sl.__len__()`` <==> ``len(sl)`` > + > + :return: size of sorted list > > - def bisect_left(self, val): > """ > - Similar to the *bisect* module in the standard library, this returns an > - appropriate index to insert *val*. If *val* is already present, the > - insertion point will be before (to the left of) any existing entries. > + return self._len > + > + > + def bisect_left(self, value): > + """Return an index to insert `value` in the sorted list. > + > + If the `value` is already present, the insertion point will be before > + (to the left of) any existing values. > + > + Similar to the `bisect` module in the standard library. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> sl = SortedList([10, 11, 12, 13, 14]) > + >>> sl.bisect_left(12) > + 2 > + > + :param value: insertion index of value in sorted list > + :return: index > + > """ > _maxes = self._maxes > > if not _maxes: > return 0 > > - pos = bisect_left(_maxes, val) > + pos = bisect_left(_maxes, value) > > if pos == len(_maxes): > return self._len > > - idx = bisect_left(self._lists[pos], val) > - > + idx = bisect_left(self._lists[pos], value) > return self._loc(pos, idx) > > - def bisect_right(self, val): > - """ > - Same as *bisect_left*, but if *val* is already present, the insertion > - point will be after (to the right of) any existing entries. > + > + def bisect_right(self, value): > + """Return an index to insert `value` in the sorted list. > + > + Similar to `bisect_left`, but if `value` is already present, the > + insertion point will be after (to the right of) any existing values. > + > + Similar to the `bisect` module in the standard library. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> sl = SortedList([10, 11, 12, 13, 14]) > + >>> sl.bisect_right(12) > + 3 > + > + :param value: insertion index of value in sorted list > + :return: index > + > """ > _maxes = self._maxes > > if not _maxes: > return 0 > > - pos = bisect_right(_maxes, val) > + pos = bisect_right(_maxes, value) > > if pos == len(_maxes): > return self._len > > - idx = bisect_right(self._lists[pos], val) > - > + idx = bisect_right(self._lists[pos], value) > return self._loc(pos, idx) > > bisect = bisect_right > _bisect_right = bisect_right > > - def count(self, val): > - """Return the number of occurrences of *val* in the list.""" > - # pylint: disable=arguments-differ > + > + def count(self, value): > + """Return number of occurrences of `value` in the sorted list. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> sl = SortedList([1, 2, 2, 3, 3, 3, 4, 4, 4, 4]) > + >>> sl.count(3) > + 3 > + > + :param value: value to count in sorted list > + :return: count > + > + """ > _maxes = self._maxes > > if not _maxes: > return 0 > > - pos_left = bisect_left(_maxes, val) > + pos_left = bisect_left(_maxes, value) > > if pos_left == len(_maxes): > return 0 > > _lists = self._lists > - idx_left = bisect_left(_lists[pos_left], val) > - pos_right = bisect_right(_maxes, val) > + idx_left = bisect_left(_lists[pos_left], value) > + pos_right = bisect_right(_maxes, value) > > if pos_right == len(_maxes): > return self._len - self._loc(pos_left, idx_left) > > - idx_right = bisect_right(_lists[pos_right], val) > + idx_right = bisect_right(_lists[pos_right], value) > > if pos_left == pos_right: > return idx_right - idx_left > > right = self._loc(pos_right, idx_right) > left = self._loc(pos_left, idx_left) > - > return right - left > > + > def copy(self): > - """Return a shallow copy of the sorted list.""" > - return self.__class__(self) > + """Return a shallow copy of the sorted list. > > - __copy__ = copy > + Runtime complexity: `O(n)` > + > + :return: new sorted list > > - def append(self, val): > - """ > - Append the element *val* to the list. Raises a ValueError if the *val* > - would violate the sort order. > """ > - # pylint: disable=arguments-differ > - _lists = self._lists > - _maxes = self._maxes > + return self.__class__(self) > > - if not _maxes: > - _maxes.append(val) > - _lists.append([val]) > - self._len = 1 > - return > + __copy__ = copy > > - pos = len(_lists) - 1 > > - if val < _lists[pos][-1]: > - msg = '{0!r} not in sort order at index {1}'.format(val, self._len) > - raise ValueError(msg) > + def append(self, value): > + """Raise not-implemented error. > > - _maxes[pos] = val > - _lists[pos].append(val) > - self._len += 1 > - self._expand(pos) > + Implemented to override `MutableSequence.append` which provides an > + erroneous default implementation. > > - def extend(self, values): > - """ > - Extend the list by appending all elements from the *values*. Raises a > - ValueError if the sort order would be violated. > - """ > - _lists = self._lists > - _maxes = self._maxes > - _load = self._load > - > - if not isinstance(values, list): > - values = list(values) > + :raises NotImplementedError: use ``sl.add(value)`` instead > > - if not values: > - return > - > - if any(values[pos - 1] > values[pos] > - for pos in range(1, len(values))): > - raise ValueError('given sequence not in sort order') > + """ > + raise NotImplementedError('use ``sl.add(value)`` instead') > > - offset = 0 > > - if _maxes: > - if values[0] < _lists[-1][-1]: > - msg = '{0!r} not in sort order at index {1}'.format(values[0], self._len) > - raise ValueError(msg) > + def extend(self, values): > + """Raise not-implemented error. > > - if len(_lists[-1]) < self._half: > - _lists[-1].extend(values[:_load]) > - _maxes[-1] = _lists[-1][-1] > - offset = _load > + Implemented to override `MutableSequence.extend` which provides an > + erroneous default implementation. > > - len_lists = len(_lists) > + :raises NotImplementedError: use ``sl.update(values)`` instead > > - for idx in range(offset, len(values), _load): > - _lists.append(values[idx:(idx + _load)]) > - _maxes.append(_lists[-1][-1]) > + """ > + raise NotImplementedError('use ``sl.update(values)`` instead') > > - _index = self._index > > - if len_lists == len(_lists): > - len_index = len(_index) > - if len_index > 0: > - len_values = len(values) > - child = len_index - 1 > - while child: > - _index[child] += len_values > - child = (child - 1) >> 1 > - _index[0] += len_values > - else: > - del _index[:] > + def insert(self, index, value): > + """Raise not-implemented error. > > - self._len += len(values) > + :raises NotImplementedError: use ``sl.add(value)`` instead > > - def insert(self, idx, val): > - """ > - Insert the element *val* into the list at *idx*. Raises a ValueError if > - the *val* at *idx* would violate the sort order. > """ > - # pylint: disable=arguments-differ > - _len = self._len > - _lists = self._lists > - _maxes = self._maxes > + raise NotImplementedError('use ``sl.add(value)`` instead') > > - if idx < 0: > - idx += _len > - if idx < 0: > - idx = 0 > - if idx > _len: > - idx = _len > > - if not _maxes: > - # The idx must be zero by the inequalities above. > - _maxes.append(val) > - _lists.append([val]) > - self._len = 1 > - return > + def pop(self, index=-1): > + """Remove and return value at `index` in sorted list. > > - if not idx: > - if val > _lists[0][0]: > - msg = '{0!r} not in sort order at index {1}'.format(val, 0) > - raise ValueError(msg) > - else: > - _lists[0].insert(0, val) > - self._expand(0) > - self._len += 1 > - return > + Raise :exc:`IndexError` if the sorted list is empty or index is out of > + range. > > - if idx == _len: > - pos = len(_lists) - 1 > - if _lists[pos][-1] > val: > - msg = '{0!r} not in sort order at index {1}'.format(val, _len) > - raise ValueError(msg) > - else: > - _lists[pos].append(val) > - _maxes[pos] = _lists[pos][-1] > - self._expand(pos) > - self._len += 1 > - return > + Negative indices are supported. > > - pos, idx = self._pos(idx) > - idx_before = idx - 1 > - if idx_before < 0: > - pos_before = pos - 1 > - idx_before = len(_lists[pos_before]) - 1 > - else: > - pos_before = pos > + Runtime complexity: `O(log(n))` -- approximate. > > - before = _lists[pos_before][idx_before] > - if before <= val <= _lists[pos][idx]: > - _lists[pos].insert(idx, val) > - self._expand(pos) > - self._len += 1 > - else: > - msg = '{0!r} not in sort order at index {1}'.format(val, idx) > - raise ValueError(msg) > + >>> sl = SortedList('abcde') > + >>> sl.pop() > + 'e' > + >>> sl.pop(2) > + 'c' > + >>> sl > + SortedList(['a', 'b', 'd']) > + > + :param int index: index of value (default -1) > + :return: value > + :raises IndexError: if index is out of range > > - def pop(self, idx=-1): > - """ > - Remove and return item at *idx* (default last). Raises IndexError if > - list is empty or index is out of range. Negative indices are supported, > - as for slice indices. > """ > - # pylint: disable=arguments-differ > if not self._len: > raise IndexError('pop index out of range') > > _lists = self._lists > > - if idx == 0: > + if index == 0: > val = _lists[0][0] > self._delete(0, 0) > return val > > - if idx == -1: > + if index == -1: > pos = len(_lists) - 1 > loc = len(_lists[pos]) - 1 > val = _lists[pos][loc] > self._delete(pos, loc) > return val > > - if 0 <= idx < len(_lists[0]): > - val = _lists[0][idx] > - self._delete(0, idx) > + if 0 <= index < len(_lists[0]): > + val = _lists[0][index] > + self._delete(0, index) > return val > > len_last = len(_lists[-1]) > > - if -len_last < idx < 0: > + if -len_last < index < 0: > pos = len(_lists) - 1 > - loc = len_last + idx > + loc = len_last + index > val = _lists[pos][loc] > self._delete(pos, loc) > return val > > - pos, idx = self._pos(idx) > + pos, idx = self._pos(index) > val = _lists[pos][idx] > self._delete(pos, idx) > - > return val > > - def index(self, val, start=None, stop=None): > - """ > - Return the smallest *k* such that L[k] == val and i <= k < j`. Raises > - ValueError if *val* is not present. *stop* defaults to the end of the > - list. *start* defaults to the beginning. Negative indices are supported, > - as for slice indices. > + > + def index(self, value, start=None, stop=None): > + """Return first index of value in sorted list. > + > + Raise ValueError if `value` is not present. > + > + Index must be between `start` and `stop` for the `value` to be > + considered present. The default value, None, for `start` and `stop` > + indicate the beginning and end of the sorted list. > + > + Negative indices are supported. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> sl = SortedList('abcde') > + >>> sl.index('d') > + 3 > + >>> sl.index('z') > + Traceback (most recent call last): > + ... > + ValueError: 'z' is not in list > + > + :param value: value in sorted list > + :param int start: start index (default None, start of sorted list) > + :param int stop: stop index (default None, end of sorted list) > + :return: index of value > + :raises ValueError: if value is not present > + > """ > - # pylint: disable=arguments-differ > _len = self._len > > if not _len: > - raise ValueError('{0!r} is not in list'.format(val)) > + raise ValueError('{0!r} is not in list'.format(value)) > > if start is None: > start = 0 > @@ -1275,19 +1423,19 @@ class SortedList(MutableSequence): > stop = _len > > if stop <= start: > - raise ValueError('{0!r} is not in list'.format(val)) > + raise ValueError('{0!r} is not in list'.format(value)) > > _maxes = self._maxes > - pos_left = bisect_left(_maxes, val) > + pos_left = bisect_left(_maxes, value) > > if pos_left == len(_maxes): > - raise ValueError('{0!r} is not in list'.format(val)) > + raise ValueError('{0!r} is not in list'.format(value)) > > _lists = self._lists > - idx_left = bisect_left(_lists[pos_left], val) > + idx_left = bisect_left(_lists[pos_left], value) > > - if _lists[pos_left][idx_left] != val: > - raise ValueError('{0!r} is not in list'.format(val)) > + if _lists[pos_left][idx_left] != value: > + raise ValueError('{0!r} is not in list'.format(value)) > > stop -= 1 > left = self._loc(pos_left, idx_left) > @@ -1296,153 +1444,220 @@ class SortedList(MutableSequence): > if left <= stop: > return left > else: > - right = self._bisect_right(val) - 1 > + right = self._bisect_right(value) - 1 > > if start <= right: > return start > > - raise ValueError('{0!r} is not in list'.format(val)) > + raise ValueError('{0!r} is not in list'.format(value)) > + > + > + def __add__(self, other): > + """Return new sorted list containing all values in both sequences. > + > + ``sl.__add__(other)`` <==> ``sl + other`` > + > + Values in `other` do not need to be in sorted order. > + > + Runtime complexity: `O(n*log(n))` > + > + >>> sl1 = SortedList('bat') > + >>> sl2 = SortedList('cat') > + >>> sl1 + sl2 > + SortedList(['a', 'a', 'b', 'c', 't', 't']) > + > + :param other: other iterable > + :return: new sorted list > > - def __add__(self, that): > - """ > - Return a new sorted list containing all the elements in *self* and > - *that*. Elements in *that* do not need to be properly ordered with > - respect to *self*. > """ > values = reduce(iadd, self._lists, []) > - values.extend(that) > + values.extend(other) > return self.__class__(values) > > - def __iadd__(self, that): > - """ > - Update *self* to include all values in *that*. Elements in *that* do not > - need to be properly ordered with respect to *self*. > + __radd__ = __add__ > + > + > + def __iadd__(self, other): > + """Update sorted list with values from `other`. > + > + ``sl.__iadd__(other)`` <==> ``sl += other`` > + > + Values in `other` do not need to be in sorted order. > + > + Runtime complexity: `O(k*log(n))` -- approximate. > + > + >>> sl = SortedList('bat') > + >>> sl += 'cat' > + >>> sl > + SortedList(['a', 'a', 'b', 'c', 't', 't']) > + > + :param other: other iterable > + :return: existing sorted list > + > """ > - self._update(that) > + self._update(other) > return self > > - def __mul__(self, that): > - """ > - Return a new sorted list containing *that* shallow copies of each item > - in SortedList. > + > + def __mul__(self, num): > + """Return new sorted list with `num` shallow copies of values. > + > + ``sl.__mul__(num)`` <==> ``sl * num`` > + > + Runtime complexity: `O(n*log(n))` > + > + >>> sl = SortedList('abc') > + >>> sl * 3 > + SortedList(['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'c']) > + > + :param int num: count of shallow copies > + :return: new sorted list > + > """ > - values = reduce(iadd, self._lists, []) * that > + values = reduce(iadd, self._lists, []) * num > return self.__class__(values) > > - def __imul__(self, that): > - """ > - Increase the length of the list by appending *that* shallow copies of > - each item. > + __rmul__ = __mul__ > + > + > + def __imul__(self, num): > + """Update the sorted list with `num` shallow copies of values. > + > + ``sl.__imul__(num)`` <==> ``sl *= num`` > + > + Runtime complexity: `O(n*log(n))` > + > + >>> sl = SortedList('abc') > + >>> sl *= 3 > + >>> sl > + SortedList(['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'c']) > + > + :param int num: count of shallow copies > + :return: existing sorted list > + > """ > - values = reduce(iadd, self._lists, []) * that > + values = reduce(iadd, self._lists, []) * num > self._clear() > self._update(values) > return self > > - def _make_cmp(self, seq_op, doc): > + > + def __make_cmp(seq_op, symbol, doc): > "Make comparator method." > - def comparer(self, that): > + def comparer(self, other): > "Compare method for sorted list and sequence." > - # pylint: disable=protected-access > - if not isinstance(that, Sequence): > + if not isinstance(other, Sequence): > return NotImplemented > > self_len = self._len > - len_that = len(that) > + len_other = len(other) > > - if self_len != len_that: > - if seq_op is op.eq: > + if self_len != len_other: > + if seq_op is eq: > return False > - if seq_op is op.ne: > + if seq_op is ne: > return True > > - for alpha, beta in zip(self, that): > + for alpha, beta in zip(self, other): > if alpha != beta: > return seq_op(alpha, beta) > > - return seq_op(self_len, len_that) > + return seq_op(self_len, len_other) > + > + seq_op_name = seq_op.__name__ > + comparer.__name__ = '__{0}__'.format(seq_op_name) > + doc_str = """Return true if and only if sorted list is {0} `other`. > + > + ``sl.__{1}__(other)`` <==> ``sl {2} other`` > + > + Comparisons use lexicographical order as with sequences. > + > + Runtime complexity: `O(n)` > > - comparer.__name__ = '__{0}__'.format(seq_op.__name__) > - doc_str = 'Return `True` if and only if Sequence is {0} `that`.' > - comparer.__doc__ = doc_str.format(doc) > + :param other: `other` sequence > + :return: true if sorted list is {0} `other` > > + """ > + comparer.__doc__ = dedent(doc_str.format(doc, seq_op_name, symbol)) > return comparer > > - __eq__ = _make_cmp(None, op.eq, 'equal to') > - __ne__ = _make_cmp(None, op.ne, 'not equal to') > - __lt__ = _make_cmp(None, op.lt, 'less than') > - __gt__ = _make_cmp(None, op.gt, 'greater than') > - __le__ = _make_cmp(None, op.le, 'less than or equal to') > - __ge__ = _make_cmp(None, op.ge, 'greater than or equal to') > > - @recursive_repr > - def __repr__(self): > - """Return string representation of sequence.""" > - return '{0}({1!r})'.format(type(self).__name__, list(self)) > + __eq__ = __make_cmp(eq, '==', 'equal to') > + __ne__ = __make_cmp(ne, '!=', 'not equal to') > + __lt__ = __make_cmp(lt, '<', 'less than') > + __gt__ = __make_cmp(gt, '>', 'greater than') > + __le__ = __make_cmp(le, '<=', 'less than or equal to') > + __ge__ = __make_cmp(ge, '>=', 'greater than or equal to') > + __make_cmp = staticmethod(__make_cmp) > > - def _check(self): > - try: > - # Check load parameters. > > - assert self._load >= 4 > - assert self._half == (self._load >> 1) > - assert self._dual == (self._load << 1) > + def __reduce__(self): > + values = reduce(iadd, self._lists, []) > + return (type(self), (values,)) > > - # Check empty sorted list case. > > - if self._maxes == []: > - assert self._lists == [] > - return > + @recursive_repr() > + def __repr__(self): > + """Return string representation of sorted list. > > - assert self._maxes and self._lists > + ``sl.__repr__()`` <==> ``repr(sl)`` > > - # Check all sublists are sorted. > + :return: string representation > > - assert all(sublist[pos - 1] <= sublist[pos] > - for sublist in self._lists > - for pos in range(1, len(sublist))) > + """ > + return '{0}({1!r})'.format(type(self).__name__, list(self)) > > - # Check beginning/end of sublists are sorted. > > - for pos in range(1, len(self._lists)): > - assert self._lists[pos - 1][-1] <= self._lists[pos][0] > + def _check(self): > + """Check invariants of sorted list. > > - # Check length of _maxes and _lists match. > + Runtime complexity: `O(n)` > > + """ > + try: > + assert self._load >= 4 > assert len(self._maxes) == len(self._lists) > + assert self._len == sum(len(sublist) for sublist in self._lists) > > - # Check _maxes is a map of _lists. > + # Check all sublists are sorted. > > - assert all(self._maxes[pos] == self._lists[pos][-1] > - for pos in range(len(self._maxes))) > + for sublist in self._lists: > + for pos in range(1, len(sublist)): > + assert sublist[pos - 1] <= sublist[pos] > > - # Check load level is less than _dual. > + # Check beginning/end of sublists are sorted. > > - assert all(len(sublist) <= self._dual for sublist in self._lists) > + for pos in range(1, len(self._lists)): > + assert self._lists[pos - 1][-1] <= self._lists[pos][0] > > - # Check load level is greater than _half for all > - # but the last sublist. > + # Check _maxes index is the last value of each sublist. > > - assert all(len(self._lists[pos]) >= self._half > - for pos in range(0, len(self._lists) - 1)) > + for pos in range(len(self._maxes)): > + assert self._maxes[pos] == self._lists[pos][-1] > > - # Check length. > + # Check sublist lengths are less than double load-factor. > > - assert self._len == sum(len(sublist) for sublist in self._lists) > + double = self._load << 1 > + assert all(len(sublist) <= double for sublist in self._lists) > + > + # Check sublist lengths are greater than half load-factor for all > + # but the last sublist. > > - # Check index. > + half = self._load >> 1 > + for pos in range(0, len(self._lists) - 1): > + assert len(self._lists[pos]) >= half > > if self._index: > - assert len(self._index) == self._offset + len(self._lists) > assert self._len == self._index[0] > + assert len(self._index) == self._offset + len(self._lists) > + > + # Check index leaf nodes equal length of sublists. > > - def test_offset_pos(pos): > - "Test positional indexing offset." > - from_index = self._index[self._offset + pos] > - return from_index == len(self._lists[pos]) > + for pos in range(len(self._lists)): > + leaf = self._index[self._offset + pos] > + assert leaf == len(self._lists[pos]) > > - assert all(test_offset_pos(pos) > - for pos in range(len(self._lists))) > + # Check index branch nodes are the sum of their children. > > for pos in range(self._offset): > child = (pos << 1) + 1 > @@ -1452,16 +1667,11 @@ class SortedList(MutableSequence): > assert self._index[pos] == self._index[child] > else: > child_sum = self._index[child] + self._index[child + 1] > - assert self._index[pos] == child_sum > - > + assert child_sum == self._index[pos] > except: > - import sys > - import traceback > - > traceback.print_exc(file=sys.stdout) > - > print('len', self._len) > - print('load', self._load, self._half, self._dual) > + print('load', self._load) > print('offset', self._offset) > print('len_index', len(self._index)) > print('index', self._index) > @@ -1469,55 +1679,92 @@ class SortedList(MutableSequence): > print('maxes', self._maxes) > print('len_lists', len(self._lists)) > print('lists', self._lists) > - > raise > > + > def identity(value): > "Identity function." > return value > > -class SortedListWithKey(SortedList): > - """ > - SortedListWithKey provides most of the same methods as a list but keeps > - the items in sorted order. > + > +class SortedKeyList(SortedList): > + """Sorted-key list is a subtype of sorted list. > + > + The sorted-key list maintains values in comparison order based on the > + result of a key function applied to every value. > + > + All the same methods that are available in :class:`SortedList` are also > + available in :class:`SortedKeyList`. > + > + Additional methods provided: > + > + * :attr:`SortedKeyList.key` > + * :func:`SortedKeyList.bisect_key_left` > + * :func:`SortedKeyList.bisect_key_right` > + * :func:`SortedKeyList.irange_key` > + > + Some examples below use: > + > + >>> from operator import neg > + >>> neg > + <built-in function neg> > + >>> neg(1) > + -1 > + > """ > - # pylint: disable=too-many-ancestors,abstract-method > def __init__(self, iterable=None, key=identity): > - """SortedListWithKey provides most of the same methods as list but keeps the > - items in sorted order. > + """Initialize sorted-key list instance. > > - An optional *iterable* provides an initial series of items to populate > - the SortedListWithKey. > + Optional `iterable` argument provides an initial iterable of values to > + initialize the sorted-key list. > > - An optional *key* argument defines a callable that, like the `key` > + Optional `key` argument defines a callable that, like the `key` > argument to Python's `sorted` function, extracts a comparison key from > - each element. The default is the identity function. > + each value. The default is the identity function. > + > + Runtime complexity: `O(n*log(n))` > + > + >>> from operator import neg > + >>> skl = SortedKeyList(key=neg) > + >>> skl > + SortedKeyList([], key=<built-in function neg>) > + >>> skl = SortedKeyList([3, 1, 2], key=neg) > + >>> skl > + SortedKeyList([3, 2, 1], key=<built-in function neg>) > + > + :param iterable: initial values (optional) > + :param key: function used to extract comparison key (optional) > + > """ > - # pylint: disable=super-init-not-called > + self._key = key > self._len = 0 > + self._load = self.DEFAULT_LOAD_FACTOR > self._lists = [] > self._keys = [] > self._maxes = [] > self._index = [] > - self._key = key > - self._load = LOAD > - self._half = LOAD >> 1 > - self._dual = LOAD << 1 > self._offset = 0 > > if iterable is not None: > self._update(iterable) > > + > def __new__(cls, iterable=None, key=identity): > return object.__new__(cls) > > + > @property > def key(self): > - """Key function used to extract comparison key for sorting.""" > + "Function used to extract comparison key from values." > return self._key > > + > def clear(self): > - """Remove all the elements from the list.""" > + """Remove all values from sorted-key list. > + > + Runtime complexity: `O(n)` > + > + """ > self._len = 0 > del self._lists[:] > del self._keys[:] > @@ -1526,48 +1773,65 @@ class SortedListWithKey(SortedList): > > _clear = clear > > - def add(self, val): > - """Add the element *val* to the list.""" > + > + def add(self, value): > + """Add `value` to sorted-key list. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> from operator import neg > + >>> skl = SortedKeyList(key=neg) > + >>> skl.add(3) > + >>> skl.add(1) > + >>> skl.add(2) > + >>> skl > + SortedKeyList([3, 2, 1], key=<built-in function neg>) > + > + :param value: value to add to sorted-key list > + > + """ > _lists = self._lists > _keys = self._keys > _maxes = self._maxes > > - key = self._key(val) > + key = self._key(value) > > if _maxes: > pos = bisect_right(_maxes, key) > > if pos == len(_maxes): > pos -= 1 > - _lists[pos].append(val) > + _lists[pos].append(value) > _keys[pos].append(key) > _maxes[pos] = key > else: > idx = bisect_right(_keys[pos], key) > - _lists[pos].insert(idx, val) > + _lists[pos].insert(idx, value) > _keys[pos].insert(idx, key) > > self._expand(pos) > else: > - _lists.append([val]) > + _lists.append([value]) > _keys.append([key]) > _maxes.append(key) > > self._len += 1 > > + > def _expand(self, pos): > - """Splits sublists that are more than double the load level. > + """Split sublists with length greater than double the load-factor. > > Updates the index when the sublist length is less than double the load > level. This requires incrementing the nodes in a traversal from the > - leaf node to the root. For an example traversal see self._loc. > + leaf node to the root. For an example traversal see > + ``SortedList._loc``. > > """ > _lists = self._lists > _keys = self._keys > _index = self._index > > - if len(_keys[pos]) > self._dual: > + if len(_keys[pos]) > (self._load << 1): > _maxes = self._maxes > _load = self._load > > @@ -1592,8 +1856,21 @@ class SortedListWithKey(SortedList): > child = (child - 1) >> 1 > _index[0] += 1 > > + > def update(self, iterable): > - """Update the list by adding all elements from *iterable*.""" > + """Update sorted-key list by adding all values from `iterable`. > + > + Runtime complexity: `O(k*log(n))` -- approximate. > + > + >>> from operator import neg > + >>> skl = SortedKeyList(key=neg) > + >>> skl.update([3, 1, 2]) > + >>> skl > + SortedKeyList([3, 2, 1], key=<built-in function neg>) > + > + :param iterable: iterable of values to add > + > + """ > _lists = self._lists > _keys = self._keys > _maxes = self._maxes > @@ -1601,7 +1878,8 @@ class SortedListWithKey(SortedList): > > if _maxes: > if len(values) * 4 >= self._len: > - values.extend(chain.from_iterable(_lists)) > + _lists.append(values) > + values = reduce(iadd, _lists, []) > values.sort(key=self._key) > self._clear() > else: > @@ -1620,14 +1898,29 @@ class SortedListWithKey(SortedList): > > _update = update > > - def __contains__(self, val): > - """Return True if and only if *val* is an element in the list.""" > + > + def __contains__(self, value): > + """Return true if `value` is an element of the sorted-key list. > + > + ``skl.__contains__(value)`` <==> ``value in skl`` > + > + Runtime complexity: `O(log(n))` > + > + >>> from operator import neg > + >>> skl = SortedKeyList([1, 2, 3, 4, 5], key=neg) > + >>> 3 in skl > + True > + > + :param value: search for value in sorted-key list > + :return: true if `value` in sorted-key list > + > + """ > _maxes = self._maxes > > if not _maxes: > return False > > - key = self._key(val) > + key = self._key(value) > pos = bisect_left(_maxes, key) > > if pos == len(_maxes): > @@ -1644,7 +1937,7 @@ class SortedListWithKey(SortedList): > while True: > if _keys[pos][idx] != key: > return False > - if _lists[pos][idx] == val: > + if _lists[pos][idx] == value: > return True > idx += 1 > if idx == len_sublist: > @@ -1654,18 +1947,30 @@ class SortedListWithKey(SortedList): > len_sublist = len(_keys[pos]) > idx = 0 > > - def discard(self, val): > - """ > - Remove the first occurrence of *val*. > > - If *val* is not a member, does nothing. > + def discard(self, value): > + """Remove `value` from sorted-key list if it is a member. > + > + If `value` is not a member, do nothing. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> from operator import neg > + >>> skl = SortedKeyList([5, 4, 3, 2, 1], key=neg) > + >>> skl.discard(1) > + >>> skl.discard(0) > + >>> skl == [5, 4, 3, 2] > + True > + > + :param value: `value` to discard from sorted-key list > + > """ > _maxes = self._maxes > > if not _maxes: > return > > - key = self._key(val) > + key = self._key(value) > pos = bisect_left(_maxes, key) > > if pos == len(_maxes): > @@ -1680,7 +1985,7 @@ class SortedListWithKey(SortedList): > while True: > if _keys[pos][idx] != key: > return > - if _lists[pos][idx] == val: > + if _lists[pos][idx] == value: > self._delete(pos, idx) > return > idx += 1 > @@ -1691,22 +1996,38 @@ class SortedListWithKey(SortedList): > len_sublist = len(_keys[pos]) > idx = 0 > > - def remove(self, val): > - """ > - Remove first occurrence of *val*. > > - Raises ValueError if *val* is not present. > + def remove(self, value): > + """Remove `value` from sorted-key list; `value` must be a member. > + > + If `value` is not a member, raise ValueError. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> from operator import neg > + >>> skl = SortedKeyList([1, 2, 3, 4, 5], key=neg) > + >>> skl.remove(5) > + >>> skl == [4, 3, 2, 1] > + True > + >>> skl.remove(0) > + Traceback (most recent call last): > + ... > + ValueError: 0 not in list > + > + :param value: `value` to remove from sorted-key list > + :raises ValueError: if `value` is not in sorted-key list > + > """ > _maxes = self._maxes > > if not _maxes: > - raise ValueError('{0!r} not in list'.format(val)) > + raise ValueError('{0!r} not in list'.format(value)) > > - key = self._key(val) > + key = self._key(value) > pos = bisect_left(_maxes, key) > > if pos == len(_maxes): > - raise ValueError('{0!r} not in list'.format(val)) > + raise ValueError('{0!r} not in list'.format(value)) > > _lists = self._lists > _keys = self._keys > @@ -1716,27 +2037,32 @@ class SortedListWithKey(SortedList): > > while True: > if _keys[pos][idx] != key: > - raise ValueError('{0!r} not in list'.format(val)) > - if _lists[pos][idx] == val: > + raise ValueError('{0!r} not in list'.format(value)) > + if _lists[pos][idx] == value: > self._delete(pos, idx) > return > idx += 1 > if idx == len_sublist: > pos += 1 > if pos == len_keys: > - raise ValueError('{0!r} not in list'.format(val)) > + raise ValueError('{0!r} not in list'.format(value)) > len_sublist = len(_keys[pos]) > idx = 0 > > + > def _delete(self, pos, idx): > - """ > - Delete the item at the given (pos, idx). > + """Delete value at the given `(pos, idx)`. > > Combines lists that are less than half the load level. > > Updates the index when the sublist length is more than half the load > - level. This requires decrementing the nodes in a traversal from the leaf > - node to the root. For an example traversal see self._loc. > + level. This requires decrementing the nodes in a traversal from the > + leaf node to the root. For an example traversal see > + ``SortedList._loc``. > + > + :param int pos: lists index > + :param int idx: sublist index > + > """ > _lists = self._lists > _keys = self._keys > @@ -1751,8 +2077,7 @@ class SortedListWithKey(SortedList): > > len_keys_pos = len(keys_pos) > > - if len_keys_pos > self._half: > - > + if len_keys_pos > (self._load >> 1): > _maxes[pos] = keys_pos[-1] > > if _index: > @@ -1761,9 +2086,7 @@ class SortedListWithKey(SortedList): > _index[child] -= 1 > child = (child - 1) >> 1 > _index[0] -= 1 > - > elif len(_keys) > 1: > - > if not pos: > pos += 1 > > @@ -1778,220 +2101,78 @@ class SortedListWithKey(SortedList): > del _index[:] > > self._expand(prev) > - > elif len_keys_pos: > - > _maxes[pos] = keys_pos[-1] > - > else: > - > del _lists[pos] > del _keys[pos] > del _maxes[pos] > del _index[:] > > - def _check_order(self, idx, key, val): > - # pylint: disable=arguments-differ > - _len = self._len > - _keys = self._keys > - > - pos, loc = self._pos(idx) > - > - if idx < 0: > - idx += _len > - > - # Check that the inserted value is not less than the > - # previous value. > - > - if idx > 0: > - idx_prev = loc - 1 > - pos_prev = pos > - > - if idx_prev < 0: > - pos_prev -= 1 > - idx_prev = len(_keys[pos_prev]) - 1 > - > - if _keys[pos_prev][idx_prev] > key: > - msg = '{0!r} not in sort order at index {1}'.format(val, idx) > - raise ValueError(msg) > - > - # Check that the inserted value is not greater than > - # the previous value. > - > - if idx < (_len - 1): > - idx_next = loc + 1 > - pos_next = pos > - > - if idx_next == len(_keys[pos_next]): > - pos_next += 1 > - idx_next = 0 > - > - if _keys[pos_next][idx_next] < key: > - msg = '{0!r} not in sort order at index {1}'.format(val, idx) > - raise ValueError(msg) > - > - def __setitem__(self, index, value): > - """Replace the item at position *index* with *value*. > - > - Supports slice notation. Raises a :exc:`ValueError` if the sort order > - would be violated. When used with a slice and iterable, the > - :exc:`ValueError` is raised before the list is mutated if the sort > - order would be violated by the operation. > - > - """ > - # pylint: disable=too-many-locals > - _lists = self._lists > - _keys = self._keys > - _maxes = self._maxes > - _check_order = self._check_order > - _pos = self._pos > - > - if isinstance(index, slice): > - _len = self._len > - start, stop, step = index.indices(_len) > - indices = range(start, stop, step) > - > - # Copy value to avoid aliasing issues with self and cases where an > - # iterator is given. > - > - values = tuple(value) > - > - if step != 1: > - if len(values) != len(indices): > - raise ValueError( > - 'attempt to assign sequence of size %s' > - ' to extended slice of size %s' > - % (len(values), len(indices))) > - > - # Keep a log of values that are set so that we can > - # roll back changes if ordering is violated. > - > - log = [] > - _append = log.append > - > - for idx, val in zip(indices, values): > - pos, loc = _pos(idx) > - key = self._key(val) > - _append((idx, _keys[pos][loc], key, _lists[pos][loc], val)) > - _keys[pos][loc] = key > - _lists[pos][loc] = val > - if len(_keys[pos]) == (loc + 1): > - _maxes[pos] = key > - > - try: > - # Validate ordering of new values. > - > - for idx, oldkey, newkey, oldval, newval in log: > - _check_order(idx, newkey, newval) > - > - except ValueError: > - > - # Roll back changes from log. > - > - for idx, oldkey, newkey, oldval, newval in log: > - pos, loc = _pos(idx) > - _keys[pos][loc] = oldkey > - _lists[pos][loc] = oldval > - if len(_keys[pos]) == (loc + 1): > - _maxes[pos] = oldkey > - > - raise > - else: > - if start == 0 and stop == self._len: > - self._clear() > - return self._update(values) > - > - if stop < start: > - # When calculating indices, stop may be less than start. > - # For example: ...[5:3:1] results in slice(5, 3, 1) which > - # is a valid but not useful stop index. > - stop = start > - > - if values: > - > - # Check that given values are ordered properly. > - > - keys = tuple(map(self._key, values)) > - alphas = iter(keys) > - betas = iter(keys) > - next(betas) > - pairs = zip(alphas, betas) > - > - if not all(alpha <= beta for alpha, beta in pairs): > - raise ValueError('given values not in sort order') > - > - # Check ordering in context of sorted list. > - > - if start: > - pos, loc = _pos(start - 1) > - if _keys[pos][loc] > keys[0]: > - msg = '{0!r} not in sort order at index {1}'.format( > - values[0], start) > - raise ValueError(msg) > - > - if stop != _len: > - pos, loc = _pos(stop) > - if _keys[pos][loc] < keys[-1]: > - msg = '{0!r} not in sort order at index {1}'.format( > - values[-1], stop) > - raise ValueError(msg) > - > - # Delete the existing values. > - > - self._delitem(index) > - > - # Insert the new values. > - > - _insert = self.insert > - for idx, val in enumerate(values): > - _insert(start + idx, val) > - else: > - pos, loc = _pos(index) > - key = self._key(value) > - _check_order(index, key, value) > - _lists[pos][loc] = value > - _keys[pos][loc] = key > - if len(_lists[pos]) == (loc + 1): > - _maxes[pos] = key > > def irange(self, minimum=None, maximum=None, inclusive=(True, True), > reverse=False): > - """ > - Create an iterator of values between `minimum` and `maximum`. > - > - `inclusive` is a pair of booleans that indicates whether the minimum > - and maximum ought to be included in the range, respectively. The > - default is (True, True) such that the range is inclusive of both > - minimum and maximum. > + """Create an iterator of values between `minimum` and `maximum`. > > Both `minimum` and `maximum` default to `None` which is automatically > - inclusive of the start and end of the list, respectively. > + inclusive of the beginning and end of the sorted-key list. > + > + The argument `inclusive` is a pair of booleans that indicates whether > + the minimum and maximum ought to be included in the range, > + respectively. The default is ``(True, True)`` such that the range is > + inclusive of both minimum and maximum. > > When `reverse` is `True` the values are yielded from the iterator in > reverse order; `reverse` defaults to `False`. > + > + >>> from operator import neg > + >>> skl = SortedKeyList([11, 12, 13, 14, 15], key=neg) > + >>> it = skl.irange(14.5, 11.5) > + >>> list(it) > + [14, 13, 12] > + > + :param minimum: minimum value to start iterating > + :param maximum: maximum value to stop iterating > + :param inclusive: pair of booleans > + :param bool reverse: yield values in reverse order > + :return: iterator > + > """ > - minimum = self._key(minimum) if minimum is not None else None > - maximum = self._key(maximum) if maximum is not None else None > + min_key = self._key(minimum) if minimum is not None else None > + max_key = self._key(maximum) if maximum is not None else None > return self._irange_key( > - min_key=minimum, max_key=maximum, > + min_key=min_key, max_key=max_key, > inclusive=inclusive, reverse=reverse, > ) > > + > def irange_key(self, min_key=None, max_key=None, inclusive=(True, True), > reverse=False): > - """ > - Create an iterator of values between `min_key` and `max_key`. > - > - `inclusive` is a pair of booleans that indicates whether the min_key > - and max_key ought to be included in the range, respectively. The > - default is (True, True) such that the range is inclusive of both > - `min_key` and `max_key`. > + """Create an iterator of values between `min_key` and `max_key`. > > Both `min_key` and `max_key` default to `None` which is automatically > - inclusive of the start and end of the list, respectively. > + inclusive of the beginning and end of the sorted-key list. > + > + The argument `inclusive` is a pair of booleans that indicates whether > + the minimum and maximum ought to be included in the range, > + respectively. The default is ``(True, True)`` such that the range is > + inclusive of both minimum and maximum. > > When `reverse` is `True` the values are yielded from the iterator in > reverse order; `reverse` defaults to `False`. > + > + >>> from operator import neg > + >>> skl = SortedKeyList([11, 12, 13, 14, 15], key=neg) > + >>> it = skl.irange_key(-14, -12) > + >>> list(it) > + [14, 13, 12] > + > + :param min_key: minimum key to start iterating > + :param max_key: maximum key to stop iterating > + :param inclusive: pair of booleans > + :param bool reverse: yield values in reverse order > + :return: iterator > + > """ > _maxes = self._maxes > > @@ -2050,29 +2231,71 @@ class SortedListWithKey(SortedList): > > _irange_key = irange_key > > - def bisect_left(self, val): > - """ > - Similar to the *bisect* module in the standard library, this returns an > - appropriate index to insert *val*. If *val* is already present, the > - insertion point will be before (to the left of) any existing entries. > - """ > - return self._bisect_key_left(self._key(val)) > > - def bisect_right(self, val): > + def bisect_left(self, value): > + """Return an index to insert `value` in the sorted-key list. > + > + If the `value` is already present, the insertion point will be before > + (to the left of) any existing values. > + > + Similar to the `bisect` module in the standard library. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> from operator import neg > + >>> skl = SortedKeyList([5, 4, 3, 2, 1], key=neg) > + >>> skl.bisect_left(1) > + 4 > + > + :param value: insertion index of value in sorted-key list > + :return: index > + > """ > - Same as *bisect_left*, but if *val* is already present, the insertion > - point will be after (to the right of) any existing entries. > + return self._bisect_key_left(self._key(value)) > + > + > + def bisect_right(self, value): > + """Return an index to insert `value` in the sorted-key list. > + > + Similar to `bisect_left`, but if `value` is already present, the > + insertion point will be after (to the right of) any existing values. > + > + Similar to the `bisect` module in the standard library. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> from operator import neg > + >>> skl = SortedList([5, 4, 3, 2, 1], key=neg) > + >>> skl.bisect_right(1) > + 5 > + > + :param value: insertion index of value in sorted-key list > + :return: index > + > """ > - return self._bisect_key_right(self._key(val)) > + return self._bisect_key_right(self._key(value)) > > bisect = bisect_right > > + > def bisect_key_left(self, key): > - """ > - Similar to the *bisect* module in the standard library, this returns an > - appropriate index to insert a value with a given *key*. If values with > - *key* are already present, the insertion point will be before (to the > - left of) any existing entries. > + """Return an index to insert `key` in the sorted-key list. > + > + If the `key` is already present, the insertion point will be before (to > + the left of) any existing keys. > + > + Similar to the `bisect` module in the standard library. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> from operator import neg > + >>> skl = SortedKeyList([5, 4, 3, 2, 1], key=neg) > + >>> skl.bisect_key_left(-1) > + 4 > + > + :param key: insertion index of key in sorted-key list > + :return: index > + > """ > _maxes = self._maxes > > @@ -2090,10 +2313,25 @@ class SortedListWithKey(SortedList): > > _bisect_key_left = bisect_key_left > > + > def bisect_key_right(self, key): > - """ > - Same as *bisect_key_left*, but if *key* is already present, the insertion > - point will be after (to the right of) any existing entries. > + """Return an index to insert `key` in the sorted-key list. > + > + Similar to `bisect_key_left`, but if `key` is already present, the > + insertion point will be after (to the right of) any existing keys. > + > + Similar to the `bisect` module in the standard library. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> from operator import neg > + >>> skl = SortedList([5, 4, 3, 2, 1], key=neg) > + >>> skl.bisect_key_right(-1) > + 5 > + > + :param key: insertion index of key in sorted-key list > + :return: index > + > """ > _maxes = self._maxes > > @@ -2112,14 +2350,27 @@ class SortedListWithKey(SortedList): > bisect_key = bisect_key_right > _bisect_key_right = bisect_key_right > > - def count(self, val): > - """Return the number of occurrences of *val* in the list.""" > + > + def count(self, value): > + """Return number of occurrences of `value` in the sorted-key list. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> from operator import neg > + >>> skl = SortedKeyList([4, 4, 4, 4, 3, 3, 3, 2, 2, 1], key=neg) > + >>> skl.count(2) > + 2 > + > + :param value: value to count in sorted-key list > + :return: count > + > + """ > _maxes = self._maxes > > if not _maxes: > return 0 > > - key = self._key(val) > + key = self._key(value) > pos = bisect_left(_maxes, key) > > if pos == len(_maxes): > @@ -2135,7 +2386,7 @@ class SortedListWithKey(SortedList): > while True: > if _keys[pos][idx] != key: > return total > - if _lists[pos][idx] == val: > + if _lists[pos][idx] == value: > total += 1 > idx += 1 > if idx == len_sublist: > @@ -2145,176 +2396,53 @@ class SortedListWithKey(SortedList): > len_sublist = len(_keys[pos]) > idx = 0 > > - def copy(self): > - """Return a shallow copy of the sorted list.""" > - return self.__class__(self, key=self._key) > - > - __copy__ = copy > > - def append(self, val): > - """ > - Append the element *val* to the list. Raises a ValueError if the *val* > - would violate the sort order. > - """ > - # pylint: disable=arguments-differ > - _lists = self._lists > - _keys = self._keys > - _maxes = self._maxes > - key = self._key(val) > - > - if not _maxes: > - _maxes.append(key) > - _keys.append([key]) > - _lists.append([val]) > - self._len = 1 > - return > - > - pos = len(_keys) - 1 > + def copy(self): > + """Return a shallow copy of the sorted-key list. > > - if key < _keys[pos][-1]: > - msg = '{0!r} not in sort order at index {1}'.format(val, self._len) > - raise ValueError(msg) > + Runtime complexity: `O(n)` > > - _lists[pos].append(val) > - _keys[pos].append(key) > - _maxes[pos] = key > - self._len += 1 > - self._expand(pos) > + :return: new sorted-key list > > - def extend(self, values): > """ > - Extend the list by appending all elements from the *values*. Raises a > - ValueError if the sort order would be violated. > - """ > - _lists = self._lists > - _keys = self._keys > - _maxes = self._maxes > - _load = self._load > - > - if not isinstance(values, list): > - values = list(values) > - > - keys = list(map(self._key, values)) > + return self.__class__(self, key=self._key) > > - if any(keys[pos - 1] > keys[pos] > - for pos in range(1, len(keys))): > - raise ValueError('given sequence not in sort order') > + __copy__ = copy > > - offset = 0 > > - if _maxes: > - if keys[0] < _keys[-1][-1]: > - msg = '{0!r} not in sort order at index {1}'.format(values[0], self._len) > - raise ValueError(msg) > + def index(self, value, start=None, stop=None): > + """Return first index of value in sorted-key list. > > - if len(_keys[-1]) < self._half: > - _lists[-1].extend(values[:_load]) > - _keys[-1].extend(keys[:_load]) > - _maxes[-1] = _keys[-1][-1] > - offset = _load > + Raise ValueError if `value` is not present. > > - len_keys = len(_keys) > + Index must be between `start` and `stop` for the `value` to be > + considered present. The default value, None, for `start` and `stop` > + indicate the beginning and end of the sorted-key list. > > - for idx in range(offset, len(keys), _load): > - _lists.append(values[idx:(idx + _load)]) > - _keys.append(keys[idx:(idx + _load)]) > - _maxes.append(_keys[-1][-1]) > + Negative indices are supported. > > - _index = self._index > + Runtime complexity: `O(log(n))` -- approximate. > > - if len_keys == len(_keys): > - len_index = len(_index) > - if len_index > 0: > - len_values = len(values) > - child = len_index - 1 > - while child: > - _index[child] += len_values > - child = (child - 1) >> 1 > - _index[0] += len_values > - else: > - del _index[:] > + >>> from operator import neg > + >>> skl = SortedKeyList([5, 4, 3, 2, 1], key=neg) > + >>> skl.index(2) > + 3 > + >>> skl.index(0) > + Traceback (most recent call last): > + ... > + ValueError: 0 is not in list > > - self._len += len(values) > + :param value: value in sorted-key list > + :param int start: start index (default None, start of sorted-key list) > + :param int stop: stop index (default None, end of sorted-key list) > + :return: index of value > + :raises ValueError: if value is not present > > - def insert(self, idx, val): > - """ > - Insert the element *val* into the list at *idx*. Raises a ValueError if > - the *val* at *idx* would violate the sort order. > - """ > - _len = self._len > - _lists = self._lists > - _keys = self._keys > - _maxes = self._maxes > - > - if idx < 0: > - idx += _len > - if idx < 0: > - idx = 0 > - if idx > _len: > - idx = _len > - > - key = self._key(val) > - > - if not _maxes: > - self._len = 1 > - _lists.append([val]) > - _keys.append([key]) > - _maxes.append(key) > - return > - > - if not idx: > - if key > _keys[0][0]: > - msg = '{0!r} not in sort order at index {1}'.format(val, 0) > - raise ValueError(msg) > - else: > - self._len += 1 > - _lists[0].insert(0, val) > - _keys[0].insert(0, key) > - self._expand(0) > - return > - > - if idx == _len: > - pos = len(_keys) - 1 > - if _keys[pos][-1] > key: > - msg = '{0!r} not in sort order at index {1}'.format(val, _len) > - raise ValueError(msg) > - else: > - self._len += 1 > - _lists[pos].append(val) > - _keys[pos].append(key) > - _maxes[pos] = _keys[pos][-1] > - self._expand(pos) > - return > - > - pos, idx = self._pos(idx) > - idx_before = idx - 1 > - if idx_before < 0: > - pos_before = pos - 1 > - idx_before = len(_keys[pos_before]) - 1 > - else: > - pos_before = pos > - > - before = _keys[pos_before][idx_before] > - if before <= key <= _keys[pos][idx]: > - self._len += 1 > - _lists[pos].insert(idx, val) > - _keys[pos].insert(idx, key) > - self._expand(pos) > - else: > - msg = '{0!r} not in sort order at index {1}'.format(val, idx) > - raise ValueError(msg) > - > - def index(self, val, start=None, stop=None): > - """ > - Return the smallest *k* such that L[k] == val and i <= k < j`. Raises > - ValueError if *val* is not present. *stop* defaults to the end of the > - list. *start* defaults to the beginning. Negative indices are supported, > - as for slice indices. > """ > _len = self._len > > if not _len: > - raise ValueError('{0!r} is not in list'.format(val)) > + raise ValueError('{0!r} is not in list'.format(value)) > > if start is None: > start = 0 > @@ -2331,14 +2459,14 @@ class SortedListWithKey(SortedList): > stop = _len > > if stop <= start: > - raise ValueError('{0!r} is not in list'.format(val)) > + raise ValueError('{0!r} is not in list'.format(value)) > > _maxes = self._maxes > - key = self._key(val) > + key = self._key(value) > pos = bisect_left(_maxes, key) > > if pos == len(_maxes): > - raise ValueError('{0!r} is not in list'.format(val)) > + raise ValueError('{0!r} is not in list'.format(value)) > > stop -= 1 > _lists = self._lists > @@ -2349,8 +2477,8 @@ class SortedListWithKey(SortedList): > > while True: > if _keys[pos][idx] != key: > - raise ValueError('{0!r} is not in list'.format(val)) > - if _lists[pos][idx] == val: > + raise ValueError('{0!r} is not in list'.format(value)) > + if _lists[pos][idx] == value: > loc = self._loc(pos, idx) > if start <= loc <= stop: > return loc > @@ -2360,139 +2488,148 @@ class SortedListWithKey(SortedList): > if idx == len_sublist: > pos += 1 > if pos == len_keys: > - raise ValueError('{0!r} is not in list'.format(val)) > + raise ValueError('{0!r} is not in list'.format(value)) > len_sublist = len(_keys[pos]) > idx = 0 > > - raise ValueError('{0!r} is not in list'.format(val)) > + raise ValueError('{0!r} is not in list'.format(value)) > + > + > + def __add__(self, other): > + """Return new sorted-key list containing all values in both sequences. > + > + ``skl.__add__(other)`` <==> ``skl + other`` > + > + Values in `other` do not need to be in sorted-key order. > + > + Runtime complexity: `O(n*log(n))` > + > + >>> from operator import neg > + >>> skl1 = SortedKeyList([5, 4, 3], key=neg) > + >>> skl2 = SortedKeyList([2, 1, 0], key=neg) > + >>> skl1 + skl2 > + SortedKeyList([5, 4, 3, 2, 1, 0], key=<built-in function neg>) > + > + :param other: other iterable > + :return: new sorted-key list > > - def __add__(self, that): > - """ > - Return a new sorted list containing all the elements in *self* and > - *that*. Elements in *that* do not need to be properly ordered with > - respect to *self*. > """ > values = reduce(iadd, self._lists, []) > - values.extend(that) > + values.extend(other) > return self.__class__(values, key=self._key) > > - def __mul__(self, that): > - """ > - Return a new sorted list containing *that* shallow copies of each item > - in SortedListWithKey. > + __radd__ = __add__ > + > + > + def __mul__(self, num): > + """Return new sorted-key list with `num` shallow copies of values. > + > + ``skl.__mul__(num)`` <==> ``skl * num`` > + > + Runtime complexity: `O(n*log(n))` > + > + >>> from operator import neg > + >>> skl = SortedKeyList([3, 2, 1], key=neg) > + >>> skl * 2 > + SortedKeyList([3, 3, 2, 2, 1, 1], key=<built-in function neg>) > + > + :param int num: count of shallow copies > + :return: new sorted-key list > + > """ > - values = reduce(iadd, self._lists, []) * that > + values = reduce(iadd, self._lists, []) * num > return self.__class__(values, key=self._key) > > - def __imul__(self, that): > - """ > - Increase the length of the list by appending *that* shallow copies of > - each item. > - """ > - values = reduce(iadd, self._lists, []) * that > - self._clear() > - self._update(values) > - return self > > - @recursive_repr > + def __reduce__(self): > + values = reduce(iadd, self._lists, []) > + return (type(self), (values, self.key)) > + > + > + @recursive_repr() > def __repr__(self): > - """Return string representation of sequence.""" > - name = type(self).__name__ > - values = list(self) > - _key = self._key > - return '{0}({1!r}, key={2!r})'.format(name, values, _key) > + """Return string representation of sorted-key list. > > - def _check(self): > - try: > - # Check load parameters. > + ``skl.__repr__()`` <==> ``repr(skl)`` > > - assert self._load >= 4 > - assert self._half == (self._load >> 1) > - assert self._dual == (self._load << 1) > + :return: string representation > > - # Check empty sorted list case. > + """ > + type_name = type(self).__name__ > + return '{0}({1!r}, key={2!r})'.format(type_name, list(self), self._key) > > - if self._maxes == []: > - assert self._keys == [] > - assert self._lists == [] > - return > > - assert self._maxes and self._keys and self._lists > + def _check(self): > + """Check invariants of sorted-key list. > + > + Runtime complexity: `O(n)` > + > + """ > + try: > + assert self._load >= 4 > + assert len(self._maxes) == len(self._lists) == len(self._keys) > + assert self._len == sum(len(sublist) for sublist in self._lists) > > # Check all sublists are sorted. > > - assert all(sublist[pos - 1] <= sublist[pos] > - for sublist in self._keys > - for pos in range(1, len(sublist))) > + for sublist in self._keys: > + for pos in range(1, len(sublist)): > + assert sublist[pos - 1] <= sublist[pos] > > # Check beginning/end of sublists are sorted. > > for pos in range(1, len(self._keys)): > assert self._keys[pos - 1][-1] <= self._keys[pos][0] > > - # Check length of _maxes and _lists match. > - > - assert len(self._maxes) == len(self._lists) == len(self._keys) > - > # Check _keys matches _key mapped to _lists. > > - assert all(len(val_list) == len(key_list) > - for val_list, key_list in zip(self._lists, self._keys)) > - assert all(self._key(val) == key for val, key in > - zip((_val for _val_list in self._lists for _val in _val_list), > - (_key for _key_list in self._keys for _key in _key_list))) > + for val_sublist, key_sublist in zip(self._lists, self._keys): > + assert len(val_sublist) == len(key_sublist) > + for val, key in zip(val_sublist, key_sublist): > + assert self._key(val) == key > > - # Check _maxes is a map of _keys. > + # Check _maxes index is the last value of each sublist. > > - assert all(self._maxes[pos] == self._keys[pos][-1] > - for pos in range(len(self._maxes))) > + for pos in range(len(self._maxes)): > + assert self._maxes[pos] == self._keys[pos][-1] > > - # Check load level is less than _dual. > + # Check sublist lengths are less than double load-factor. > > - assert all(len(sublist) <= self._dual for sublist in self._lists) > + double = self._load << 1 > + assert all(len(sublist) <= double for sublist in self._lists) > > - # Check load level is greater than _half for all > + # Check sublist lengths are greater than half load-factor for all > # but the last sublist. > > - assert all(len(self._lists[pos]) >= self._half > - for pos in range(0, len(self._lists) - 1)) > - > - # Check length. > - > - assert self._len == sum(len(sublist) for sublist in self._lists) > - > - # Check index. > + half = self._load >> 1 > + for pos in range(0, len(self._lists) - 1): > + assert len(self._lists[pos]) >= half > > if self._index: > - assert len(self._index) == self._offset + len(self._lists) > assert self._len == self._index[0] > + assert len(self._index) == self._offset + len(self._lists) > > - def test_offset_pos(pos): > - "Test positional indexing offset." > - from_index = self._index[self._offset + pos] > - return from_index == len(self._lists[pos]) > + # Check index leaf nodes equal length of sublists. > > - assert all(test_offset_pos(pos) > - for pos in range(len(self._lists))) > + for pos in range(len(self._lists)): > + leaf = self._index[self._offset + pos] > + assert leaf == len(self._lists[pos]) > + > + # Check index branch nodes are the sum of their children. > > for pos in range(self._offset): > child = (pos << 1) + 1 > - if self._index[pos] == 0: > - assert child >= len(self._index) > + if child >= len(self._index): > + assert self._index[pos] == 0 > elif child + 1 == len(self._index): > assert self._index[pos] == self._index[child] > else: > child_sum = self._index[child] + self._index[child + 1] > - assert self._index[pos] == child_sum > - > + assert child_sum == self._index[pos] > except: > - import sys > - import traceback > - > traceback.print_exc(file=sys.stdout) > - > print('len', self._len) > - print('load', self._load, self._half, self._dual) > + print('load', self._load) > print('offset', self._offset) > print('len_index', len(self._index)) > print('index', self._index) > @@ -2502,5 +2639,7 @@ class SortedListWithKey(SortedList): > print('keys', self._keys) > print('len_lists', len(self._lists)) > print('lists', self._lists) > - > raise > + > + > +SortedListWithKey = SortedKeyList > diff --git a/python/ovs/compat/sortedcontainers/sortedset.py b/python/ovs/compat/sortedcontainers/sortedset.py > index 6d82b387b..be2b8999c 100644 > --- a/python/ovs/compat/sortedcontainers/sortedset.py > +++ b/python/ovs/compat/sortedcontainers/sortedset.py > @@ -1,50 +1,153 @@ > -"""Sorted set implementation. > +"""Sorted Set > +============= > + > +:doc:`Sorted Containers<index>` is an Apache2 licensed Python sorted > +collections library, written in pure-Python, and fast as C-extensions. The > +:doc:`introduction<introduction>` is the best way to get started. > + > +Sorted set implementations: > + > +.. currentmodule:: sortedcontainers > + > +* :class:`SortedSet` > > """ > > -from collections import Set, MutableSet, Sequence > from itertools import chain > -import operator as op > +from operator import eq, ne, gt, ge, lt, le > +from textwrap import dedent > + > +from .sortedlist import SortedList, recursive_repr > + > +############################################################################### > +# BEGIN Python 2/3 Shims > +############################################################################### > + > +try: > + from collections.abc import MutableSet, Sequence, Set > +except ImportError: > + from collections import MutableSet, Sequence, Set > + > +############################################################################### > +# END Python 2/3 Shims > +############################################################################### > > -from .sortedlist import SortedList, recursive_repr, SortedListWithKey > > class SortedSet(MutableSet, Sequence): > - """ > - A `SortedSet` provides the same methods as a `set`. Additionally, a > - `SortedSet` maintains its items in sorted order, allowing the `SortedSet` to > - be indexed. > + """Sorted set is a sorted mutable set. > + > + Sorted set values are maintained in sorted order. The design of sorted set > + is simple: sorted set uses a set for set-operations and maintains a sorted > + list of values. > + > + Sorted set values must be hashable and comparable. The hash and total > + ordering of values must not change while they are stored in the sorted set. > + > + Mutable set methods: > + > + * :func:`SortedSet.__contains__` > + * :func:`SortedSet.__iter__` > + * :func:`SortedSet.__len__` > + * :func:`SortedSet.add` > + * :func:`SortedSet.discard` > + > + Sequence methods: > + > + * :func:`SortedSet.__getitem__` > + * :func:`SortedSet.__delitem__` > + * :func:`SortedSet.__reversed__` > + > + Methods for removing values: > + > + * :func:`SortedSet.clear` > + * :func:`SortedSet.pop` > + * :func:`SortedSet.remove` > + > + Set-operation methods: > + > + * :func:`SortedSet.difference` > + * :func:`SortedSet.difference_update` > + * :func:`SortedSet.intersection` > + * :func:`SortedSet.intersection_update` > + * :func:`SortedSet.symmetric_difference` > + * :func:`SortedSet.symmetric_difference_update` > + * :func:`SortedSet.union` > + * :func:`SortedSet.update` > + > + Methods for miscellany: > + > + * :func:`SortedSet.copy` > + * :func:`SortedSet.count` > + * :func:`SortedSet.__repr__` > + * :func:`SortedSet._check` > + > + Sorted list methods available: > + > + * :func:`SortedList.bisect_left` > + * :func:`SortedList.bisect_right` > + * :func:`SortedList.index` > + * :func:`SortedList.irange` > + * :func:`SortedList.islice` > + * :func:`SortedList._reset` > + > + Additional sorted list methods available, if key-function used: > + > + * :func:`SortedKeyList.bisect_key_left` > + * :func:`SortedKeyList.bisect_key_right` > + * :func:`SortedKeyList.irange_key` > + > + Sorted set comparisons use subset and superset relations. Two sorted sets > + are equal if and only if every element of each sorted set is contained in > + the other (each is a subset of the other). A sorted set is less than > + another sorted set if and only if the first sorted set is a proper subset > + of the second sorted set (is a subset, but is not equal). A sorted set is > + greater than another sorted set if and only if the first sorted set is a > + proper superset of the second sorted set (is a superset, but is not equal). > > - Unlike a `set`, a `SortedSet` requires items be hashable and comparable. > """ > - # pylint: disable=too-many-ancestors > def __init__(self, iterable=None, key=None): > - """ > - A `SortedSet` provides the same methods as a `set`. Additionally, a > - `SortedSet` maintains its items in sorted order, allowing the > - `SortedSet` to be indexed. > + """Initialize sorted set instance. > > - An optional *iterable* provides an initial series of items to populate > - the `SortedSet`. > + Optional `iterable` argument provides an initial iterable of values to > + initialize the sorted set. > > - An optional *key* argument defines a callable that, like the `key` > + Optional `key` argument defines a callable that, like the `key` > argument to Python's `sorted` function, extracts a comparison key from > - each set item. If no function is specified, the default compares the > - set items directly. > + each value. The default, none, compares values directly. > + > + Runtime complexity: `O(n*log(n))` > + > + >>> ss = SortedSet([3, 1, 2, 5, 4]) > + >>> ss > + SortedSet([1, 2, 3, 4, 5]) > + >>> from operator import neg > + >>> ss = SortedSet([3, 1, 2, 5, 4], neg) > + >>> ss > + SortedSet([5, 4, 3, 2, 1], key=<built-in function neg>) > + > + :param iterable: initial values (optional) > + :param key: function used to extract comparison key (optional) > + > """ > self._key = key > > + # SortedSet._fromset calls SortedSet.__init__ after initializing the > + # _set attribute. So only create a new set if the _set attribute is not > + # already present. > + > if not hasattr(self, '_set'): > self._set = set() > > + self._list = SortedList(self._set, key=key) > + > + # Expose some set methods publicly. > + > _set = self._set > self.isdisjoint = _set.isdisjoint > self.issubset = _set.issubset > self.issuperset = _set.issuperset > > - if key is None: > - self._list = SortedList(self._set) > - else: > - self._list = SortedListWithKey(self._set, key=key) > + # Expose some sorted list methods publicly. > > _list = self._list > self.bisect_left = _list.bisect_left > @@ -53,7 +156,7 @@ class SortedSet(MutableSet, Sequence): > self.index = _list.index > self.irange = _list.irange > self.islice = _list.islice > - self._reset = _list._reset # pylint: disable=protected-access > + self._reset = _list._reset > > if key is not None: > self.bisect_key_left = _list.bisect_key_left > @@ -64,36 +167,93 @@ class SortedSet(MutableSet, Sequence): > if iterable is not None: > self._update(iterable) > > - @property > - def key(self): > - """Key function used to extract comparison key for sorting.""" > - return self._key > > @classmethod > def _fromset(cls, values, key=None): > - """Initialize sorted set from existing set.""" > + """Initialize sorted set from existing set. > + > + Used internally by set operations that return a new set. > + > + """ > sorted_set = object.__new__(cls) > - sorted_set._set = values # pylint: disable=protected-access > + sorted_set._set = values > sorted_set.__init__(key=key) > return sorted_set > > + > + @property > + def key(self): > + """Function used to extract comparison key from values. > + > + Sorted set compares values directly when the key function is none. > + > + """ > + return self._key > + > + > def __contains__(self, value): > - """Return True if and only if *value* is an element in the set.""" > + """Return true if `value` is an element of the sorted set. > + > + ``ss.__contains__(value)`` <==> ``value in ss`` > + > + Runtime complexity: `O(1)` > + > + >>> ss = SortedSet([1, 2, 3, 4, 5]) > + >>> 3 in ss > + True > + > + :param value: search for value in sorted set > + :return: true if `value` in sorted set > + > + """ > return value in self._set > > + > def __getitem__(self, index): > - """ > - Return the element at position *index*. > + """Lookup value at `index` in sorted set. > + > + ``ss.__getitem__(index)`` <==> ``ss[index]`` > + > + Supports slicing. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> ss = SortedSet('abcde') > + >>> ss[2] > + 'c' > + >>> ss[-1] > + 'e' > + >>> ss[2:5] > + ['c', 'd', 'e'] > + > + :param index: integer or slice for indexing > + :return: value or list of values > + :raises IndexError: if index out of range > > - Supports slice notation and negative indexes. > """ > return self._list[index] > > + > def __delitem__(self, index): > - """ > - Remove the element at position *index*. > + """Remove value at `index` from sorted set. > + > + ``ss.__delitem__(index)`` <==> ``del ss[index]`` > + > + Supports slicing. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> ss = SortedSet('abcde') > + >>> del ss[2] > + >>> ss > + SortedSet(['a', 'b', 'd', 'e']) > + >>> del ss[:2] > + >>> ss > + SortedSet(['d', 'e']) > + > + :param index: integer or slice for indexing > + :raises IndexError: if index out of range > > - Supports slice notation and negative indexes. > """ > _set = self._set > _list = self._list > @@ -105,149 +265,316 @@ class SortedSet(MutableSet, Sequence): > _set.remove(value) > del _list[index] > > - def _make_cmp(self, set_op, doc): > + > + def __make_cmp(set_op, symbol, doc): > "Make comparator method." > - def comparer(self, that): > - "Compare method for sorted set and set-like object." > - # pylint: disable=protected-access > - if isinstance(that, SortedSet): > - return set_op(self._set, that._set) > - elif isinstance(that, Set): > - return set_op(self._set, that) > + def comparer(self, other): > + "Compare method for sorted set and set." > + if isinstance(other, SortedSet): > + return set_op(self._set, other._set) > + elif isinstance(other, Set): > + return set_op(self._set, other) > return NotImplemented > > - comparer.__name__ = '__{0}__'.format(set_op.__name__) > - doc_str = 'Return True if and only if Set is {0} `that`.' > - comparer.__doc__ = doc_str.format(doc) > + set_op_name = set_op.__name__ > + comparer.__name__ = '__{0}__'.format(set_op_name) > + doc_str = """Return true if and only if sorted set is {0} `other`. > + > + ``ss.__{1}__(other)`` <==> ``ss {2} other`` > + > + Comparisons use subset and superset semantics as with sets. > + > + Runtime complexity: `O(n)` > + > + :param other: `other` set > + :return: true if sorted set is {0} `other` > > + """ > + comparer.__doc__ = dedent(doc_str.format(doc, set_op_name, symbol)) > return comparer > > - __eq__ = _make_cmp(None, op.eq, 'equal to') > - __ne__ = _make_cmp(None, op.ne, 'not equal to') > - __lt__ = _make_cmp(None, op.lt, 'a proper subset of') > - __gt__ = _make_cmp(None, op.gt, 'a proper superset of') > - __le__ = _make_cmp(None, op.le, 'a subset of') > - __ge__ = _make_cmp(None, op.ge, 'a superset of') > + > + __eq__ = __make_cmp(eq, '==', 'equal to') > + __ne__ = __make_cmp(ne, '!=', 'not equal to') > + __lt__ = __make_cmp(lt, '<', 'a proper subset of') > + __gt__ = __make_cmp(gt, '>', 'a proper superset of') > + __le__ = __make_cmp(le, '<=', 'a subset of') > + __ge__ = __make_cmp(ge, '>=', 'a superset of') > + __make_cmp = staticmethod(__make_cmp) > + > > def __len__(self): > - """Return the number of elements in the set.""" > + """Return the size of the sorted set. > + > + ``ss.__len__()`` <==> ``len(ss)`` > + > + :return: size of sorted set > + > + """ > return len(self._set) > > + > def __iter__(self): > - """ > - Return an iterator over the Set. Elements are iterated in their sorted > - order. > + """Return an iterator over the sorted set. > + > + ``ss.__iter__()`` <==> ``iter(ss)`` > + > + Iterating the sorted set while adding or deleting values may raise a > + :exc:`RuntimeError` or fail to iterate over all values. > > - Iterating the Set while adding or deleting values may raise a > - `RuntimeError` or fail to iterate over all entries. > """ > return iter(self._list) > > + > def __reversed__(self): > - """ > - Return an iterator over the Set. Elements are iterated in their reverse > - sorted order. > + """Return a reverse iterator over the sorted set. > + > + ``ss.__reversed__()`` <==> ``reversed(ss)`` > + > + Iterating the sorted set while adding or deleting values may raise a > + :exc:`RuntimeError` or fail to iterate over all values. > > - Iterating the Set while adding or deleting values may raise a > - `RuntimeError` or fail to iterate over all entries. > """ > return reversed(self._list) > > + > def add(self, value): > - """Add the element *value* to the set.""" > + """Add `value` to sorted set. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> ss = SortedSet() > + >>> ss.add(3) > + >>> ss.add(1) > + >>> ss.add(2) > + >>> ss > + SortedSet([1, 2, 3]) > + > + :param value: value to add to sorted set > + > + """ > _set = self._set > if value not in _set: > _set.add(value) > self._list.add(value) > > + _add = add > + > + > def clear(self): > - """Remove all elements from the set.""" > + """Remove all values from sorted set. > + > + Runtime complexity: `O(n)` > + > + """ > self._set.clear() > self._list.clear() > > + > def copy(self): > - """Create a shallow copy of the sorted set.""" > + """Return a shallow copy of the sorted set. > + > + Runtime complexity: `O(n)` > + > + :return: new sorted set > + > + """ > return self._fromset(set(self._set), key=self._key) > > __copy__ = copy > > + > def count(self, value): > - """Return the number of occurrences of *value* in the set.""" > + """Return number of occurrences of `value` in the sorted set. > + > + Runtime complexity: `O(1)` > + > + >>> ss = SortedSet([1, 2, 3, 4, 5]) > + >>> ss.count(3) > + 1 > + > + :param value: value to count in sorted set > + :return: count > + > + """ > return 1 if value in self._set else 0 > > + > def discard(self, value): > - """ > - Remove the first occurrence of *value*. If *value* is not a member, > - does nothing. > + """Remove `value` from sorted set if it is a member. > + > + If `value` is not a member, do nothing. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> ss = SortedSet([1, 2, 3, 4, 5]) > + >>> ss.discard(5) > + >>> ss.discard(0) > + >>> ss == set([1, 2, 3, 4]) > + True > + > + :param value: `value` to discard from sorted set > + > """ > _set = self._set > if value in _set: > _set.remove(value) > - self._list.discard(value) > + self._list.remove(value) > + > + _discard = discard > + > > def pop(self, index=-1): > - """ > - Remove and return item at *index* (default last). Raises IndexError if > - set is empty or index is out of range. Negative indexes are supported, > - as for slice indices. > + """Remove and return value at `index` in sorted set. > + > + Raise :exc:`IndexError` if the sorted set is empty or index is out of > + range. > + > + Negative indices are supported. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> ss = SortedSet('abcde') > + >>> ss.pop() > + 'e' > + >>> ss.pop(2) > + 'c' > + >>> ss > + SortedSet(['a', 'b', 'd']) > + > + :param int index: index of value (default -1) > + :return: value > + :raises IndexError: if index is out of range > + > """ > # pylint: disable=arguments-differ > value = self._list.pop(index) > self._set.remove(value) > return value > > + > def remove(self, value): > - """ > - Remove first occurrence of *value*. Raises ValueError if > - *value* is not present. > + """Remove `value` from sorted set; `value` must be a member. > + > + If `value` is not a member, raise :exc:`KeyError`. > + > + Runtime complexity: `O(log(n))` -- approximate. > + > + >>> ss = SortedSet([1, 2, 3, 4, 5]) > + >>> ss.remove(5) > + >>> ss == set([1, 2, 3, 4]) > + True > + >>> ss.remove(0) > + Traceback (most recent call last): > + ... > + KeyError: 0 > + > + :param value: `value` to remove from sorted set > + :raises KeyError: if `value` is not in sorted set > + > """ > self._set.remove(value) > self._list.remove(value) > > + > def difference(self, *iterables): > - """ > - Return a new set with elements in the set that are not in the > - *iterables*. > + """Return the difference of two or more sets as a new sorted set. > + > + The `difference` method also corresponds to operator ``-``. > + > + ``ss.__sub__(iterable)`` <==> ``ss - iterable`` > + > + The difference is all values that are in this sorted set but not the > + other `iterables`. > + > + >>> ss = SortedSet([1, 2, 3, 4, 5]) > + >>> ss.difference([4, 5, 6, 7]) > + SortedSet([1, 2, 3]) > + > + :param iterables: iterable arguments > + :return: new sorted set > + > """ > diff = self._set.difference(*iterables) > return self._fromset(diff, key=self._key) > > __sub__ = difference > - __rsub__ = __sub__ > + > > def difference_update(self, *iterables): > - """ > - Update the set, removing elements found in keeping only elements > - found in any of the *iterables*. > + """Remove all values of `iterables` from this sorted set. > + > + The `difference_update` method also corresponds to operator ``-=``. > + > + ``ss.__isub__(iterable)`` <==> ``ss -= iterable`` > + > + >>> ss = SortedSet([1, 2, 3, 4, 5]) > + >>> _ = ss.difference_update([4, 5, 6, 7]) > + >>> ss > + SortedSet([1, 2, 3]) > + > + :param iterables: iterable arguments > + :return: itself > + > """ > _set = self._set > + _list = self._list > values = set(chain(*iterables)) > if (4 * len(values)) > len(_set): > - _list = self._list > _set.difference_update(values) > _list.clear() > _list.update(_set) > else: > - _discard = self.discard > + _discard = self._discard > for value in values: > _discard(value) > return self > > __isub__ = difference_update > > + > def intersection(self, *iterables): > + """Return the intersection of two or more sets as a new sorted set. > + > + The `intersection` method also corresponds to operator ``&``. > + > + ``ss.__and__(iterable)`` <==> ``ss & iterable`` > + > + The intersection is all values that are in this sorted set and each of > + the other `iterables`. > + > + >>> ss = SortedSet([1, 2, 3, 4, 5]) > + >>> ss.intersection([4, 5, 6, 7]) > + SortedSet([4, 5]) > + > + :param iterables: iterable arguments > + :return: new sorted set > + > """ > - Return a new set with elements common to the set and all *iterables*. > - """ > - comb = self._set.intersection(*iterables) > - return self._fromset(comb, key=self._key) > + intersect = self._set.intersection(*iterables) > + return self._fromset(intersect, key=self._key) > > __and__ = intersection > __rand__ = __and__ > > + > def intersection_update(self, *iterables): > - """ > - Update the set, keeping only elements found in it and all *iterables*. > + """Update the sorted set with the intersection of `iterables`. > + > + The `intersection_update` method also corresponds to operator ``&=``. > + > + ``ss.__iand__(iterable)`` <==> ``ss &= iterable`` > + > + Keep only values found in itself and all `iterables`. > + > + >>> ss = SortedSet([1, 2, 3, 4, 5]) > + >>> _ = ss.intersection_update([4, 5, 6, 7]) > + >>> ss > + SortedSet([4, 5]) > + > + :param iterables: iterable arguments > + :return: itself > + > """ > _set = self._set > _list = self._list > @@ -258,42 +585,100 @@ class SortedSet(MutableSet, Sequence): > > __iand__ = intersection_update > > - def symmetric_difference(self, that): > - """ > - Return a new set with elements in either *self* or *that* but not both. > + > + def symmetric_difference(self, other): > + """Return the symmetric difference with `other` as a new sorted set. > + > + The `symmetric_difference` method also corresponds to operator ``^``. > + > + ``ss.__xor__(other)`` <==> ``ss ^ other`` > + > + The symmetric difference is all values tha are in exactly one of the > + sets. > + > + >>> ss = SortedSet([1, 2, 3, 4, 5]) > + >>> ss.symmetric_difference([4, 5, 6, 7]) > + SortedSet([1, 2, 3, 6, 7]) > + > + :param other: `other` iterable > + :return: new sorted set > + > """ > - diff = self._set.symmetric_difference(that) > + diff = self._set.symmetric_difference(other) > return self._fromset(diff, key=self._key) > > __xor__ = symmetric_difference > __rxor__ = __xor__ > > - def symmetric_difference_update(self, that): > - """ > - Update the set, keeping only elements found in either *self* or *that*, > - but not in both. > + > + def symmetric_difference_update(self, other): > + """Update the sorted set with the symmetric difference with `other`. > + > + The `symmetric_difference_update` method also corresponds to operator > + ``^=``. > + > + ``ss.__ixor__(other)`` <==> ``ss ^= other`` > + > + Keep only values found in exactly one of itself and `other`. > + > + >>> ss = SortedSet([1, 2, 3, 4, 5]) > + >>> _ = ss.symmetric_difference_update([4, 5, 6, 7]) > + >>> ss > + SortedSet([1, 2, 3, 6, 7]) > + > + :param other: `other` iterable > + :return: itself > + > """ > _set = self._set > _list = self._list > - _set.symmetric_difference_update(that) > + _set.symmetric_difference_update(other) > _list.clear() > _list.update(_set) > return self > > __ixor__ = symmetric_difference_update > > + > def union(self, *iterables): > - """ > - Return a new SortedSet with elements from the set and all *iterables*. > + """Return new sorted set with values from itself and all `iterables`. > + > + The `union` method also corresponds to operator ``|``. > + > + ``ss.__or__(iterable)`` <==> ``ss | iterable`` > + > + >>> ss = SortedSet([1, 2, 3, 4, 5]) > + >>> ss.union([4, 5, 6, 7]) > + SortedSet([1, 2, 3, 4, 5, 6, 7]) > + > + :param iterables: iterable arguments > + :return: new sorted set > + > """ > return self.__class__(chain(iter(self), *iterables), key=self._key) > > __or__ = union > __ror__ = __or__ > > + > def update(self, *iterables): > - """Update the set, adding elements from all *iterables*.""" > + """Update the sorted set adding values from all `iterables`. > + > + The `update` method also corresponds to operator ``|=``. > + > + ``ss.__ior__(iterable)`` <==> ``ss |= iterable`` > + > + >>> ss = SortedSet([1, 2, 3, 4, 5]) > + >>> _ = ss.update([4, 5, 6, 7]) > + >>> ss > + SortedSet([1, 2, 3, 4, 5, 6, 7]) > + > + :param iterables: iterable arguments > + :return: itself > + > + """ > _set = self._set > + _list = self._list > values = set(chain(*iterables)) > if (4 * len(values)) > len(_set): > _list = self._list > @@ -301,7 +686,7 @@ class SortedSet(MutableSet, Sequence): > _list.clear() > _list.update(_set) > else: > - _add = self.add > + _add = self._add > for value in values: > _add(value) > return self > @@ -309,19 +694,40 @@ class SortedSet(MutableSet, Sequence): > __ior__ = update > _update = update > > + > def __reduce__(self): > + """Support for pickle. > + > + The tricks played with exposing methods in :func:`SortedSet.__init__` > + confuse pickle so customize the reducer. > + > + """ > return (type(self), (self._set, self._key)) > > - @recursive_repr > + > + @recursive_repr() > def __repr__(self): > + """Return string representation of sorted set. > + > + ``ss.__repr__()`` <==> ``repr(ss)`` > + > + :return: string representation > + > + """ > _key = self._key > key = '' if _key is None else ', key={0!r}'.format(_key) > - name = type(self).__name__ > - return '{0}({1!r}{2})'.format(name, list(self), key) > + type_name = type(self).__name__ > + return '{0}({1!r}{2})'.format(type_name, list(self), key) > + > > def _check(self): > - # pylint: disable=protected-access > - self._list._check() > - assert len(self._set) == len(self._list) > + """Check invariants of sorted set. > + > + Runtime complexity: `O(n)` > + > + """ > _set = self._set > - assert all(val in _set for val in self._list) > + _list = self._list > + _list._check() > + assert len(_set) == len(_list) > + assert all(value in _set for value in _list) >
On 7/2/21 12:47 PM, Ilya Maximets wrote: > On 7/2/21 12:17 PM, Timothy Redaelli wrote: >> This is needed since the current bundled version doesn't work on Python >> 3.10+. > > Make sense. > > Terry, could you, please, take a look? > > I also wonder if we need to apply this patch to stable branches. > Thoughts? > > Best regards, Ilya Maximets. > >> >> Signed-off-by: Timothy Redaelli <tredaelli@redhat.com> >> --- >> python/ovs/compat/sortedcontainers/LICENSE | 2 +- >> .../ovs/compat/sortedcontainers/__init__.py | 70 +- >> .../ovs/compat/sortedcontainers/sorteddict.py | 1233 +++++---- >> .../ovs/compat/sortedcontainers/sortedlist.py | 2409 +++++++++-------- >> .../ovs/compat/sortedcontainers/sortedset.py | 636 ++++- >> 5 files changed, 2494 insertions(+), 1856 deletions(-) >> I looked through this patch again and it looks good to me. Applied to master. Best regards, Ilya Maximets.
diff --git a/python/ovs/compat/sortedcontainers/LICENSE b/python/ovs/compat/sortedcontainers/LICENSE index 8794014e0..668d8ecd6 100644 --- a/python/ovs/compat/sortedcontainers/LICENSE +++ b/python/ovs/compat/sortedcontainers/LICENSE @@ -1,4 +1,4 @@ -Copyright 2014-2016 Grant Jenks +Copyright 2014-2019 Grant Jenks Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/python/ovs/compat/sortedcontainers/__init__.py b/python/ovs/compat/sortedcontainers/__init__.py index 392adfad6..a141dd1de 100644 --- a/python/ovs/compat/sortedcontainers/__init__.py +++ b/python/ovs/compat/sortedcontainers/__init__.py @@ -1,9 +1,8 @@ -"""Sorted Container Types: SortedList, SortedDict, SortedSet +"""Sorted Containers -- Sorted List, Sorted Dict, Sorted Set -SortedContainers is an Apache2 licensed containers library, written in +Sorted Containers is an Apache2 licensed containers library, written in pure-Python, and fast as C-extensions. - Python's standard library is great until you need a sorted collections type. Many will attest that you can get really far without one, but the moment you **really need** a sorted list, dict, or set, you're faced with a dozen @@ -14,39 +13,62 @@ In Python, we can do better. And we can do it in pure-Python! :: - >>> from sortedcontainers import SortedList, SortedDict, SortedSet - >>> sl = SortedList(xrange(10000000)) - >>> 1234567 in sl - True - >>> sl[7654321] - 7654321 - >>> sl.add(1234567) - >>> sl.count(1234567) + >>> from sortedcontainers import SortedList + >>> sl = SortedList(['e', 'a', 'c', 'd', 'b']) + >>> sl + SortedList(['a', 'b', 'c', 'd', 'e']) + >>> sl *= 1000000 + >>> sl.count('c') + 1000000 + >>> sl[-3:] + ['e', 'e', 'e'] + >>> from sortedcontainers import SortedDict + >>> sd = SortedDict({'c': 3, 'a': 1, 'b': 2}) + >>> sd + SortedDict({'a': 1, 'b': 2, 'c': 3}) + >>> sd.popitem(index=-1) + ('c', 3) + >>> from sortedcontainers import SortedSet + >>> ss = SortedSet('abracadabra') + >>> ss + SortedSet(['a', 'b', 'c', 'd', 'r']) + >>> ss.bisect_left('c') 2 - >>> sl *= 3 - >>> len(sl) - 30000003 -SortedContainers takes all of the work out of Python sorted types - making your -deployment and use of Python easy. There's no need to install a C compiler or -pre-build and distribute custom extensions. Performance is a feature and +Sorted Containers takes all of the work out of Python sorted types - making +your deployment and use of Python easy. There's no need to install a C compiler +or pre-build and distribute custom extensions. Performance is a feature and testing has 100% coverage with unit tests and hours of stress. -:copyright: (c) 2016 by Grant Jenks. +:copyright: (c) 2014-2019 by Grant Jenks. :license: Apache 2.0, see LICENSE for more details. """ -from .sortedlist import SortedList, SortedListWithKey +from .sortedlist import SortedList, SortedKeyList, SortedListWithKey from .sortedset import SortedSet -from .sorteddict import SortedDict +from .sorteddict import ( + SortedDict, + SortedKeysView, + SortedItemsView, + SortedValuesView, +) -__all__ = ['SortedList', 'SortedSet', 'SortedDict', 'SortedListWithKey'] +__all__ = [ + 'SortedList', + 'SortedKeyList', + 'SortedListWithKey', + 'SortedDict', + 'SortedKeysView', + 'SortedItemsView', + 'SortedValuesView', + 'SortedSet', +] __title__ = 'sortedcontainers' -__version__ = '1.5.9' -__build__ = 0x010509 +__version__ = '2.4.0' +__build__ = 0x020400 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' -__copyright__ = 'Copyright 2016 Grant Jenks' +__copyright__ = '2014-2019, Grant Jenks' diff --git a/python/ovs/compat/sortedcontainers/sorteddict.py b/python/ovs/compat/sortedcontainers/sorteddict.py index 5d425fee6..910f26088 100644 --- a/python/ovs/compat/sortedcontainers/sorteddict.py +++ b/python/ovs/compat/sortedcontainers/sorteddict.py @@ -1,342 +1,563 @@ -"""Sorted dictionary implementation. +"""Sorted Dict +============== + +:doc:`Sorted Containers<index>` is an Apache2 licensed Python sorted +collections library, written in pure-Python, and fast as C-extensions. The +:doc:`introduction<introduction>` is the best way to get started. + +Sorted dict implementations: + +.. currentmodule:: sortedcontainers + +* :class:`SortedDict` +* :class:`SortedKeysView` +* :class:`SortedItemsView` +* :class:`SortedValuesView` """ -from collections import Set, Sequence -from collections import KeysView as AbstractKeysView -from collections import ValuesView as AbstractValuesView -from collections import ItemsView as AbstractItemsView -from sys import hexversion +import sys +import warnings + +from itertools import chain -from .sortedlist import SortedList, recursive_repr, SortedListWithKey +from .sortedlist import SortedList, recursive_repr from .sortedset import SortedSet -NONE = object() +############################################################################### +# BEGIN Python 2/3 Shims +############################################################################### +try: + from collections.abc import ( + ItemsView, KeysView, Mapping, ValuesView, Sequence + ) +except ImportError: + from collections import ItemsView, KeysView, Mapping, ValuesView, Sequence -class _IlocWrapper(object): - "Positional indexing support for sorted dictionary objects." - # pylint: disable=protected-access, too-few-public-methods - def __init__(self, _dict): - self._dict = _dict - def __len__(self): - return len(self._dict) - def __getitem__(self, index): - """ - Very efficiently return the key at index *index* in iteration. Supports - negative indices and slice notation. Raises IndexError on invalid - *index*. - """ - return self._dict._list[index] - def __delitem__(self, index): - """ - Remove the ``sdict[sdict.iloc[index]]`` from *sdict*. Supports negative - indices and slice notation. Raises IndexError on invalid *index*. - """ - _dict = self._dict - _list = _dict._list - _delitem = _dict._delitem - - if isinstance(index, slice): - keys = _list[index] - del _list[index] - for key in keys: - _delitem(key) - else: - key = _list[index] - del _list[index] - _delitem(key) +############################################################################### +# END Python 2/3 Shims +############################################################################### class SortedDict(dict): - """SortedDict provides the same methods as a dict. Additionally, SortedDict - efficiently maintains its keys in sorted order. Consequently, the keys - method will return the keys in sorted order, the popitem method will remove - the item with the highest key, etc. + """Sorted dict is a sorted mutable mapping. + + Sorted dict keys are maintained in sorted order. The design of sorted dict + is simple: sorted dict inherits from dict to store items and maintains a + sorted list of keys. + + Sorted dict keys must be hashable and comparable. The hash and total + ordering of keys must not change while they are stored in the sorted dict. + + Mutable mapping methods: + + * :func:`SortedDict.__getitem__` (inherited from dict) + * :func:`SortedDict.__setitem__` + * :func:`SortedDict.__delitem__` + * :func:`SortedDict.__iter__` + * :func:`SortedDict.__len__` (inherited from dict) + + Methods for adding items: + + * :func:`SortedDict.setdefault` + * :func:`SortedDict.update` + + Methods for removing items: + + * :func:`SortedDict.clear` + * :func:`SortedDict.pop` + * :func:`SortedDict.popitem` + + Methods for looking up items: + + * :func:`SortedDict.__contains__` (inherited from dict) + * :func:`SortedDict.get` (inherited from dict) + * :func:`SortedDict.peekitem` + + Methods for views: + + * :func:`SortedDict.keys` + * :func:`SortedDict.items` + * :func:`SortedDict.values` + + Methods for miscellany: + + * :func:`SortedDict.copy` + * :func:`SortedDict.fromkeys` + * :func:`SortedDict.__reversed__` + * :func:`SortedDict.__eq__` (inherited from dict) + * :func:`SortedDict.__ne__` (inherited from dict) + * :func:`SortedDict.__repr__` + * :func:`SortedDict._check` + + Sorted list methods available (applies to keys): + + * :func:`SortedList.bisect_left` + * :func:`SortedList.bisect_right` + * :func:`SortedList.count` + * :func:`SortedList.index` + * :func:`SortedList.irange` + * :func:`SortedList.islice` + * :func:`SortedList._reset` + + Additional sorted list methods available, if key-function used: + + * :func:`SortedKeyList.bisect_key_left` + * :func:`SortedKeyList.bisect_key_right` + * :func:`SortedKeyList.irange_key` + + Sorted dicts may only be compared for equality and inequality. """ def __init__(self, *args, **kwargs): - """SortedDict provides the same methods as a dict. Additionally, SortedDict - efficiently maintains its keys in sorted order. Consequently, the keys - method will return the keys in sorted order, the popitem method will - remove the item with the highest key, etc. - - An optional *key* argument defines a callable that, like the `key` - argument to Python's `sorted` function, extracts a comparison key from - each dict key. If no function is specified, the default compares the - dict keys directly. The `key` argument must be provided as a positional - argument and must come before all other arguments. - - An optional *iterable* argument provides an initial series of items to - populate the SortedDict. Each item in the series must itself contain - two items. The first is used as a key in the new dictionary, and the - second as the key's value. If a given key is seen more than once, the - last value associated with it is retained in the new dictionary. - - If keyword arguments are given, the keywords themselves with their - associated values are added as items to the dictionary. If a key is - specified both in the positional argument and as a keyword argument, the - value associated with the keyword is retained in the dictionary. For - example, these all return a dictionary equal to ``{"one": 2, "two": - 3}``: - - * ``SortedDict(one=2, two=3)`` - * ``SortedDict({'one': 2, 'two': 3})`` - * ``SortedDict(zip(('one', 'two'), (2, 3)))`` - * ``SortedDict([['two', 3], ['one', 2]])`` - - The first example only works for keys that are valid Python - identifiers; the others work with any valid keys. + """Initialize sorted dict instance. + + Optional key-function argument defines a callable that, like the `key` + argument to the built-in `sorted` function, extracts a comparison key + from each dictionary key. If no function is specified, the default + compares the dictionary keys directly. The key-function argument must + be provided as a positional argument and must come before all other + arguments. + + Optional iterable argument provides an initial sequence of pairs to + initialize the sorted dict. Each pair in the sequence defines the key + and corresponding value. If a key is seen more than once, the last + value associated with it is stored in the new sorted dict. + + Optional mapping argument provides an initial mapping of items to + initialize the sorted dict. + + If keyword arguments are given, the keywords themselves, with their + associated values, are added as items to the dictionary. If a key is + specified both in the positional argument and as a keyword argument, + the value associated with the keyword is stored in the + sorted dict. + + Sorted dict keys must be hashable, per the requirement for Python's + dictionaries. Keys (or the result of the key-function) must also be + comparable, per the requirement for sorted lists. + + >>> d = {'alpha': 1, 'beta': 2} + >>> SortedDict([('alpha', 1), ('beta', 2)]) == d + True + >>> SortedDict({'alpha': 1, 'beta': 2}) == d + True + >>> SortedDict(alpha=1, beta=2) == d + True """ - # pylint: disable=super-init-not-called if args and (args[0] is None or callable(args[0])): - self._key = args[0] + _key = self._key = args[0] args = args[1:] else: - self._key = None + _key = self._key = None - if self._key is None: - self._list = SortedList() - else: - self._list = SortedListWithKey(key=self._key) + self._list = SortedList(key=_key) - # Cache function pointers to dict methods. - - _dict = super(SortedDict, self) - self._dict = _dict - self._clear = _dict.clear - self._delitem = _dict.__delitem__ - self._iter = _dict.__iter__ - self._pop = _dict.pop - self._setdefault = _dict.setdefault - self._setitem = _dict.__setitem__ - self._dict_update = _dict.update - - # Cache function pointers to SortedList methods. + # Reaching through ``self._list`` repeatedly adds unnecessary overhead + # so cache references to sorted list methods. _list = self._list self._list_add = _list.add - self.bisect_left = _list.bisect_left - self.bisect = _list.bisect_right - self.bisect_right = _list.bisect_right self._list_clear = _list.clear - self.index = _list.index + self._list_iter = _list.__iter__ + self._list_reversed = _list.__reversed__ self._list_pop = _list.pop self._list_remove = _list.remove self._list_update = _list.update + + # Expose some sorted list methods publicly. + + self.bisect_left = _list.bisect_left + self.bisect = _list.bisect_right + self.bisect_right = _list.bisect_right + self.index = _list.index self.irange = _list.irange self.islice = _list.islice - self._reset = _list._reset # pylint: disable=protected-access + self._reset = _list._reset - if self._key is not None: + if _key is not None: self.bisect_key_left = _list.bisect_key_left self.bisect_key_right = _list.bisect_key_right self.bisect_key = _list.bisect_key self.irange_key = _list.irange_key - self.iloc = _IlocWrapper(self) - self._update(*args, **kwargs) + @property def key(self): - """Key function used to extract comparison key for sorting.""" + """Function used to extract comparison key from keys. + + Sorted dict compares keys directly when the key function is none. + + """ return self._key + + @property + def iloc(self): + """Cached reference of sorted keys view. + + Deprecated in version 2 of Sorted Containers. Use + :func:`SortedDict.keys` instead. + + """ + # pylint: disable=attribute-defined-outside-init + try: + return self._iloc + except AttributeError: + warnings.warn( + 'sorted_dict.iloc is deprecated.' + ' Use SortedDict.keys() instead.', + DeprecationWarning, + stacklevel=2, + ) + _iloc = self._iloc = SortedKeysView(self) + return _iloc + + def clear(self): - """Remove all elements from the dictionary.""" - self._clear() + + """Remove all items from sorted dict. + + Runtime complexity: `O(n)` + + """ + dict.clear(self) self._list_clear() + def __delitem__(self, key): + """Remove item from sorted dict identified by `key`. + + ``sd.__delitem__(key)`` <==> ``del sd[key]`` + + Runtime complexity: `O(log(n))` -- approximate. + + >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3}) + >>> del sd['b'] + >>> sd + SortedDict({'a': 1, 'c': 3}) + >>> del sd['z'] + Traceback (most recent call last): + ... + KeyError: 'z' + + :param key: `key` for item lookup + :raises KeyError: if key not found + """ - Remove ``d[key]`` from *d*. Raises a KeyError if *key* is not in the - dictionary. - """ - self._delitem(key) + dict.__delitem__(self, key) self._list_remove(key) + def __iter__(self): - """ - Return an iterator over the sorted keys of the dictionary. + """Return an iterator over the keys of the sorted dict. + + ``sd.__iter__()`` <==> ``iter(sd)`` + + Iterating the sorted dict while adding or deleting items may raise a + :exc:`RuntimeError` or fail to iterate over all keys. - Iterating the Mapping while adding or deleting keys may raise a - `RuntimeError` or fail to iterate over all entries. """ - return iter(self._list) + return self._list_iter() + def __reversed__(self): - """ - Return a reversed iterator over the sorted keys of the dictionary. + """Return a reverse iterator over the keys of the sorted dict. + + ``sd.__reversed__()`` <==> ``reversed(sd)`` + + Iterating the sorted dict while adding or deleting items may raise a + :exc:`RuntimeError` or fail to iterate over all keys. - Iterating the Mapping while adding or deleting keys may raise a - `RuntimeError` or fail to iterate over all entries. """ - return reversed(self._list) + return self._list_reversed() + def __setitem__(self, key, value): - """Set `d[key]` to *value*.""" + """Store item in sorted dict with `key` and corresponding `value`. + + ``sd.__setitem__(key, value)`` <==> ``sd[key] = value`` + + Runtime complexity: `O(log(n))` -- approximate. + + >>> sd = SortedDict() + >>> sd['c'] = 3 + >>> sd['a'] = 1 + >>> sd['b'] = 2 + >>> sd + SortedDict({'a': 1, 'b': 2, 'c': 3}) + + :param key: key for item + :param value: value for item + + """ if key not in self: self._list_add(key) - self._setitem(key, value) + dict.__setitem__(self, key, value) + + _setitem = __setitem__ + + + def __or__(self, other): + if not isinstance(other, Mapping): + return NotImplemented + items = chain(self.items(), other.items()) + return self.__class__(self._key, items) + + + def __ror__(self, other): + if not isinstance(other, Mapping): + return NotImplemented + items = chain(other.items(), self.items()) + return self.__class__(self._key, items) + + + def __ior__(self, other): + self._update(other) + return self + def copy(self): - """Return a shallow copy of the sorted dictionary.""" - return self.__class__(self._key, self._iteritems()) + """Return a shallow copy of the sorted dict. + + Runtime complexity: `O(n)` + + :return: new sorted dict + + """ + return self.__class__(self._key, self.items()) __copy__ = copy + @classmethod - def fromkeys(cls, seq, value=None): - """ - Create a new dictionary with keys from *seq* and values set to *value*. - """ - return cls((key, value) for key in seq) - - if hexversion < 0x03000000: - def items(self): - """ - Return a list of the dictionary's items (``(key, value)`` pairs). - """ - return list(self._iteritems()) - else: - def items(self): - """ - Return a new ItemsView of the dictionary's items. In addition to - the methods provided by the built-in `view` the ItemsView is - indexable (e.g. ``d.items()[5]``). - """ - return ItemsView(self) - - def iteritems(self): - """ - Return an iterator over the items (``(key, value)`` pairs). + def fromkeys(cls, iterable, value=None): + """Return a new sorted dict initailized from `iterable` and `value`. - Iterating the Mapping while adding or deleting keys may raise a - `RuntimeError` or fail to iterate over all entries. - """ - return iter((key, self[key]) for key in self._list) + Items in the sorted dict have keys from `iterable` and values equal to + `value`. - _iteritems = iteritems + Runtime complexity: `O(n*log(n))` - if hexversion < 0x03000000: - def keys(self): - """Return a SortedSet of the dictionary's keys.""" - return SortedSet(self._list, key=self._key) - else: - def keys(self): - """ - Return a new KeysView of the dictionary's keys. In addition to the - methods provided by the built-in `view` the KeysView is indexable - (e.g. ``d.keys()[5]``). - """ - return KeysView(self) - - def iterkeys(self): - """ - Return an iterator over the sorted keys of the Mapping. + :return: new sorted dict - Iterating the Mapping while adding or deleting keys may raise a - `RuntimeError` or fail to iterate over all entries. """ - return iter(self._list) + return cls((key, value) for key in iterable) + + + def keys(self): + """Return new sorted keys view of the sorted dict's keys. + + See :class:`SortedKeysView` for details. + + :return: new sorted keys view - if hexversion < 0x03000000: - def values(self): - """Return a list of the dictionary's values.""" - return list(self._itervalues()) - else: - def values(self): - """ - Return a new :class:`ValuesView` of the dictionary's values. - In addition to the methods provided by the built-in `view` the - ValuesView is indexable (e.g., ``d.values()[5]``). - """ - return ValuesView(self) - - def itervalues(self): """ - Return an iterator over the values of the Mapping. + return SortedKeysView(self) + + + def items(self): + """Return new sorted items view of the sorted dict's items. + + See :class:`SortedItemsView` for details. + + :return: new sorted items view - Iterating the Mapping while adding or deleting keys may raise a - `RuntimeError` or fail to iterate over all entries. """ - return iter(self[key] for key in self._list) + return SortedItemsView(self) + - _itervalues = itervalues + def values(self): + """Return new sorted values view of the sorted dict's values. + + See :class:`SortedValuesView` for details. + + :return: new sorted values view - def pop(self, key, default=NONE): """ - If *key* is in the dictionary, remove it and return its value, - else return *default*. If *default* is not given and *key* is not in - the dictionary, a KeyError is raised. + return SortedValuesView(self) + + + if sys.hexversion < 0x03000000: + def __make_raise_attributeerror(original, alternate): + # pylint: disable=no-self-argument + message = ( + 'SortedDict.{original}() is not implemented.' + ' Use SortedDict.{alternate}() instead.' + ).format(original=original, alternate=alternate) + def method(self): + # pylint: disable=missing-docstring,unused-argument + raise AttributeError(message) + method.__name__ = original # pylint: disable=non-str-assignment-to-dunder-name + method.__doc__ = message + return property(method) + + iteritems = __make_raise_attributeerror('iteritems', 'items') + iterkeys = __make_raise_attributeerror('iterkeys', 'keys') + itervalues = __make_raise_attributeerror('itervalues', 'values') + viewitems = __make_raise_attributeerror('viewitems', 'items') + viewkeys = __make_raise_attributeerror('viewkeys', 'keys') + viewvalues = __make_raise_attributeerror('viewvalues', 'values') + + + class _NotGiven(object): + # pylint: disable=too-few-public-methods + def __repr__(self): + return '<not-given>' + + __not_given = _NotGiven() + + def pop(self, key, default=__not_given): + """Remove and return value for item identified by `key`. + + If the `key` is not found then return `default` if given. If `default` + is not given then raise :exc:`KeyError`. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3}) + >>> sd.pop('c') + 3 + >>> sd.pop('z', 26) + 26 + >>> sd.pop('y') + Traceback (most recent call last): + ... + KeyError: 'y' + + :param key: `key` for item + :param default: `default` value if key not found (optional) + :return: value for item + :raises KeyError: if `key` not found and `default` not given + """ if key in self: self._list_remove(key) - return self._pop(key) + return dict.pop(self, key) else: - if default is NONE: + if default is self.__not_given: raise KeyError(key) - else: - return default + return default - def popitem(self, last=True): - """ - Remove and return a ``(key, value)`` pair from the dictionary. If - last=True (default) then remove the *greatest* `key` from the - diciontary. Else, remove the *least* key from the dictionary. - If the dictionary is empty, calling `popitem` raises a - KeyError`. + def popitem(self, index=-1): + """Remove and return ``(key, value)`` pair at `index` from sorted dict. + + Optional argument `index` defaults to -1, the last item in the sorted + dict. Specify ``index=0`` for the first item in the sorted dict. + + If the sorted dict is empty, raises :exc:`KeyError`. + + If the `index` is out of range, raises :exc:`IndexError`. + + Runtime complexity: `O(log(n))` + + >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3}) + >>> sd.popitem() + ('c', 3) + >>> sd.popitem(0) + ('a', 1) + >>> sd.popitem(100) + Traceback (most recent call last): + ... + IndexError: list index out of range + + :param int index: `index` of item (default -1) + :return: key and value pair + :raises KeyError: if sorted dict is empty + :raises IndexError: if `index` out of range + """ if not self: raise KeyError('popitem(): dictionary is empty') - key = self._list_pop(-1 if last else 0) - value = self._pop(key) - + key = self._list_pop(index) + value = dict.pop(self, key) return (key, value) + def peekitem(self, index=-1): - """Return (key, value) item pair at index. + """Return ``(key, value)`` pair at `index` in sorted dict. + + Optional argument `index` defaults to -1, the last item in the sorted + dict. Specify ``index=0`` for the first item in the sorted dict. - Unlike ``popitem``, the sorted dictionary is not modified. Index - defaults to -1, the last/greatest key in the dictionary. Specify - ``index=0`` to lookup the first/least key in the dictiony. + Unlike :func:`SortedDict.popitem`, the sorted dict is not modified. - If index is out of range, raise IndexError. + If the `index` is out of range, raises :exc:`IndexError`. + + Runtime complexity: `O(log(n))` + + >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3}) + >>> sd.peekitem() + ('c', 3) + >>> sd.peekitem(0) + ('a', 1) + >>> sd.peekitem(100) + Traceback (most recent call last): + ... + IndexError: list index out of range + + :param int index: index of item (default -1) + :return: key and value pair + :raises IndexError: if `index` out of range """ key = self._list[index] return key, self[key] + def setdefault(self, key, default=None): - """ - If *key* is in the dictionary, return its value. If not, insert *key* - with a value of *default* and return *default*. *default* defaults to - ``None``. + """Return value for item identified by `key` in sorted dict. + + If `key` is in the sorted dict then return its value. If `key` is not + in the sorted dict then insert `key` with value `default` and return + `default`. + + Optional argument `default` defaults to none. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> sd = SortedDict() + >>> sd.setdefault('a', 1) + 1 + >>> sd.setdefault('a', 10) + 1 + >>> sd + SortedDict({'a': 1}) + + :param key: key for item + :param default: value for item (default None) + :return: value for item identified by `key` + """ if key in self: return self[key] - - self._setitem(key, default) + dict.__setitem__(self, key, default) self._list_add(key) return default + def update(self, *args, **kwargs): - """ - Update the dictionary with the key/value pairs from *other*, overwriting - existing keys. + """Update sorted dict with items from `args` and `kwargs`. + + Overwrites existing items. + + Optional arguments `args` and `kwargs` may be a mapping, an iterable of + pairs or keyword arguments. See :func:`SortedDict.__init__` for + details. + + :param args: mapping or iterable of pairs + :param kwargs: keyword arguments mapping - *update* accepts either another dictionary object or an iterable of - key/value pairs (as a tuple or other iterable of length two). If - keyword arguments are specified, the dictionary is then updated with - those key/value pairs: ``d.update(red=1, blue=2)``. """ if not self: - self._dict_update(*args, **kwargs) - self._list_update(self._iter()) + dict.update(self, *args, **kwargs) + self._list_update(dict.__iter__(self)) return if not kwargs and len(args) == 1 and isinstance(args[0], dict): @@ -345,397 +566,247 @@ class SortedDict(dict): pairs = dict(*args, **kwargs) if (10 * len(pairs)) > len(self): - self._dict_update(pairs) + dict.update(self, pairs) self._list_clear() - self._list_update(self._iter()) + self._list_update(dict.__iter__(self)) else: for key in pairs: - self[key] = pairs[key] + self._setitem(key, pairs[key]) _update = update - if hexversion >= 0x02070000: - def viewkeys(self): - "Return ``KeysView`` of dictionary keys." - return KeysView(self) - def viewvalues(self): - "Return ``ValuesView`` of dictionary values." - return ValuesView(self) + def __reduce__(self): + """Support for pickle. - def viewitems(self): - "Return ``ItemsView`` of dictionary (key, value) item pairs." - return ItemsView(self) + The tricks played with caching references in + :func:`SortedDict.__init__` confuse pickle so customize the reducer. - def __reduce__(self): - return (self.__class__, (self._key, list(self._iteritems()))) + """ + items = dict.copy(self) + return (type(self), (self._key, items)) - @recursive_repr + + @recursive_repr() def __repr__(self): + """Return string representation of sorted dict. + + ``sd.__repr__()`` <==> ``repr(sd)`` + + :return: string representation + + """ _key = self._key - name = type(self).__name__ - key = '' if _key is None else '{0!r}, '.format(_key) - func = '{0!r}: {1!r}'.format - items = ', '.join(func(key, self[key]) for key in self._list) - return '{0}({1}{{{2}}})'.format(name, key, items) + type_name = type(self).__name__ + key_arg = '' if _key is None else '{0!r}, '.format(_key) + item_format = '{0!r}: {1!r}'.format + items = ', '.join(item_format(key, self[key]) for key in self._list) + return '{0}({1}{{{2}}})'.format(type_name, key_arg, items) + def _check(self): - # pylint: disable=protected-access - self._list._check() - assert len(self) == len(self._list) - assert all(key in self for key in self._list) + """Check invariants of sorted dict. + Runtime complexity: `O(n)` -class KeysView(AbstractKeysView, Set, Sequence): - """ - A KeysView object is a dynamic view of the dictionary's keys, which - means that when the dictionary's keys change, the view reflects - those changes. + """ + _list = self._list + _list._check() + assert len(self) == len(_list) + assert all(key in self for key in _list) + + +def _view_delitem(self, index): + """Remove item at `index` from sorted dict. + + ``view.__delitem__(index)`` <==> ``del view[index]`` + + Supports slicing. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3}) + >>> view = sd.keys() + >>> del view[0] + >>> sd + SortedDict({'b': 2, 'c': 3}) + >>> del view[-1] + >>> sd + SortedDict({'b': 2}) + >>> del view[:] + >>> sd + SortedDict({}) + + :param index: integer or slice for indexing + :raises IndexError: if index out of range - The KeysView class implements the Set and Sequence Abstract Base Classes. """ - # pylint: disable=too-many-ancestors - if hexversion < 0x03000000: - def __init__(self, sorted_dict): - """ - Initialize a KeysView from a SortedDict container as *sorted_dict*. - """ - # pylint: disable=super-init-not-called, protected-access - self._list = sorted_dict._list - self._view = sorted_dict._dict.viewkeys() + _mapping = self._mapping + _list = _mapping._list + dict_delitem = dict.__delitem__ + if isinstance(index, slice): + keys = _list[index] + del _list[index] + for key in keys: + dict_delitem(_mapping, key) else: - def __init__(self, sorted_dict): - """ - Initialize a KeysView from a SortedDict container as *sorted_dict*. - """ - # pylint: disable=super-init-not-called, protected-access - self._list = sorted_dict._list - self._view = sorted_dict._dict.keys() - def __len__(self): - """Return the number of entries in the dictionary.""" - return len(self._view) - def __contains__(self, key): - """ - Return True if and only if *key* is one of the underlying dictionary's - keys. - """ - return key in self._view - def __iter__(self): - """ - Return an iterable over the keys in the dictionary. Keys are iterated - over in their sorted order. + key = _list.pop(index) + dict_delitem(_mapping, key) + + +class SortedKeysView(KeysView, Sequence): + """Sorted keys view is a dynamic view of the sorted dict's keys. + + When the sorted dict's keys change, the view reflects those changes. + + The keys view implements the set and sequence abstract base classes. + + """ + __slots__ = () + + + @classmethod + def _from_iterable(cls, it): + return SortedSet(it) + - Iterating views while adding or deleting entries in the dictionary may - raise a `RuntimeError` or fail to iterate over all entries. - """ - return iter(self._list) def __getitem__(self, index): - """Return the key at position *index*.""" - return self._list[index] - def __reversed__(self): - """ - Return a reversed iterable over the keys in the dictionary. Keys are - iterated over in their reverse sort order. + """Lookup key at `index` in sorted keys views. + + ``skv.__getitem__(index)`` <==> ``skv[index]`` + + Supports slicing. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3}) + >>> skv = sd.keys() + >>> skv[0] + 'a' + >>> skv[-1] + 'c' + >>> skv[:] + ['a', 'b', 'c'] + >>> skv[100] + Traceback (most recent call last): + ... + IndexError: list index out of range + + :param index: integer or slice for indexing + :return: key or list of keys + :raises IndexError: if index out of range - Iterating views while adding or deleting entries in the dictionary may - raise a RuntimeError or fail to iterate over all entries. - """ - return reversed(self._list) - def index(self, value, start=None, stop=None): - """ - Return the smallest *k* such that `keysview[k] == value` and `start <= k - < end`. Raises `KeyError` if *value* is not present. *stop* defaults - to the end of the set. *start* defaults to the beginning. Negative - indexes are supported, as for slice indices. """ - # pylint: disable=arguments-differ - return self._list.index(value, start, stop) - def count(self, value): - """Return the number of occurrences of *value* in the set.""" - return 1 if value in self._view else 0 - def __eq__(self, that): - """Test set-like equality with *that*.""" - return self._view == that - def __ne__(self, that): - """Test set-like inequality with *that*.""" - return self._view != that - def __lt__(self, that): - """Test whether self is a proper subset of *that*.""" - return self._view < that - def __gt__(self, that): - """Test whether self is a proper superset of *that*.""" - return self._view > that - def __le__(self, that): - """Test whether self is contained within *that*.""" - return self._view <= that - def __ge__(self, that): - """Test whether *that* is contained within self.""" - return self._view >= that - def __and__(self, that): - """Return a SortedSet of the intersection of self and *that*.""" - return SortedSet(self._view & that) - def __or__(self, that): - """Return a SortedSet of the union of self and *that*.""" - return SortedSet(self._view | that) - def __sub__(self, that): - """Return a SortedSet of the difference of self and *that*.""" - return SortedSet(self._view - that) - def __xor__(self, that): - """Return a SortedSet of the symmetric difference of self and *that*.""" - return SortedSet(self._view ^ that) - if hexversion < 0x03000000: - def isdisjoint(self, that): - """Return True if and only if *that* is disjoint with self.""" - # pylint: disable=arguments-differ - return not any(key in self._list for key in that) - else: - def isdisjoint(self, that): - """Return True if and only if *that* is disjoint with self.""" - # pylint: disable=arguments-differ - return self._view.isdisjoint(that) - @recursive_repr - def __repr__(self): - return 'SortedDict_keys({0!r})'.format(list(self)) + return self._mapping._list[index] -class ValuesView(AbstractValuesView, Sequence): - """ - A ValuesView object is a dynamic view of the dictionary's values, which - means that when the dictionary's values change, the view reflects those - changes. + __delitem__ = _view_delitem + + +class SortedItemsView(ItemsView, Sequence): + """Sorted items view is a dynamic view of the sorted dict's items. + + When the sorted dict's items change, the view reflects those changes. + + The items view implements the set and sequence abstract base classes. - The ValuesView class implements the Sequence Abstract Base Class. """ - # pylint: disable=too-many-ancestors - if hexversion < 0x03000000: - def __init__(self, sorted_dict): - """ - Initialize a ValuesView from a SortedDict container as - *sorted_dict*. - """ - # pylint: disable=super-init-not-called, protected-access - self._dict = sorted_dict - self._list = sorted_dict._list - self._view = sorted_dict._dict.viewvalues() - else: - def __init__(self, sorted_dict): - """ - Initialize a ValuesView from a SortedDict container as - *sorted_dict*. - """ - # pylint: disable=super-init-not-called, protected-access - self._dict = sorted_dict - self._list = sorted_dict._list - self._view = sorted_dict._dict.values() - def __len__(self): - """Return the number of entries in the dictionary.""" - return len(self._dict) - def __contains__(self, value): - """ - Return True if and only if *value* is in the underlying Mapping's - values. - """ - return value in self._view - def __iter__(self): - """ - Return an iterator over the values in the dictionary. Values are - iterated over in sorted order of the keys. + __slots__ = () + + + @classmethod + def _from_iterable(cls, it): + return SortedSet(it) + - Iterating views while adding or deleting entries in the dictionary may - raise a `RuntimeError` or fail to iterate over all entries. - """ - _dict = self._dict - return iter(_dict[key] for key in self._list) def __getitem__(self, index): - """ - Efficiently return value at *index* in iteration. + """Lookup item at `index` in sorted items view. + + ``siv.__getitem__(index)`` <==> ``siv[index]`` + + Supports slicing. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3}) + >>> siv = sd.items() + >>> siv[0] + ('a', 1) + >>> siv[-1] + ('c', 3) + >>> siv[:] + [('a', 1), ('b', 2), ('c', 3)] + >>> siv[100] + Traceback (most recent call last): + ... + IndexError: list index out of range + + :param index: integer or slice for indexing + :return: item or list of items + :raises IndexError: if index out of range - Supports slice notation and negative indexes. """ - _dict, _list = self._dict, self._list + _mapping = self._mapping + _mapping_list = _mapping._list + if isinstance(index, slice): - return [_dict[key] for key in _list[index]] - return _dict[_list[index]] - def __reversed__(self): - """ - Return a reverse iterator over the values in the dictionary. Values are - iterated over in reverse sort order of the keys. + keys = _mapping_list[index] + return [(key, _mapping[key]) for key in keys] - Iterating views while adding or deleting entries in the dictionary may - raise a `RuntimeError` or fail to iterate over all entries. - """ - _dict = self._dict - return iter(_dict[key] for key in reversed(self._list)) - def index(self, value): - """ - Return index of *value* in self. + key = _mapping_list[index] + return key, _mapping[key] - Raises ValueError if *value* is not found. - """ - # pylint: disable=arguments-differ - for idx, val in enumerate(self): - if value == val: - return idx - raise ValueError('{0!r} is not in dict'.format(value)) - if hexversion < 0x03000000: - def count(self, value): - """Return the number of occurrences of *value* in self.""" - return sum(1 for val in self._dict.itervalues() if val == value) - else: - def count(self, value): - """Return the number of occurrences of *value* in self.""" - return sum(1 for val in self._dict.values() if val == value) - def __lt__(self, that): - raise TypeError - def __gt__(self, that): - raise TypeError - def __le__(self, that): - raise TypeError - def __ge__(self, that): - raise TypeError - def __and__(self, that): - raise TypeError - def __or__(self, that): - raise TypeError - def __sub__(self, that): - raise TypeError - def __xor__(self, that): - raise TypeError - @recursive_repr - def __repr__(self): - return 'SortedDict_values({0!r})'.format(list(self)) + __delitem__ = _view_delitem -class ItemsView(AbstractItemsView, Set, Sequence): - """ - An ItemsView object is a dynamic view of the dictionary's ``(key, - value)`` pairs, which means that when the dictionary changes, the - view reflects those changes. - The ItemsView class implements the Set and Sequence Abstract Base Classes. - However, the set-like operations (``&``, ``|``, ``-``, ``^``) will only - operate correctly if all of the dictionary's values are hashable. +class SortedValuesView(ValuesView, Sequence): + """Sorted values view is a dynamic view of the sorted dict's values. + + When the sorted dict's values change, the view reflects those changes. + + The values view implements the sequence abstract base class. + """ - # pylint: disable=too-many-ancestors - if hexversion < 0x03000000: - def __init__(self, sorted_dict): - """ - Initialize an ItemsView from a SortedDict container as - *sorted_dict*. - """ - # pylint: disable=super-init-not-called, protected-access - self._dict = sorted_dict - self._list = sorted_dict._list - self._view = sorted_dict._dict.viewitems() - else: - def __init__(self, sorted_dict): - """ - Initialize an ItemsView from a SortedDict container as - *sorted_dict*. - """ - # pylint: disable=super-init-not-called, protected-access - self._dict = sorted_dict - self._list = sorted_dict._list - self._view = sorted_dict._dict.items() - def __len__(self): - """Return the number of entries in the dictionary.""" - return len(self._view) - def __contains__(self, key): - """ - Return True if and only if *key* is one of the underlying dictionary's - items. - """ - return key in self._view - def __iter__(self): - """ - Return an iterable over the items in the dictionary. Items are iterated - over in their sorted order. + __slots__ = () + - Iterating views while adding or deleting entries in the dictionary may - raise a `RuntimeError` or fail to iterate over all entries. - """ - _dict = self._dict - return iter((key, _dict[key]) for key in self._list) def __getitem__(self, index): - """Return the item as position *index*.""" - _dict, _list = self._dict, self._list - if isinstance(index, slice): - return [(key, _dict[key]) for key in _list[index]] - key = _list[index] - return (key, _dict[key]) - def __reversed__(self): - """ - Return a reversed iterable over the items in the dictionary. Items are - iterated over in their reverse sort order. + """Lookup value at `index` in sorted values view. + + ``siv.__getitem__(index)`` <==> ``siv[index]`` + + Supports slicing. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3}) + >>> svv = sd.values() + >>> svv[0] + 1 + >>> svv[-1] + 3 + >>> svv[:] + [1, 2, 3] + >>> svv[100] + Traceback (most recent call last): + ... + IndexError: list index out of range + + :param index: integer or slice for indexing + :return: value or list of values + :raises IndexError: if index out of range - Iterating views while adding or deleting entries in the dictionary may - raise a RuntimeError or fail to iterate over all entries. - """ - _dict = self._dict - return iter((key, _dict[key]) for key in reversed(self._list)) - def index(self, key, start=None, stop=None): - """ - Return the smallest *k* such that `itemssview[k] == key` and `start <= k - < end`. Raises `KeyError` if *key* is not present. *stop* defaults - to the end of the set. *start* defaults to the beginning. Negative - indexes are supported, as for slice indices. """ - # pylint: disable=arguments-differ - temp, value = key - pos = self._list.index(temp, start, stop) - if value == self._dict[temp]: - return pos - else: - raise ValueError('{0!r} is not in dict'.format(key)) - def count(self, item): - """Return the number of occurrences of *item* in the set.""" - # pylint: disable=arguments-differ - key, value = item - return 1 if key in self._dict and self._dict[key] == value else 0 - def __eq__(self, that): - """Test set-like equality with *that*.""" - return self._view == that - def __ne__(self, that): - """Test set-like inequality with *that*.""" - return self._view != that - def __lt__(self, that): - """Test whether self is a proper subset of *that*.""" - return self._view < that - def __gt__(self, that): - """Test whether self is a proper superset of *that*.""" - return self._view > that - def __le__(self, that): - """Test whether self is contained within *that*.""" - return self._view <= that - def __ge__(self, that): - """Test whether *that* is contained within self.""" - return self._view >= that - def __and__(self, that): - """Return a SortedSet of the intersection of self and *that*.""" - return SortedSet(self._view & that) - def __or__(self, that): - """Return a SortedSet of the union of self and *that*.""" - return SortedSet(self._view | that) - def __sub__(self, that): - """Return a SortedSet of the difference of self and *that*.""" - return SortedSet(self._view - that) - def __xor__(self, that): - """Return a SortedSet of the symmetric difference of self and *that*.""" - return SortedSet(self._view ^ that) - if hexversion < 0x03000000: - def isdisjoint(self, that): - """Return True if and only if *that* is disjoint with self.""" - # pylint: disable=arguments-differ - _dict = self._dict - for key, value in that: - if key in _dict and _dict[key] == value: - return False - return True - else: - def isdisjoint(self, that): - """Return True if and only if *that* is disjoint with self.""" - # pylint: disable=arguments-differ - return self._view.isdisjoint(that) - @recursive_repr - def __repr__(self): - return 'SortedDict_items({0!r})'.format(list(self)) + _mapping = self._mapping + _mapping_list = _mapping._list + + if isinstance(index, slice): + keys = _mapping_list[index] + return [_mapping[key] for key in keys] + + key = _mapping_list[index] + return _mapping[key] + + + __delitem__ = _view_delitem diff --git a/python/ovs/compat/sortedcontainers/sortedlist.py b/python/ovs/compat/sortedcontainers/sortedlist.py index ba5556692..5f283fbb0 100644 --- a/python/ovs/compat/sortedcontainers/sortedlist.py +++ b/python/ovs/compat/sortedcontainers/sortedlist.py @@ -1,20 +1,44 @@ -"""Sorted list implementation. +"""Sorted List +============== + +:doc:`Sorted Containers<index>` is an Apache2 licensed Python sorted +collections library, written in pure-Python, and fast as C-extensions. The +:doc:`introduction<introduction>` is the best way to get started. + +Sorted list implementations: + +.. currentmodule:: sortedcontainers + +* :class:`SortedList` +* :class:`SortedKeyList` """ -# pylint: disable=redefined-builtin, ungrouped-imports +# pylint: disable=too-many-lines + +import sys +import traceback from bisect import bisect_left, bisect_right, insort -from collections import Sequence, MutableSequence -from functools import wraps from itertools import chain, repeat, starmap -from math import log as log_e -import operator as op -from operator import iadd, add +from math import log +from operator import add, eq, ne, gt, ge, lt, le, iadd +from textwrap import dedent + +############################################################################### +# BEGIN Python 2/3 Shims +############################################################################### + +try: + from collections.abc import Sequence, MutableSequence +except ImportError: + from collections import Sequence, MutableSequence + +from functools import wraps from sys import hexversion if hexversion < 0x03000000: - from itertools import izip as zip # pylint: disable=no-name-in-module - from itertools import imap as map # pylint: disable=no-name-in-module + from itertools import imap as map # pylint: disable=redefined-builtin + from itertools import izip as zip # pylint: disable=redefined-builtin try: from thread import get_ident except ImportError: @@ -24,145 +48,258 @@ else: try: from _thread import get_ident except ImportError: - from _dummy_thread import get_ident # pylint: disable=import-error + from _dummy_thread import get_ident -LOAD = 1000 -def recursive_repr(func): - """Decorator to prevent infinite repr recursion.""" - repr_running = set() +def recursive_repr(fillvalue='...'): + "Decorator to make a repr function return fillvalue for a recursive call." + # pylint: disable=missing-docstring + # Copied from reprlib in Python 3 + # https://hg.python.org/cpython/file/3.6/Lib/reprlib.py - @wraps(func) - def wrapper(self): - "Return ellipsis on recursive re-entry to function." - key = id(self), get_ident() + def decorating_function(user_function): + repr_running = set() - if key in repr_running: - return '...' + @wraps(user_function) + def wrapper(self): + key = id(self), get_ident() + if key in repr_running: + return fillvalue + repr_running.add(key) + try: + result = user_function(self) + finally: + repr_running.discard(key) + return result - repr_running.add(key) + return wrapper - try: - return func(self) - finally: - repr_running.discard(key) + return decorating_function + +############################################################################### +# END Python 2/3 Shims +############################################################################### - return wrapper class SortedList(MutableSequence): + """Sorted list is a sorted mutable sequence. + + Sorted list values are maintained in sorted order. + + Sorted list values must be comparable. The total ordering of values must + not change while they are stored in the sorted list. + + Methods for adding values: + + * :func:`SortedList.add` + * :func:`SortedList.update` + * :func:`SortedList.__add__` + * :func:`SortedList.__iadd__` + * :func:`SortedList.__mul__` + * :func:`SortedList.__imul__` + + Methods for removing values: + + * :func:`SortedList.clear` + * :func:`SortedList.discard` + * :func:`SortedList.remove` + * :func:`SortedList.pop` + * :func:`SortedList.__delitem__` + + Methods for looking up values: + + * :func:`SortedList.bisect_left` + * :func:`SortedList.bisect_right` + * :func:`SortedList.count` + * :func:`SortedList.index` + * :func:`SortedList.__contains__` + * :func:`SortedList.__getitem__` + + Methods for iterating values: + + * :func:`SortedList.irange` + * :func:`SortedList.islice` + * :func:`SortedList.__iter__` + * :func:`SortedList.__reversed__` + + Methods for miscellany: + + * :func:`SortedList.copy` + * :func:`SortedList.__len__` + * :func:`SortedList.__repr__` + * :func:`SortedList._check` + * :func:`SortedList._reset` + + Sorted lists use lexicographical ordering semantics when compared to other + sequences. + + Some methods of mutable sequences are not supported and will raise + not-implemented error. + """ - SortedList provides most of the same methods as a list but keeps the items - in sorted order. - """ - # pylint: disable=too-many-ancestors - def __init__(self, iterable=None): - """ - SortedList provides most of the same methods as a list but keeps the - items in sorted order. + DEFAULT_LOAD_FACTOR = 1000 + + + def __init__(self, iterable=None, key=None): + """Initialize sorted list instance. + + Optional `iterable` argument provides an initial iterable of values to + initialize the sorted list. + + Runtime complexity: `O(n*log(n))` + + >>> sl = SortedList() + >>> sl + SortedList([]) + >>> sl = SortedList([3, 1, 2, 5, 4]) + >>> sl + SortedList([1, 2, 3, 4, 5]) + + :param iterable: initial values (optional) - An optional *iterable* provides an initial series of items to populate - the SortedList. """ + assert key is None self._len = 0 + self._load = self.DEFAULT_LOAD_FACTOR self._lists = [] self._maxes = [] self._index = [] - self._load = LOAD - self._half = LOAD >> 1 - self._dual = LOAD << 1 self._offset = 0 if iterable is not None: self._update(iterable) + def __new__(cls, iterable=None, key=None): - """ - SortedList provides most of the same methods as a list but keeps the - items in sorted order. + """Create new sorted list or sorted-key list instance. + + Optional `key`-function argument will return an instance of subtype + :class:`SortedKeyList`. - An optional *iterable* provides an initial series of items to populate - the SortedList. + >>> sl = SortedList() + >>> isinstance(sl, SortedList) + True + >>> sl = SortedList(key=lambda x: -x) + >>> isinstance(sl, SortedList) + True + >>> isinstance(sl, SortedKeyList) + True + + :param iterable: initial values (optional) + :param key: function used to extract comparison key (optional) + :return: sorted list or sorted-key list instance - An optional *key* argument will return an instance of subtype - SortedListWithKey. """ # pylint: disable=unused-argument if key is None: return object.__new__(cls) else: if cls is SortedList: - return object.__new__(SortedListWithKey) + return object.__new__(SortedKeyList) else: - raise TypeError('inherit SortedListWithKey for key argument') + raise TypeError('inherit SortedKeyList for key argument') + @property - def key(self): - """Key function used to extract comparison key for sorting.""" + def key(self): # pylint: disable=useless-return + """Function used to extract comparison key from values. + + Sorted list compares values directly so the key function is none. + + """ return None + def _reset(self, load): - """ - Reset sorted list load. - - The *load* specifies the load-factor of the list. The default load - factor of '1000' works well for lists from tens to tens of millions of - elements. Good practice is to use a value that is the cube root of the - list size. With billions of elements, the best load factor depends on - your usage. It's best to leave the load factor at the default until - you start benchmarking. + """Reset sorted list load factor. + + The `load` specifies the load-factor of the list. The default load + factor of 1000 works well for lists from tens to tens-of-millions of + values. Good practice is to use a value that is the cube root of the + list size. With billions of elements, the best load factor depends on + your usage. It's best to leave the load factor at the default until you + start benchmarking. + + See :doc:`implementation` and :doc:`performance-scale` for more + information. + + Runtime complexity: `O(n)` + + :param int load: load-factor for sorted list sublists + """ values = reduce(iadd, self._lists, []) self._clear() self._load = load - self._half = load >> 1 - self._dual = load << 1 self._update(values) + def clear(self): - """Remove all the elements from the list.""" + """Remove all values from sorted list. + + Runtime complexity: `O(n)` + + """ self._len = 0 del self._lists[:] del self._maxes[:] del self._index[:] + self._offset = 0 _clear = clear - def add(self, val): - """Add the element *val* to the list.""" + + def add(self, value): + """Add `value` to sorted list. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> sl = SortedList() + >>> sl.add(3) + >>> sl.add(1) + >>> sl.add(2) + >>> sl + SortedList([1, 2, 3]) + + :param value: value to add to sorted list + + """ _lists = self._lists _maxes = self._maxes if _maxes: - pos = bisect_right(_maxes, val) + pos = bisect_right(_maxes, value) if pos == len(_maxes): pos -= 1 - _lists[pos].append(val) - _maxes[pos] = val + _lists[pos].append(value) + _maxes[pos] = value else: - insort(_lists[pos], val) + insort(_lists[pos], value) self._expand(pos) else: - _lists.append([val]) - _maxes.append(val) + _lists.append([value]) + _maxes.append(value) self._len += 1 + def _expand(self, pos): - """Splits sublists that are more than double the load level. + """Split sublists with length greater than double the load-factor. Updates the index when the sublist length is less than double the load level. This requires incrementing the nodes in a traversal from the - leaf node to the root. For an example traversal see self._loc. + leaf node to the root. For an example traversal see + ``SortedList._loc``. """ + _load = self._load _lists = self._lists _index = self._index - if len(_lists[pos]) > self._dual: + if len(_lists[pos]) > (_load << 1): _maxes = self._maxes - _load = self._load _lists_pos = _lists[pos] half = _lists_pos[_load:] @@ -181,15 +318,28 @@ class SortedList(MutableSequence): child = (child - 1) >> 1 _index[0] += 1 + def update(self, iterable): - """Update the list by adding all elements from *iterable*.""" + """Update sorted list by adding all values from `iterable`. + + Runtime complexity: `O(k*log(n))` -- approximate. + + >>> sl = SortedList() + >>> sl.update([3, 1, 2]) + >>> sl + SortedList([1, 2, 3]) + + :param iterable: iterable of values to add + + """ _lists = self._lists _maxes = self._maxes values = sorted(iterable) if _maxes: if len(values) * 4 >= self._len: - values.extend(chain.from_iterable(_lists)) + _lists.append(values) + values = reduce(iadd, _lists, []) values.sort() self._clear() else: @@ -207,78 +357,123 @@ class SortedList(MutableSequence): _update = update - def __contains__(self, val): - """Return True if and only if *val* is an element in the list.""" + + def __contains__(self, value): + """Return true if `value` is an element of the sorted list. + + ``sl.__contains__(value)`` <==> ``value in sl`` + + Runtime complexity: `O(log(n))` + + >>> sl = SortedList([1, 2, 3, 4, 5]) + >>> 3 in sl + True + + :param value: search for value in sorted list + :return: true if `value` in sorted list + + """ _maxes = self._maxes if not _maxes: return False - pos = bisect_left(_maxes, val) + pos = bisect_left(_maxes, value) if pos == len(_maxes): return False _lists = self._lists - idx = bisect_left(_lists[pos], val) + idx = bisect_left(_lists[pos], value) - return _lists[pos][idx] == val + return _lists[pos][idx] == value - def discard(self, val): - """ - Remove the first occurrence of *val*. - If *val* is not a member, does nothing. + def discard(self, value): + """Remove `value` from sorted list if it is a member. + + If `value` is not a member, do nothing. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> sl = SortedList([1, 2, 3, 4, 5]) + >>> sl.discard(5) + >>> sl.discard(0) + >>> sl == [1, 2, 3, 4] + True + + :param value: `value` to discard from sorted list + """ _maxes = self._maxes if not _maxes: return - pos = bisect_left(_maxes, val) + pos = bisect_left(_maxes, value) if pos == len(_maxes): return _lists = self._lists - idx = bisect_left(_lists[pos], val) + idx = bisect_left(_lists[pos], value) - if _lists[pos][idx] == val: + if _lists[pos][idx] == value: self._delete(pos, idx) - def remove(self, val): - """ - Remove first occurrence of *val*. - Raises ValueError if *val* is not present. + def remove(self, value): + """Remove `value` from sorted list; `value` must be a member. + + If `value` is not a member, raise ValueError. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> sl = SortedList([1, 2, 3, 4, 5]) + >>> sl.remove(5) + >>> sl == [1, 2, 3, 4] + True + >>> sl.remove(0) + Traceback (most recent call last): + ... + ValueError: 0 not in list + + :param value: `value` to remove from sorted list + :raises ValueError: if `value` is not in sorted list + """ - # pylint: disable=arguments-differ _maxes = self._maxes if not _maxes: - raise ValueError('{0!r} not in list'.format(val)) + raise ValueError('{0!r} not in list'.format(value)) - pos = bisect_left(_maxes, val) + pos = bisect_left(_maxes, value) if pos == len(_maxes): - raise ValueError('{0!r} not in list'.format(val)) + raise ValueError('{0!r} not in list'.format(value)) _lists = self._lists - idx = bisect_left(_lists[pos], val) + idx = bisect_left(_lists[pos], value) - if _lists[pos][idx] == val: + if _lists[pos][idx] == value: self._delete(pos, idx) else: - raise ValueError('{0!r} not in list'.format(val)) + raise ValueError('{0!r} not in list'.format(value)) + def _delete(self, pos, idx): - """Delete the item at the given (pos, idx). + """Delete value at the given `(pos, idx)`. Combines lists that are less than half the load level. Updates the index when the sublist length is more than half the load - level. This requires decrementing the nodes in a traversal from the leaf - node to the root. For an example traversal see self._loc. + level. This requires decrementing the nodes in a traversal from the + leaf node to the root. For an example traversal see + ``SortedList._loc``. + + :param int pos: lists index + :param int idx: sublist index + """ _lists = self._lists _maxes = self._maxes @@ -291,8 +486,7 @@ class SortedList(MutableSequence): len_lists_pos = len(_lists_pos) - if len_lists_pos > self._half: - + if len_lists_pos > (self._load >> 1): _maxes[pos] = _lists_pos[-1] if _index: @@ -301,9 +495,7 @@ class SortedList(MutableSequence): _index[child] -= 1 child = (child - 1) >> 1 _index[0] -= 1 - elif len(_lists) > 1: - if not pos: pos += 1 @@ -316,26 +508,24 @@ class SortedList(MutableSequence): del _index[:] self._expand(prev) - elif len_lists_pos: - _maxes[pos] = _lists_pos[-1] - else: - del _lists[pos] del _maxes[pos] del _index[:] + def _loc(self, pos, idx): - """Convert an index pair (alpha, beta) into a single index that corresponds to - the position of the value in the sorted list. + """Convert an index pair (lists index, sublist index) into a single + index number that corresponds to the position of the value in the + sorted list. - Most queries require the index be built. Details of the index are - described in self._build_index. + Many queries require the index be built. Details of the index are + described in ``SortedList._build_index``. Indexing requires traversing the tree from a leaf node to the root. The - parent of each node is easily computable at (pos - 1) // 2. + parent of each node is easily computable at ``(pos - 1) // 2``. Left-child nodes are always at odd indices and right-child nodes are always at even indices. @@ -345,19 +535,19 @@ class SortedList(MutableSequence): The final index is the sum from traversal and the index in the sublist. - For example, using the index from self._build_index: + For example, using the index from ``SortedList._build_index``:: - _index = 14 5 9 3 2 4 5 - _offset = 3 + _index = 14 5 9 3 2 4 5 + _offset = 3 - Tree: + Tree:: 14 5 9 3 2 4 5 - Converting index pair (2, 3) into a single index involves iterating like - so: + Converting an index pair (2, 3) into a single index involves iterating + like so: 1. Starting at the leaf node: offset + alpha = 3 + 2 = 5. We identify the node as a left-child node. At such nodes, we simply traverse to @@ -369,7 +559,12 @@ class SortedList(MutableSequence): 3. Iteration ends at the root. - Computing the index is the sum of the total and beta: 5 + 3 = 8. + The index is then the sum of the total and sublist index: 5 + 3 = 8. + + :param int pos: lists index + :param int idx: sublist index + :return: index in sorted list + """ if not pos: return idx @@ -401,16 +596,18 @@ class SortedList(MutableSequence): return total + idx + def _pos(self, idx): - """Convert an index into a pair (alpha, beta) that can be used to access - the corresponding _lists[alpha][beta] position. + """Convert an index into an index pair (lists index, sublist index) + that can be used to access the corresponding lists position. - Most queries require the index be built. Details of the index are - described in self._build_index. + Many queries require the index be built. Details of the index are + described in ``SortedList._build_index``. - Indexing requires traversing the tree to a leaf node. Each node has - two children which are easily computable. Given an index, pos, the - left-child is at pos * 2 + 1 and the right-child is at pos * 2 + 2. + Indexing requires traversing the tree to a leaf node. Each node has two + children which are easily computable. Given an index, pos, the + left-child is at ``pos * 2 + 1`` and the right-child is at ``pos * 2 + + 2``. When the index is less than the left-child, traversal moves to the left sub-tree. Otherwise, the index is decremented by the left-child @@ -420,12 +617,12 @@ class SortedList(MutableSequence): position of the child node as compared with the offset and the remaining index. - For example, using the index from self._build_index: + For example, using the index from ``SortedList._build_index``:: - _index = 14 5 9 3 2 4 5 - _offset = 3 + _index = 14 5 9 3 2 4 5 + _offset = 3 - Tree: + Tree:: 14 5 9 @@ -450,6 +647,10 @@ class SortedList(MutableSequence): The final index pair from our example is (2, 3) which corresponds to index 8 in the sorted list. + + :param int idx: index in sorted list + :return: (lists index, sublist index) pair + """ if idx < 0: last_len = len(self._lists[-1]) @@ -489,39 +690,42 @@ class SortedList(MutableSequence): return (pos - self._offset, idx) + def _build_index(self): - """Build an index for indexing the sorted list. + """Build a positional index for indexing the sorted list. Indexes are represented as binary trees in a dense array notation similar to a binary heap. - For example, given a _lists representation storing integers: + For example, given a lists representation storing integers:: - [0]: 1 2 3 - [1]: 4 5 - [2]: 6 7 8 9 - [3]: 10 11 12 13 14 + 0: [1, 2, 3] + 1: [4, 5] + 2: [6, 7, 8, 9] + 3: [10, 11, 12, 13, 14] The first transformation maps the sub-lists by their length. The - first row of the index is the length of the sub-lists. + first row of the index is the length of the sub-lists:: - [0]: 3 2 4 5 + 0: [3, 2, 4, 5] - Each row after that is the sum of consecutive pairs of the previous row: + Each row after that is the sum of consecutive pairs of the previous + row:: - [1]: 5 9 - [2]: 14 + 1: [5, 9] + 2: [14] - Finally, the index is built by concatenating these lists together: + Finally, the index is built by concatenating these lists together:: - _index = 14 5 9 3 2 4 5 + _index = [14, 5, 9, 3, 2, 4, 5] - An offset storing the start of the first row is also stored: + An offset storing the start of the first row is also stored:: - _offset = 3 + _offset = 3 When built, the index can be used for efficient indexing into the list. - See the comment and notes on self._pos for details. + See the comment and notes on ``SortedList._pos`` for details. + """ row0 = list(map(len, self._lists)) @@ -542,7 +746,7 @@ class SortedList(MutableSequence): self._offset = 1 return - size = 2 ** (int(log_e(len(row1) - 1, 2)) + 1) + size = 2 ** (int(log(len(row1) - 1, 2)) + 1) row1.extend(repeat(0, size - len(row1))) tree = [row0, row1] @@ -555,10 +759,30 @@ class SortedList(MutableSequence): reduce(iadd, reversed(tree), self._index) self._offset = size * 2 - 1 - def __delitem__(self, idx): - """Remove the element at *idx*. Supports slicing.""" - if isinstance(idx, slice): - start, stop, step = idx.indices(self._len) + + def __delitem__(self, index): + """Remove value at `index` from sorted list. + + ``sl.__delitem__(index)`` <==> ``del sl[index]`` + + Supports slicing. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> sl = SortedList('abcde') + >>> del sl[2] + >>> sl + SortedList(['a', 'b', 'd', 'e']) + >>> del sl[:2] + >>> sl + SortedList(['d', 'e']) + + :param index: integer or slice for indexing + :raises IndexError: if index out of range + + """ + if isinstance(index, slice): + start, stop, step = index.indices(self._len) if step == 1 and start < stop: if start == 0 and stop == self._len: @@ -584,23 +808,53 @@ class SortedList(MutableSequence): pos, idx = _pos(index) _delete(pos, idx) else: - pos, idx = self._pos(idx) + pos, idx = self._pos(index) self._delete(pos, idx) - _delitem = __delitem__ - def __getitem__(self, idx): - """Return the element at *idx*. Supports slicing.""" + def __getitem__(self, index): + """Lookup value at `index` in sorted list. + + ``sl.__getitem__(index)`` <==> ``sl[index]`` + + Supports slicing. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> sl = SortedList('abcde') + >>> sl[1] + 'b' + >>> sl[-1] + 'e' + >>> sl[2:5] + ['c', 'd', 'e'] + + :param index: integer or slice for indexing + :return: value or list of values + :raises IndexError: if index out of range + + """ _lists = self._lists - if isinstance(idx, slice): - start, stop, step = idx.indices(self._len) + if isinstance(index, slice): + start, stop, step = index.indices(self._len) if step == 1 and start < stop: + # Whole slice optimization: start to stop slices the whole + # sorted list. + if start == 0 and stop == self._len: return reduce(iadd, self._lists, []) start_pos, start_idx = self._pos(start) + start_list = _lists[start_pos] + stop_idx = start_idx + stop - start + + # Small slice optimization: start index and stop index are + # within the start list. + + if len(start_list) >= stop_idx: + return start_list[start_idx:stop_idx] if stop == self._len: stop_pos = len(_lists) - 1 @@ -608,9 +862,6 @@ class SortedList(MutableSequence): else: stop_pos, stop_idx = self._pos(stop) - if start_pos == stop_pos: - return _lists[start_pos][start_idx:stop_idx] - prefix = _lists[start_pos][start_idx:] middle = _lists[(start_pos + 1):stop_pos] result = reduce(iadd, middle, prefix) @@ -631,221 +882,104 @@ class SortedList(MutableSequence): return list(self._getitem(index) for index in indices) else: if self._len: - if idx == 0: + if index == 0: return _lists[0][0] - elif idx == -1: + elif index == -1: return _lists[-1][-1] else: raise IndexError('list index out of range') - if 0 <= idx < len(_lists[0]): - return _lists[0][idx] + if 0 <= index < len(_lists[0]): + return _lists[0][index] len_last = len(_lists[-1]) - if -len_last < idx < 0: - return _lists[-1][len_last + idx] + if -len_last < index < 0: + return _lists[-1][len_last + index] - pos, idx = self._pos(idx) + pos, idx = self._pos(index) return _lists[pos][idx] _getitem = __getitem__ - def _check_order(self, idx, val): - _len = self._len - _lists = self._lists - - pos, loc = self._pos(idx) - - if idx < 0: - idx += _len - - # Check that the inserted value is not less than the - # previous value. - - if idx > 0: - idx_prev = loc - 1 - pos_prev = pos - - if idx_prev < 0: - pos_prev -= 1 - idx_prev = len(_lists[pos_prev]) - 1 - - if _lists[pos_prev][idx_prev] > val: - msg = '{0!r} not in sort order at index {1}'.format(val, idx) - raise ValueError(msg) - - # Check that the inserted value is not greater than - # the previous value. - - if idx < (_len - 1): - idx_next = loc + 1 - pos_next = pos - - if idx_next == len(_lists[pos_next]): - pos_next += 1 - idx_next = 0 - - if _lists[pos_next][idx_next] < val: - msg = '{0!r} not in sort order at index {1}'.format(val, idx) - raise ValueError(msg) def __setitem__(self, index, value): - """Replace item at position *index* with *value*. - - Supports slice notation. Raises :exc:`ValueError` if the sort order - would be violated. When used with a slice and iterable, the - :exc:`ValueError` is raised before the list is mutated if the sort - order would be violated by the operation. - - """ - _lists = self._lists - _maxes = self._maxes - _check_order = self._check_order - _pos = self._pos - - if isinstance(index, slice): - _len = self._len - start, stop, step = index.indices(_len) - indices = range(start, stop, step) - - # Copy value to avoid aliasing issues with self and cases where an - # iterator is given. - - values = tuple(value) - - if step != 1: - if len(values) != len(indices): - raise ValueError( - 'attempt to assign sequence of size %s' - ' to extended slice of size %s' - % (len(values), len(indices))) - - # Keep a log of values that are set so that we can - # roll back changes if ordering is violated. - - log = [] - _append = log.append - - for idx, val in zip(indices, values): - pos, loc = _pos(idx) - _append((idx, _lists[pos][loc], val)) - _lists[pos][loc] = val - if len(_lists[pos]) == (loc + 1): - _maxes[pos] = val - - try: - # Validate ordering of new values. - - for idx, _, newval in log: - _check_order(idx, newval) + """Raise not-implemented error. - except ValueError: + ``sl.__setitem__(index, value)`` <==> ``sl[index] = value`` - # Roll back changes from log. + :raises NotImplementedError: use ``del sl[index]`` and + ``sl.add(value)`` instead - for idx, oldval, _ in log: - pos, loc = _pos(idx) - _lists[pos][loc] = oldval - if len(_lists[pos]) == (loc + 1): - _maxes[pos] = oldval - - raise - else: - if start == 0 and stop == _len: - self._clear() - return self._update(values) - - if stop < start: - # When calculating indices, stop may be less than start. - # For example: ...[5:3:1] results in slice(5, 3, 1) which - # is a valid but not useful stop index. - stop = start - - if values: - - # Check that given values are ordered properly. - - alphas = iter(values) - betas = iter(values) - next(betas) - pairs = zip(alphas, betas) - - if not all(alpha <= beta for alpha, beta in pairs): - raise ValueError('given values not in sort order') - - # Check ordering in context of sorted list. - - if start and self._getitem(start - 1) > values[0]: - message = '{0!r} not in sort order at index {1}'.format( - values[0], start) - raise ValueError(message) - - if stop != _len and self._getitem(stop) < values[-1]: - message = '{0!r} not in sort order at index {1}'.format( - values[-1], stop) - raise ValueError(message) - - # Delete the existing values. + """ + message = 'use ``del sl[index]`` and ``sl.add(value)`` instead' + raise NotImplementedError(message) - self._delitem(index) - # Insert the new values. + def __iter__(self): + """Return an iterator over the sorted list. - _insert = self.insert - for idx, val in enumerate(values): - _insert(start + idx, val) - else: - pos, loc = _pos(index) - _check_order(index, value) - _lists[pos][loc] = value - if len(_lists[pos]) == (loc + 1): - _maxes[pos] = value + ``sl.__iter__()`` <==> ``iter(sl)`` - def __iter__(self): - """ - Return an iterator over the Sequence. + Iterating the sorted list while adding or deleting values may raise a + :exc:`RuntimeError` or fail to iterate over all values. - Iterating the Sequence while adding or deleting values may raise a - `RuntimeError` or fail to iterate over all entries. """ return chain.from_iterable(self._lists) + def __reversed__(self): - """ - Return an iterator to traverse the Sequence in reverse. + """Return a reverse iterator over the sorted list. + + ``sl.__reversed__()`` <==> ``reversed(sl)`` + + Iterating the sorted list while adding or deleting values may raise a + :exc:`RuntimeError` or fail to iterate over all values. - Iterating the Sequence while adding or deleting values may raise a - `RuntimeError` or fail to iterate over all entries. """ return chain.from_iterable(map(reversed, reversed(self._lists))) + def reverse(self): - """Raise NotImplementedError + """Raise not-implemented error. - SortedList maintains values in ascending sort order. Values may not be + Sorted list maintains values in ascending sort order. Values may not be reversed in-place. - Use ``reversed(sorted_list)`` for a reverse iterator over values in - descending sort order. + Use ``reversed(sl)`` for an iterator over values in descending sort + order. - Implemented to override MutableSequence.reverse which provides an + Implemented to override `MutableSequence.reverse` which provides an erroneous default implementation. + :raises NotImplementedError: use ``reversed(sl)`` instead + """ - raise NotImplementedError('.reverse() not defined') + raise NotImplementedError('use ``reversed(sl)`` instead') - def islice(self, start=None, stop=None, reverse=False): - """ - Returns an iterator that slices `self` from `start` to `stop` index, - inclusive and exclusive respectively. + def islice(self, start=None, stop=None, reverse=False): + """Return an iterator that slices sorted list from `start` to `stop`. - When `reverse` is `True`, values are yielded from the iterator in - reverse order. + The `start` and `stop` index are treated inclusive and exclusive, + respectively. Both `start` and `stop` default to `None` which is automatically - inclusive of the beginning and end. + inclusive of the beginning and end of the sorted list. + + When `reverse` is `True` the values are yielded from the iterator in + reverse order; `reverse` defaults to `False`. + + >>> sl = SortedList('abcdefghij') + >>> it = sl.islice(2, 6) + >>> list(it) + ['c', 'd', 'e', 'f'] + + :param int start: start index (inclusive) + :param int stop: stop index (exclusive) + :param bool reverse: yield values in reverse order + :return: iterator + """ _len = self._len @@ -869,60 +1003,97 @@ class SortedList(MutableSequence): return self._islice(min_pos, min_idx, max_pos, max_idx, reverse) + def _islice(self, min_pos, min_idx, max_pos, max_idx, reverse): - """ - Returns an iterator that slices `self` using two index pairs, - `(min_pos, min_idx)` and `(max_pos, max_idx)`; the first inclusive - and the latter exclusive. See `_pos` for details on how an index - is converted to an index pair. + """Return an iterator that slices sorted list using two index pairs. + + The index pairs are (min_pos, min_idx) and (max_pos, max_idx), the + first inclusive and the latter exclusive. See `_pos` for details on how + an index is converted to an index pair. When `reverse` is `True`, values are yielded from the iterator in reverse order. + """ _lists = self._lists if min_pos > max_pos: return iter(()) - elif min_pos == max_pos and not reverse: - return iter(_lists[min_pos][min_idx:max_idx]) - elif min_pos == max_pos and reverse: - return reversed(_lists[min_pos][min_idx:max_idx]) - elif min_pos + 1 == max_pos and not reverse: - return chain(_lists[min_pos][min_idx:], _lists[max_pos][:max_idx]) - elif min_pos + 1 == max_pos and reverse: + + if min_pos == max_pos: + if reverse: + indices = reversed(range(min_idx, max_idx)) + return map(_lists[min_pos].__getitem__, indices) + + indices = range(min_idx, max_idx) + return map(_lists[min_pos].__getitem__, indices) + + next_pos = min_pos + 1 + + if next_pos == max_pos: + if reverse: + min_indices = range(min_idx, len(_lists[min_pos])) + max_indices = range(max_idx) + return chain( + map(_lists[max_pos].__getitem__, reversed(max_indices)), + map(_lists[min_pos].__getitem__, reversed(min_indices)), + ) + + min_indices = range(min_idx, len(_lists[min_pos])) + max_indices = range(max_idx) return chain( - reversed(_lists[max_pos][:max_idx]), - reversed(_lists[min_pos][min_idx:]), + map(_lists[min_pos].__getitem__, min_indices), + map(_lists[max_pos].__getitem__, max_indices), ) - elif not reverse: + + if reverse: + min_indices = range(min_idx, len(_lists[min_pos])) + sublist_indices = range(next_pos, max_pos) + sublists = map(_lists.__getitem__, reversed(sublist_indices)) + max_indices = range(max_idx) return chain( - _lists[min_pos][min_idx:], - chain.from_iterable(_lists[(min_pos + 1):max_pos]), - _lists[max_pos][:max_idx], + map(_lists[max_pos].__getitem__, reversed(max_indices)), + chain.from_iterable(map(reversed, sublists)), + map(_lists[min_pos].__getitem__, reversed(min_indices)), ) - temp = map(reversed, reversed(_lists[(min_pos + 1):max_pos])) + min_indices = range(min_idx, len(_lists[min_pos])) + sublist_indices = range(next_pos, max_pos) + sublists = map(_lists.__getitem__, sublist_indices) + max_indices = range(max_idx) return chain( - reversed(_lists[max_pos][:max_idx]), - chain.from_iterable(temp), - reversed(_lists[min_pos][min_idx:]), + map(_lists[min_pos].__getitem__, min_indices), + chain.from_iterable(sublists), + map(_lists[max_pos].__getitem__, max_indices), ) + def irange(self, minimum=None, maximum=None, inclusive=(True, True), reverse=False): - """ - Create an iterator of values between `minimum` and `maximum`. - - `inclusive` is a pair of booleans that indicates whether the minimum - and maximum ought to be included in the range, respectively. The - default is (True, True) such that the range is inclusive of both - minimum and maximum. + """Create an iterator of values between `minimum` and `maximum`. Both `minimum` and `maximum` default to `None` which is automatically - inclusive of the start and end of the list, respectively. + inclusive of the beginning and end of the sorted list. + + The argument `inclusive` is a pair of booleans that indicates whether + the minimum and maximum ought to be included in the range, + respectively. The default is ``(True, True)`` such that the range is + inclusive of both minimum and maximum. When `reverse` is `True` the values are yielded from the iterator in reverse order; `reverse` defaults to `False`. + + >>> sl = SortedList('abcdefghij') + >>> it = sl.irange('c', 'f') + >>> list(it) + ['c', 'd', 'e', 'f'] + + :param minimum: minimum value to start iterating + :param maximum: maximum value to stop iterating + :param inclusive: pair of booleans + :param bool reverse: yield values in reverse order + :return: iterator + """ _maxes = self._maxes @@ -979,286 +1150,263 @@ class SortedList(MutableSequence): return self._islice(min_pos, min_idx, max_pos, max_idx, reverse) + def __len__(self): - """Return the number of elements in the list.""" - return self._len + """Return the size of the sorted list. + + ``sl.__len__()`` <==> ``len(sl)`` + + :return: size of sorted list - def bisect_left(self, val): """ - Similar to the *bisect* module in the standard library, this returns an - appropriate index to insert *val*. If *val* is already present, the - insertion point will be before (to the left of) any existing entries. + return self._len + + + def bisect_left(self, value): + """Return an index to insert `value` in the sorted list. + + If the `value` is already present, the insertion point will be before + (to the left of) any existing values. + + Similar to the `bisect` module in the standard library. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> sl = SortedList([10, 11, 12, 13, 14]) + >>> sl.bisect_left(12) + 2 + + :param value: insertion index of value in sorted list + :return: index + """ _maxes = self._maxes if not _maxes: return 0 - pos = bisect_left(_maxes, val) + pos = bisect_left(_maxes, value) if pos == len(_maxes): return self._len - idx = bisect_left(self._lists[pos], val) - + idx = bisect_left(self._lists[pos], value) return self._loc(pos, idx) - def bisect_right(self, val): - """ - Same as *bisect_left*, but if *val* is already present, the insertion - point will be after (to the right of) any existing entries. + + def bisect_right(self, value): + """Return an index to insert `value` in the sorted list. + + Similar to `bisect_left`, but if `value` is already present, the + insertion point will be after (to the right of) any existing values. + + Similar to the `bisect` module in the standard library. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> sl = SortedList([10, 11, 12, 13, 14]) + >>> sl.bisect_right(12) + 3 + + :param value: insertion index of value in sorted list + :return: index + """ _maxes = self._maxes if not _maxes: return 0 - pos = bisect_right(_maxes, val) + pos = bisect_right(_maxes, value) if pos == len(_maxes): return self._len - idx = bisect_right(self._lists[pos], val) - + idx = bisect_right(self._lists[pos], value) return self._loc(pos, idx) bisect = bisect_right _bisect_right = bisect_right - def count(self, val): - """Return the number of occurrences of *val* in the list.""" - # pylint: disable=arguments-differ + + def count(self, value): + """Return number of occurrences of `value` in the sorted list. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> sl = SortedList([1, 2, 2, 3, 3, 3, 4, 4, 4, 4]) + >>> sl.count(3) + 3 + + :param value: value to count in sorted list + :return: count + + """ _maxes = self._maxes if not _maxes: return 0 - pos_left = bisect_left(_maxes, val) + pos_left = bisect_left(_maxes, value) if pos_left == len(_maxes): return 0 _lists = self._lists - idx_left = bisect_left(_lists[pos_left], val) - pos_right = bisect_right(_maxes, val) + idx_left = bisect_left(_lists[pos_left], value) + pos_right = bisect_right(_maxes, value) if pos_right == len(_maxes): return self._len - self._loc(pos_left, idx_left) - idx_right = bisect_right(_lists[pos_right], val) + idx_right = bisect_right(_lists[pos_right], value) if pos_left == pos_right: return idx_right - idx_left right = self._loc(pos_right, idx_right) left = self._loc(pos_left, idx_left) - return right - left + def copy(self): - """Return a shallow copy of the sorted list.""" - return self.__class__(self) + """Return a shallow copy of the sorted list. - __copy__ = copy + Runtime complexity: `O(n)` + + :return: new sorted list - def append(self, val): - """ - Append the element *val* to the list. Raises a ValueError if the *val* - would violate the sort order. """ - # pylint: disable=arguments-differ - _lists = self._lists - _maxes = self._maxes + return self.__class__(self) - if not _maxes: - _maxes.append(val) - _lists.append([val]) - self._len = 1 - return + __copy__ = copy - pos = len(_lists) - 1 - if val < _lists[pos][-1]: - msg = '{0!r} not in sort order at index {1}'.format(val, self._len) - raise ValueError(msg) + def append(self, value): + """Raise not-implemented error. - _maxes[pos] = val - _lists[pos].append(val) - self._len += 1 - self._expand(pos) + Implemented to override `MutableSequence.append` which provides an + erroneous default implementation. - def extend(self, values): - """ - Extend the list by appending all elements from the *values*. Raises a - ValueError if the sort order would be violated. - """ - _lists = self._lists - _maxes = self._maxes - _load = self._load - - if not isinstance(values, list): - values = list(values) + :raises NotImplementedError: use ``sl.add(value)`` instead - if not values: - return - - if any(values[pos - 1] > values[pos] - for pos in range(1, len(values))): - raise ValueError('given sequence not in sort order') + """ + raise NotImplementedError('use ``sl.add(value)`` instead') - offset = 0 - if _maxes: - if values[0] < _lists[-1][-1]: - msg = '{0!r} not in sort order at index {1}'.format(values[0], self._len) - raise ValueError(msg) + def extend(self, values): + """Raise not-implemented error. - if len(_lists[-1]) < self._half: - _lists[-1].extend(values[:_load]) - _maxes[-1] = _lists[-1][-1] - offset = _load + Implemented to override `MutableSequence.extend` which provides an + erroneous default implementation. - len_lists = len(_lists) + :raises NotImplementedError: use ``sl.update(values)`` instead - for idx in range(offset, len(values), _load): - _lists.append(values[idx:(idx + _load)]) - _maxes.append(_lists[-1][-1]) + """ + raise NotImplementedError('use ``sl.update(values)`` instead') - _index = self._index - if len_lists == len(_lists): - len_index = len(_index) - if len_index > 0: - len_values = len(values) - child = len_index - 1 - while child: - _index[child] += len_values - child = (child - 1) >> 1 - _index[0] += len_values - else: - del _index[:] + def insert(self, index, value): + """Raise not-implemented error. - self._len += len(values) + :raises NotImplementedError: use ``sl.add(value)`` instead - def insert(self, idx, val): - """ - Insert the element *val* into the list at *idx*. Raises a ValueError if - the *val* at *idx* would violate the sort order. """ - # pylint: disable=arguments-differ - _len = self._len - _lists = self._lists - _maxes = self._maxes + raise NotImplementedError('use ``sl.add(value)`` instead') - if idx < 0: - idx += _len - if idx < 0: - idx = 0 - if idx > _len: - idx = _len - if not _maxes: - # The idx must be zero by the inequalities above. - _maxes.append(val) - _lists.append([val]) - self._len = 1 - return + def pop(self, index=-1): + """Remove and return value at `index` in sorted list. - if not idx: - if val > _lists[0][0]: - msg = '{0!r} not in sort order at index {1}'.format(val, 0) - raise ValueError(msg) - else: - _lists[0].insert(0, val) - self._expand(0) - self._len += 1 - return + Raise :exc:`IndexError` if the sorted list is empty or index is out of + range. - if idx == _len: - pos = len(_lists) - 1 - if _lists[pos][-1] > val: - msg = '{0!r} not in sort order at index {1}'.format(val, _len) - raise ValueError(msg) - else: - _lists[pos].append(val) - _maxes[pos] = _lists[pos][-1] - self._expand(pos) - self._len += 1 - return + Negative indices are supported. - pos, idx = self._pos(idx) - idx_before = idx - 1 - if idx_before < 0: - pos_before = pos - 1 - idx_before = len(_lists[pos_before]) - 1 - else: - pos_before = pos + Runtime complexity: `O(log(n))` -- approximate. - before = _lists[pos_before][idx_before] - if before <= val <= _lists[pos][idx]: - _lists[pos].insert(idx, val) - self._expand(pos) - self._len += 1 - else: - msg = '{0!r} not in sort order at index {1}'.format(val, idx) - raise ValueError(msg) + >>> sl = SortedList('abcde') + >>> sl.pop() + 'e' + >>> sl.pop(2) + 'c' + >>> sl + SortedList(['a', 'b', 'd']) + + :param int index: index of value (default -1) + :return: value + :raises IndexError: if index is out of range - def pop(self, idx=-1): - """ - Remove and return item at *idx* (default last). Raises IndexError if - list is empty or index is out of range. Negative indices are supported, - as for slice indices. """ - # pylint: disable=arguments-differ if not self._len: raise IndexError('pop index out of range') _lists = self._lists - if idx == 0: + if index == 0: val = _lists[0][0] self._delete(0, 0) return val - if idx == -1: + if index == -1: pos = len(_lists) - 1 loc = len(_lists[pos]) - 1 val = _lists[pos][loc] self._delete(pos, loc) return val - if 0 <= idx < len(_lists[0]): - val = _lists[0][idx] - self._delete(0, idx) + if 0 <= index < len(_lists[0]): + val = _lists[0][index] + self._delete(0, index) return val len_last = len(_lists[-1]) - if -len_last < idx < 0: + if -len_last < index < 0: pos = len(_lists) - 1 - loc = len_last + idx + loc = len_last + index val = _lists[pos][loc] self._delete(pos, loc) return val - pos, idx = self._pos(idx) + pos, idx = self._pos(index) val = _lists[pos][idx] self._delete(pos, idx) - return val - def index(self, val, start=None, stop=None): - """ - Return the smallest *k* such that L[k] == val and i <= k < j`. Raises - ValueError if *val* is not present. *stop* defaults to the end of the - list. *start* defaults to the beginning. Negative indices are supported, - as for slice indices. + + def index(self, value, start=None, stop=None): + """Return first index of value in sorted list. + + Raise ValueError if `value` is not present. + + Index must be between `start` and `stop` for the `value` to be + considered present. The default value, None, for `start` and `stop` + indicate the beginning and end of the sorted list. + + Negative indices are supported. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> sl = SortedList('abcde') + >>> sl.index('d') + 3 + >>> sl.index('z') + Traceback (most recent call last): + ... + ValueError: 'z' is not in list + + :param value: value in sorted list + :param int start: start index (default None, start of sorted list) + :param int stop: stop index (default None, end of sorted list) + :return: index of value + :raises ValueError: if value is not present + """ - # pylint: disable=arguments-differ _len = self._len if not _len: - raise ValueError('{0!r} is not in list'.format(val)) + raise ValueError('{0!r} is not in list'.format(value)) if start is None: start = 0 @@ -1275,19 +1423,19 @@ class SortedList(MutableSequence): stop = _len if stop <= start: - raise ValueError('{0!r} is not in list'.format(val)) + raise ValueError('{0!r} is not in list'.format(value)) _maxes = self._maxes - pos_left = bisect_left(_maxes, val) + pos_left = bisect_left(_maxes, value) if pos_left == len(_maxes): - raise ValueError('{0!r} is not in list'.format(val)) + raise ValueError('{0!r} is not in list'.format(value)) _lists = self._lists - idx_left = bisect_left(_lists[pos_left], val) + idx_left = bisect_left(_lists[pos_left], value) - if _lists[pos_left][idx_left] != val: - raise ValueError('{0!r} is not in list'.format(val)) + if _lists[pos_left][idx_left] != value: + raise ValueError('{0!r} is not in list'.format(value)) stop -= 1 left = self._loc(pos_left, idx_left) @@ -1296,153 +1444,220 @@ class SortedList(MutableSequence): if left <= stop: return left else: - right = self._bisect_right(val) - 1 + right = self._bisect_right(value) - 1 if start <= right: return start - raise ValueError('{0!r} is not in list'.format(val)) + raise ValueError('{0!r} is not in list'.format(value)) + + + def __add__(self, other): + """Return new sorted list containing all values in both sequences. + + ``sl.__add__(other)`` <==> ``sl + other`` + + Values in `other` do not need to be in sorted order. + + Runtime complexity: `O(n*log(n))` + + >>> sl1 = SortedList('bat') + >>> sl2 = SortedList('cat') + >>> sl1 + sl2 + SortedList(['a', 'a', 'b', 'c', 't', 't']) + + :param other: other iterable + :return: new sorted list - def __add__(self, that): - """ - Return a new sorted list containing all the elements in *self* and - *that*. Elements in *that* do not need to be properly ordered with - respect to *self*. """ values = reduce(iadd, self._lists, []) - values.extend(that) + values.extend(other) return self.__class__(values) - def __iadd__(self, that): - """ - Update *self* to include all values in *that*. Elements in *that* do not - need to be properly ordered with respect to *self*. + __radd__ = __add__ + + + def __iadd__(self, other): + """Update sorted list with values from `other`. + + ``sl.__iadd__(other)`` <==> ``sl += other`` + + Values in `other` do not need to be in sorted order. + + Runtime complexity: `O(k*log(n))` -- approximate. + + >>> sl = SortedList('bat') + >>> sl += 'cat' + >>> sl + SortedList(['a', 'a', 'b', 'c', 't', 't']) + + :param other: other iterable + :return: existing sorted list + """ - self._update(that) + self._update(other) return self - def __mul__(self, that): - """ - Return a new sorted list containing *that* shallow copies of each item - in SortedList. + + def __mul__(self, num): + """Return new sorted list with `num` shallow copies of values. + + ``sl.__mul__(num)`` <==> ``sl * num`` + + Runtime complexity: `O(n*log(n))` + + >>> sl = SortedList('abc') + >>> sl * 3 + SortedList(['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'c']) + + :param int num: count of shallow copies + :return: new sorted list + """ - values = reduce(iadd, self._lists, []) * that + values = reduce(iadd, self._lists, []) * num return self.__class__(values) - def __imul__(self, that): - """ - Increase the length of the list by appending *that* shallow copies of - each item. + __rmul__ = __mul__ + + + def __imul__(self, num): + """Update the sorted list with `num` shallow copies of values. + + ``sl.__imul__(num)`` <==> ``sl *= num`` + + Runtime complexity: `O(n*log(n))` + + >>> sl = SortedList('abc') + >>> sl *= 3 + >>> sl + SortedList(['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'c']) + + :param int num: count of shallow copies + :return: existing sorted list + """ - values = reduce(iadd, self._lists, []) * that + values = reduce(iadd, self._lists, []) * num self._clear() self._update(values) return self - def _make_cmp(self, seq_op, doc): + + def __make_cmp(seq_op, symbol, doc): "Make comparator method." - def comparer(self, that): + def comparer(self, other): "Compare method for sorted list and sequence." - # pylint: disable=protected-access - if not isinstance(that, Sequence): + if not isinstance(other, Sequence): return NotImplemented self_len = self._len - len_that = len(that) + len_other = len(other) - if self_len != len_that: - if seq_op is op.eq: + if self_len != len_other: + if seq_op is eq: return False - if seq_op is op.ne: + if seq_op is ne: return True - for alpha, beta in zip(self, that): + for alpha, beta in zip(self, other): if alpha != beta: return seq_op(alpha, beta) - return seq_op(self_len, len_that) + return seq_op(self_len, len_other) + + seq_op_name = seq_op.__name__ + comparer.__name__ = '__{0}__'.format(seq_op_name) + doc_str = """Return true if and only if sorted list is {0} `other`. + + ``sl.__{1}__(other)`` <==> ``sl {2} other`` + + Comparisons use lexicographical order as with sequences. + + Runtime complexity: `O(n)` - comparer.__name__ = '__{0}__'.format(seq_op.__name__) - doc_str = 'Return `True` if and only if Sequence is {0} `that`.' - comparer.__doc__ = doc_str.format(doc) + :param other: `other` sequence + :return: true if sorted list is {0} `other` + """ + comparer.__doc__ = dedent(doc_str.format(doc, seq_op_name, symbol)) return comparer - __eq__ = _make_cmp(None, op.eq, 'equal to') - __ne__ = _make_cmp(None, op.ne, 'not equal to') - __lt__ = _make_cmp(None, op.lt, 'less than') - __gt__ = _make_cmp(None, op.gt, 'greater than') - __le__ = _make_cmp(None, op.le, 'less than or equal to') - __ge__ = _make_cmp(None, op.ge, 'greater than or equal to') - @recursive_repr - def __repr__(self): - """Return string representation of sequence.""" - return '{0}({1!r})'.format(type(self).__name__, list(self)) + __eq__ = __make_cmp(eq, '==', 'equal to') + __ne__ = __make_cmp(ne, '!=', 'not equal to') + __lt__ = __make_cmp(lt, '<', 'less than') + __gt__ = __make_cmp(gt, '>', 'greater than') + __le__ = __make_cmp(le, '<=', 'less than or equal to') + __ge__ = __make_cmp(ge, '>=', 'greater than or equal to') + __make_cmp = staticmethod(__make_cmp) - def _check(self): - try: - # Check load parameters. - assert self._load >= 4 - assert self._half == (self._load >> 1) - assert self._dual == (self._load << 1) + def __reduce__(self): + values = reduce(iadd, self._lists, []) + return (type(self), (values,)) - # Check empty sorted list case. - if self._maxes == []: - assert self._lists == [] - return + @recursive_repr() + def __repr__(self): + """Return string representation of sorted list. - assert self._maxes and self._lists + ``sl.__repr__()`` <==> ``repr(sl)`` - # Check all sublists are sorted. + :return: string representation - assert all(sublist[pos - 1] <= sublist[pos] - for sublist in self._lists - for pos in range(1, len(sublist))) + """ + return '{0}({1!r})'.format(type(self).__name__, list(self)) - # Check beginning/end of sublists are sorted. - for pos in range(1, len(self._lists)): - assert self._lists[pos - 1][-1] <= self._lists[pos][0] + def _check(self): + """Check invariants of sorted list. - # Check length of _maxes and _lists match. + Runtime complexity: `O(n)` + """ + try: + assert self._load >= 4 assert len(self._maxes) == len(self._lists) + assert self._len == sum(len(sublist) for sublist in self._lists) - # Check _maxes is a map of _lists. + # Check all sublists are sorted. - assert all(self._maxes[pos] == self._lists[pos][-1] - for pos in range(len(self._maxes))) + for sublist in self._lists: + for pos in range(1, len(sublist)): + assert sublist[pos - 1] <= sublist[pos] - # Check load level is less than _dual. + # Check beginning/end of sublists are sorted. - assert all(len(sublist) <= self._dual for sublist in self._lists) + for pos in range(1, len(self._lists)): + assert self._lists[pos - 1][-1] <= self._lists[pos][0] - # Check load level is greater than _half for all - # but the last sublist. + # Check _maxes index is the last value of each sublist. - assert all(len(self._lists[pos]) >= self._half - for pos in range(0, len(self._lists) - 1)) + for pos in range(len(self._maxes)): + assert self._maxes[pos] == self._lists[pos][-1] - # Check length. + # Check sublist lengths are less than double load-factor. - assert self._len == sum(len(sublist) for sublist in self._lists) + double = self._load << 1 + assert all(len(sublist) <= double for sublist in self._lists) + + # Check sublist lengths are greater than half load-factor for all + # but the last sublist. - # Check index. + half = self._load >> 1 + for pos in range(0, len(self._lists) - 1): + assert len(self._lists[pos]) >= half if self._index: - assert len(self._index) == self._offset + len(self._lists) assert self._len == self._index[0] + assert len(self._index) == self._offset + len(self._lists) + + # Check index leaf nodes equal length of sublists. - def test_offset_pos(pos): - "Test positional indexing offset." - from_index = self._index[self._offset + pos] - return from_index == len(self._lists[pos]) + for pos in range(len(self._lists)): + leaf = self._index[self._offset + pos] + assert leaf == len(self._lists[pos]) - assert all(test_offset_pos(pos) - for pos in range(len(self._lists))) + # Check index branch nodes are the sum of their children. for pos in range(self._offset): child = (pos << 1) + 1 @@ -1452,16 +1667,11 @@ class SortedList(MutableSequence): assert self._index[pos] == self._index[child] else: child_sum = self._index[child] + self._index[child + 1] - assert self._index[pos] == child_sum - + assert child_sum == self._index[pos] except: - import sys - import traceback - traceback.print_exc(file=sys.stdout) - print('len', self._len) - print('load', self._load, self._half, self._dual) + print('load', self._load) print('offset', self._offset) print('len_index', len(self._index)) print('index', self._index) @@ -1469,55 +1679,92 @@ class SortedList(MutableSequence): print('maxes', self._maxes) print('len_lists', len(self._lists)) print('lists', self._lists) - raise + def identity(value): "Identity function." return value -class SortedListWithKey(SortedList): - """ - SortedListWithKey provides most of the same methods as a list but keeps - the items in sorted order. + +class SortedKeyList(SortedList): + """Sorted-key list is a subtype of sorted list. + + The sorted-key list maintains values in comparison order based on the + result of a key function applied to every value. + + All the same methods that are available in :class:`SortedList` are also + available in :class:`SortedKeyList`. + + Additional methods provided: + + * :attr:`SortedKeyList.key` + * :func:`SortedKeyList.bisect_key_left` + * :func:`SortedKeyList.bisect_key_right` + * :func:`SortedKeyList.irange_key` + + Some examples below use: + + >>> from operator import neg + >>> neg + <built-in function neg> + >>> neg(1) + -1 + """ - # pylint: disable=too-many-ancestors,abstract-method def __init__(self, iterable=None, key=identity): - """SortedListWithKey provides most of the same methods as list but keeps the - items in sorted order. + """Initialize sorted-key list instance. - An optional *iterable* provides an initial series of items to populate - the SortedListWithKey. + Optional `iterable` argument provides an initial iterable of values to + initialize the sorted-key list. - An optional *key* argument defines a callable that, like the `key` + Optional `key` argument defines a callable that, like the `key` argument to Python's `sorted` function, extracts a comparison key from - each element. The default is the identity function. + each value. The default is the identity function. + + Runtime complexity: `O(n*log(n))` + + >>> from operator import neg + >>> skl = SortedKeyList(key=neg) + >>> skl + SortedKeyList([], key=<built-in function neg>) + >>> skl = SortedKeyList([3, 1, 2], key=neg) + >>> skl + SortedKeyList([3, 2, 1], key=<built-in function neg>) + + :param iterable: initial values (optional) + :param key: function used to extract comparison key (optional) + """ - # pylint: disable=super-init-not-called + self._key = key self._len = 0 + self._load = self.DEFAULT_LOAD_FACTOR self._lists = [] self._keys = [] self._maxes = [] self._index = [] - self._key = key - self._load = LOAD - self._half = LOAD >> 1 - self._dual = LOAD << 1 self._offset = 0 if iterable is not None: self._update(iterable) + def __new__(cls, iterable=None, key=identity): return object.__new__(cls) + @property def key(self): - """Key function used to extract comparison key for sorting.""" + "Function used to extract comparison key from values." return self._key + def clear(self): - """Remove all the elements from the list.""" + """Remove all values from sorted-key list. + + Runtime complexity: `O(n)` + + """ self._len = 0 del self._lists[:] del self._keys[:] @@ -1526,48 +1773,65 @@ class SortedListWithKey(SortedList): _clear = clear - def add(self, val): - """Add the element *val* to the list.""" + + def add(self, value): + """Add `value` to sorted-key list. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> from operator import neg + >>> skl = SortedKeyList(key=neg) + >>> skl.add(3) + >>> skl.add(1) + >>> skl.add(2) + >>> skl + SortedKeyList([3, 2, 1], key=<built-in function neg>) + + :param value: value to add to sorted-key list + + """ _lists = self._lists _keys = self._keys _maxes = self._maxes - key = self._key(val) + key = self._key(value) if _maxes: pos = bisect_right(_maxes, key) if pos == len(_maxes): pos -= 1 - _lists[pos].append(val) + _lists[pos].append(value) _keys[pos].append(key) _maxes[pos] = key else: idx = bisect_right(_keys[pos], key) - _lists[pos].insert(idx, val) + _lists[pos].insert(idx, value) _keys[pos].insert(idx, key) self._expand(pos) else: - _lists.append([val]) + _lists.append([value]) _keys.append([key]) _maxes.append(key) self._len += 1 + def _expand(self, pos): - """Splits sublists that are more than double the load level. + """Split sublists with length greater than double the load-factor. Updates the index when the sublist length is less than double the load level. This requires incrementing the nodes in a traversal from the - leaf node to the root. For an example traversal see self._loc. + leaf node to the root. For an example traversal see + ``SortedList._loc``. """ _lists = self._lists _keys = self._keys _index = self._index - if len(_keys[pos]) > self._dual: + if len(_keys[pos]) > (self._load << 1): _maxes = self._maxes _load = self._load @@ -1592,8 +1856,21 @@ class SortedListWithKey(SortedList): child = (child - 1) >> 1 _index[0] += 1 + def update(self, iterable): - """Update the list by adding all elements from *iterable*.""" + """Update sorted-key list by adding all values from `iterable`. + + Runtime complexity: `O(k*log(n))` -- approximate. + + >>> from operator import neg + >>> skl = SortedKeyList(key=neg) + >>> skl.update([3, 1, 2]) + >>> skl + SortedKeyList([3, 2, 1], key=<built-in function neg>) + + :param iterable: iterable of values to add + + """ _lists = self._lists _keys = self._keys _maxes = self._maxes @@ -1601,7 +1878,8 @@ class SortedListWithKey(SortedList): if _maxes: if len(values) * 4 >= self._len: - values.extend(chain.from_iterable(_lists)) + _lists.append(values) + values = reduce(iadd, _lists, []) values.sort(key=self._key) self._clear() else: @@ -1620,14 +1898,29 @@ class SortedListWithKey(SortedList): _update = update - def __contains__(self, val): - """Return True if and only if *val* is an element in the list.""" + + def __contains__(self, value): + """Return true if `value` is an element of the sorted-key list. + + ``skl.__contains__(value)`` <==> ``value in skl`` + + Runtime complexity: `O(log(n))` + + >>> from operator import neg + >>> skl = SortedKeyList([1, 2, 3, 4, 5], key=neg) + >>> 3 in skl + True + + :param value: search for value in sorted-key list + :return: true if `value` in sorted-key list + + """ _maxes = self._maxes if not _maxes: return False - key = self._key(val) + key = self._key(value) pos = bisect_left(_maxes, key) if pos == len(_maxes): @@ -1644,7 +1937,7 @@ class SortedListWithKey(SortedList): while True: if _keys[pos][idx] != key: return False - if _lists[pos][idx] == val: + if _lists[pos][idx] == value: return True idx += 1 if idx == len_sublist: @@ -1654,18 +1947,30 @@ class SortedListWithKey(SortedList): len_sublist = len(_keys[pos]) idx = 0 - def discard(self, val): - """ - Remove the first occurrence of *val*. - If *val* is not a member, does nothing. + def discard(self, value): + """Remove `value` from sorted-key list if it is a member. + + If `value` is not a member, do nothing. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> from operator import neg + >>> skl = SortedKeyList([5, 4, 3, 2, 1], key=neg) + >>> skl.discard(1) + >>> skl.discard(0) + >>> skl == [5, 4, 3, 2] + True + + :param value: `value` to discard from sorted-key list + """ _maxes = self._maxes if not _maxes: return - key = self._key(val) + key = self._key(value) pos = bisect_left(_maxes, key) if pos == len(_maxes): @@ -1680,7 +1985,7 @@ class SortedListWithKey(SortedList): while True: if _keys[pos][idx] != key: return - if _lists[pos][idx] == val: + if _lists[pos][idx] == value: self._delete(pos, idx) return idx += 1 @@ -1691,22 +1996,38 @@ class SortedListWithKey(SortedList): len_sublist = len(_keys[pos]) idx = 0 - def remove(self, val): - """ - Remove first occurrence of *val*. - Raises ValueError if *val* is not present. + def remove(self, value): + """Remove `value` from sorted-key list; `value` must be a member. + + If `value` is not a member, raise ValueError. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> from operator import neg + >>> skl = SortedKeyList([1, 2, 3, 4, 5], key=neg) + >>> skl.remove(5) + >>> skl == [4, 3, 2, 1] + True + >>> skl.remove(0) + Traceback (most recent call last): + ... + ValueError: 0 not in list + + :param value: `value` to remove from sorted-key list + :raises ValueError: if `value` is not in sorted-key list + """ _maxes = self._maxes if not _maxes: - raise ValueError('{0!r} not in list'.format(val)) + raise ValueError('{0!r} not in list'.format(value)) - key = self._key(val) + key = self._key(value) pos = bisect_left(_maxes, key) if pos == len(_maxes): - raise ValueError('{0!r} not in list'.format(val)) + raise ValueError('{0!r} not in list'.format(value)) _lists = self._lists _keys = self._keys @@ -1716,27 +2037,32 @@ class SortedListWithKey(SortedList): while True: if _keys[pos][idx] != key: - raise ValueError('{0!r} not in list'.format(val)) - if _lists[pos][idx] == val: + raise ValueError('{0!r} not in list'.format(value)) + if _lists[pos][idx] == value: self._delete(pos, idx) return idx += 1 if idx == len_sublist: pos += 1 if pos == len_keys: - raise ValueError('{0!r} not in list'.format(val)) + raise ValueError('{0!r} not in list'.format(value)) len_sublist = len(_keys[pos]) idx = 0 + def _delete(self, pos, idx): - """ - Delete the item at the given (pos, idx). + """Delete value at the given `(pos, idx)`. Combines lists that are less than half the load level. Updates the index when the sublist length is more than half the load - level. This requires decrementing the nodes in a traversal from the leaf - node to the root. For an example traversal see self._loc. + level. This requires decrementing the nodes in a traversal from the + leaf node to the root. For an example traversal see + ``SortedList._loc``. + + :param int pos: lists index + :param int idx: sublist index + """ _lists = self._lists _keys = self._keys @@ -1751,8 +2077,7 @@ class SortedListWithKey(SortedList): len_keys_pos = len(keys_pos) - if len_keys_pos > self._half: - + if len_keys_pos > (self._load >> 1): _maxes[pos] = keys_pos[-1] if _index: @@ -1761,9 +2086,7 @@ class SortedListWithKey(SortedList): _index[child] -= 1 child = (child - 1) >> 1 _index[0] -= 1 - elif len(_keys) > 1: - if not pos: pos += 1 @@ -1778,220 +2101,78 @@ class SortedListWithKey(SortedList): del _index[:] self._expand(prev) - elif len_keys_pos: - _maxes[pos] = keys_pos[-1] - else: - del _lists[pos] del _keys[pos] del _maxes[pos] del _index[:] - def _check_order(self, idx, key, val): - # pylint: disable=arguments-differ - _len = self._len - _keys = self._keys - - pos, loc = self._pos(idx) - - if idx < 0: - idx += _len - - # Check that the inserted value is not less than the - # previous value. - - if idx > 0: - idx_prev = loc - 1 - pos_prev = pos - - if idx_prev < 0: - pos_prev -= 1 - idx_prev = len(_keys[pos_prev]) - 1 - - if _keys[pos_prev][idx_prev] > key: - msg = '{0!r} not in sort order at index {1}'.format(val, idx) - raise ValueError(msg) - - # Check that the inserted value is not greater than - # the previous value. - - if idx < (_len - 1): - idx_next = loc + 1 - pos_next = pos - - if idx_next == len(_keys[pos_next]): - pos_next += 1 - idx_next = 0 - - if _keys[pos_next][idx_next] < key: - msg = '{0!r} not in sort order at index {1}'.format(val, idx) - raise ValueError(msg) - - def __setitem__(self, index, value): - """Replace the item at position *index* with *value*. - - Supports slice notation. Raises a :exc:`ValueError` if the sort order - would be violated. When used with a slice and iterable, the - :exc:`ValueError` is raised before the list is mutated if the sort - order would be violated by the operation. - - """ - # pylint: disable=too-many-locals - _lists = self._lists - _keys = self._keys - _maxes = self._maxes - _check_order = self._check_order - _pos = self._pos - - if isinstance(index, slice): - _len = self._len - start, stop, step = index.indices(_len) - indices = range(start, stop, step) - - # Copy value to avoid aliasing issues with self and cases where an - # iterator is given. - - values = tuple(value) - - if step != 1: - if len(values) != len(indices): - raise ValueError( - 'attempt to assign sequence of size %s' - ' to extended slice of size %s' - % (len(values), len(indices))) - - # Keep a log of values that are set so that we can - # roll back changes if ordering is violated. - - log = [] - _append = log.append - - for idx, val in zip(indices, values): - pos, loc = _pos(idx) - key = self._key(val) - _append((idx, _keys[pos][loc], key, _lists[pos][loc], val)) - _keys[pos][loc] = key - _lists[pos][loc] = val - if len(_keys[pos]) == (loc + 1): - _maxes[pos] = key - - try: - # Validate ordering of new values. - - for idx, oldkey, newkey, oldval, newval in log: - _check_order(idx, newkey, newval) - - except ValueError: - - # Roll back changes from log. - - for idx, oldkey, newkey, oldval, newval in log: - pos, loc = _pos(idx) - _keys[pos][loc] = oldkey - _lists[pos][loc] = oldval - if len(_keys[pos]) == (loc + 1): - _maxes[pos] = oldkey - - raise - else: - if start == 0 and stop == self._len: - self._clear() - return self._update(values) - - if stop < start: - # When calculating indices, stop may be less than start. - # For example: ...[5:3:1] results in slice(5, 3, 1) which - # is a valid but not useful stop index. - stop = start - - if values: - - # Check that given values are ordered properly. - - keys = tuple(map(self._key, values)) - alphas = iter(keys) - betas = iter(keys) - next(betas) - pairs = zip(alphas, betas) - - if not all(alpha <= beta for alpha, beta in pairs): - raise ValueError('given values not in sort order') - - # Check ordering in context of sorted list. - - if start: - pos, loc = _pos(start - 1) - if _keys[pos][loc] > keys[0]: - msg = '{0!r} not in sort order at index {1}'.format( - values[0], start) - raise ValueError(msg) - - if stop != _len: - pos, loc = _pos(stop) - if _keys[pos][loc] < keys[-1]: - msg = '{0!r} not in sort order at index {1}'.format( - values[-1], stop) - raise ValueError(msg) - - # Delete the existing values. - - self._delitem(index) - - # Insert the new values. - - _insert = self.insert - for idx, val in enumerate(values): - _insert(start + idx, val) - else: - pos, loc = _pos(index) - key = self._key(value) - _check_order(index, key, value) - _lists[pos][loc] = value - _keys[pos][loc] = key - if len(_lists[pos]) == (loc + 1): - _maxes[pos] = key def irange(self, minimum=None, maximum=None, inclusive=(True, True), reverse=False): - """ - Create an iterator of values between `minimum` and `maximum`. - - `inclusive` is a pair of booleans that indicates whether the minimum - and maximum ought to be included in the range, respectively. The - default is (True, True) such that the range is inclusive of both - minimum and maximum. + """Create an iterator of values between `minimum` and `maximum`. Both `minimum` and `maximum` default to `None` which is automatically - inclusive of the start and end of the list, respectively. + inclusive of the beginning and end of the sorted-key list. + + The argument `inclusive` is a pair of booleans that indicates whether + the minimum and maximum ought to be included in the range, + respectively. The default is ``(True, True)`` such that the range is + inclusive of both minimum and maximum. When `reverse` is `True` the values are yielded from the iterator in reverse order; `reverse` defaults to `False`. + + >>> from operator import neg + >>> skl = SortedKeyList([11, 12, 13, 14, 15], key=neg) + >>> it = skl.irange(14.5, 11.5) + >>> list(it) + [14, 13, 12] + + :param minimum: minimum value to start iterating + :param maximum: maximum value to stop iterating + :param inclusive: pair of booleans + :param bool reverse: yield values in reverse order + :return: iterator + """ - minimum = self._key(minimum) if minimum is not None else None - maximum = self._key(maximum) if maximum is not None else None + min_key = self._key(minimum) if minimum is not None else None + max_key = self._key(maximum) if maximum is not None else None return self._irange_key( - min_key=minimum, max_key=maximum, + min_key=min_key, max_key=max_key, inclusive=inclusive, reverse=reverse, ) + def irange_key(self, min_key=None, max_key=None, inclusive=(True, True), reverse=False): - """ - Create an iterator of values between `min_key` and `max_key`. - - `inclusive` is a pair of booleans that indicates whether the min_key - and max_key ought to be included in the range, respectively. The - default is (True, True) such that the range is inclusive of both - `min_key` and `max_key`. + """Create an iterator of values between `min_key` and `max_key`. Both `min_key` and `max_key` default to `None` which is automatically - inclusive of the start and end of the list, respectively. + inclusive of the beginning and end of the sorted-key list. + + The argument `inclusive` is a pair of booleans that indicates whether + the minimum and maximum ought to be included in the range, + respectively. The default is ``(True, True)`` such that the range is + inclusive of both minimum and maximum. When `reverse` is `True` the values are yielded from the iterator in reverse order; `reverse` defaults to `False`. + + >>> from operator import neg + >>> skl = SortedKeyList([11, 12, 13, 14, 15], key=neg) + >>> it = skl.irange_key(-14, -12) + >>> list(it) + [14, 13, 12] + + :param min_key: minimum key to start iterating + :param max_key: maximum key to stop iterating + :param inclusive: pair of booleans + :param bool reverse: yield values in reverse order + :return: iterator + """ _maxes = self._maxes @@ -2050,29 +2231,71 @@ class SortedListWithKey(SortedList): _irange_key = irange_key - def bisect_left(self, val): - """ - Similar to the *bisect* module in the standard library, this returns an - appropriate index to insert *val*. If *val* is already present, the - insertion point will be before (to the left of) any existing entries. - """ - return self._bisect_key_left(self._key(val)) - def bisect_right(self, val): + def bisect_left(self, value): + """Return an index to insert `value` in the sorted-key list. + + If the `value` is already present, the insertion point will be before + (to the left of) any existing values. + + Similar to the `bisect` module in the standard library. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> from operator import neg + >>> skl = SortedKeyList([5, 4, 3, 2, 1], key=neg) + >>> skl.bisect_left(1) + 4 + + :param value: insertion index of value in sorted-key list + :return: index + """ - Same as *bisect_left*, but if *val* is already present, the insertion - point will be after (to the right of) any existing entries. + return self._bisect_key_left(self._key(value)) + + + def bisect_right(self, value): + """Return an index to insert `value` in the sorted-key list. + + Similar to `bisect_left`, but if `value` is already present, the + insertion point will be after (to the right of) any existing values. + + Similar to the `bisect` module in the standard library. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> from operator import neg + >>> skl = SortedList([5, 4, 3, 2, 1], key=neg) + >>> skl.bisect_right(1) + 5 + + :param value: insertion index of value in sorted-key list + :return: index + """ - return self._bisect_key_right(self._key(val)) + return self._bisect_key_right(self._key(value)) bisect = bisect_right + def bisect_key_left(self, key): - """ - Similar to the *bisect* module in the standard library, this returns an - appropriate index to insert a value with a given *key*. If values with - *key* are already present, the insertion point will be before (to the - left of) any existing entries. + """Return an index to insert `key` in the sorted-key list. + + If the `key` is already present, the insertion point will be before (to + the left of) any existing keys. + + Similar to the `bisect` module in the standard library. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> from operator import neg + >>> skl = SortedKeyList([5, 4, 3, 2, 1], key=neg) + >>> skl.bisect_key_left(-1) + 4 + + :param key: insertion index of key in sorted-key list + :return: index + """ _maxes = self._maxes @@ -2090,10 +2313,25 @@ class SortedListWithKey(SortedList): _bisect_key_left = bisect_key_left + def bisect_key_right(self, key): - """ - Same as *bisect_key_left*, but if *key* is already present, the insertion - point will be after (to the right of) any existing entries. + """Return an index to insert `key` in the sorted-key list. + + Similar to `bisect_key_left`, but if `key` is already present, the + insertion point will be after (to the right of) any existing keys. + + Similar to the `bisect` module in the standard library. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> from operator import neg + >>> skl = SortedList([5, 4, 3, 2, 1], key=neg) + >>> skl.bisect_key_right(-1) + 5 + + :param key: insertion index of key in sorted-key list + :return: index + """ _maxes = self._maxes @@ -2112,14 +2350,27 @@ class SortedListWithKey(SortedList): bisect_key = bisect_key_right _bisect_key_right = bisect_key_right - def count(self, val): - """Return the number of occurrences of *val* in the list.""" + + def count(self, value): + """Return number of occurrences of `value` in the sorted-key list. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> from operator import neg + >>> skl = SortedKeyList([4, 4, 4, 4, 3, 3, 3, 2, 2, 1], key=neg) + >>> skl.count(2) + 2 + + :param value: value to count in sorted-key list + :return: count + + """ _maxes = self._maxes if not _maxes: return 0 - key = self._key(val) + key = self._key(value) pos = bisect_left(_maxes, key) if pos == len(_maxes): @@ -2135,7 +2386,7 @@ class SortedListWithKey(SortedList): while True: if _keys[pos][idx] != key: return total - if _lists[pos][idx] == val: + if _lists[pos][idx] == value: total += 1 idx += 1 if idx == len_sublist: @@ -2145,176 +2396,53 @@ class SortedListWithKey(SortedList): len_sublist = len(_keys[pos]) idx = 0 - def copy(self): - """Return a shallow copy of the sorted list.""" - return self.__class__(self, key=self._key) - - __copy__ = copy - def append(self, val): - """ - Append the element *val* to the list. Raises a ValueError if the *val* - would violate the sort order. - """ - # pylint: disable=arguments-differ - _lists = self._lists - _keys = self._keys - _maxes = self._maxes - key = self._key(val) - - if not _maxes: - _maxes.append(key) - _keys.append([key]) - _lists.append([val]) - self._len = 1 - return - - pos = len(_keys) - 1 + def copy(self): + """Return a shallow copy of the sorted-key list. - if key < _keys[pos][-1]: - msg = '{0!r} not in sort order at index {1}'.format(val, self._len) - raise ValueError(msg) + Runtime complexity: `O(n)` - _lists[pos].append(val) - _keys[pos].append(key) - _maxes[pos] = key - self._len += 1 - self._expand(pos) + :return: new sorted-key list - def extend(self, values): """ - Extend the list by appending all elements from the *values*. Raises a - ValueError if the sort order would be violated. - """ - _lists = self._lists - _keys = self._keys - _maxes = self._maxes - _load = self._load - - if not isinstance(values, list): - values = list(values) - - keys = list(map(self._key, values)) + return self.__class__(self, key=self._key) - if any(keys[pos - 1] > keys[pos] - for pos in range(1, len(keys))): - raise ValueError('given sequence not in sort order') + __copy__ = copy - offset = 0 - if _maxes: - if keys[0] < _keys[-1][-1]: - msg = '{0!r} not in sort order at index {1}'.format(values[0], self._len) - raise ValueError(msg) + def index(self, value, start=None, stop=None): + """Return first index of value in sorted-key list. - if len(_keys[-1]) < self._half: - _lists[-1].extend(values[:_load]) - _keys[-1].extend(keys[:_load]) - _maxes[-1] = _keys[-1][-1] - offset = _load + Raise ValueError if `value` is not present. - len_keys = len(_keys) + Index must be between `start` and `stop` for the `value` to be + considered present. The default value, None, for `start` and `stop` + indicate the beginning and end of the sorted-key list. - for idx in range(offset, len(keys), _load): - _lists.append(values[idx:(idx + _load)]) - _keys.append(keys[idx:(idx + _load)]) - _maxes.append(_keys[-1][-1]) + Negative indices are supported. - _index = self._index + Runtime complexity: `O(log(n))` -- approximate. - if len_keys == len(_keys): - len_index = len(_index) - if len_index > 0: - len_values = len(values) - child = len_index - 1 - while child: - _index[child] += len_values - child = (child - 1) >> 1 - _index[0] += len_values - else: - del _index[:] + >>> from operator import neg + >>> skl = SortedKeyList([5, 4, 3, 2, 1], key=neg) + >>> skl.index(2) + 3 + >>> skl.index(0) + Traceback (most recent call last): + ... + ValueError: 0 is not in list - self._len += len(values) + :param value: value in sorted-key list + :param int start: start index (default None, start of sorted-key list) + :param int stop: stop index (default None, end of sorted-key list) + :return: index of value + :raises ValueError: if value is not present - def insert(self, idx, val): - """ - Insert the element *val* into the list at *idx*. Raises a ValueError if - the *val* at *idx* would violate the sort order. - """ - _len = self._len - _lists = self._lists - _keys = self._keys - _maxes = self._maxes - - if idx < 0: - idx += _len - if idx < 0: - idx = 0 - if idx > _len: - idx = _len - - key = self._key(val) - - if not _maxes: - self._len = 1 - _lists.append([val]) - _keys.append([key]) - _maxes.append(key) - return - - if not idx: - if key > _keys[0][0]: - msg = '{0!r} not in sort order at index {1}'.format(val, 0) - raise ValueError(msg) - else: - self._len += 1 - _lists[0].insert(0, val) - _keys[0].insert(0, key) - self._expand(0) - return - - if idx == _len: - pos = len(_keys) - 1 - if _keys[pos][-1] > key: - msg = '{0!r} not in sort order at index {1}'.format(val, _len) - raise ValueError(msg) - else: - self._len += 1 - _lists[pos].append(val) - _keys[pos].append(key) - _maxes[pos] = _keys[pos][-1] - self._expand(pos) - return - - pos, idx = self._pos(idx) - idx_before = idx - 1 - if idx_before < 0: - pos_before = pos - 1 - idx_before = len(_keys[pos_before]) - 1 - else: - pos_before = pos - - before = _keys[pos_before][idx_before] - if before <= key <= _keys[pos][idx]: - self._len += 1 - _lists[pos].insert(idx, val) - _keys[pos].insert(idx, key) - self._expand(pos) - else: - msg = '{0!r} not in sort order at index {1}'.format(val, idx) - raise ValueError(msg) - - def index(self, val, start=None, stop=None): - """ - Return the smallest *k* such that L[k] == val and i <= k < j`. Raises - ValueError if *val* is not present. *stop* defaults to the end of the - list. *start* defaults to the beginning. Negative indices are supported, - as for slice indices. """ _len = self._len if not _len: - raise ValueError('{0!r} is not in list'.format(val)) + raise ValueError('{0!r} is not in list'.format(value)) if start is None: start = 0 @@ -2331,14 +2459,14 @@ class SortedListWithKey(SortedList): stop = _len if stop <= start: - raise ValueError('{0!r} is not in list'.format(val)) + raise ValueError('{0!r} is not in list'.format(value)) _maxes = self._maxes - key = self._key(val) + key = self._key(value) pos = bisect_left(_maxes, key) if pos == len(_maxes): - raise ValueError('{0!r} is not in list'.format(val)) + raise ValueError('{0!r} is not in list'.format(value)) stop -= 1 _lists = self._lists @@ -2349,8 +2477,8 @@ class SortedListWithKey(SortedList): while True: if _keys[pos][idx] != key: - raise ValueError('{0!r} is not in list'.format(val)) - if _lists[pos][idx] == val: + raise ValueError('{0!r} is not in list'.format(value)) + if _lists[pos][idx] == value: loc = self._loc(pos, idx) if start <= loc <= stop: return loc @@ -2360,139 +2488,148 @@ class SortedListWithKey(SortedList): if idx == len_sublist: pos += 1 if pos == len_keys: - raise ValueError('{0!r} is not in list'.format(val)) + raise ValueError('{0!r} is not in list'.format(value)) len_sublist = len(_keys[pos]) idx = 0 - raise ValueError('{0!r} is not in list'.format(val)) + raise ValueError('{0!r} is not in list'.format(value)) + + + def __add__(self, other): + """Return new sorted-key list containing all values in both sequences. + + ``skl.__add__(other)`` <==> ``skl + other`` + + Values in `other` do not need to be in sorted-key order. + + Runtime complexity: `O(n*log(n))` + + >>> from operator import neg + >>> skl1 = SortedKeyList([5, 4, 3], key=neg) + >>> skl2 = SortedKeyList([2, 1, 0], key=neg) + >>> skl1 + skl2 + SortedKeyList([5, 4, 3, 2, 1, 0], key=<built-in function neg>) + + :param other: other iterable + :return: new sorted-key list - def __add__(self, that): - """ - Return a new sorted list containing all the elements in *self* and - *that*. Elements in *that* do not need to be properly ordered with - respect to *self*. """ values = reduce(iadd, self._lists, []) - values.extend(that) + values.extend(other) return self.__class__(values, key=self._key) - def __mul__(self, that): - """ - Return a new sorted list containing *that* shallow copies of each item - in SortedListWithKey. + __radd__ = __add__ + + + def __mul__(self, num): + """Return new sorted-key list with `num` shallow copies of values. + + ``skl.__mul__(num)`` <==> ``skl * num`` + + Runtime complexity: `O(n*log(n))` + + >>> from operator import neg + >>> skl = SortedKeyList([3, 2, 1], key=neg) + >>> skl * 2 + SortedKeyList([3, 3, 2, 2, 1, 1], key=<built-in function neg>) + + :param int num: count of shallow copies + :return: new sorted-key list + """ - values = reduce(iadd, self._lists, []) * that + values = reduce(iadd, self._lists, []) * num return self.__class__(values, key=self._key) - def __imul__(self, that): - """ - Increase the length of the list by appending *that* shallow copies of - each item. - """ - values = reduce(iadd, self._lists, []) * that - self._clear() - self._update(values) - return self - @recursive_repr + def __reduce__(self): + values = reduce(iadd, self._lists, []) + return (type(self), (values, self.key)) + + + @recursive_repr() def __repr__(self): - """Return string representation of sequence.""" - name = type(self).__name__ - values = list(self) - _key = self._key - return '{0}({1!r}, key={2!r})'.format(name, values, _key) + """Return string representation of sorted-key list. - def _check(self): - try: - # Check load parameters. + ``skl.__repr__()`` <==> ``repr(skl)`` - assert self._load >= 4 - assert self._half == (self._load >> 1) - assert self._dual == (self._load << 1) + :return: string representation - # Check empty sorted list case. + """ + type_name = type(self).__name__ + return '{0}({1!r}, key={2!r})'.format(type_name, list(self), self._key) - if self._maxes == []: - assert self._keys == [] - assert self._lists == [] - return - assert self._maxes and self._keys and self._lists + def _check(self): + """Check invariants of sorted-key list. + + Runtime complexity: `O(n)` + + """ + try: + assert self._load >= 4 + assert len(self._maxes) == len(self._lists) == len(self._keys) + assert self._len == sum(len(sublist) for sublist in self._lists) # Check all sublists are sorted. - assert all(sublist[pos - 1] <= sublist[pos] - for sublist in self._keys - for pos in range(1, len(sublist))) + for sublist in self._keys: + for pos in range(1, len(sublist)): + assert sublist[pos - 1] <= sublist[pos] # Check beginning/end of sublists are sorted. for pos in range(1, len(self._keys)): assert self._keys[pos - 1][-1] <= self._keys[pos][0] - # Check length of _maxes and _lists match. - - assert len(self._maxes) == len(self._lists) == len(self._keys) - # Check _keys matches _key mapped to _lists. - assert all(len(val_list) == len(key_list) - for val_list, key_list in zip(self._lists, self._keys)) - assert all(self._key(val) == key for val, key in - zip((_val for _val_list in self._lists for _val in _val_list), - (_key for _key_list in self._keys for _key in _key_list))) + for val_sublist, key_sublist in zip(self._lists, self._keys): + assert len(val_sublist) == len(key_sublist) + for val, key in zip(val_sublist, key_sublist): + assert self._key(val) == key - # Check _maxes is a map of _keys. + # Check _maxes index is the last value of each sublist. - assert all(self._maxes[pos] == self._keys[pos][-1] - for pos in range(len(self._maxes))) + for pos in range(len(self._maxes)): + assert self._maxes[pos] == self._keys[pos][-1] - # Check load level is less than _dual. + # Check sublist lengths are less than double load-factor. - assert all(len(sublist) <= self._dual for sublist in self._lists) + double = self._load << 1 + assert all(len(sublist) <= double for sublist in self._lists) - # Check load level is greater than _half for all + # Check sublist lengths are greater than half load-factor for all # but the last sublist. - assert all(len(self._lists[pos]) >= self._half - for pos in range(0, len(self._lists) - 1)) - - # Check length. - - assert self._len == sum(len(sublist) for sublist in self._lists) - - # Check index. + half = self._load >> 1 + for pos in range(0, len(self._lists) - 1): + assert len(self._lists[pos]) >= half if self._index: - assert len(self._index) == self._offset + len(self._lists) assert self._len == self._index[0] + assert len(self._index) == self._offset + len(self._lists) - def test_offset_pos(pos): - "Test positional indexing offset." - from_index = self._index[self._offset + pos] - return from_index == len(self._lists[pos]) + # Check index leaf nodes equal length of sublists. - assert all(test_offset_pos(pos) - for pos in range(len(self._lists))) + for pos in range(len(self._lists)): + leaf = self._index[self._offset + pos] + assert leaf == len(self._lists[pos]) + + # Check index branch nodes are the sum of their children. for pos in range(self._offset): child = (pos << 1) + 1 - if self._index[pos] == 0: - assert child >= len(self._index) + if child >= len(self._index): + assert self._index[pos] == 0 elif child + 1 == len(self._index): assert self._index[pos] == self._index[child] else: child_sum = self._index[child] + self._index[child + 1] - assert self._index[pos] == child_sum - + assert child_sum == self._index[pos] except: - import sys - import traceback - traceback.print_exc(file=sys.stdout) - print('len', self._len) - print('load', self._load, self._half, self._dual) + print('load', self._load) print('offset', self._offset) print('len_index', len(self._index)) print('index', self._index) @@ -2502,5 +2639,7 @@ class SortedListWithKey(SortedList): print('keys', self._keys) print('len_lists', len(self._lists)) print('lists', self._lists) - raise + + +SortedListWithKey = SortedKeyList diff --git a/python/ovs/compat/sortedcontainers/sortedset.py b/python/ovs/compat/sortedcontainers/sortedset.py index 6d82b387b..be2b8999c 100644 --- a/python/ovs/compat/sortedcontainers/sortedset.py +++ b/python/ovs/compat/sortedcontainers/sortedset.py @@ -1,50 +1,153 @@ -"""Sorted set implementation. +"""Sorted Set +============= + +:doc:`Sorted Containers<index>` is an Apache2 licensed Python sorted +collections library, written in pure-Python, and fast as C-extensions. The +:doc:`introduction<introduction>` is the best way to get started. + +Sorted set implementations: + +.. currentmodule:: sortedcontainers + +* :class:`SortedSet` """ -from collections import Set, MutableSet, Sequence from itertools import chain -import operator as op +from operator import eq, ne, gt, ge, lt, le +from textwrap import dedent + +from .sortedlist import SortedList, recursive_repr + +############################################################################### +# BEGIN Python 2/3 Shims +############################################################################### + +try: + from collections.abc import MutableSet, Sequence, Set +except ImportError: + from collections import MutableSet, Sequence, Set + +############################################################################### +# END Python 2/3 Shims +############################################################################### -from .sortedlist import SortedList, recursive_repr, SortedListWithKey class SortedSet(MutableSet, Sequence): - """ - A `SortedSet` provides the same methods as a `set`. Additionally, a - `SortedSet` maintains its items in sorted order, allowing the `SortedSet` to - be indexed. + """Sorted set is a sorted mutable set. + + Sorted set values are maintained in sorted order. The design of sorted set + is simple: sorted set uses a set for set-operations and maintains a sorted + list of values. + + Sorted set values must be hashable and comparable. The hash and total + ordering of values must not change while they are stored in the sorted set. + + Mutable set methods: + + * :func:`SortedSet.__contains__` + * :func:`SortedSet.__iter__` + * :func:`SortedSet.__len__` + * :func:`SortedSet.add` + * :func:`SortedSet.discard` + + Sequence methods: + + * :func:`SortedSet.__getitem__` + * :func:`SortedSet.__delitem__` + * :func:`SortedSet.__reversed__` + + Methods for removing values: + + * :func:`SortedSet.clear` + * :func:`SortedSet.pop` + * :func:`SortedSet.remove` + + Set-operation methods: + + * :func:`SortedSet.difference` + * :func:`SortedSet.difference_update` + * :func:`SortedSet.intersection` + * :func:`SortedSet.intersection_update` + * :func:`SortedSet.symmetric_difference` + * :func:`SortedSet.symmetric_difference_update` + * :func:`SortedSet.union` + * :func:`SortedSet.update` + + Methods for miscellany: + + * :func:`SortedSet.copy` + * :func:`SortedSet.count` + * :func:`SortedSet.__repr__` + * :func:`SortedSet._check` + + Sorted list methods available: + + * :func:`SortedList.bisect_left` + * :func:`SortedList.bisect_right` + * :func:`SortedList.index` + * :func:`SortedList.irange` + * :func:`SortedList.islice` + * :func:`SortedList._reset` + + Additional sorted list methods available, if key-function used: + + * :func:`SortedKeyList.bisect_key_left` + * :func:`SortedKeyList.bisect_key_right` + * :func:`SortedKeyList.irange_key` + + Sorted set comparisons use subset and superset relations. Two sorted sets + are equal if and only if every element of each sorted set is contained in + the other (each is a subset of the other). A sorted set is less than + another sorted set if and only if the first sorted set is a proper subset + of the second sorted set (is a subset, but is not equal). A sorted set is + greater than another sorted set if and only if the first sorted set is a + proper superset of the second sorted set (is a superset, but is not equal). - Unlike a `set`, a `SortedSet` requires items be hashable and comparable. """ - # pylint: disable=too-many-ancestors def __init__(self, iterable=None, key=None): - """ - A `SortedSet` provides the same methods as a `set`. Additionally, a - `SortedSet` maintains its items in sorted order, allowing the - `SortedSet` to be indexed. + """Initialize sorted set instance. - An optional *iterable* provides an initial series of items to populate - the `SortedSet`. + Optional `iterable` argument provides an initial iterable of values to + initialize the sorted set. - An optional *key* argument defines a callable that, like the `key` + Optional `key` argument defines a callable that, like the `key` argument to Python's `sorted` function, extracts a comparison key from - each set item. If no function is specified, the default compares the - set items directly. + each value. The default, none, compares values directly. + + Runtime complexity: `O(n*log(n))` + + >>> ss = SortedSet([3, 1, 2, 5, 4]) + >>> ss + SortedSet([1, 2, 3, 4, 5]) + >>> from operator import neg + >>> ss = SortedSet([3, 1, 2, 5, 4], neg) + >>> ss + SortedSet([5, 4, 3, 2, 1], key=<built-in function neg>) + + :param iterable: initial values (optional) + :param key: function used to extract comparison key (optional) + """ self._key = key + # SortedSet._fromset calls SortedSet.__init__ after initializing the + # _set attribute. So only create a new set if the _set attribute is not + # already present. + if not hasattr(self, '_set'): self._set = set() + self._list = SortedList(self._set, key=key) + + # Expose some set methods publicly. + _set = self._set self.isdisjoint = _set.isdisjoint self.issubset = _set.issubset self.issuperset = _set.issuperset - if key is None: - self._list = SortedList(self._set) - else: - self._list = SortedListWithKey(self._set, key=key) + # Expose some sorted list methods publicly. _list = self._list self.bisect_left = _list.bisect_left @@ -53,7 +156,7 @@ class SortedSet(MutableSet, Sequence): self.index = _list.index self.irange = _list.irange self.islice = _list.islice - self._reset = _list._reset # pylint: disable=protected-access + self._reset = _list._reset if key is not None: self.bisect_key_left = _list.bisect_key_left @@ -64,36 +167,93 @@ class SortedSet(MutableSet, Sequence): if iterable is not None: self._update(iterable) - @property - def key(self): - """Key function used to extract comparison key for sorting.""" - return self._key @classmethod def _fromset(cls, values, key=None): - """Initialize sorted set from existing set.""" + """Initialize sorted set from existing set. + + Used internally by set operations that return a new set. + + """ sorted_set = object.__new__(cls) - sorted_set._set = values # pylint: disable=protected-access + sorted_set._set = values sorted_set.__init__(key=key) return sorted_set + + @property + def key(self): + """Function used to extract comparison key from values. + + Sorted set compares values directly when the key function is none. + + """ + return self._key + + def __contains__(self, value): - """Return True if and only if *value* is an element in the set.""" + """Return true if `value` is an element of the sorted set. + + ``ss.__contains__(value)`` <==> ``value in ss`` + + Runtime complexity: `O(1)` + + >>> ss = SortedSet([1, 2, 3, 4, 5]) + >>> 3 in ss + True + + :param value: search for value in sorted set + :return: true if `value` in sorted set + + """ return value in self._set + def __getitem__(self, index): - """ - Return the element at position *index*. + """Lookup value at `index` in sorted set. + + ``ss.__getitem__(index)`` <==> ``ss[index]`` + + Supports slicing. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> ss = SortedSet('abcde') + >>> ss[2] + 'c' + >>> ss[-1] + 'e' + >>> ss[2:5] + ['c', 'd', 'e'] + + :param index: integer or slice for indexing + :return: value or list of values + :raises IndexError: if index out of range - Supports slice notation and negative indexes. """ return self._list[index] + def __delitem__(self, index): - """ - Remove the element at position *index*. + """Remove value at `index` from sorted set. + + ``ss.__delitem__(index)`` <==> ``del ss[index]`` + + Supports slicing. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> ss = SortedSet('abcde') + >>> del ss[2] + >>> ss + SortedSet(['a', 'b', 'd', 'e']) + >>> del ss[:2] + >>> ss + SortedSet(['d', 'e']) + + :param index: integer or slice for indexing + :raises IndexError: if index out of range - Supports slice notation and negative indexes. """ _set = self._set _list = self._list @@ -105,149 +265,316 @@ class SortedSet(MutableSet, Sequence): _set.remove(value) del _list[index] - def _make_cmp(self, set_op, doc): + + def __make_cmp(set_op, symbol, doc): "Make comparator method." - def comparer(self, that): - "Compare method for sorted set and set-like object." - # pylint: disable=protected-access - if isinstance(that, SortedSet): - return set_op(self._set, that._set) - elif isinstance(that, Set): - return set_op(self._set, that) + def comparer(self, other): + "Compare method for sorted set and set." + if isinstance(other, SortedSet): + return set_op(self._set, other._set) + elif isinstance(other, Set): + return set_op(self._set, other) return NotImplemented - comparer.__name__ = '__{0}__'.format(set_op.__name__) - doc_str = 'Return True if and only if Set is {0} `that`.' - comparer.__doc__ = doc_str.format(doc) + set_op_name = set_op.__name__ + comparer.__name__ = '__{0}__'.format(set_op_name) + doc_str = """Return true if and only if sorted set is {0} `other`. + + ``ss.__{1}__(other)`` <==> ``ss {2} other`` + + Comparisons use subset and superset semantics as with sets. + + Runtime complexity: `O(n)` + + :param other: `other` set + :return: true if sorted set is {0} `other` + """ + comparer.__doc__ = dedent(doc_str.format(doc, set_op_name, symbol)) return comparer - __eq__ = _make_cmp(None, op.eq, 'equal to') - __ne__ = _make_cmp(None, op.ne, 'not equal to') - __lt__ = _make_cmp(None, op.lt, 'a proper subset of') - __gt__ = _make_cmp(None, op.gt, 'a proper superset of') - __le__ = _make_cmp(None, op.le, 'a subset of') - __ge__ = _make_cmp(None, op.ge, 'a superset of') + + __eq__ = __make_cmp(eq, '==', 'equal to') + __ne__ = __make_cmp(ne, '!=', 'not equal to') + __lt__ = __make_cmp(lt, '<', 'a proper subset of') + __gt__ = __make_cmp(gt, '>', 'a proper superset of') + __le__ = __make_cmp(le, '<=', 'a subset of') + __ge__ = __make_cmp(ge, '>=', 'a superset of') + __make_cmp = staticmethod(__make_cmp) + def __len__(self): - """Return the number of elements in the set.""" + """Return the size of the sorted set. + + ``ss.__len__()`` <==> ``len(ss)`` + + :return: size of sorted set + + """ return len(self._set) + def __iter__(self): - """ - Return an iterator over the Set. Elements are iterated in their sorted - order. + """Return an iterator over the sorted set. + + ``ss.__iter__()`` <==> ``iter(ss)`` + + Iterating the sorted set while adding or deleting values may raise a + :exc:`RuntimeError` or fail to iterate over all values. - Iterating the Set while adding or deleting values may raise a - `RuntimeError` or fail to iterate over all entries. """ return iter(self._list) + def __reversed__(self): - """ - Return an iterator over the Set. Elements are iterated in their reverse - sorted order. + """Return a reverse iterator over the sorted set. + + ``ss.__reversed__()`` <==> ``reversed(ss)`` + + Iterating the sorted set while adding or deleting values may raise a + :exc:`RuntimeError` or fail to iterate over all values. - Iterating the Set while adding or deleting values may raise a - `RuntimeError` or fail to iterate over all entries. """ return reversed(self._list) + def add(self, value): - """Add the element *value* to the set.""" + """Add `value` to sorted set. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> ss = SortedSet() + >>> ss.add(3) + >>> ss.add(1) + >>> ss.add(2) + >>> ss + SortedSet([1, 2, 3]) + + :param value: value to add to sorted set + + """ _set = self._set if value not in _set: _set.add(value) self._list.add(value) + _add = add + + def clear(self): - """Remove all elements from the set.""" + """Remove all values from sorted set. + + Runtime complexity: `O(n)` + + """ self._set.clear() self._list.clear() + def copy(self): - """Create a shallow copy of the sorted set.""" + """Return a shallow copy of the sorted set. + + Runtime complexity: `O(n)` + + :return: new sorted set + + """ return self._fromset(set(self._set), key=self._key) __copy__ = copy + def count(self, value): - """Return the number of occurrences of *value* in the set.""" + """Return number of occurrences of `value` in the sorted set. + + Runtime complexity: `O(1)` + + >>> ss = SortedSet([1, 2, 3, 4, 5]) + >>> ss.count(3) + 1 + + :param value: value to count in sorted set + :return: count + + """ return 1 if value in self._set else 0 + def discard(self, value): - """ - Remove the first occurrence of *value*. If *value* is not a member, - does nothing. + """Remove `value` from sorted set if it is a member. + + If `value` is not a member, do nothing. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> ss = SortedSet([1, 2, 3, 4, 5]) + >>> ss.discard(5) + >>> ss.discard(0) + >>> ss == set([1, 2, 3, 4]) + True + + :param value: `value` to discard from sorted set + """ _set = self._set if value in _set: _set.remove(value) - self._list.discard(value) + self._list.remove(value) + + _discard = discard + def pop(self, index=-1): - """ - Remove and return item at *index* (default last). Raises IndexError if - set is empty or index is out of range. Negative indexes are supported, - as for slice indices. + """Remove and return value at `index` in sorted set. + + Raise :exc:`IndexError` if the sorted set is empty or index is out of + range. + + Negative indices are supported. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> ss = SortedSet('abcde') + >>> ss.pop() + 'e' + >>> ss.pop(2) + 'c' + >>> ss + SortedSet(['a', 'b', 'd']) + + :param int index: index of value (default -1) + :return: value + :raises IndexError: if index is out of range + """ # pylint: disable=arguments-differ value = self._list.pop(index) self._set.remove(value) return value + def remove(self, value): - """ - Remove first occurrence of *value*. Raises ValueError if - *value* is not present. + """Remove `value` from sorted set; `value` must be a member. + + If `value` is not a member, raise :exc:`KeyError`. + + Runtime complexity: `O(log(n))` -- approximate. + + >>> ss = SortedSet([1, 2, 3, 4, 5]) + >>> ss.remove(5) + >>> ss == set([1, 2, 3, 4]) + True + >>> ss.remove(0) + Traceback (most recent call last): + ... + KeyError: 0 + + :param value: `value` to remove from sorted set + :raises KeyError: if `value` is not in sorted set + """ self._set.remove(value) self._list.remove(value) + def difference(self, *iterables): - """ - Return a new set with elements in the set that are not in the - *iterables*. + """Return the difference of two or more sets as a new sorted set. + + The `difference` method also corresponds to operator ``-``. + + ``ss.__sub__(iterable)`` <==> ``ss - iterable`` + + The difference is all values that are in this sorted set but not the + other `iterables`. + + >>> ss = SortedSet([1, 2, 3, 4, 5]) + >>> ss.difference([4, 5, 6, 7]) + SortedSet([1, 2, 3]) + + :param iterables: iterable arguments + :return: new sorted set + """ diff = self._set.difference(*iterables) return self._fromset(diff, key=self._key) __sub__ = difference - __rsub__ = __sub__ + def difference_update(self, *iterables): - """ - Update the set, removing elements found in keeping only elements - found in any of the *iterables*. + """Remove all values of `iterables` from this sorted set. + + The `difference_update` method also corresponds to operator ``-=``. + + ``ss.__isub__(iterable)`` <==> ``ss -= iterable`` + + >>> ss = SortedSet([1, 2, 3, 4, 5]) + >>> _ = ss.difference_update([4, 5, 6, 7]) + >>> ss + SortedSet([1, 2, 3]) + + :param iterables: iterable arguments + :return: itself + """ _set = self._set + _list = self._list values = set(chain(*iterables)) if (4 * len(values)) > len(_set): - _list = self._list _set.difference_update(values) _list.clear() _list.update(_set) else: - _discard = self.discard + _discard = self._discard for value in values: _discard(value) return self __isub__ = difference_update + def intersection(self, *iterables): + """Return the intersection of two or more sets as a new sorted set. + + The `intersection` method also corresponds to operator ``&``. + + ``ss.__and__(iterable)`` <==> ``ss & iterable`` + + The intersection is all values that are in this sorted set and each of + the other `iterables`. + + >>> ss = SortedSet([1, 2, 3, 4, 5]) + >>> ss.intersection([4, 5, 6, 7]) + SortedSet([4, 5]) + + :param iterables: iterable arguments + :return: new sorted set + """ - Return a new set with elements common to the set and all *iterables*. - """ - comb = self._set.intersection(*iterables) - return self._fromset(comb, key=self._key) + intersect = self._set.intersection(*iterables) + return self._fromset(intersect, key=self._key) __and__ = intersection __rand__ = __and__ + def intersection_update(self, *iterables): - """ - Update the set, keeping only elements found in it and all *iterables*. + """Update the sorted set with the intersection of `iterables`. + + The `intersection_update` method also corresponds to operator ``&=``. + + ``ss.__iand__(iterable)`` <==> ``ss &= iterable`` + + Keep only values found in itself and all `iterables`. + + >>> ss = SortedSet([1, 2, 3, 4, 5]) + >>> _ = ss.intersection_update([4, 5, 6, 7]) + >>> ss + SortedSet([4, 5]) + + :param iterables: iterable arguments + :return: itself + """ _set = self._set _list = self._list @@ -258,42 +585,100 @@ class SortedSet(MutableSet, Sequence): __iand__ = intersection_update - def symmetric_difference(self, that): - """ - Return a new set with elements in either *self* or *that* but not both. + + def symmetric_difference(self, other): + """Return the symmetric difference with `other` as a new sorted set. + + The `symmetric_difference` method also corresponds to operator ``^``. + + ``ss.__xor__(other)`` <==> ``ss ^ other`` + + The symmetric difference is all values tha are in exactly one of the + sets. + + >>> ss = SortedSet([1, 2, 3, 4, 5]) + >>> ss.symmetric_difference([4, 5, 6, 7]) + SortedSet([1, 2, 3, 6, 7]) + + :param other: `other` iterable + :return: new sorted set + """ - diff = self._set.symmetric_difference(that) + diff = self._set.symmetric_difference(other) return self._fromset(diff, key=self._key) __xor__ = symmetric_difference __rxor__ = __xor__ - def symmetric_difference_update(self, that): - """ - Update the set, keeping only elements found in either *self* or *that*, - but not in both. + + def symmetric_difference_update(self, other): + """Update the sorted set with the symmetric difference with `other`. + + The `symmetric_difference_update` method also corresponds to operator + ``^=``. + + ``ss.__ixor__(other)`` <==> ``ss ^= other`` + + Keep only values found in exactly one of itself and `other`. + + >>> ss = SortedSet([1, 2, 3, 4, 5]) + >>> _ = ss.symmetric_difference_update([4, 5, 6, 7]) + >>> ss + SortedSet([1, 2, 3, 6, 7]) + + :param other: `other` iterable + :return: itself + """ _set = self._set _list = self._list - _set.symmetric_difference_update(that) + _set.symmetric_difference_update(other) _list.clear() _list.update(_set) return self __ixor__ = symmetric_difference_update + def union(self, *iterables): - """ - Return a new SortedSet with elements from the set and all *iterables*. + """Return new sorted set with values from itself and all `iterables`. + + The `union` method also corresponds to operator ``|``. + + ``ss.__or__(iterable)`` <==> ``ss | iterable`` + + >>> ss = SortedSet([1, 2, 3, 4, 5]) + >>> ss.union([4, 5, 6, 7]) + SortedSet([1, 2, 3, 4, 5, 6, 7]) + + :param iterables: iterable arguments + :return: new sorted set + """ return self.__class__(chain(iter(self), *iterables), key=self._key) __or__ = union __ror__ = __or__ + def update(self, *iterables): - """Update the set, adding elements from all *iterables*.""" + """Update the sorted set adding values from all `iterables`. + + The `update` method also corresponds to operator ``|=``. + + ``ss.__ior__(iterable)`` <==> ``ss |= iterable`` + + >>> ss = SortedSet([1, 2, 3, 4, 5]) + >>> _ = ss.update([4, 5, 6, 7]) + >>> ss + SortedSet([1, 2, 3, 4, 5, 6, 7]) + + :param iterables: iterable arguments + :return: itself + + """ _set = self._set + _list = self._list values = set(chain(*iterables)) if (4 * len(values)) > len(_set): _list = self._list @@ -301,7 +686,7 @@ class SortedSet(MutableSet, Sequence): _list.clear() _list.update(_set) else: - _add = self.add + _add = self._add for value in values: _add(value) return self @@ -309,19 +694,40 @@ class SortedSet(MutableSet, Sequence): __ior__ = update _update = update + def __reduce__(self): + """Support for pickle. + + The tricks played with exposing methods in :func:`SortedSet.__init__` + confuse pickle so customize the reducer. + + """ return (type(self), (self._set, self._key)) - @recursive_repr + + @recursive_repr() def __repr__(self): + """Return string representation of sorted set. + + ``ss.__repr__()`` <==> ``repr(ss)`` + + :return: string representation + + """ _key = self._key key = '' if _key is None else ', key={0!r}'.format(_key) - name = type(self).__name__ - return '{0}({1!r}{2})'.format(name, list(self), key) + type_name = type(self).__name__ + return '{0}({1!r}{2})'.format(type_name, list(self), key) + def _check(self): - # pylint: disable=protected-access - self._list._check() - assert len(self._set) == len(self._list) + """Check invariants of sorted set. + + Runtime complexity: `O(n)` + + """ _set = self._set - assert all(val in _set for val in self._list) + _list = self._list + _list._check() + assert len(_set) == len(_list) + assert all(value in _set for value in _list)
This is needed since the current bundled version doesn't work on Python 3.10+. Signed-off-by: Timothy Redaelli <tredaelli@redhat.com> --- python/ovs/compat/sortedcontainers/LICENSE | 2 +- .../ovs/compat/sortedcontainers/__init__.py | 70 +- .../ovs/compat/sortedcontainers/sorteddict.py | 1233 +++++---- .../ovs/compat/sortedcontainers/sortedlist.py | 2409 +++++++++-------- .../ovs/compat/sortedcontainers/sortedset.py | 636 ++++- 5 files changed, 2494 insertions(+), 1856 deletions(-)