# ordereddict.py
# A dictionary that remembers insertion order
# Tested under Python 2.7 and 2.6.6 only
#
# Copyright (C) 2011 by Lucio Santi <lukius at gmail dot com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from _abcoll import *
try:
from thread import get_ident as _get_ident
except ImportError:
from dummy_thread import get_ident as _get_ident
from operator import eq as _eq
from itertools import imap as _imap
__author__ = 'Lucio Santi <lukius at gmail dot com>'
__version__ = '1.1'
__all__ = ['OrderedDict']
########################### Constants ###########################
FORWARD = 0
BACKWARDS = 1
KEY = 0
VALUE = 1
NEXT = 3
PREVIOUS = 2
#################################################################
class OrderedDict(dict, MutableMapping):
'A dictionary that remembers insertion order.'
# This implementation uses a doubly-linked list of nodes, each
# node being a 4-tuple <key, value, previous node, next node>.
# Despite this, the interesting thing about it is that the list
# is actually embedded in the dictionary. As a consequence,
# there is little space penalty, and also every operation
# exhibits an efficient implementation (i.e., no need to perform
# lookups or deletions multiple times, as it happens with other
# versions of this data structure.).
#
# It is worth noticing that passing an OrderedDict as an argument
# to the dict constructor won't behave as expected. This is due
# to the fact that the internal dictionary keeps additional information
# apart from a key's value. If needed, the instance method dict()
# provides a dict copy of an OrderedDict.
update = MutableMapping.update
setdefault = MutableMapping.setdefault
__ne__ = MutableMapping.__ne__
######################## Class methods #########################
@classmethod
def fromkeys(cls, iterable, value = None):
'''od.fromkeys(S[, v]) -> New ordered dictionary with keys from S
and values equal to v (which defaults to None).
'''
d = cls()
for key in iterable:
d[key] = value
return d
################################################################
######################## Initialization ########################
def __init__(self, *args, **kwds):
"""Initialize an ordered dictionary. Signature is the same as for
regular dictionaries, but keyword arguments are not recommended
because their insertion order is arbitrary.
"""
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
try:
self.first_node
except AttributeError:
self.first_node = None
self.last_node = None
self.update(*args, **kwds)
################################################################
################## Data access & manipulation ##################
__marker = object()
def __getitem__(self, key):
'od.__getitem__(y) <==> od[y]'
node = dict.__getitem__(self, key)
return node[VALUE]
def get(self, key, default = None):
'od.get(k[,d]) -> od[k] if k in od, else d. d defaults to None.'
try:
value = self.__getitem__(key)
except KeyError:
value = default
return value
def __setitem__(self, key, value):
'od.__setitem__(i, y) <==> od[i]=y'
try:
node = dict.__getitem__(self, key)
node[VALUE] = value
except KeyError:
new_node = [key, value, self.last_node, None]
if( self.first_node is None ):
self.first_node = new_node
if( self.last_node is not None ):
self.last_node[NEXT] = new_node
self.last_node = new_node
dict.__setitem__(self, key, new_node)
def __delitem__(self, key):
'od.__delitem__(y) <==> del od[y]'
removed_node = dict.pop(self,key)
self.__adjust_after_removing(removed_node)
def pop(self, key, default = __marker):
'''od.pop(k[,d]) -> v, remove specified key and return the corresponding
value. If key is not found, d is returned if given, otherwise KeyError
is raised.'''
removed_node = dict.pop(self, key, default)
if( removed_node is self.__marker ):
raise KeyError, key
if( removed_node is default ):
return default
self.__adjust_after_removing(removed_node)
return removed_node[VALUE]
def popitem(self, last = True):
'''od.popitem() -> (k, v), remove and return some (key, value) pair as a
2-tuple; but raise KeyError if od is empty.'''
if not self:
raise KeyError('dictionary is empty')
key = next(reversed(self) if last else iter(self))
value = self.pop(key)
return key, value
def clear(self):
'od.clear() -> None. Remove all items from od.'
dict.clear(self)
self.first_node = None
self.last_node = None
def __adjust_after_removing(self, a_node):
'Adjust a_node previous and next pointers after its removal.'
previous = a_node[PREVIOUS]
next = a_node[NEXT]
if( next ):
next[PREVIOUS] = previous
else:
self.last_node = previous
if( previous ):
previous[NEXT] = next
else:
self.first_node = next
################################################################
#################### Iteration & keys/values ###################
def __walk(self, direction = FORWARD, action = lambda x: x, *arguments):
'Iterate over action applied to each node, in the appropriate order.'
if( direction == FORWARD ):
next = NEXT
first = self.first_node
elif( direction == BACKWARDS ):
next = PREVIOUS
first = self.last_node
current_node = first
while( current_node ):
yield action(current_node, *arguments)
current_node = current_node[next]
def __walk_to_list(self, direction = FORWARD, action = lambda x: x, *arguments):
'''Obtain a list of objects resulting from applying action to
each node, in the appropriate order.'''
return_list = list()
item_generator = self.__walk(direction = direction, action = action, *arguments)
for item in item_generator: return_list.append(item)
return return_list
def __iter__(self):
'od.__iter__() <==> iter(od)'
return self.__walk( action = lambda node: node[KEY] )
def __reversed__(self):
'od.__reversed__() <==> reversed(od)'
return self.__walk( direction = BACKWARDS, action = lambda node: node[KEY] )
def keys(self):
"od.keys() -> list of od's keys"
return self.__walk_to_list( action = lambda node: node[KEY] )
def values(self):
"od.values() -> list of od's values"
return self.__walk_to_list( action = lambda node: node[VALUE] )
def items(self):
"od.items() -> list of od's (key, value) pairs, as 2-tuples"
return self.__walk_to_list( action = lambda node: (node[KEY], node[VALUE]) )
def iterkeys(self):
'od.iterkeys() -> an iterator over the keys of od'
return iter(self)
def itervalues(self):
'od.itervalues() -> an iterator over the values of od'
return self.__walk( action = lambda node: node[VALUE] )
def iteritems(self):
'od.iteritems() -> an iterator over the (key, value) items of od'
return self.__walk( action = lambda node: (node[KEY], node[VALUE]) )
################################################################
############################# Copies ###########################
def copy(self):
'od.copy() -> a shallow copy of od'
return self.__class__(self)
def dict(self):
'od.dict() -> a dict copy of od'
d = {}
for item in self.iteritems(): d[item[KEY]] = item[VALUE]
return d
################################################################
########################## Miscellaneous #######################
def __repr__(self, _repr_running = {}):
'od.__repr__() <==> repr(od)'
call_key = id(self), _get_ident()
if call_key in _repr_running:
return '...'
_repr_running[call_key] = 1
try:
if not self:
return '%s()' % (self.__class__.__name__,)
return '%s(%r)' % (self.__class__.__name__, self.items())
finally:
del _repr_running[call_key]
def __reduce__(self):
'Return state information for pickling'
items = self.items()
tmp = self.first_node, self.last_node
del self.first_node, self.last_node
inst_dict = vars(self).copy()
self.first_node, self.last_node = tmp
if inst_dict:
return (self.__class__, (items,), inst_dict)
return self.__class__, (items,)
def __eq__(self, other):
'''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive
while comparison to a regular mapping is order-insensitive.
'''
if isinstance(other, OrderedDict):
return len(self) == len(other) and \
all(_imap(_eq, self.iteritems(), other.iteritems()))
return dict.__eq__(self.dict(), other)
def viewkeys(self):
"od.viewkeys() -> a set-like object providing a view on od's keys"
return KeysView(self)
def viewvalues(self):
"od.viewvalues() -> an object providing a view on od's values"
return ValuesView(self)
def viewitems(self):
"od.viewitems() -> a set-like object providing a view on od's items"
return ItemsView(self)
################################################################