我正在尝试使用自定义排序谓词构建堆。由于输入堆中的值是“用户定义”类型,我无法修改它们内置的比较谓词。
是否有办法做到这样:
h = heapq.heapify([...], key=my_lt_pred)
h = heapq.heappush(h, key=my_lt_pred)
或者更好的做法是,我可以将heapq
函数包装在自己的容器中,这样我就不需要不断传递谓词了。
我正在尝试使用自定义排序谓词构建堆。由于输入堆中的值是“用户定义”类型,我无法修改它们内置的比较谓词。
是否有办法做到这样:
h = heapq.heapify([...], key=my_lt_pred)
h = heapq.heappush(h, key=my_lt_pred)
或者更好的做法是,我可以将heapq
函数包装在自己的容器中,这样我就不需要不断传递谓词了。
key
函数,并将堆表示为对象。下面的类保留了一个内部列表,其中每个元素都是一个元组,其中第一个成员是一个关键字,插入时使用传递给Heap实例化的key
参数计算出来:# -*- 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错误)
id(item)
作为中间元素来打破并列。 - Georgi Yanchev定义一个类,在其中覆盖 __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]
__gt__
进行了测试,同样有效。为什么我们使用哪个魔术方法并不重要呢?我在heapq
的文档中找不到任何相关信息。也许这与Python通常如何进行比较有关? - Josh Clarkheapq
进行比较时,Python 会首先查找 __lt__()
方法。如果没有定义,则会查找 __gt__()
方法。如果两者都没有定义,则会抛出 TypeError: '<' not supported between instances of 'Node' and 'Node'
错误。可以通过同时定义 __lt__()
和 __gt__()
方法,并在每个方法中添加打印语句,然后让 __lt__()
返回 NotImplemented
来确认这一点。 - Fanchen Baosetattr(ListNode, "__lt__", lambda self, other: self.val <= other.val)
请使用此函数对heapq中的对象进行值比较
Leetcode
可能有效,但它在 heapq
中不起作用。 - mittalfunctools
模块中的cmp_to_key
。请参考cpython源代码。
假设您需要一个三元组的优先队列,并指定最后一个属性作为优先级。
from heapq import *
from functools import cmp_to_key
def mycmp(triplet_left, triplet_right):
key_l, key_r = triplet_left[2], triplet_right[2]
if key_l > key_r:
return -1 # larger first
elif key_l == key_r:
return 0 # equal
else:
return 1
WrapperCls = cmp_to_key(mycmp)
pq = []
myobj = tuple(1, 2, "anystring")
# to push an object myobj into pq
heappush(pq, WrapperCls(myobj))
# to get the heap top use the `obj` attribute
inner = pq[0].obj
Python 3.10.2
from functools import cmp_to_key
from timeit import default_timer as time
from random import randint
from heapq import *
class WrapperCls1:
__slots__ = 'obj'
def __init__(self, obj):
self.obj = obj
def __lt__(self, other):
kl, kr = self.obj[2], other.obj[2]
return True if kl > kr else False
def cmp_class2(obj1, obj2):
kl, kr = obj1[2], obj2[2]
return -1 if kl > kr else 0 if kl == kr else 1
WrapperCls2 = cmp_to_key(cmp_class2)
triplets = [[randint(-1000000, 1000000) for _ in range(3)] for _ in range(100000)]
# tuple_triplets = [tuple(randint(-1000000, 1000000) for _ in range(3)) for _ in range(100000)]
def test_cls1():
pq = []
for triplet in triplets:
heappush(pq, WrapperCls1(triplet))
def test_cls2():
pq = []
for triplet in triplets:
heappush(pq, WrapperCls2(triplet))
def test_cls3():
pq = []
for triplet in triplets:
heappush(pq, (-triplet[2], triplet))
start = time()
for _ in range(10):
test_cls1()
# test_cls2()
# test_cls3()
print("total running time (seconds): ", -start+(start:=time()))
每个函数使用list
而不是tuple
:
__slots__
的WrapperCls1: 9.8毫秒因此,该方法比使用具有覆盖__lt__()
函数和__slots__
属性的自定义类略快。
[<functools.KeyWrapper> ...]
的东西,而不是值。 - jizhihaoSAMA.obj
属性? - Voyager简单的小技巧:
假设你有一个(name,age)的列表如下:
a = [('Tim',4), ('Radha',9), ('Rob',7), ('Krsna',3)]
而且你想根据年龄对这个列表进行排序,可以通过将它们添加到最小堆中来实现,而不是编写所有自定义比较器的代码,你只需在将元组推入队列之前翻转其内容的顺序即可。这是因为heapq.heappush()默认按元组的第一个元素排序。
import heapq
heap = []
heapq.heapify(heap)
for element in a:
heapq.heappush(heap, (element[1],element[0]))
这是一个简单的技巧,如果这样做能满足你的需求,而且你不想陷入编写自定义比较器的麻烦中。
类似地,默认情况下它按升序对值进行排序。如果你想按年龄降序排序,请翻转内容并将元组的第一个元素的值设为负数:
import heapq
heap = []
heapq.heapify(heap)
for element in a:
heapq.heappush(heap, (-element[1],element[0]))
一个简单的解决方案是将条目存储为每个元组的列表,为每个元组定义所需顺序的优先级,如果您需要元组内每个项的不同顺序,请将其定义为负数以获取降序。
请参阅此主题中的官方heapq Python文档 Priority Queue Implementation Notes。