사용자 정의 비교 술어가있는 heapq


82

사용자 지정 정렬 조건 자로 힙을 만들려고합니다. 여기에 들어가는 값은 '사용자 정의'유형이므로 내장 된 비교 술어를 수정할 수 없습니다.

다음과 같은 방법이 있습니까?

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

또는 더 좋은 점은 내 컨테이너에 heapq 함수를 래핑하여 술어를 계속 전달할 필요가 없다는 것입니다.



답변:


120

heapq 문서 에 따르면 힙 순서를 사용자 정의하는 방법은 힙의 각 요소가 튜플이되도록하는 것입니다. 첫 번째 튜플 요소는 일반적인 Python 비교를 허용하는 요소입니다.

heapq 모듈의 함수는 약간 번거롭고 (객체 지향적이지 않기 때문에) 항상 첫 번째 매개 변수로 명시 적으로 전달되는 힙 객체 (힙화 된 목록)가 필요합니다. 하나의 돌로 두 마리의 새를 죽일 수 있습니다.key함수 하고 힙을 객체로 .

아래 클래스는 내부 목록을 유지합니다. 여기서 각 요소는 튜플이며 첫 번째 멤버는 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]

(추가 self.index부분은 평가 된 키 값이 그리기이고 저장된 값이 직접 비교할 수 없을 때 충돌을 방지하는 것입니다. 그렇지 않으면 heapq가 TypeError로 실패 할 수 있습니다)


4
아주 좋아요! 더 나아가서 트리플 (self.key (item), id, item)을 사용할 수도 있습니다. 여기서 id는 클래스 속성으로 처리되는 정수일 수 있으며 푸시 할 때마다 증가합니다. 이렇게하면 key (item1) = key (item2) 일 때 발생하는 예외를 방지 할 수 있습니다. 키는 고유하기 때문입니다.
zeycus

4
나는 실제로 이것을 (또는 이것에 기반한 것) 파이썬의 stdlib에 밀어 넣으려고 시도했지만 제안이 거부되었습니다.
jsbueno

1
유감스럽게도 대부분의 Python 기능의 객체 지향 스타일에 적합하며 핵심 인수는 추가 유연성을 제공합니다.
zeycus

예를 들어 [self.key (item), id, item]에 대해 튜플 대신 목록을 사용했으며 첫 번째 인덱스가 키인 한 제대로 작동합니다.
Deepak Yadav

5
요소가 비교 가능하지 않고 키 값에 동점이 있으면 실패합니다. 나는 id(item)관계를 끊기 위해 튜플의 중간 요소로 넣었습니다 .
Georgi Yanchev

47

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


4
이것은 지금까지 가장 깨끗한 솔루션처럼 보입니다!
Roymunson

이전 두 의견에 절대적으로 동의합니다. 이것은 파이썬 3을위한 더 나은, 청소기 해결책이 될 것 같다
Chiraz BenAbdelkader에게

또한 비슷한 질문에 대한 매우 유사한 솔루션이 있습니다. stackoverflow.com/questions/2501457/…
Chiraz BenAbdelkader

1
__gt__대신 사용하여 테스트 했으며 잘 작동합니다. 우리가 사용하는 마법 방법이 왜 중요하지 않습니까? heapq의 문서 에서 아무것도 찾을 수 없습니다 . 아마도 파이썬이 일반적으로 비교하는 방법과 관련이 있습니까?
Josh Clark

1
에서 비교를 수행 할 때 heapqPython은 __lt__()먼저 찾습니다 . 정의되지 않은 경우 __gt__(). 둘 다 정의되지 않은 경우 TypeError: '<' not supported between instances of 'Node' and 'Node'. 이는 __lt__()및 둘 다 정의 __gt__()하고 각각에 print 문을 배치하고 __lt__()return을 사용 하여 확인할 수 있습니다 NotImplemented.
Fanchen Bao

19

heapq 문서는 힙 요소는 첫 번째 요소는 우선 순위 및 정렬 순서를 정의하는 튜플이 될 수 있음을 시사한다.

그러나 귀하의 질문과 더 관련이 있다는 것은 문서에 자체 heapq 래퍼 함수를 ​​구현하여 정렬 안정성 및 동일한 우선 순위를 가진 요소 (다른 문제 중에서) 문제를 처리하는 방법에 대한 샘플 코드 에 대한 토론이 포함되어 있다는 것입니다.

요컨대, 그들의 해결책은 heapq의 각 요소가 우선 순위, 항목 수 및 삽입 할 요소가있는 트리플이되도록하는 것입니다. 항목 수는 같은 우선 순위를 가진 요소가 힙에 추가 된 순서대로 정렬되도록합니다.


이것이 올바른 솔루션입니다. heappush와 heappushpop은 모두 튜플과 직접 작동합니다
daisy

2

두 답변의 한계는 동점이 동점으로 취급되는 것을 허용하지 않는다는 것입니다. 첫 번째는 항목을 비교하여 연결을 끊고 두 번째는 입력 순서를 비교하여 연결합니다. 동점을 묶어 두는 것이 더 빠르며, 많은 경우 큰 차이를 만들 수 있습니다. 위와 문서를 기반으로 이것이 heapq에서 달성 될 수 있는지 명확하지 않습니다. heapq가 키를 받아들이지 않는 반면 동일한 모듈에서 파생 된 함수는 키를 받아들이지 않는 것이 이상해 보입니다.
추신 : 첫 번째 주석 ( "중복 가능성 ...")의 링크를 따라 가면 해결책처럼 보이는 파일을 정의하는 또 다른 제안이 있습니다.


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

heapq의 객체 값을 비교할 때 사용합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.