我希望可以使用堆(heap)来处理一组对象,而不仅仅是数字。这些对象会带有一个整数属性(attribute),通过该属性,堆可以进行排序。在Python中,使用heapq是最简单的方法,但是如果我想要按照特定属性(attribute)对其进行排序,应该怎么做呢?
我希望可以使用堆(heap)来处理一组对象,而不仅仅是数字。这些对象会带有一个整数属性(attribute),通过该属性,堆可以进行排序。在Python中,使用heapq是最简单的方法,但是如果我想要按照特定属性(attribute)对其进行排序,应该怎么做呢?
根据文档中的示例,您可以使用元组,并且它将按照元组的第一个元素进行排序:
>>> 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')
所以如果你不想(或者不能)编写一个__cmp__
方法,你可以在推送时手动提取排序关键字。
请注意,如果一对元组的第一个元素相等,那么将比较进一步的元素。如果这不是你想要的,请确保每个第一个元素都是唯一的。
(some_value, dict)
的元组,你可以将 (some_value, counter, dict)
插入堆中,以便在两个元组的 some_value
相等时使用递增计数器来打破平局。 - akshaynagpalheapq
和 list.sort
排序对象的方式相同,因此只需在类定义中定义一个方法 __cmp__()
,该方法将自身与同一类的另一个实例进行比较:
def __cmp__(self, other):
return cmp(self.intAttribute, other.intAttribute)
适用于 Python 2.x。
在 3.x 中请使用:
def __lt__(self, other):
return self.intAttribute < other.intAttribute
__cmp__
已经被移除,请使用__lt__
代替。 - Daniel Stutzbach__lt__
同样适用,因此最好完全避免使用__cmp__
。 - Daniel Stutzbachsort
的 cmp
和 key
),你应该能够告诉 heapq
基于不同的键来进行排序。换句话说,你不应该重新定义对象本身就能改变包含它的特定数据结构;你应该只需告诉数据结构本身即可。这是 heapq
API 中一个值得注意的基本缺失部分。 - Glenn Maynard__lt__
而不是__gt__
?这真的很重要吗? - Shrikant Sheteimport 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)
输出:
(0, 'one', 1)
(1, 'one', 1) (1, 'one', 4)
(1, 'one', 3) (1, 'two', 3) (1, 'two', 2) (1, 'two', 5)
(1, 'two', 11)
关于在python中漂亮地打印堆(更新了链接):show_tree()
其他答案已过时:
__cmp__
方法。__lt__
,而不是像 PEP 8 建议的所有 rich comparisons。使用dataclasses,可以轻松创建一个带有自定义排序的数据容器。例如,这是一个人类,它在比较顺序中排除了姓名字段:
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),
]
>>> 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)
有时候你需要按照提供的数据来工作,并且需要控制比较的顺序,而不改变原始类。
解决方案是添加一个带有新比较方式的包装器。这样可以保留原始数据和它的类不变。以下是一个现代化的添加这样一个包装器的方法:
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
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),
]
可以使用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)
正如现代文档中所指出的,传统的元组装饰方法在某些关键用例中已经不再适用。特别是,如果堆中的对象是函数,则形如(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)
即使item对象不可比较,也总是有效的。
我认为最简单的方法是覆盖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
注意:如有更合适的编码实践建议,请有资格的人员进行评论。但是在编码面试中,如果时间有限且有更多任务需要完成而不是花时间正确地进行子类化,则仍然可能很有用。
注:更合格的人应该评论是否与推荐的编码实践冲突。但对于一些“快而脏”的东西(例如,在编写面试时时间有限且有更多事情要做而不是花时间正确地进行子类化),它仍然可以很有用。
我有同样的问题,但以上答案都没有完全解决我的问题,虽然有些接近但不够详细。无论如何,我进行了一些研究并尝试了这段代码,希望这对下一个正在寻找答案的人有所帮助:
使用元组的问题在于它只使用第一个项目,不是很灵活。我想要类似于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))
PQNode
中的 __lt__
是关键。请参见:https://docs.python.org/3/library/operator.html#operator.__lt__ - Arun很遗憾,您不能这样做,尽管这是一个经常被请求的功能。
一种选择是将(键,值)元组插入堆中。但是,如果在比较值时抛出异常(在键相同时会进行比较),则此方法无法使用。
第二个选择是在类中定义__lt__
(小于)方法,该方法将使用适当的属性来比较元素以进行排序。但是,如果对象是由另一个包创建的,或者您需要它们在程序的其他地方进行不同的比较,则可能无法实现。
第三个选择是使用sortedlist类来自blist模块(免责声明:我是作者)。sortedlist
的构造函数接受一个key
参数,该参数允许您指定一个函数来返回元素的排序键,类似于list.sort
和sorted
的key
参数。
blist
问题可能是PEBCAK(再次感谢您的模块),所以我只复制了先前评论的第一部分:始终可以通过子类化或封装来定义具有__lt__
的类。 - tzot有一个名为heaps
的模块。Github地址是https://github.com/gekco/heapy。您可以在类的实例化或从数组创建堆时应用自己的键/排序函数,这非常有用,因为这样可以节省每次执行操作时添加它作为参数的时间。
以下是一个示例,其中我希望列表中元组的最小元素位于堆的顶部:
>>> from heapy.heap import Heap
>>> a = [(3, 5, 10), (-5, 3, 8), (7, 8, 9), (-4, 0, 2)]
>>> x = Heap.from_array(a, key=lambda t : t[-1])
>>> x.length
4
>>> x.top()
(-4, 0, 2)
>>> x.insert((-1, 0, 1))
>>> x.length
5
>>> x.top()
(-1, 0, 1)
>>> a
[(3, 5, 10), (-5, 3, 8), (7, 8, 9), (-4, 0, 2)]
import heapdict as hd
import string
import numpy as np
h = hd.heapdict()
keys = [char for char in string.ascii_lowercase[:10]]
vals = [i for i in np.random.randint(0,10, 10)]
for k,v in zip(keys,vals):
h[k] = v
for i in range(len(vals)):
print h.popitem()