我正在寻找有序关联数组的可靠实现,即有序字典。我想要按键排序,而不是插入顺序。
更准确地说,我正在寻找一个空间效率的实现int-to-float(或另一个用例的字符串到float)映射结构:
我想出的最好的方法是粘贴一个字典和一个键列表,保留最后一个用bisect和insert命令。
有更好的想法吗?
答案 0 :(得分:28)
“随机访问O(1)”是一个非常严格的要求,它基本上强加了一个底层哈希表 - 我希望你的意思只是随机的READS,因为我认为它可以在数学上证明,而不是在一般情况下是不可能的有O(1)写入以及O(N)有序迭代。
我认为您不会找到适合您需求的预包装容器,因为它们非常极端 - O(log N)访问当然会在世界上产生重大影响。要获得读取和迭代所需的大O行为,您需要粘合两个数据结构,实质上是dict和堆(或排序列表或树),并使它们保持同步。虽然您没有指定,但我认为您只会获得所需类型的摊销行为 - 除非您真的愿意为插入和删除支付任何性能命中,这是字面含义您所表达的规格,但确实看起来不太可能。但
对于O(1)读取和摊销 O(N)有序迭代,只需保留dict一侧的所有键的列表。 E.g:
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,然后在self.L.sort()
本身重复__setitem__
。这使得写入O(N log N),当然(虽然我仍然在O(1)处写入)。这两种方法都是可行的,并且很难将其视为本质上优于另一种方法。如果你倾向于做一堆写操作然后进行一堆迭代,那么上面代码中的方法是最好的;如果它通常是一次写入,一次迭代,另一次写入,另一次迭代,那么它只是一次洗涤。
答案 1 :(得分:9)
sortedcontainers模块提供符合您要求的SortedDict类型。它基本上将SortedList和dict类型粘合在一起。 dict提供O(1)查找,SortedList提供O(N)迭代(它非常快)。整个模块是纯Python,并且benchmark graphs用于备份性能声明(fast-as-C实现)。 SortedDict也经过全面测试,覆盖范围100%,压力小时。它与Python 2.6到3.4兼容。
答案 2 :(得分:5)
这是我自己的实现:
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中)更聪明结构的东西?
答案 3 :(得分:4)
对于这种情况,有序树通常更好,但随机访问将是log(n)。您还应该考虑插入和移除成本......
答案 4 :(得分:1)
您可以通过在每个位置存储一对(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)。如果你需要更快的速度,你可以在它上面构建一个skip list。
答案 5 :(得分:1)
我不确定您使用的是哪个python版本,但是如果您想要进行实验,Python 3.1包含Ordered词典的正式实现: http://www.python.org/dev/peps/pep-0372/ http://docs.python.org/3.1/whatsnew/3.1.html#pep-372-ordered-dictionaries
答案 6 :(得分:1)
我在2007年实施的ordereddict包(http://anthon.home.xs4all.nl/Python/ordereddict/)包括sorteddict。 sorteddict是一个KSO(密钥排序)字典。它以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
>>>
对于迟到的回复感到抱歉,也许这个答案可以帮助其他人找到该库。
答案 7 :(得分:0)
这是一个馅饼:我需要类似的东西。但请注意,这个特定的实现是不可变的,一旦创建实例就没有插入:但是,确切的性能并不完全符合您的要求。查找为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()
答案 8 :(得分:0)
对于“string to float”问题,您可以使用Trie - 它提供O(1)访问时间和O(n)排序迭代。 “排序”是指“按键按字母顺序排序” - 似乎问题暗示相同。
一些实现(每个实现都有自己的优点和缺点):
答案 9 :(得分:0)