具有自定义比较谓词的heapq


80

我正在尝试使用自定义排序谓词构建堆。由于输入的值属于“用户定义”类型,因此我无法修改其内置比较谓词。

有没有办法做类似的事情:

h = heapq.heapify([...], key=my_lt_pred)
h = heapq.heappush(h, key=my_lt_pred)

甚至更好的是,我可以将heapq函数包装在自己的容器中,这样就不需要继续传递谓词。



Answers:


118

根据heapq文档,自定义堆顺序的方法是使堆上的每个元素成为一个元组,第一个tuple元素是一个接受常规Python比较的元素。

heapq模块中的函数有点麻烦(因为它们不是面向对象的),并且始终需要将我们的堆对象(一个堆化的列表)作为第一个参数显式传递。通过创建一个非常简单的包装器类,我们可以指定一个key函数,并将堆显示为一个对象,从而用一块石头杀死两只鸟。

下面的类保留一个内部列表,其中每个元素是一个元组,其第一个成员是一个键,在元素插入时使用key参数在Heap实例化时传递该键来计算:

# -*- coding: utf-8 -*-
import heapq

class MyHeap(object):
   def __init__(self, initial=None, key=lambda x:x):
       self.key = key
       self.index = 0
       if initial:
           self._data = [(key(item), i, item) for i, item in enumerate(initial)]
           self.index = len(self._data)
           heapq.heapify(self._data)
       else:
           self._data = []

   def push(self, item):
       heapq.heappush(self._data, (self.key(item), self.index, item))
       self.index += 1

   def pop(self):
       return heapq.heappop(self._data)[2]

(额外的self.index部分是避免当评估的键值是平局并且存储的值不能直接比较时发生冲突-否则heapq可能因TypeError失败)


4
非常好!您甚至可以更进一步,使用三元组(self.key(item),id,item),其中id可以是作为类属性处理的整数,并且在每次按下后递增。这样,您可以避免在key(item1)= key(item2)时引发异常。因为键是唯一的。
zeycus '16

3
我实际上试图将其(或基于此的东西)推入Python的stdlib中,但该建议被拒绝了。
jsbueno '16

1
可惜,适合大多数Python功能的面向对象风格,并且key参数提供了额外的灵活性。
zeycus '16

我已经用列表代替了元组,例如[self.key(item),id,item],只要第一个索引是关键,它就可以正常工作。
Deepak Yadav '18

5
如果元素不具有可比性,并且键值存在联系,则此操作将失败。我把id(item)打破联系作为元组的中间元素。
乔治·延切夫

37

定义一个类,在其中重写__lt__()函数。请参见下面的示例(适用于Python 3.7):

import heapq

class Node(object):
    def __init__(self, val: int):
        self.val = val

    def __repr__(self):
        return f'Node value: {self.val}'

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

heap = [Node(2), Node(0), Node(1), Node(4), Node(2)]
heapq.heapify(heap)
print(heap)  # output: [Node value: 0, Node value: 2, Node value: 1, Node value: 4, Node value: 2]

heapq.heappop(heap)
print(heap)  # output: [Node value: 1, Node value: 2, Node value: 2, Node value: 4]


3
这似乎是迄今为止最干净的解决方案!
Roymunson

完全同意前两个评论。这似乎是为Python 3.一个更好,更清洁的解决方案
Chiraz BenAbdelkader

此外,这里是非常相似的解决了类似的问题:stackoverflow.com/questions/2501457/...
Chiraz BenAbdelkader

__gt__改为使用此工具进行了测试,并且效果很好。为什么使用哪种魔术方法都没有关系?我在heapq的文档中找不到任何内容。也许与Python一般如何进行比较有关?
乔什·克拉克

在中进行比较时heapq,Python__lt__()首先查找。如果未定义,它将寻找__gt__()。如果都未定义,则抛出TypeError: '<' not supported between instances of 'Node' and 'Node'。可以通过定义__lt__()和并__gt__()在每个语句中放置一个打印语句并具有__lt__()return来确认这一点NotImplemented
樊尚宝

19

所述heapq文档表明,堆元件可以是元组,其中所述第一元件是所述优先级,并限定的排序顺序。

但是,与您的问题更相关的是,该文档中包含有关示例代码讨论,该示例代码说明了如何实现自己的heapq包装函数来处理排序稳定性和具有相同优先级的元素(以及其他问题)的问题。

简而言之,他们的解决方案是使heapq中的每个元素都是三元组,并具有优先级,条目计数和要插入的元素。条目计数确保具有相同优先级的元素按照添加到heapq的顺序进行排序。


这是正确的解决方案,heappush和heappushpop都可以直接与元组一起使用
雏菊

2

这两个答案的局限性在于它们不允许将领带视为领带。在第一个中,通过比较项目来打破联系,在第二个中,通过比较输入顺序来打破关系。让领带成为领带会更快,而且如果领带很多,这可能会带来很大的不同。基于上面和文档,尚不清楚是否可以在heapq中实现。奇怪的是,heapq不接受键,而在同一模块中从它派生的功能却接受。
PS:如果您点击第一个注释中的链接(“可能重复...”),那么还有另一种定义le的建议,这似乎是一种解决方案。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.