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.
116 lines
3.6 KiB
116 lines
3.6 KiB
class SortedDict(dict): |
|
""" |
|
A dictionary that keeps its keys in the order in which they're inserted. |
|
""" |
|
def __new__(cls, *args, **kwargs): |
|
instance = super(SortedDict, cls).__new__(cls, *args, **kwargs) |
|
instance.keyOrder = [] |
|
return instance |
|
|
|
def __init__(self, data=None): |
|
if data is None: |
|
data = {} |
|
elif isinstance(data, GeneratorType): |
|
# Unfortunately we need to be able to read a generator twice. Once |
|
# to get the data into self with our super().__init__ call and a |
|
# second time to setup keyOrder correctly |
|
data = list(data) |
|
super(SortedDict, self).__init__(data) |
|
if isinstance(data, dict): |
|
self.keyOrder = data.keys() |
|
else: |
|
self.keyOrder = [] |
|
for key, value in data: |
|
if key not in self.keyOrder: |
|
self.keyOrder.append(key) |
|
|
|
def __deepcopy__(self, memo): |
|
return self.__class__([(key, deepcopy(value, memo)) |
|
for key, value in self.iteritems()]) |
|
|
|
def __setitem__(self, key, value): |
|
if key not in self: |
|
self.keyOrder.append(key) |
|
super(SortedDict, self).__setitem__(key, value) |
|
|
|
def __delitem__(self, key): |
|
super(SortedDict, self).__delitem__(key) |
|
self.keyOrder.remove(key) |
|
|
|
def __iter__(self): |
|
return iter(self.keyOrder) |
|
|
|
def pop(self, k, *args): |
|
result = super(SortedDict, self).pop(k, *args) |
|
try: |
|
self.keyOrder.remove(k) |
|
except ValueError: |
|
# Key wasn't in the dictionary in the first place. No problem. |
|
pass |
|
return result |
|
|
|
def popitem(self): |
|
result = super(SortedDict, self).popitem() |
|
self.keyOrder.remove(result[0]) |
|
return result |
|
|
|
def items(self): |
|
return zip(self.keyOrder, self.values()) |
|
|
|
def iteritems(self): |
|
for key in self.keyOrder: |
|
yield key, self[key] |
|
|
|
def keys(self): |
|
return self.keyOrder[:] |
|
|
|
def iterkeys(self): |
|
return iter(self.keyOrder) |
|
|
|
def values(self): |
|
return map(self.__getitem__, self.keyOrder) |
|
|
|
def itervalues(self): |
|
for key in self.keyOrder: |
|
yield self[key] |
|
|
|
def update(self, dict_): |
|
for k, v in dict_.iteritems(): |
|
self[k] = v |
|
|
|
def setdefault(self, key, default): |
|
if key not in self: |
|
self.keyOrder.append(key) |
|
return super(SortedDict, self).setdefault(key, default) |
|
|
|
def value_for_index(self, index): |
|
"""Returns the value of the item at the given zero-based index.""" |
|
return self[self.keyOrder[index]] |
|
|
|
def insert(self, index, key, value): |
|
"""Inserts the key, value pair before the item with the given index.""" |
|
if key in self.keyOrder: |
|
n = self.keyOrder.index(key) |
|
del self.keyOrder[n] |
|
if n < index: |
|
index -= 1 |
|
self.keyOrder.insert(index, key) |
|
super(SortedDict, self).__setitem__(key, value) |
|
|
|
def copy(self): |
|
"""Returns a copy of this object.""" |
|
# This way of initializing the copy means it works for subclasses, too. |
|
obj = self.__class__(self) |
|
obj.keyOrder = self.keyOrder[:] |
|
return obj |
|
|
|
def __repr__(self): |
|
""" |
|
Replaces the normal dict.__repr__ with a version that returns the keys |
|
in their sorted order. |
|
""" |
|
return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self.items()]) |
|
|
|
def clear(self): |
|
super(SortedDict, self).clear() |
|
self.keyOrder = [] |