mirror of https://github.com/H-uru/korman.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
201 lines
6.2 KiB
201 lines
6.2 KiB
8 years ago
|
"""
|
||
|
An OrderedSet is a custom MutableSet that remembers its order, so that every
|
||
|
entry has an index that can be looked up.
|
||
|
|
||
|
Based on a recipe originally posted to ActiveState Recipes by Raymond Hettiger,
|
||
|
and released under the MIT license.
|
||
|
|
||
|
Rob Speer's changes are as follows:
|
||
|
|
||
|
- changed the content from a doubly-linked list to a regular Python list.
|
||
|
Seriously, who wants O(1) deletes but O(N) lookups by index?
|
||
|
- add() returns the index of the added item
|
||
|
- index() just returns the index of an item
|
||
|
- added a __getstate__ and __setstate__ so it can be pickled
|
||
|
- added __getitem__
|
||
|
|
||
|
Updated for Python 3.5 by Adam Johnson
|
||
|
"""
|
||
|
import collections.abc
|
||
|
|
||
|
SLICE_ALL = slice(None)
|
||
|
__version__ = '2.0.1'
|
||
|
|
||
|
|
||
|
def is_iterable(obj):
|
||
|
"""
|
||
|
Are we being asked to look up a list of things, instead of a single thing?
|
||
|
We check for the `__iter__` attribute so that this can cover types that
|
||
|
don't have to be known by this module, such as NumPy arrays.
|
||
|
|
||
|
Strings, however, should be considered as atomic values to look up, not
|
||
|
iterables. The same goes for tuples, since they are immutable and therefore
|
||
|
valid entries.
|
||
|
|
||
|
We don't need to check for the Python 2 `unicode` type, because it doesn't
|
||
|
have an `__iter__` attribute anyway.
|
||
|
"""
|
||
|
return hasattr(obj, '__iter__') and not isinstance(obj, str) and not isinstance(obj, tuple)
|
||
|
|
||
|
|
||
|
class OrderedSet(collections.abc.MutableSet):
|
||
|
"""
|
||
|
An OrderedSet is a custom MutableSet that remembers its order, so that
|
||
|
every entry has an index that can be looked up.
|
||
|
"""
|
||
|
def __init__(self, iterable=None):
|
||
|
self.items = []
|
||
|
self.map = {}
|
||
|
if iterable is not None:
|
||
|
self |= iterable
|
||
|
|
||
|
def __len__(self):
|
||
|
return len(self.items)
|
||
|
|
||
|
def __getitem__(self, index):
|
||
|
"""
|
||
|
Get the item at a given index.
|
||
|
|
||
|
If `index` is a slice, you will get back that slice of items. If it's
|
||
|
the slice [:], exactly the same object is returned. (If you want an
|
||
|
independent copy of an OrderedSet, use `OrderedSet.copy()`.)
|
||
|
|
||
|
If `index` is an iterable, you'll get the OrderedSet of items
|
||
|
corresponding to those indices. This is similar to NumPy's
|
||
|
"fancy indexing".
|
||
|
"""
|
||
|
if index == SLICE_ALL:
|
||
|
return self
|
||
|
elif hasattr(index, '__index__') or isinstance(index, slice):
|
||
|
result = self.items[index]
|
||
|
if isinstance(result, list):
|
||
|
return OrderedSet(result)
|
||
|
else:
|
||
|
return result
|
||
|
elif is_iterable(index):
|
||
|
return OrderedSet([self.items[i] for i in index])
|
||
|
else:
|
||
|
raise TypeError("Don't know how to index an OrderedSet by %r" %
|
||
|
index)
|
||
|
|
||
|
def copy(self):
|
||
|
return OrderedSet(self)
|
||
|
|
||
|
def __getstate__(self):
|
||
|
if len(self) == 0:
|
||
|
# The state can't be an empty list.
|
||
|
# We need to return a truthy value, or else __setstate__ won't be run.
|
||
|
#
|
||
|
# This could have been done more gracefully by always putting the state
|
||
|
# in a tuple, but this way is backwards- and forwards- compatible with
|
||
|
# previous versions of OrderedSet.
|
||
|
return (None,)
|
||
|
else:
|
||
|
return list(self)
|
||
|
|
||
|
def __setstate__(self, state):
|
||
|
if state == (None,):
|
||
|
self.__init__([])
|
||
|
else:
|
||
|
self.__init__(state)
|
||
|
|
||
|
def __contains__(self, key):
|
||
|
return key in self.map
|
||
|
|
||
|
def add(self, key):
|
||
|
"""
|
||
|
Add `key` as an item to this OrderedSet, then return its index.
|
||
|
|
||
|
If `key` is already in the OrderedSet, return the index it already
|
||
|
had.
|
||
|
"""
|
||
|
if key not in self.map:
|
||
|
self.map[key] = len(self.items)
|
||
|
self.items.append(key)
|
||
|
return self.map[key]
|
||
|
append = add
|
||
|
|
||
|
def update(self, sequence):
|
||
|
"""
|
||
|
Update the set with the given iterable sequence, then return the index
|
||
|
of the last element inserted.
|
||
|
"""
|
||
|
item_index = None
|
||
|
try:
|
||
|
for item in sequence:
|
||
|
item_index = self.add(item)
|
||
|
except TypeError:
|
||
|
raise ValueError('Argument needs to be an iterable, got %s' % type(sequence))
|
||
|
return item_index
|
||
|
|
||
|
def index(self, key):
|
||
|
"""
|
||
|
Get the index of a given entry, raising an IndexError if it's not
|
||
|
present.
|
||
|
|
||
|
`key` can be an iterable of entries that is not a string, in which case
|
||
|
this returns a list of indices.
|
||
|
"""
|
||
|
if is_iterable(key):
|
||
|
return [self.index(subkey) for subkey in key]
|
||
|
return self.map[key]
|
||
|
|
||
|
def pop(self):
|
||
|
"""
|
||
|
Remove and return the last element from the set.
|
||
|
|
||
|
Raises KeyError if the set is empty.
|
||
|
"""
|
||
|
if not self.items:
|
||
|
raise KeyError('Set is empty')
|
||
|
|
||
|
elem = self.items[-1]
|
||
|
del self.items[-1]
|
||
|
del self.map[elem]
|
||
|
return elem
|
||
|
|
||
|
def discard(self, key):
|
||
|
"""
|
||
|
Remove an element. Do not raise an exception if absent.
|
||
|
|
||
|
The MutableSet mixin uses this to implement the .remove() method, which
|
||
|
*does* raise an error when asked to remove a non-existent item.
|
||
|
"""
|
||
|
if key in self:
|
||
|
i = self.items.index(key)
|
||
|
del self.items[i]
|
||
|
del self.map[key]
|
||
|
for k, v in self.map.items():
|
||
|
if v >= i:
|
||
|
self.map[k] = v - 1
|
||
|
|
||
|
def clear(self):
|
||
|
"""
|
||
|
Remove all items from this OrderedSet.
|
||
|
"""
|
||
|
del self.items[:]
|
||
|
self.map.clear()
|
||
|
|
||
|
def __iter__(self):
|
||
|
return iter(self.items)
|
||
|
|
||
|
def __reversed__(self):
|
||
|
return reversed(self.items)
|
||
|
|
||
|
def __repr__(self):
|
||
|
if not self:
|
||
|
return '%s()' % (self.__class__.__name__,)
|
||
|
return '%s(%r)' % (self.__class__.__name__, list(self))
|
||
|
|
||
|
def __eq__(self, other):
|
||
|
if isinstance(other, OrderedSet):
|
||
|
return len(self) == len(other) and self.items == other.items
|
||
|
try:
|
||
|
other_as_set = set(other)
|
||
|
except TypeError:
|
||
|
# If `other` can't be converted into a set, it's not equal.
|
||
|
return False
|
||
|
else:
|
||
|
return set(self) == other_as_set
|
||
|
|