【问题标题】:Python data structure for sorted key-value pairs用于排序键值对的 Python 数据结构
【发布时间】:2013-10-17 14:45:55
【问题描述】:

我有一组(固定的)键,我为其存储一个值。我经常查找一个键的值并增加或减少它。一个典型的 dict 用法。

x = {'a': 1, 'b': 4, 'c': 3}
x['a'] += 1

此外,就像增加或减少值一样,我还需要知道第 i 个最大(或最小)值的键。我当然可以进行排序:

s = sorted(x, key=lambda k:(x[k],k))
s[1] == 'c'

问题是每次排序似乎都相当昂贵。特别是因为我只在排序之间增加一项。我觉得我可以使用另一种更适合此的数据结构。也许是一棵树?

【问题讨论】:

  • 我不知道在后端会更有效的结构。如果您保留了一个排序的键列表,那么您可能会在那里节省几个时钟周期。您只需要在插入时对其进行排序,而不是像您现在有效的那样每次都进行排序。您只需说 s[keyList[i]] 即可获取您的数据。

标签: python data-structures mapping


【解决方案1】:

您可以使用 blist 的 sorteddict 来保持值的顺序。这是一个字典的快速实现,当迭代时,它按值的顺序返回它的键(没有真正深入测试):

import collections
from blist import sorteddict

class ValueSortedDict(collections.MutableMapping):
    def __init__(self, data):
        self._dict = {}
        self._sorted = sorteddict()
        self.update(data)

    def __getitem__(self, key):
        return self._dict[key]

    def __setitem__(self, key, value):
        # remove old value from sorted dictionary
        if key in self._dict:
            self.__delitem__(key)
        # update structure with new value
        self._dict[key] = value
        try:
            keys = self._sorted[value]
        except KeyError:
            self._sorted[value] = set([key])
        else:
            keys.add(key)            

    def __delitem__(self, key):
        value = self._dict.pop(key)
        keys = self._sorted[value]
        keys.remove(key)
        if not keys:
            del self._sorted[value]

    def __iter__(self):
        for value, keys in self._sorted.items():
            for key in keys:
                yield key

    def __len__(self):
        return len(self._dict)

x = ValueSortedDict(dict(a=1, b=4, c=3))
x['a'] += 1
print list(x.items())
x['a'] += 10
print list(x.items())
x['d'] = 4
print list(x.items())

这给出了:

[('a', 2), ('c', 3), ('b', 4)]
[('c', 3), ('b', 4), ('a', 12)]
[('c', 3), ('b', 4), ('d', 4), ('a', 12)]

【讨论】:

  • 这看起来很棒。如果我做对了,setitem 大约需要 O(log(n))。根据排序值查找第 i 个键只需要遍历 sorteddict,这似乎比完整排序的 O(n log(n)) 复杂度要快得多。谢谢!
  • 不客气。同时,我意识到对于您相当特殊的用例,可以进行明显的进一步优化。与其删除一个项目然后重新插入更新,这需要在 btree 上重新搜索,您实际上提前知道 self._sorted 中的一个项目将最多移动一个位置(或保持原位)。您可以通过滚动您自己的特殊大小写 sorteddict 来对此进行优化,其键是仅递增和递减的整数,每次更新为您节省一次搜索。
  • 也许我说的很密集,但是为什么除了blist.sorteddict之外还需要维护一个单独的Python dictsorteddict 不是已经处理所有事情了吗?
  • @JohnY 我认为时间复杂度不同,特别是分配,内置dict 可能更有效。 @MatthiasC.M.Troffaes 是的,这可能会加快速度,但会失去一般性。我突然想到,我正在寻找的内容与具有两个视图的数据库表非常相似——一个在第一列排序,另一个在第二列排序——除了我可能希望在第二列中按索引行看法。有这么大的区别吗?
  • @Paul:分配给dict 比分配给sorteddict 更有效,但是如果您必须分配给bothdict,您将如何节省时间 sorteddict? sorteddict 的全部意义在于完成您在问题中要求的一切。
【解决方案2】:

您可以从collections 使用OrderDict。虽然它在旧的 python 版本中不可用。

from collections import OrderedDict

如果你安装了 django,你可以使用django.utils.datastructures.SortedDict

【讨论】:

  • OrderedDict 仅维护插入顺序,因此不适合 OP 的情况(他需要按值排序)。
  • 任何方式排序都没有问题。重要的是,订单已保存。
  • 你读过这个问题吗?他需要能够找到第 i 个最大值。要使用 OrderedDict 做到这一点,他需要搜索所有值,或者他必须首先按值排序,这是他试图避免的。
【解决方案3】:

使用运算符:

import operator

max(x.iteritems(), key=operator.itemgetter(1))[0]

来自文档:

operator.itemgetter(*items)

返回一个可调用对象,该对象使用 操作数的 getitem() 方法。如果指定了多个项目, 返回查找值的元组。例如:

我不知道这是否是最好的解决方案,但它确实有效。

【讨论】:

    【解决方案4】:

    为什么不使用来自collectionsCounter?然后就可以使用Counter.most_common()获取排序列表了。

    >>> from collections import Counter
    >>> x = Counter({'a': 1, 'b': 4, 'c': 3})
    >>> x['a'] += 1
    >>> x.most_common()
    [('b', 4), ('c', 3), ('a', 2)]
    

    【讨论】:

    • 这正是我所需要的。然而,阅读源代码,most_common 只是调用sorted,这违背了整个目的。 :(
    【解决方案5】:

    我认为大多数 python 结构都会做一些类似于您在示例中所做的事情。我唯一能想到的让它更有效率的事情就是保留一个排序的键列表。这样,您只需在每次插入时进行排序。在您的方法中,每次要按索引访问值时都必须进行排序。这是一个例子:

    x = {'a': 1, 'b': 4, 'c': 3}
    x['a'] += 1
    
    keyList = sorted(x.keys())
    
    print x[keyList[1]]
    4
    
    x['e'] = 7
    x['j'] = 11
    x['d'] = 6
    x['h'] = 8
    
    keyList = sorted(x.keys())
    
    print x[keyList[3]]
    6
    print x[keyList[4]]
    7
    

    希望对您有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-05-12
      • 2013-06-19
      • 2015-08-13
      • 1970-01-01
      • 2015-04-07
      • 2017-01-24
      • 1970-01-01
      • 2018-09-02
      相关资源
      最近更新 更多