我正在尝试使用自定义排序 predicate 构建堆。由于进入它的值是“用户定义的”类型,所以我不能修改它们的内置比较 predicate 。有没有一种方法可以做到这样的事情:
h = heapq.heapify([...], key=my_lt_pred) h = heapq.heappush(h, key=my_lt_pred)
或者更好的是,我可以将heapq函数 Package 在自己的容器中,这样就不需要一直传递 predicate 。
heapq
ahy6op9u1#
根据heapq documentation,自定义堆顺序的方法是让堆上的每个元素都是一个元组,第一个元组元素是接受正常Python比较的元素。heapq模块中的函数有点麻烦(因为它们不是面向对象的),并且总是要求我们的堆对象(一个堆化列表)作为第一个参数显式传递。我们可以通过创建一个非常简单的 Package 器类来一箭双雕,这个 Package 器类允许我们指定一个key函数,并将堆作为一个对象呈现。下面的类保持一个内部列表,其中每个元素都是一个元组,其第一个成员是一个键,在元素插入时使用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]
(The额外的self.index部分是为了避免当评估的键值是一个draw并且存储的值不能直接比较时发生冲突-否则heapq可能会因TypeError而失败)
self.index
ilmyapht2#
定义一个类,在其中重写__lt__()函数。参见下面的示例(适用于Python 3.7):
__lt__()
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]
vxqlmq5t3#
heapq documentation建议堆元素可以是元组,其中第一个元素是优先级,并定义排序顺序。然而,与您的问题更相关的是,文档包含了如何实现自己的heapq Package 器函数来处理排序稳定性和具有相等优先级的元素(以及其他问题)的示例代码的讨论。简而言之,他们的解决方案是让heapq中的每个元素都是一个三元组,其中包含优先级、条目计数和要插入的元素。条目计数确保具有相同优先级的元素按照它们被添加到heapq的顺序排序。
8ljdwjyq4#
setattr(ListNode, "__lt__", lambda self, other: self.val <= other.val)
使用它来比较heapq中对象的值
9q78igpj5#
这两个答案的局限性在于,它们不允许将关系视为关系。在第一种情况下,通过比较项来打破联系,在第二种情况下,通过比较输入顺序来打破联系。让关系成为关系会更快,如果有很多关系,可能会有很大的不同。基于上述内容和文档,尚不清楚这是否可以在heapq中实现。heapq不接受键,而在同一模块中从它派生的函数接受键,这看起来确实很奇怪。P.S.:如果你按照第一条评论中的链接(“可能重复...”),还有另一个定义le的建议,这似乎是一个解决方案。
wkyowqbh6#
在python3中,可以从functools模块中使用cmp_to_key。cpython源代码。假设您需要一个三元组的优先级队列,并使用最后一个属性指定优先级。
functools
cmp_to_key
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,每个函数:
list
tuple
__slots__
因此,此方法比使用具有重写的__lt__()函数和__slots__属性的自定义类稍快。
toe950277#
一个简单的解决方案是为每个元组store entries as a list of tuples定义所需顺序的优先级,如果需要为元组中的每个项目设置不同的顺序,只需将其设置为降序的负数。请参阅本主题优先级队列实现说明中的官方heapq python文档
store entries as a list of tuples
eagi6jfj8#
简单的小技巧:你说:“你们这群人。
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]))
8条答案
按热度按时间ahy6op9u1#
根据heapq documentation,自定义堆顺序的方法是让堆上的每个元素都是一个元组,第一个元组元素是接受正常Python比较的元素。
heapq模块中的函数有点麻烦(因为它们不是面向对象的),并且总是要求我们的堆对象(一个堆化列表)作为第一个参数显式传递。我们可以通过创建一个非常简单的 Package 器类来一箭双雕,这个 Package 器类允许我们指定一个
key
函数,并将堆作为一个对象呈现。下面的类保持一个内部列表,其中每个元素都是一个元组,其第一个成员是一个键,在元素插入时使用
key
参数计算,在Heap示例化时传递:(The额外的
self.index
部分是为了避免当评估的键值是一个draw并且存储的值不能直接比较时发生冲突-否则heapq可能会因TypeError而失败)ilmyapht2#
定义一个类,在其中重写
__lt__()
函数。参见下面的示例(适用于Python 3.7):vxqlmq5t3#
heapq documentation建议堆元素可以是元组,其中第一个元素是优先级,并定义排序顺序。
然而,与您的问题更相关的是,文档包含了如何实现自己的heapq Package 器函数来处理排序稳定性和具有相等优先级的元素(以及其他问题)的示例代码的讨论。
简而言之,他们的解决方案是让heapq中的每个元素都是一个三元组,其中包含优先级、条目计数和要插入的元素。条目计数确保具有相同优先级的元素按照它们被添加到heapq的顺序排序。
8ljdwjyq4#
使用它来比较heapq中对象的值
9q78igpj5#
这两个答案的局限性在于,它们不允许将关系视为关系。在第一种情况下,通过比较项来打破联系,在第二种情况下,通过比较输入顺序来打破联系。让关系成为关系会更快,如果有很多关系,可能会有很大的不同。基于上述内容和文档,尚不清楚这是否可以在heapq中实现。heapq不接受键,而在同一模块中从它派生的函数接受键,这看起来确实很奇怪。
P.S.:如果你按照第一条评论中的链接(“可能重复...”),还有另一个定义le的建议,这似乎是一个解决方案。
wkyowqbh6#
在python3中,可以从
functools
模块中使用cmp_to_key
。cpython源代码。假设您需要一个三元组的优先级队列,并使用最后一个属性指定优先级。
性能测试:
环境
Python 3.10.2
代码
结果
使用
list
代替tuple
,每个函数:__slots__
:9.8ms因此,此方法比使用具有重写的
__lt__()
函数和__slots__
属性的自定义类稍快。toe950277#
简单和最近
一个简单的解决方案是为每个元组
store entries as a list of tuples
定义所需顺序的优先级,如果需要为元组中的每个项目设置不同的顺序,只需将其设置为降序的负数。请参阅本主题优先级队列实现说明中的官方heapq python文档
eagi6jfj8#
简单的小技巧:
你说:“你们这群人。
你想根据它们的年龄对这个列表进行排序,通过将它们添加到一个最小堆,而不是编写所有自定义比较器的东西,你可以在将元组推入队列之前翻转元组内容的顺序。这是因为heapq.heappush()默认按元组的第一个元素排序。像这样:
这是一个简单的技巧,如果这是你的工作,你不想进入编写自定义比较器混乱。
同样,默认情况下,它会按升序对值进行排序。如果你想按年龄降序排序,翻转内容并使元组的第一个元素的值为负数: