カスタム比較述語を使用したheapq


82

カスタムソート述語を使用してヒープを構築しようとしています。そこに入る値は「ユーザー定義」タイプであるため、組み込みの比較述語を変更することはできません。

次のようなことを行う方法はありますか?

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

または、さらに良いことに、heapq関数を自分のコンテナーでラップして、述語を渡し続ける必要がないようにすることもできます。



回答:


120

heapqのドキュメントによると、ヒープの順序をカスタマイズする方法は、ヒープ上の各要素をタプルにすることです。最初のタプル要素は、通常のPython比較を受け入れる要素です。

heapqモジュールの関数は(オブジェクト指向ではないため)少し面倒であり、常に最初のパラメーターとしてヒープオブジェクト(ヒープリスト)を明示的に渡す必要があります。key関数を指定し、ヒープをオブジェクトとして提示できる非常に単純なラッパークラスを作成することで、1つの石で2羽の鳥を殺すことができます。

以下のクラスは内部リストを保持します。各要素はタプルであり、その最初のメンバーはキーであり、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 2016

4
私は実際にこれ(またはこれに基づくもの)をPythonのstdlibにプッシュしようとしましたが、提案は拒否されました。
jsbueno 2016

1
残念ながら、ほとんどのPython機能のオブジェクト指向スタイルに適合し、重要な引数は追加の柔軟性を提供します。
zeycus 2016

たとえば、[self.key(item)、id、item]にタプルの代わりにリストを使用しましたが、最初のインデックスがキーである限り、問題なく機能します。
Deepak Yadav 2018年

5
要素が比較できず、キー値に同点がある場合、これは失敗します。id(item)ネクタイを壊すためにタプルの中間要素として配置します。
GeorgiYanchev19年

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
これは、これまでで最もクリーンなソリューションのようです。
ロイマンソン

前の2つのコメントに完全に同意します。これは、Python 3のためのより良い、クリーンなソリューションであると思われる
シラズBenAbdelkader

また、ここでは同様の問題に非常によく似ソリューションです:stackoverflow.com/questions/2501457/...は
シラズBenAbdelkader

1
__gt__代わりにこれを使用してテストしましたが、同様に機能します。どの魔法の方法を使用しても問題がないのはなぜですか?heapqのドキュメントに何も見つかりません。たぶんそれはPythonが一般的に比較を行う方法に関連していますか?
ジョシュ・クラーク

1
で比較を行うときheapq、Pythonは__lt__()最初に検索します。定義されていない場合は、を検索し__gt__()ます。どちらも定義されていない場合は、をスローしTypeError: '<' not supported between instances of 'Node' and 'Node'ます。これは、__lt__()との両方を定義し__gt__()、それぞれにprintステートメントを配置し、__lt__()returnを持つことで確認できますNotImplemented
FanchenBao20年

19

heapqドキュメントは、ヒープ要素は最初の要素が優先され、ソート順を定義するタプルであり得ることを示唆しています。

ただし、あなたの質問により適切なのは、ドキュメントに、ソートの安定性と同じ優先度の要素(他の問題の中でも)の問題に対処するために独自のheapqラッパー関数を実装する方法のサンプルコードに関する説明が含まれていることです。

一言で言えば、彼らの解決策は、ヒープ内の各要素を、優先度、エントリ数、および挿入される要素を持つトリプルにすることです。エントリ数により、同じ優先度の要素がヒープに追加された順序でソートされます。


これは正しい解決策です。heappushとheappushpopはどちらもタプルで直接機能します
デイジー

2

両方の回答の制限は、同点を同点として扱うことができないことです。1つ目はアイテムを比較することで結びつきを解消し、2つ目は入力順序を比較することで結びつきを解消します。ネクタイをネクタイにする方が速く、ネクタイがたくさんあると大きな違いが生まれます。上記とドキュメントに基づいて、これがheapqで達成できるかどうかは明らかではありません。heapqがキーを受け入れないのに、同じモジュール内でそれから派生した関数が受け入れるのは奇妙に思えます。
PS:最初のコメント(「重複の可能性...」)のリンクをたどると、解決策のように見えるファイルを定義する別の提案があります。


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

heapq内のオブジェクトの値を比較するためにこれを使用します

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.