【问题标题】:Value-sorted dict for Python?Python的值排序字典?
【发布时间】:2012-01-29 16:14:04
【问题描述】:

我对 Python 的 dict 实现感兴趣,它为排序值提供了迭代接口。即,带有“sortedvalues()”功能的dict

天真地可以做sorted(dict.values()),但这不是我想要的。每次插入或删除项目时,都必须运行一次完全排序,效率不高。

请注意,我也不是在询问按键排序的 dict(对于该问题,Key-ordered dict in PythonPython 2.6 TreeMap/SortedDictionary? 中有很好的答案)。

【问题讨论】:

  • 您可以编写自己的哈希函数来覆盖内置的字典哈希
  • @JoelCornett 散列函数必须仅基于 key。否则将无法正常工作。

标签: python dictionary


【解决方案1】:

一种解决方案是编写一个继承自 dict 的类,但还维护一个按其值排序的键列表 (sorted_keys),以及相应(排序的)值列表 (sorted_values)。

然后您可以定义一个使用bisect 模块的__setitem__() 方法,以便快速了解k 应该在两个列表中插入新(键、值)对的位置。然后,您可以在字典本身以及您维护的两个列表中插入新键和新值,sorted_values[k:k] = [new_value]sorted_keys[k:k] = [new_key];不幸的是,这种插入的时间复杂度是O(n)(所以整个字典都是O(n^2))。

插入有序元素的另一种方法是使用heapq 模块并在其中插入(value, key) 对。这适用于O(log n),而不是上一段的基于列表的方法。

然后可以通过遍历您维护的键列表 (sorted_keys) 来简单地遍历字典。

这种方法节省了每次您想要遍历字典(使用排序值)时对键进行排序所花费的时间,基本上将这个时间成本转移(不幸的是,增加了)这个时间成本来构建排序列表键和值。

【讨论】:

  • 插入列表通常是O(n),当它们基于数组时。这很可能是这种情况,否则list[123] 将是O(n) 而不是O(1)
  • @Anony-Mousse:谢谢!你是对的 (wiki.python.org/moin/TimeComplexity)。
  • 您可以使用的一种方法是在每次添加项目时简单地对列表进行排序。当列表几乎按排序顺序时,Python 的排序非常有效。它可能甚至比bisect 更快,因为bisect 是用Python 编写的,而sort 是用C 编写的。
  • 但是,CPU 可以很好地优化该操作,并且可能会击败幼稚的树实现。我也在考虑推荐使用 bisect 的排序列表。然而,问题是维护dict 端。如果您使用映射到键的列表位置的 dict,您还需要在字典中的任何插入或删除操作时对此列表进行 O(n) 更新。据我了解,它仍然应该是dict
  • @Anony-Mousse:您可以通过将(value, key) 对保持在堆中来克服O(n) 更新。
【解决方案2】:

问题是您需要通过 keys 对其进行排序或散列以获得合理的插入和查找性能。实现它的一种天真的方法是按值排序的条目树结构,以及用于查找键的树位置的字典。不过,您需要深入更新树,因为此查找字典需要保持正确。本质上,就像您对可更新堆所做的那样。

我认为有太多的选择可以从这样的结构中创建一个合理的标准库选项,而它却很少需要。

更新:可能对您有用的一个技巧是使用双重结构:

  1. 像往常一样存储键值对的常规dict

  2. 任何类型的排序列表,例如使用bisect

然后你必须在两者上实现共同的操作:一个新的值被插入到两个结构中。棘手的部分是更新和删除操作。您使用第一个结构查找旧值,从第二个结构中删除旧值,然后(更新时)像以前一样重新插入。

如果您也需要知道键,请将 (value, key) 对存储在您的 b 列表中。

更新 2:试试这个课程:

import bisect
class dictvs(dict):
    def __init__(self):
        self._list = []

    def __setitem__(self, key, value):
        old = self.get(key)
        if old is None:
            bisect.insort(self._list, value)
            dict.__setitem__(self, key, value)
        else:
            oldpos = bisect.bisect_left(self._list, old)
            newpos = bisect.bisect_left(self._list, value)
            if newpos > oldpos:
                newpos -= 1
                for i in xrange(oldpos, newpos):
                    self._list[i] = self._list[i + 1]
            else:
                for i in xrange(oldpos, newpos, -1):
                    self._list[i] = self._list[i - 1]
            self._list[newpos] = value
            dict.__setitem__(self, key, value)

    def __delitem__(self, key):
        old = self.get(key)
        if old is not None:
            oldpos = bisect.bisect(self._list, old)
            del self._list[oldpos]
        dict.__delitem__(self, key)

    def values(self):
        return list(self._list)

我猜这不是一个完整的dict。我还没有测试过删除,只是一个很小的更新集。您应该对其进行更大的单元测试,并将values() 的返回与sorted(dict.values(instance)) 的返回进行比较。这只是为了展示如何使用bisect更新排序列表

【讨论】:

  • 正如在其他 cmets(包括你的)中提到的,将新值附加到值列表然后重新排序在 Python 中应该非常快。我不确定更新一个值(在列表中间)然后重新排序的效率如何——如果它也很快,那么这个解决方案(一个字典 + 一个排序的值列表)将是完美的,因为在我的使用中很少发生删除。
  • 更新是棘手的部分。 “删除并重新插入”更好还是“更新和使用”更好,这在很大程度上取决于您的数据特征。优化的实现将计算删除 重新插入位置,然后仅在此间隔内移动列表的内容。但显然,这最多比使用 bisect 的简单删除和重新插入快两倍。 Resorting 还需要至少 n-1 步骤来检查列表是否仍然排序。
  • @Anony-Mousse:难道不能通过使用切片分配让代码中的删除插入更快吗? self._list[oldpos:newpos] = self._list[oldpos+1:newpos] + [value] 之类的东西(我没有检查索引是否正确,但想法应该很清楚)。
  • 这可能取决于您收到的更新类型。我的代码针对值变化不大的情况进行了优化,因此newpos-oldpos 非常小。由于数组重复,切片可能会导致大量内存管理成本。
【解决方案3】:

这是另一个更简单的想法:

  • 您创建了一个继承自 dict 的类。
  • 您使用缓存:仅在遍历字典时对键进行排序,并将字典标记为已排序;插入应该简单地附加到键列表中。

在评论中提到排序几乎排序的列表很快,所以这种方法应该很快。

【讨论】:

  • 如何删除/更新缓存?当您在任何此类操作中销毁它时,您实际上是在使用sorted(dict.values())
  • 缓存仅在遍历字典时更新;如果它已过时;创建它之后,后续的排序应该很快,因为缓存已经部分排序(kindall 的评论)。无需销毁缓存,因为在每次插入时都会将值附加到缓存中;在追加期间,缓存应该被标记为脏(带有标志)。
  • 插入是微不足道的。我说的是删除对象和更新字典中的值。
  • 对象可以立即被删除,但这很昂贵,具有缓存更新的优势:可以再次对键进行排序(通常很快,因为它们大部分都已排序,并且在 O(n log n)否则),删除的key+value可以在O(log n)中找到bisect,删除key和value列表本身是O(n)。但是,根据我的经验,删除可能不像插入那么常见,因此根据情况,这可能根本不是问题。更新值还可以触发缓存更新(排序),然后在 O(log n) 中进行平分 + 在 O(1) 中进行简单更新。
【解决方案4】:

您可以使用skip dict。它是一个永久按值排序的 Python 字典。

插入比普通字典稍微贵一些,但如果您经常需要按顺序迭代,或者执行基于值的查询,例如:

  1. 最高/最低项目是什么?
  2. 哪些项目的值介于 X 和 Y 之间?

【讨论】:

    猜你喜欢
    • 2011-08-28
    • 2013-05-01
    • 2021-05-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-24
    • 2021-08-25
    相关资源
    最近更新 更多