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

cof*_*fee 38 python heap data-structures

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

eum*_*iro 57

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

def __cmp__(self, other):
    return cmp(self.intAttribute, other.intAttribute)
Run Code Online (Sandbox Code Playgroud)

适用于Python 2.x.

在3.x中使用:

def __lt__(self, other):
    return self.intAttribute < other.intAttribute
Run Code Online (Sandbox Code Playgroud)

  • 正如您可以根据除对象的自然排序之外的条件(例如``cmp`和`key`进行排序)来排序任何排序,您应该能够告诉`heapq`基于不同的排序键.换句话说,你不应该*重新定义对象本身*来改变持有它的特定数据结构; 你应该能够告诉数据结构本身.这是`heapq` API中缺少的一个值得注意的基本部分. (13认同)
  • `__lt__`也适用于Python 2,因此最好完全避免使用`__cmp__`. (11认同)
  • `__cmp__`在3.x中消失了.请改用`__lt__`. (10认同)
  • 有什么理由让每个人都要求使用“__lt__”而不是“__gt__”?还是真的没关系? (3认同)

Jan*_*der 48

根据文档中的示例,您可以使用元组,它将按元组的第一个元素排序:

>>> 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')
Run Code Online (Sandbox Code Playgroud)

所以,如果你不想(或不能?)做一个 __cmp__方法,您可以在推送时手动提取排序键.

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

  • “请注意,如果一对元组中的第一个元素相等,则将比较其他元素。” 你应该把它加粗,因为在文档中它不清楚。我假设给予相同的优先级它会返回我找到的第一个对象(这个假设没有充分的理由,所以这是我的错,我明白了)。 (13认同)
  • 如果你有一个像 `(some_value, dict)` 这样的元组,你可以在堆中插入 `(some_value, counter, dict)` 以打破与递增计数器的联系,以防 `some_value` 等于 2 个元组。 (4认同)

Ray*_*ger 18

Python 3 更新

这里的其他答案已经过时了:

  • 有些是 Python 2 特定的。该__cmp__方法不再存在。
  • 有些不反映最佳实践,并且仅__lt__针对PEP 8建议的所有丰富比较。
  • 有些不使用现代工具,例如dataclassesattrgettertotal_ordering

具有数据类的现代解决方案

使用dataclasses,可以轻松制作具有自定义排序的数据持有者。例如,下面是一个从比较顺序中排除姓名字段的Person类:

from dataclasses import dataclass, field

@dataclass(order=True)
class Person:
    name: str = field(compare=False)
    age: int

actors = [
    Person('T Hanks', 65),
    Person('E Olson', 33),
    Person('A Tapping', 58),
]
Run Code Online (Sandbox Code Playgroud)

这与堆完美配合:

>>> heapify(actors)
>>> heappop(actors)
Person(name='E Olson', age=33)
>>> heappop(actors)
Person(name='A Tapping', age=58)
>>> heappop(actors)
Person(name='T Hanks', age=65)
Run Code Online (Sandbox Code Playgroud)

处理现有类

有时您必须使用所提供的数据,并且需要在不更改原始类的情况下控制比较顺序。

解决方案是添加一个包含新比较的包装器。这使得非原始数据及其类别保持不变。这是添加此类包装纸的现代配方:

from functools import total_ordering
from operator import attrgetter

def new_compare(*field_names):
    extract = attrgetter(*field_names)
    @total_ordering
    class ComparisonWrapper:
        def __init__(self, obj):
            self.obj = obj
        def __eq__(self, other):
            return extract(self.obj) == extract(other.obj)
        def __lt__(self, other):
            return extract(self.obj) < extract(other.obj)
    return ComparisonWrapper
Run Code Online (Sandbox Code Playgroud)

例如,您可能会获得以下数据,但无法直接更改它或其类:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def __repr__(self):
        return f'Person({self.name!r}, {self.age})'

actors = [
    Person('T Hanks', 65),
    Person('E Olson', 33),
    Person('A Tapping', 58),
]
Run Code Online (Sandbox Code Playgroud)

可以使用map()优雅地应用包装器。要解开数据,请访问obj属性:

>>> from heapq import heapify, heappop

>>> data = list(map(new_compare('age'), actors))
>>> heapify(data)
>>> heappop(data).obj
Person('E Olson', 33)
>>> heappop(data).obj
Person('A Tapping', 58)
>>> heappop(data).obj
Person('T Hanks', 65)
Run Code Online (Sandbox Code Playgroud)

包装器与装饰元组

正如现代文档中所指出的,带有装饰元组的传统解决方案不再适用于某些基本用例。特别是,如果堆中的对象是函数,则 形式的元组(priority, task)在 Python 3 中不再有效,因为函数无法进行比较。

新的建议是使用包装器,例如:

from dataclasses import dataclass, field
from typing import Any

@dataclass(order=True)
class PrioritizedItem:
    priority: int
    item: Any=field(compare=False)
Run Code Online (Sandbox Code Playgroud)

即使项目对象不具有可比性,这也始终有效。


Cat*_*lts 9

根据官方文件,解决此问题的方法是将条目存储为元组(请参阅8.4.18.4.2节)。

例如,您的对象是类似tuple的格式 (键,值_1,值_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)
Run Code Online (Sandbox Code Playgroud)

输出:

                                      (0, 'one', 1)                                       
                (1, 'one', 1)                                (1, 'one', 4)                
    (1, 'one', 3)         (1, 'two', 3)         (1, 'two', 2)         (1, 'two', 5)     
(1, 'two', 11)
Run Code Online (Sandbox Code Playgroud)

关于用python漂亮打印堆(更新了链接):show_tree()


tu_*_*ous 8

我觉得最简单的方法是覆盖 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
Run Code Online (Sandbox Code Playgroud)


Gur*_*uru 6

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

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

希望以下代码段有所帮助: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))
Run Code Online (Sandbox Code Playgroud)