我正在寻找一个有力的有序关联数组实现,也就是有序字典。我想要根据键(key)排序,而不是插入顺序。
更精确地说,我正在寻找一种空间高效的整数到浮点数映射结构的实现(或者针对另一种用例的字符串到浮点数映射结构),满足以下要求:
- 有序迭代时间复杂度为O(n)
- 随机访问时间复杂度为O(1)
我想到的最好方法是将一个字典和一个键(key)列表粘合在一起,使用bisect和insert保持最后一个键列表有序。
还有更好的想法吗?
我正在寻找一个有力的有序关联数组实现,也就是有序字典。我想要根据键(key)排序,而不是插入顺序。
更精确地说,我正在寻找一种空间高效的整数到浮点数映射结构的实现(或者针对另一种用例的字符串到浮点数映射结构),满足以下要求:
我想到的最好方法是将一个字典和一个键(key)列表粘合在一起,使用bisect和insert保持最后一个键列表有序。
还有更好的想法吗?
"Random access O(1)"这是一个非常严格的要求,基本上需要使用底层的哈希表--我希望你指的是随机读取,因为我认为在一般情况下无法同时实现O(1)写入和O(N)有序迭代,这个可以通过数学证明。
我认为你不会找到适合你需求的预打包容器,因为它们太极端了——当然,O(log N)访问会完全改变一切。要获得你想要的读取和迭代的大O效果,你需要将两个数据结构粘合起来,基本上是一个字典和一个堆(或排序列表或树),并保持它们同步。虽然你没有详细说明,但我认为你只能获得你想要的那种分摊行为-除非你真的愿意承担插入和删除的任何性能损失,这是你所表达的规范的字面含义,但似乎是一个相当不可能实际需求。
对于O(1)读取和分摊O(N)有序迭代,只需在字典的侧面保留所有键的列表即可。例如:
class Crazy(object):
def __init__(self):
self.d = {}
self.L = []
self.sorted = True
def __getitem__(self, k):
return self.d[k]
def __setitem__(self, k, v):
if k not in self.d:
self.L.append(k)
self.sorted = False
self.d[k] = v
def __delitem__(self, k):
del self.d[k]
self.L.remove(k)
def __iter__(self):
if not self.sorted:
self.L.sort()
self.sorted = True
return iter(self.L)
如果您不喜欢“摊销O(N)顺序”,则可以去掉self.sorted并在__setitem__本身中重复self.L.sort()。当然,这将使写操作变为O(N log N)(而我仍然保持写操作为O(1))。任何一种方法都可行,很难想象哪种方法本质上比另一种更优越。如果您倾向于进行一系列的写操作,然后进行一系列的迭代,那么上面代码中的方法是最好的;如果通常是一个写操作、一个迭代、另一个写操作、另一个迭代,那么两种方法差不多。顺便说一下,这利用了Python排序(也称为“timsort”)的不寻常且出色的性能特征:其中,对于大部分已排序但末尾添加了一些额外项的列表,排序基本上是O(N)的(如果与已排序前缀部分相比,附加的项数量足够少)。我听说Java很快也会实现这种排序,因为Josh Block非常赞赏有关Python排序的技术讲座,他在笔记本电脑上开始为JVM编码。大多数系统(包括我今天所了解的Jython和IronPython)基本上将排序作为O(N log N)操作,不能利用“大体排序”的输入;Tim Peters将“自然合并排序”转变为了Python今天的timsort,在这方面是一种奇迹。
sortedcontainers模块提供了一个SortedDict类型,满足您的要求。它基本上将SortedList和字典类型粘合在一起。字典提供O(1)查找,而SortedList提供O(N)迭代(非常快)。整个模块是纯Python的,并且有基准测试图表来支持性能声明(快如C语言实现)。SortedDict也经过100%覆盖率的完全测试和数小时的压力测试。它兼容Python 2.6到3.4。
import bisect
class KeyOrderedDict(object):
__slots__ = ['d', 'l']
def __init__(self, *args, **kwargs):
self.l = sorted(kwargs)
self.d = kwargs
def __setitem__(self, k, v):
if not k in self.d:
idx = bisect.bisect(self.l, k)
self.l.insert(idx, k)
self.d[k] = v
def __getitem__(self, k):
return self.d[k]
def __delitem__(self, k):
idx = bisect.bisect_left(self.l, k)
del self.l[idx]
del self.d[k]
def __iter__(self):
return iter(self.l)
def __contains__(self, k):
return k in self.d
使用bisect可以保持self.l有序,插入的时间复杂度为O(n)(因为插入操作,但在我的情况下不是致命的,因为我更经常地进行追加而不是真正的插入,所以通常情况下是平摊的O(1))。访问的时间复杂度为O(1),迭代的时间复杂度为O(n)。但也许有人已经发明了一个更聪明的结构(用C语言实现)?
对于这种情况,有序树通常更好,但随机访问会是log(n)。您还应考虑插入和删除的成本...
(value, next_key)
来实现遍历。my_dict[k][0] # for a key k
遍历:
k = start_key # stored somewhere
while k is not None: # next_key is None at the end of the list
v, k = my_dict[k]
yield v
保持对start
和end
的指针,您将拥有高效的更新方式,适用于那些只需要在列表末尾添加的情况。
在中间插入显然是O(n)。如果需要更快的速度,可能可以在其上构建跳表。
我在2007年实现的ordereddict包(http://anthon.home.xs4all.nl/Python/ordereddict/)包含了sorteddict。sorteddict是一个KSO(Key Sorted Order)字典,用C实现,非常节省空间,并且比纯Python实现快几倍。缺点是它只能与CPython一起使用。
>>> from _ordereddict import sorteddict
>>> x = sorteddict()
>>> x[1] = 1.0
>>> x[3] = 3.3
>>> x[2] = 2.2
>>> print x
sorteddict([(1, 1.0), (2, 2.2), (3, 3.3)])
>>> for i in x:
... print i, x[i]
...
1 1.0
2 2.2
3 3.3
>>>
抱歉回复晚了,也许这个答案可以帮助其他人找到那个库。
我不确定你正在使用哪个Python版本,但是如果你想尝试一下,Python 3.1包含了一个官方实现的有序字典:
http://www.python.org/dev/peps/pep-0372/ http://docs.python.org/3.1/whatsnew/3.1.html#pep-372-ordered-dictionaries这里有一个代码片段:我需要类似的东西。但请注意,此特定实现是不可变的,没有插入,一旦创建了实例:确切的性能并不完全符合您所要求的,查找是O(log n),全扫描是O(n)。这是使用bisect模块在键/值(元组)对的元组上工作的。即使您无法精确使用它,您也可能会成功地将其适应您的需求。
import bisect
class dictuple(object):
"""
>>> h0 = dictuple()
>>> h1 = dictuple({"apples": 1, "bananas":2})
>>> h2 = dictuple({"bananas": 3, "mangoes": 5})
>>> h1+h2
('apples':1, 'bananas':3, 'mangoes':5)
>>> h1 > h2
False
>>> h1 > 6
False
>>> 'apples' in h1
True
>>> 'apples' in h2
False
>>> d1 = {}
>>> d1[h1] = "salad"
>>> d1[h1]
'salad'
>>> d1[h2]
Traceback (most recent call last):
...
KeyError: ('bananas':3, 'mangoes':5)
"""
def __new__(cls, *args, **kwargs):
initial = {}
args = [] if args is None else args
for arg in args:
initial.update(arg)
initial.update(kwargs)
instance = object.__new__(cls)
instance.__items = tuple(sorted(initial.items(),key=lambda i:i[0]))
return instance
def __init__(self,*args, **kwargs):
pass
def __find(self,key):
return bisect.bisect(self.__items, (key,))
def __getitem__(self, key):
ind = self.__find(key)
if self.__items[ind][0] == key:
return self.__items[ind][1]
raise KeyError(key)
def __repr__(self):
return "({0})".format(", ".join(
"{0}:{1}".format(repr(item[0]),repr(item[1]))
for item in self.__items))
def __contains__(self,key):
ind = self.__find(key)
return self.__items[ind][0] == key
def __cmp__(self,other):
return cmp(self.__class__.__name__, other.__class__.__name__
) or cmp(self.__items, other.__items)
def __eq__(self,other):
return self.__items == other.__items
def __format__(self,key):
pass
#def __ge__(self,key):
# pass
#def __getattribute__(self,key):
# pass
#def __gt__(self,key):
# pass
__seed = hash("dictuple")
def __hash__(self):
return dictuple.__seed^hash(self.__items)
def __iter__(self):
return self.iterkeys()
def __len__(self):
return len(self.__items)
#def __reduce__(self,key):
# pass
#def __reduce_ex__(self,key):
# pass
#def __sizeof__(self,key):
# pass
@classmethod
def fromkeys(cls,key,v=None):
cls(dict.fromkeys(key,v))
def get(self,key, default):
ind = self.__find(key)
return self.__items[ind][1] if self.__items[ind][0] == key else default
def has_key(self,key):
ind = self.__find(key)
return self.__items[ind][0] == key
def items(self):
return list(self.iteritems())
def iteritems(self):
return iter(self.__items)
def iterkeys(self):
return (i[0] for i in self.__items)
def itervalues(self):
return (i[1] for i in self.__items)
def keys(self):
return list(self.iterkeys())
def values(self):
return list(self.itervalues())
def __add__(self, other):
_sum = dict(self.__items)
_sum.update(other.__items)
return self.__class__(_sum)
if __name__ == "__main__":
import doctest
doctest.testmod()