如何让 heapq 评估特定属性的堆?

Posted

技术标签:

【中文标题】如何让 heapq 评估特定属性的堆?【英文标题】:How to make heapq evaluate the heap off of a specific attribute? 【发布时间】:2011-04-26 15:41:51 【问题描述】:

我希望拥有一堆对象,而不仅仅是数字。它们将具有堆可以排序的整数属性。在python中使用堆最简单的方法是heapq,但是在使用heapq时如何告诉它按特定属性排序呢?

【问题讨论】:

【参考方案1】:

很遗憾,您不能,尽管这是一个经常被要求的功能。

一种选择是将(键,值)元组插入堆中。但是,如果值在比较时抛出异常(它们将在键之间存在关联的情况下进行比较),则这将不起作用。

第二种选择是在类中定义一个__lt__(小于)方法,该方法将使用适当的属性来比较元素以进行排序。但是,如果对象是由另一个包创建的,或者如果您需要它们在程序的其他地方进行不同的比较,这可能是不可能的。

第三种选择是使用blist 模块中的sortedlist 类(免责声明:我是作者)。 sortedlist 的构造函数采用 key 参数,该参数允许您指定返回元素排序键的函数,类似于 list.sortsortedkey 参数。

【讨论】:

我删除了我之前的评论,因为我与 blist 的问题可能是 PEBCAK(再次感谢您的模块),所以我只复制了之前评论的第一部分:总是可以定义一个通过子类化或封装使用__lt__ 的类。【参考方案2】:

heapq 对对象进行排序的方式与 list.sort 相同,因此只需在您的类定义中定义一个方法 __cmp__(),它将自己与同一类的另一个实例进行比较:

def __cmp__(self, other):
    return cmp(self.intAttribute, other.intAttribute)

适用于 Python 2.x。

在 3.x 中使用:

def __lt__(self, other):
    return self.intAttribute < other.intAttribute

【讨论】:

__cmp__ 在 3.x 中消失了。请改用__lt__ __lt__ 也适用于 Python 2,因此最好完全避免使用 __cmp__ 正如您可以告诉任何排序基于对象自然排序以外的标准进行排序(例如,cmpkey 用于sort),您应该能够告诉@ 987654333@ 根据不同的键进行排序。换句话说,您不必重新定义对象本身来更改保存它的特定数据结构;你应该能够告诉数据结构本身。这是heapq API 中缺少的一个值得注意的基本部分。 大家有什么理由要求使用__lt__ 而不是__gt__?还是真的没关系? 如果有时我想按这个属性排序,有时又按另一个属性排序怎么办?【参考方案3】:

根据documentation的例子,你可以使用元组,它会按照元组的第一个元素排序:

>>> h = []
>>> heappush(h, (5, 'write code'))
>>> heappush(h, (7, 'release product'))
>>> heappush(h, (1, 'write spec'))
>>> heappush(h, (3, 'create tests'))
>>> heappop(h)
(1, 'write spec')

因此,如果您不想(或不能?)执行__cmp__ 方法,您可以在推送时手动提取排序键。

请注意,如果一对元组中的第一个元素相等,则将比较后面的元素。如果这不是您想要的,您需要确保每个第一个元素都是唯一的。

【讨论】:

"请注意,如果一对元组中的第一个元素相等,则将比较后面的元素。"你应该把它加粗,因为在文档中并不清楚。我假设给予相同的优先级它会返回我找到的第一个对象(没有充分的理由假设,所以这是我的错,我明白了)。 好点。如果插入的元组是 (number, dict) 它不知道如何评估 dicts。 如果你有一个像(some_value, dict) 这样的元组,你可以在堆中插入(some_value, counter, dict) 以打破与递增计数器的联系,以防some_value 等于2 个元组。 这个例子对我不起作用。有什么建议? lst = [(18, [3, 3]), (26, [5, -1]), (20, [-2, 4])] heapq.heapify(lst)【参考方案4】:

根据Official Document,解决方案是将条目存储为元组(请查看8.4.18.4.2 节)。

例如,您的对象在 tuple 的格式中是这样的 (key, value_1, value_2)

当你把对象(即tuples)放入heap时,它会取对象中的第一个属性(在这种情况下是key) 进行比较。如果出现平局,堆将使用下一个属性(即 value_1)等等。

例如:

import heapq

heap = []
heapq.heappush(heap, (0,'one', 1))
heapq.heappush(heap, (1,'two', 11))
heapq.heappush(heap, (1, 'two', 2))
heapq.heappush(heap, (1, 'one', 3))
heapq.heappush(heap, (1,'two', 3))
heapq.heappush(heap, (1,'one', 4))
heapq.heappush(heap, (1,'two', 5))
heapq.heappush(heap, (1,'one', 1))

show_tree(heap)

输出:

                                      (0, 'one', 1)                                       
                (1, 'one', 1)                                (1, 'one', 4)                
    (1, 'one', 3)         (1, 'two', 3)         (1, 'two', 2)         (1, 'two', 5)     
(1, 'two', 11)

关于在 python 中漂亮地打印一个堆(更新了链接):show_tree()

【讨论】:

【参考方案5】:

你可以实现一个 heapdict。请注意使用 popitem() 来获取最低优先级的项目。

import heapdict as hd
import string
import numpy as np

h = hd.heapdict()
keys = [char for char in string.ascii_lowercase[:10]]
vals = [i for i in np.random.randint(0,10, 10)]
for k,v in zip(keys,vals):
    h[k] = v
for i in range(len(vals)):
    print h.popitem()

【讨论】:

【参考方案6】:

我有同样的问题,但上述答案都没有到位,尽管有些答案很接近但不够详细。无论如何,我做了一些研究并尝试了这段代码,希望这对于下一个希望得到答案的人来说已经足够了:

使用元组的问题是它只使用第一项,这不是很灵活。我想要类似于 c++ 中的 std::priority_queue 的东西,如下所示: std::priority_queue&lt;pair&lt;int, int&gt;, vector&lt;pair&lt;int, int&gt;&gt;, comparator&gt; pq; 我可以设计自己的比较器,这在现实世界的应用中更常见。

希望下面的 sn-p 帮助: https://repl.it/@gururajks/EvenAccurateCylinders

import heapq
class PQNode:

    def __init__(self, key, value):
        self.key = key
        self.value = value

    # compares the second value
    def __lt__(self, other):
        return self.value < other.value

    def __str__(self):
        return str(" : ".format(self.key, self.value))

input = [PQNode(1, 4), PQNode(7, 4), PQNode(6, 9), PQNode(2, 5)]
hinput = []
for item in input:
    heapq.heappush(hinput, item)

while (hinput):
    print (heapq.heappop(hinput))

【讨论】:

我尝试了你的代码,它对我有用。我正在使用 python 3.6.5。我很好奇 heappush() 如何进行比较。这是由 PQNode 类中的特殊 _lt_() 方法本质上完成的吗?没有它,该程序肯定会因编译器消息而崩溃: Traceback (last most recent call last): File "heap_example.py", line 18, in heapq.heappush(hinput, item) TypeError: ' 【参考方案7】:

我觉得最简单的方法是覆盖 heapq 模块现有的 cmp_lt 函数。一个简短的例子:

import heapq

# your custom function. Here, comparing tuples a and b based on their 2nd element
def new_cmp_lt(self,a,b):
    return a[1]<b[1]

#override the existing "cmp_lt" module function with your function
heapq.cmp_lt=new_cmp_lt

#Now use everything like normally used

【讨论】:

【参考方案8】:

有一个模块叫做heaps。 Github 地址是https://github.com/gekco/heapy。您可以在类的实例化或从数组创建堆时应用自己的键/排序函数,这非常有用,因为这样可以节省您在每次执行操作时将其添加为参数。

我想要列表的示例,元组最后一个位置的最小元素在堆顶部:

>>> from heapy.heap import Heap 
>>> a = [(3, 5, 10), (-5, 3, 8), (7, 8, 9), (-4, 0, 2)]
>>> x = Heap.from_array(a, key=lambda t : t[-1])
>>> x.length
4
>>> x.top()
(-4, 0, 2)
>>> x.insert((-1, 0, 1))
>>> x.length
5
>>> x.top()
(-1, 0, 1)
>>> a
[(3, 5, 10), (-5, 3, 8), (7, 8, 9), (-4, 0, 2)]
 

【讨论】:

以上是关于如何让 heapq 评估特定属性的堆?的主要内容,如果未能解决你的问题,请参考以下文章

Python堆排序之heapq

游戏数值策划属性篇:属性价值评估

挑选零食--序列的堆排序

python中的堆顺序

常用的标准模块5(heapq)

heap 堆