This commit is contained in:
2026-08-02 18:57:40 +02:00
parent 5e5ab8681a
commit 6fa59321c0
5759 changed files with 712133 additions and 66 deletions
@@ -0,0 +1,48 @@
"""Python Sorted Collections
SortedCollections is an Apache2 licensed Python sorted collections library.
>>> from sortedcollections import ValueSortedDict
>>> vsd = ValueSortedDict({'a': 2, 'b': 1, 'c': 3})
>>> list(vsd.keys())
['b', 'a', 'c']
:copyright: (c) 2015-2021 by Grant Jenks.
:license: Apache 2.0, see LICENSE for more details.
"""
from sortedcontainers import (
SortedDict,
SortedList,
SortedListWithKey,
SortedSet,
)
from .nearestdict import NearestDict
from .ordereddict import OrderedDict
from .recipes import (
IndexableDict,
IndexableSet,
ItemSortedDict,
OrderedSet,
SegmentList,
ValueSortedDict,
)
__all__ = [
'IndexableDict',
'IndexableSet',
'ItemSortedDict',
'NearestDict',
'OrderedDict',
'OrderedSet',
'SegmentList',
'SortedDict',
'SortedList',
'SortedListWithKey',
'SortedSet',
'ValueSortedDict',
]
__version__ = '2.1.0'
@@ -0,0 +1,123 @@
"""NearestDict implementation.
One primary use case for this data structure is storing data by a
`datetime.datetime` or `float` key.
"""
from sortedcontainers import SortedDict
class NearestDict(SortedDict):
"""A dict using nearest-key lookup.
A :class:`SortedDict` subclass that uses nearest-key lookup instead of
exact-key lookup. Optionally, you can specify a rounding mode to return the
nearest key less than or equal to or greater than or equal to the provided
key.
When using :attr:`NearestDict.NEAREST` the keys must support subtraction to
allow finding the nearest key (by find the key with the smallest difference
to the given one).
Additional methods:
* :meth:`NearestDict.nearest_key`
Example usage:
>>> d = NearestDict({1.0: 'foo'})
>>> d[1.0]
'foo'
>>> d[0.0]
'foo'
>>> d[2.0]
'foo'
"""
NEAREST_PREV = -1
NEAREST = 0
NEAREST_NEXT = 1
def __init__(self, *args, **kwargs):
"""Initialize a NearestDict instance.
Optional `rounding` argument dictates how
:meth:`NearestDict.nearest_key` rounds. It must be one of
:attr:`NearestDict.NEAREST_NEXT`, :attr:`NearestDict.NEAREST`, or
:attr:`NearestDict.NEAREST_PREV`. (Default:
:attr:`NearestDict.NEAREST`)
:params rounding: how to round on nearest-key lookup (optional)
:params args: positional arguments for :class:`SortedDict`.
:params kwargs: keyword arguments for :class:`SortedDict`.
"""
self.rounding = kwargs.pop('rounding', self.NEAREST)
super().__init__(*args, **kwargs)
def nearest_key(self, request):
"""Return nearest-key to `request`, respecting `self.rounding`.
>>> d = NearestDict({1.0: 'foo'})
>>> d.nearest_key(0.0)
1.0
>>> d.nearest_key(2.0)
1.0
>>> d = NearestDict({1.0: 'foo'}, rounding=NearestDict.NEAREST_PREV)
>>> d.nearest_key(0.0)
Traceback (most recent call last):
...
KeyError: 'No key below 0.0 found'
>>> d.nearest_key(2.0)
1.0
:param request: nearest-key lookup value
:return: key nearest to `request`, respecting `rounding`
:raises KeyError: if no appropriate key can be found
"""
key_list = self.keys()
if not key_list:
raise KeyError('NearestDict is empty')
index = self.bisect_left(request)
if index >= len(key_list):
if self.rounding == self.NEAREST_NEXT:
raise KeyError(f'No key above {request!r} found')
return key_list[index - 1]
if key_list[index] == request:
return key_list[index]
if index == 0 and self.rounding == self.NEAREST_PREV:
raise KeyError(f'No key below {request!r} found')
if self.rounding == self.NEAREST_PREV:
return key_list[index - 1]
if self.rounding == self.NEAREST_NEXT:
return key_list[index]
if abs(key_list[index - 1] - request) < abs(key_list[index] - request):
return key_list[index - 1]
return key_list[index]
def __getitem__(self, request):
"""Return item corresponding to :meth:`.nearest_key`.
:param request: nearest-key lookup value
:return: item corresponding to key nearest `request`
:raises KeyError: if no appropriate item can be found
>>> d = NearestDict({1.0: 'foo'})
>>> d[0.0]
'foo'
>>> d[2.0]
'foo'
>>> d = NearestDict({1.0: 'foo'}, rounding=NearestDict.NEAREST_NEXT)
>>> d[0.0]
'foo'
>>> d[2.0]
Traceback (most recent call last):
...
KeyError: 'No key above 2.0 found'
"""
key = self.nearest_key(request)
return super().__getitem__(key)
@@ -0,0 +1,207 @@
"""Ordered dictionary implementation.
"""
from itertools import count
from operator import eq
from sortedcontainers import SortedDict
from sortedcontainers.sortedlist import recursive_repr
from .recipes import abc
NONE = object()
class KeysView(abc.KeysView, abc.Sequence):
"Read-only view of mapping keys."
# noqa pylint: disable=too-few-public-methods,protected-access,too-many-ancestors
def __getitem__(self, index):
"``keys_view[index]``"
_nums = self._mapping._nums
if isinstance(index, slice):
nums = _nums._list[index]
return [_nums[num] for num in nums]
return _nums[_nums._list[index]]
class ItemsView(abc.ItemsView, abc.Sequence):
"Read-only view of mapping items."
# noqa pylint: disable=too-few-public-methods,protected-access,too-many-ancestors
def __getitem__(self, index):
"``items_view[index]``"
_mapping = self._mapping
_nums = _mapping._nums
if isinstance(index, slice):
nums = _nums._list[index]
keys = [_nums[num] for num in nums]
return [(key, _mapping[key]) for key in keys]
num = _nums._list[index]
key = _nums[num]
return key, _mapping[key]
class ValuesView(abc.ValuesView, abc.Sequence):
"Read-only view of mapping values."
# noqa pylint: disable=too-few-public-methods,protected-access,too-many-ancestors
def __getitem__(self, index):
"``items_view[index]``"
_mapping = self._mapping
_nums = _mapping._nums
if isinstance(index, slice):
nums = _nums._list[index]
keys = [_nums[num] for num in nums]
return [_mapping[key] for key in keys]
num = _nums._list[index]
key = _nums[num]
return _mapping[key]
class OrderedDict(dict):
"""Dictionary that remembers insertion order and is numerically indexable.
Keys are numerically indexable using dict views. For example::
>>> ordered_dict = OrderedDict.fromkeys('abcde')
>>> keys = ordered_dict.keys()
>>> keys[0]
'a'
>>> keys[-2:]
['d', 'e']
The dict views support the sequence abstract base class.
"""
# pylint: disable=super-init-not-called
def __init__(self, *args, **kwargs):
self._keys = {}
self._nums = SortedDict()
self._keys_view = self._nums.keys()
self._count = count()
self.update(*args, **kwargs)
def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
"``ordered_dict[key] = value``"
if key not in self:
num = next(self._count)
self._keys[key] = num
self._nums[num] = key
dict_setitem(self, key, value)
def __delitem__(self, key, dict_delitem=dict.__delitem__):
"``del ordered_dict[key]``"
dict_delitem(self, key)
num = self._keys.pop(key)
del self._nums[num]
def __iter__(self):
"``iter(ordered_dict)``"
return iter(self._nums.values())
def __reversed__(self):
"``reversed(ordered_dict)``"
nums = self._nums
for key in reversed(nums):
yield nums[key]
def clear(self, dict_clear=dict.clear):
"Remove all items from mapping."
dict_clear(self)
self._keys.clear()
self._nums.clear()
def popitem(self, last=True):
"""Remove and return (key, value) item pair.
Pairs are returned in LIFO order if last is True or FIFO order if
False.
"""
index = -1 if last else 0
num = self._keys_view[index]
key = self._nums[num]
value = self.pop(key)
return key, value
update = __update = abc.MutableMapping.update
def keys(self):
"Return set-like and sequence-like view of mapping keys."
return KeysView(self)
def items(self):
"Return set-like and sequence-like view of mapping items."
return ItemsView(self)
def values(self):
"Return set-like and sequence-like view of mapping values."
return ValuesView(self)
def pop(self, key, default=NONE):
"""Remove given key and return corresponding value.
If key is not found, default is returned if given, otherwise raise
KeyError.
"""
if key in self:
value = self[key]
del self[key]
return value
if default is NONE:
raise KeyError(key)
return default
def setdefault(self, key, default=None):
"""Return ``mapping.get(key, default)``, also set ``mapping[key] = default`` if
key not in mapping.
"""
if key in self:
return self[key]
self[key] = default
return default
@recursive_repr()
def __repr__(self):
"Text representation of mapping."
return f'{self.__class__.__name__}({list(self.items())!r})'
__str__ = __repr__
def __reduce__(self):
"Support for pickling serialization."
return (self.__class__, (list(self.items()),))
def copy(self):
"Return shallow copy of mapping."
return self.__class__(self)
@classmethod
def fromkeys(cls, iterable, value=None):
"""Return new mapping with keys from iterable.
If not specified, value defaults to None.
"""
return cls((key, value) for key in iterable)
def __eq__(self, other):
"Test self and other mapping for equality."
if isinstance(other, OrderedDict):
return dict.__eq__(self, other) and all(map(eq, self, other))
return dict.__eq__(self, other)
__ne__ = abc.MutableMapping.__ne__
def _check(self):
"Check consistency of internal member variables."
# pylint: disable=protected-access
keys = self._keys
nums = self._nums
for key, value in keys.items():
assert nums[value] == key
nums._check()
@@ -0,0 +1,345 @@
"""Sorted collections recipes implementations.
"""
from collections import abc
from copy import deepcopy
from itertools import count
from sortedcontainers import SortedDict, SortedKeyList, SortedSet
from sortedcontainers.sortedlist import recursive_repr
class IndexableDict(SortedDict):
"""Dictionary that supports numerical indexing.
Keys are numerically indexable using dict views. For example::
>>> indexable_dict = IndexableDict.fromkeys('abcde')
>>> keys = indexable_dict.keys()
>>> sorted(keys[:]) == ['a', 'b', 'c', 'd', 'e']
True
The dict views support the sequence abstract base class.
"""
def __init__(self, *args, **kwargs):
super().__init__(hash, *args, **kwargs)
class IndexableSet(SortedSet):
"""Set that supports numerical indexing.
Values are numerically indexable. For example::
>>> indexable_set = IndexableSet('abcde')
>>> sorted(indexable_set[:]) == ['a', 'b', 'c', 'd', 'e']
True
`IndexableSet` implements the sequence abstract base class.
"""
# pylint: disable=too-many-ancestors
def __init__(self, *args, **kwargs):
super().__init__(*args, key=hash, **kwargs)
def __reduce__(self):
return self.__class__, (set(self),)
class ItemSortedDict(SortedDict):
"""Sorted dictionary with key-function support for item pairs.
Requires key function callable specified as the first argument. The
callable must accept two arguments, key and value, and return a value used
to determine the sort order. For example::
def multiply(key, value):
return key * value
mapping = ItemSortedDict(multiply, [(3, 2), (4, 1), (2, 5)])
list(mapping) == [4, 3, 2]
Above, the key/value item pairs are ordered by ``key * value`` according to
the callable given as the first argument.
"""
def __init__(self, *args, **kwargs):
assert args and callable(args[0])
args = list(args)
func = self._func = args[0]
def key_func(key):
"Apply key function to (key, value) item pair."
return func(key, self[key])
args[0] = key_func
super().__init__(*args, **kwargs)
def __delitem__(self, key):
"``del mapping[key]``"
if key not in self:
raise KeyError(key)
self._list_remove(key)
dict.__delitem__(self, key)
def __setitem__(self, key, value):
"``mapping[key] = value``"
if key in self:
self._list_remove(key)
dict.__delitem__(self, key)
dict.__setitem__(self, key, value)
self._list_add(key)
_setitem = __setitem__
def copy(self):
"Return shallow copy of the mapping."
return self.__class__(self._func, iter(self.items()))
__copy__ = copy
def __deepcopy__(self, memo):
items = (deepcopy(item, memo) for item in self.items())
return self.__class__(self._func, items)
class ValueSortedDict(SortedDict):
"""Sorted dictionary that maintains (key, value) item pairs sorted by value.
- ``ValueSortedDict()`` -> new empty dictionary.
- ``ValueSortedDict(mapping)`` -> new dictionary initialized from a mapping
object's (key, value) pairs.
- ``ValueSortedDict(iterable)`` -> new dictionary initialized as if via::
d = ValueSortedDict()
for k, v in iterable:
d[k] = v
- ``ValueSortedDict(**kwargs)`` -> new dictionary initialized with the
name=value pairs in the keyword argument list. For example::
ValueSortedDict(one=1, two=2)
An optional key function callable may be specified as the first
argument. When so, the callable will be applied to the value of each item
pair to determine the comparable for sort order as with Python's builtin
``sorted`` function.
"""
def __init__(self, *args, **kwargs):
args = list(args)
if args and callable(args[0]):
func = self._func = args[0]
def key_func(key):
"Apply key function to ``mapping[value]``."
return func(self[key])
args[0] = key_func
else:
self._func = None
def key_func(key):
"Return mapping value for key."
return self[key]
if args and args[0] is None:
args[0] = key_func
else:
args.insert(0, key_func)
super().__init__(*args, **kwargs)
def __delitem__(self, key):
"``del mapping[key]``"
if key not in self:
raise KeyError(key)
self._list_remove(key)
dict.__delitem__(self, key)
def __setitem__(self, key, value):
"``mapping[key] = value``"
if key in self:
self._list_remove(key)
dict.__delitem__(self, key)
dict.__setitem__(self, key, value)
self._list_add(key)
_setitem = __setitem__
def copy(self):
"Return shallow copy of the mapping."
return self.__class__(self._func, iter(self.items()))
__copy__ = copy
def __reduce__(self):
items = [(key, self[key]) for key in self._list]
args = (self._func, items)
return (self.__class__, args)
@recursive_repr()
def __repr__(self):
items = ', '.join(f'{key!r}: {self[key]!r}' for key in self._list)
return f'{self.__class__.__name__}({self._func!r}, {{{items}}})'
class OrderedSet(abc.MutableSet, abc.Sequence):
"""Like OrderedDict, OrderedSet maintains the insertion order of elements.
For example::
>>> ordered_set = OrderedSet('abcde')
>>> list(ordered_set) == list('abcde')
True
>>> ordered_set = OrderedSet('edcba')
>>> list(ordered_set) == list('edcba')
True
OrderedSet also implements the collections.Sequence interface.
"""
# pylint: disable=too-many-ancestors
def __init__(self, iterable=()):
# pylint: disable=super-init-not-called
self._keys = {}
self._nums = SortedDict()
self._keys_view = self._nums.keys()
self._count = count()
self |= iterable
def __contains__(self, key):
"``key in ordered_set``"
return key in self._keys
count = __contains__
def __iter__(self):
"``iter(ordered_set)``"
return iter(self._nums.values())
def __reversed__(self):
"``reversed(ordered_set)``"
_nums = self._nums
for key in reversed(_nums):
yield _nums[key]
def __getitem__(self, index):
"``ordered_set[index]`` -> element; lookup element at index."
num = self._keys_view[index]
return self._nums[num]
def __len__(self):
"``len(ordered_set)``"
return len(self._keys)
def index(self, value):
"Return index of value."
# pylint: disable=arguments-differ
try:
return self._keys[value]
except KeyError:
raise ValueError(f'{value!r} is not in {type(self).__name__}')
def add(self, value):
"Add element, value, to set."
if value not in self._keys:
num = next(self._count)
self._keys[value] = num
self._nums[num] = value
def discard(self, value):
"Remove element, value, from set if it is a member."
num = self._keys.pop(value, None)
if num is not None:
del self._nums[num]
def __repr__(self):
"Text representation of set."
return f'{type(self).__name__}({list(self)!r})'
__str__ = __repr__
class SegmentList(SortedKeyList):
"""List that supports fast random insertion and deletion of elements.
SegmentList is a special case of a SortedList initialized with a key
function that always returns 0. As such, several SortedList methods are not
implemented for SegmentList.
"""
# pylint: disable=too-many-ancestors
def __init__(self, iterable=()):
super().__init__(iterable, self.zero)
@staticmethod
def zero(_):
"Return 0."
return 0
def __setitem__(self, index, value):
if isinstance(index, slice):
raise NotImplementedError
pos, idx = self._pos(index)
self._lists[pos][idx] = value
def append(self, value):
if self._len:
pos = len(self._lists) - 1
self._lists[pos].append(value)
self._keys[pos].append(0)
self._expand(pos)
else:
self._lists.append([value])
self._keys.append([0])
self._maxes.append(0)
self._len += 1
def extend(self, values):
for value in values:
self.append(value)
def insert(self, index, value):
if index == self._len:
self.append(value)
return
pos, idx = self._pos(index)
self._lists[pos].insert(idx, value)
self._keys[pos].insert(idx, 0)
self._expand(pos)
self._len += 1
def reverse(self):
values = list(self)
values.reverse()
self.clear()
self.extend(values)
def sort(self, key=None, reverse=False):
"Stable sort in place."
values = sorted(self, key=key, reverse=reverse)
self.clear()
self.extend(values)
def _not_implemented(self, *args, **kwargs):
"Not implemented."
raise NotImplementedError
add = _not_implemented
bisect = _not_implemented
bisect_left = _not_implemented
bisect_right = _not_implemented
bisect_key = _not_implemented
bisect_key_left = _not_implemented
bisect_key_right = _not_implemented
irange = _not_implemented
irange_key = _not_implemented
update = _not_implemented