繁体   English   中英

Python中按键排序的dict

[英]Key-ordered dict in Python

我正在寻找有序关联数组的可靠实现,即有序字典。 我想要按键的顺序,而不是插入顺序。

更确切地说,我正在寻找一个空间效率的实现int-to-float(或另一个用例的字符串到float)映射结构,其中:

  • 有序迭代是O(n)
  • 随机访问是O(1)

我想出的最好的方法是粘贴一个字典和一个键列表,保留最后一个用bisect和insert命令。

有更好的想法吗?

“随机访问O(1)”是一个非常严格的要求,它基本上强加了一个底层哈希表 - 我希望你的意思只是随机READS,因为我认为它可以在数学上证明,而不是在一般情况下不可能有O (1)写入以及O(N)有序迭代。

我不认为你会找到一个适合你需要的预包装容器,因为它们非常极端--O(log N)访问当然会让世界变得与众不同。 要获得读取和迭代所需的大O行为,您需要粘合两个数据结构,实质上是dict和堆(或排序列表或树),并使它们保持同步。 虽然您没有指定,但我认为您只会得到您想要的那种摊销行为 - 除非您真的愿意为插入和删除支付任何性能命中,这是您表达的规范的字面含义但是似乎是一个非常不可能的现实生活要求。

对于O(1)读取和摊销的 O(N)有序迭代,只需保留dict一侧的所有键的列表。 例如:

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时代的“自然融合”,在这方面是一个奇迹。

sortedcontainers模块提供符合您要求的SortedDict类型。 它基本上将SortedList和dict类型粘合在一起。 dict提供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)。 您还应该考虑插入和移除成本......

我在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
>>> 

对不起,迟到的回复,也许这个答案可以帮助其他人找到该库。

你可以通过在每个位置存储一对(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

保持一个指向startend的指针,您将有效地更新那些只需要添加到列表末尾的情况。

插入中间显然是O(n)。 如果你需要更快的速度,你可以在它上面建立一个跳过列表

我不确定您使用的是哪个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()

对于“字符串到浮动”问题,您可以使用Trie - 它提供O(1)访问时间和O(n)排序迭代。 通过“排序”我的意思是“按键按字母顺序排序” - 似乎问题意味着相同。

一些实现(每个都有自己的优点和缺点):

我认为这是其他答案中没有提到的一个选项:

  • 使用二叉搜索树( Tr e ap / AVL / RB )来保持映射。
  • 还可以使用hashmap(又名字典)来保持相同的映射(再次)。

这将提供O(n)有序遍历(通过树), O(1)随机访问(通过散列映射)和O(log n)插入/删除(因为您需要更新树和散列)。

缺点是需要将所有数据保留两次,但是在这种意义上,建议将键列表与哈希映射保持在一起的替代方案并不是更好。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM