heapq với vị từ so sánh tùy chỉnh


82

Tôi đang cố gắng tạo một đống với một vị từ sắp xếp tùy chỉnh. Vì các giá trị đi vào nó thuộc loại 'do người dùng xác định', tôi không thể sửa đổi vị từ so sánh tích hợp của chúng.

Có cách nào để làm điều gì đó như:

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

Hoặc thậm chí tốt hơn, tôi có thể bọc các hàm heapq trong vùng chứa của riêng mình để tôi không cần phải tiếp tục chuyển vị ngữ.



Câu trả lời:


120

Theo tài liệu heapq , cách để tùy chỉnh thứ tự heap là để mỗi phần tử trên heap là một bộ tuple, với phần tử tuple đầu tiên là một phần tử chấp nhận các so sánh Python bình thường.

Các hàm trong mô-đun heapq hơi cồng kềnh (vì chúng không phải là hướng đối tượng) và luôn yêu cầu đối tượng heap của chúng ta (danh sách được đống hóa) phải được chuyển rõ ràng làm tham số đầu tiên. Chúng ta có thể giết hai con chim bằng một viên đá bằng cách tạo một lớp wrapper rất đơn giản cho phép chúng ta chỉ định một keyhàm và trình bày heap dưới dạng một đối tượng.

Lớp bên dưới giữ một danh sách nội bộ, trong đó mỗi phần tử là một bộ, thành viên đầu tiên của nó là một khóa, được tính tại thời điểm chèn phần tử bằng cách sử dụng keytham số, được truyền vào lúc khởi tạo 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]

(Phần bổ sung self.indexlà để tránh xung đột khi giá trị khóa được đánh giá là một trận hòa và giá trị được lưu trữ không thể so sánh trực tiếp - nếu không heapq có thể không thành công với TypeError)


4
Rất đẹp! Bạn thậm chí có thể đi xa hơn và sử dụng bộ ba (self.key (item), id, item), trong đó id có thể là một số nguyên được xử lý như một thuộc tính lớp và tăng dần sau mỗi lần đẩy. Bằng cách đó, bạn tránh được ngoại lệ được đưa ra khi key (item1) = key (item2). Bởi vì các khóa sẽ là duy nhất.
zeycus

4
Tôi thực sự đã cố gắng đẩy điều này (hoặc thứ gì đó dựa trên điều này) vào stdlib của Python và đề xuất đã bị từ chối.
jsbueno

1
đáng tiếc, phù hợp với phong cách hướng đối tượng của hầu hết các tính năng Python và đối số chính cung cấp thêm tính linh hoạt.
zeycus,

Tôi đã sử dụng danh sách thay vì tuple cho ví dụ: [self.key (item), id, item] và nó hoạt động tốt miễn là chỉ mục đầu tiên là khóa.
Deepak Yadav

5
Điều này sẽ không thành công nếu các phần tử không thể so sánh được và có sự ràng buộc trong các giá trị chính. Tôi muốn đặt id(item)như một phần tử giữa của bộ tuple để phá vỡ quan hệ.
Georgi Yanchev

46

Xác định một lớp, trong đó ghi đè __lt__()hàm. Xem ví dụ bên dưới (hoạt động trong 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]


3
Đây có vẻ như là giải pháp sạch nhất cho đến nay!
Roymunson

Hoàn toàn đồng ý với hai ý kiến ​​trước. Đây có vẻ là một tốt hơn, giải pháp sạch cho Python 3.
Chiraz BenAbdelkader

Ngoài ra, đây là giải pháp rất giống với một câu hỏi tương tự: stackoverflow.com/questions/2501457/...
Chiraz BenAbdelkader

1
Tôi đã thử nghiệm điều này bằng cách sử dụng __gt__thay thế và nó hoạt động tốt. Tại sao chúng ta sử dụng phương pháp ma thuật nào không quan trọng? Tôi không thể tìm thấy bất cứ điều gì trong heapqtài liệu của. Có thể nó liên quan đến cách Python thực hiện so sánh nói chung?
Josh Clark

1
Khi thực hiện so sánh trong heapq, Python sẽ tìm kiếm __lt__()đầu tiên. Nếu nó không được xác định, nó sẽ tìm kiếm __gt__(). Nếu cả hai đều không được xác định, nó ném TypeError: '<' not supported between instances of 'Node' and 'Node'. Điều này có thể được xác nhận bằng cách xác định cả hai __lt__()__gt__(), đặt một câu lệnh in trong mỗi và có __lt__()trả về NotImplemented.
Fanchen Bao

19

Các tài liệu heapq gợi ý rằng các yếu tố có thể là đống bản ghi trong đó các yếu tố đầu tiên là ưu tiên và xác định thứ tự sắp xếp.

Tuy nhiên, phù hợp hơn với câu hỏi của bạn là tài liệu bao gồm một cuộc thảo luận với mã mẫu về cách một người có thể triển khai các hàm bao bọc heapq của riêng họ để giải quyết các vấn đề về độ ổn định của sắp xếp và các phần tử có mức độ ưu tiên ngang nhau (trong số các vấn đề khác).

Tóm lại, giải pháp của họ là đặt mỗi phần tử trong heapq là một bộ ba với mức độ ưu tiên, số mục nhập và phần tử được chèn vào. Số lượng mục nhập đảm bảo rằng các phần tử có cùng mức độ ưu tiên được sắp xếp theo thứ tự chúng được thêm vào heapq.


Đây là giải pháp đúng, cả hai heappush và heappushpop làm việc trực tiếp với các bộ
daisy

2

Hạn chế của cả hai câu trả lời là chúng không cho phép coi quan hệ như ràng buộc. Trong lần đầu tiên, mối quan hệ bị phá vỡ bằng cách so sánh các mục, trong lần thứ hai bằng cách so sánh thứ tự đầu vào. Sẽ nhanh hơn nếu cứ để quan hệ là quan hệ, và nếu có nhiều mối quan hệ thì nó có thể tạo ra sự khác biệt lớn. Dựa trên những điều trên và trên các tài liệu, không rõ liệu có thể đạt được điều này trong heapq hay không. Có vẻ lạ khi heapq không chấp nhận một khóa, trong khi các chức năng bắt nguồn từ nó trong cùng một mô-đun thì có.
Tái bút: Nếu bạn theo liên kết trong nhận xét đầu tiên ("có thể trùng lặp ..."), có một gợi ý khác về việc xác định le có vẻ giống như một giải pháp.


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

Sử dụng điều này để so sánh giá trị của các đối tượng trong heapq

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.