カスタムソート述語を使用してヒープを構築しようとしています。そこに入る値は「ユーザー定義」タイプであるため、組み込みの比較述語を変更することはできません。
次のようなことを行う方法はありますか?
h = heapq.heapify([...], key=my_lt_pred)
h = heapq.heappush(h, key=my_lt_pred)
または、さらに良いことに、heapq関数を自分のコンテナーでラップして、述語を渡し続ける必要がないようにすることもできます。
カスタムソート述語を使用してヒープを構築しようとしています。そこに入る値は「ユーザー定義」タイプであるため、組み込みの比較述語を変更することはできません。
次のようなことを行う方法はありますか?
h = heapq.heapify([...], key=my_lt_pred)
h = heapq.heappush(h, key=my_lt_pred)
または、さらに良いことに、heapq関数を自分のコンテナーでラップして、述語を渡し続ける必要がないようにすることもできます。
回答:
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で失敗する可能性があります)
id(item)
ネクタイを壊すためにタプルの中間要素として配置します。
__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]
__gt__
代わりにこれを使用してテストしましたが、同様に機能します。どの魔法の方法を使用しても問題がないのはなぜですか?heapq
のドキュメントに何も見つかりません。たぶんそれはPythonが一般的に比較を行う方法に関連していますか?
heapq
、Pythonは__lt__()
最初に検索します。定義されていない場合は、を検索し__gt__()
ます。どちらも定義されていない場合は、をスローしTypeError: '<' not supported between instances of 'Node' and 'Node'
ます。これは、__lt__()
との両方を定義し__gt__()
、それぞれにprintステートメントを配置し、__lt__()
returnを持つことで確認できますNotImplemented
。
heapqドキュメントは、ヒープ要素は最初の要素が優先され、ソート順を定義するタプルであり得ることを示唆しています。
ただし、あなたの質問により適切なのは、ドキュメントに、ソートの安定性と同じ優先度の要素(他の問題の中でも)の問題に対処するために独自のheapqラッパー関数を実装する方法のサンプルコードに関する説明が含まれていることです。
一言で言えば、彼らの解決策は、ヒープ内の各要素を、優先度、エントリ数、および挿入される要素を持つトリプルにすることです。エントリ数により、同じ優先度の要素がヒープに追加された順序でソートされます。