python 带有自定义比较 predicate heapq

egdjgwm8  于 2023-06-28  发布在  Python
关注(0)|答案(8)|浏览(173)

我正在尝试使用自定义排序 predicate 构建堆。由于进入它的值是“用户定义的”类型,所以我不能修改它们的内置比较 predicate 。
有没有一种方法可以做到这样的事情:

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

或者更好的是,我可以将heapq函数 Package 在自己的容器中,这样就不需要一直传递 predicate 。

ahy6op9u

ahy6op9u1#

根据heapq documentation,自定义堆顺序的方法是让堆上的每个元素都是一个元组,第一个元组元素是接受正常Python比较的元素。
heapq模块中的函数有点麻烦(因为它们不是面向对象的),并且总是要求我们的堆对象(一个堆化列表)作为第一个参数显式传递。我们可以通过创建一个非常简单的 Package 器类来一箭双雕,这个 Package 器类允许我们指定一个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]

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

ilmyapht

ilmyapht2#

定义一个类,在其中重写__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]
vxqlmq5t

vxqlmq5t3#

heapq documentation建议堆元素可以是元组,其中第一个元素是优先级,并定义排序顺序。
然而,与您的问题更相关的是,文档包含了如何实现自己的heapq Package 器函数来处理排序稳定性和具有相等优先级的元素(以及其他问题)的示例代码的讨论。
简而言之,他们的解决方案是让heapq中的每个元素都是一个三元组,其中包含优先级、条目计数和要插入的元素。条目计数确保具有相同优先级的元素按照它们被添加到heapq的顺序排序。

8ljdwjyq

8ljdwjyq4#

setattr(ListNode, "__lt__", lambda self, other: self.val <= other.val)

使用它来比较heapq中对象的值

9q78igpj

9q78igpj5#

这两个答案的局限性在于,它们不允许将关系视为关系。在第一种情况下,通过比较项来打破联系,在第二种情况下,通过比较输入顺序来打破联系。让关系成为关系会更快,如果有很多关系,可能会有很大的不同。基于上述内容和文档,尚不清楚这是否可以在heapq中实现。heapq不接受键,而在同一模块中从它派生的函数接受键,这看起来确实很奇怪。
P.S.:如果你按照第一条评论中的链接(“可能重复...”),还有另一个定义le的建议,这似乎是一个解决方案。

wkyowqbh

wkyowqbh6#

在python3中,可以从functools模块中使用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,每个函数:

  • WrapperCls1:16.2ms
  • WrapperCls1和__slots__:9.8ms
  • WrapperCls2:8.6ms
  • 将priority属性移到第一个位置(不支持自定义 predicate ):6.0ms.

因此,此方法比使用具有重写的__lt__()函数和__slots__属性的自定义类稍快。

toe95027

toe950277#

简单和最近

一个简单的解决方案是为每个元组store entries as a list of tuples定义所需顺序的优先级,如果需要为元组中的每个项目设置不同的顺序,只需将其设置为降序的负数。
请参阅本主题优先级队列实现说明中的官方heapq python文档

eagi6jfj

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]))

相关问题