比較するには非常に高価な構造がいくつかあります。(実際には、別個の枝を持つツリーです。) それらのハッシュ値の計算にもコストがかかります。
処理を高速化するために一部の結果をキャッシュするeq演算子のデコレータを作成したいと考えています。これは、メモ化にいくぶん似ています。
特に、このようなことが起こってほしいです。A、B、C の 3 つのオブジェクトがあるとします。A と B を比較します。eq演算子が呼び出され、True が返され、結果が格納されます。B と C を比較します。前と同じようにeq演算子が呼び出されます。次に、A と C を比較します。アルゴリズムは、A が B に等しく、B が C に等しいことを検出する必要があるため、コストのかかるeq演算子を呼び出すことなく、A が C に等しいことを返す必要があります。
union-find アルゴリズムを使用したかったのですが、等式のキャッシュのみが許可され、不等式のキャッシュは許可されません。
互いに等しい 2 つのオブジェクト、A と B があるとします。また、等しいオブジェクトの別のペア、C と D があるとします。和集合検索アルゴリズムは、それらを 2 つのカテゴリ (A、B) および (C) に正しくグループ化します。 、D)。ここで、A がC と等しくないと仮定します。私のアルゴリズムは何らかの方法でそれをキャッシュし、 eq演算子が (A, C)、(B, C)、(A, D)、(B, D) のペアで実行されないようにする必要があります。これらのペアはすべて等しくないと推測できます。Union-find はそれを許可しません。正の等式のみを保存し、多くの等しくないオブジェクトを比較する必要がある場合は惨めに失敗します。
私の現在の解決策は次のようなものです:
def optimize(original_eq):
def optimized_eq(first, second):
if first is second: return True
if hash(first) != hash(second): return False
if cache.find(first) == cache.find(second): return True
result = original_eq(first, second)
if result:
cache.union(first, second)
else:
pass # no idea how to save the negative result
return result
return optimized_eq
ハッシュ関数の計算が簡単であれば、この解決策は問題ありませんが、そうではありません。等しい可能性が高いオブジェクトに対して cache.find を呼び出すため、元の等価演算子を呼び出す必要はほとんどありません。しかし、私が言ったように、ハッシュ関数は私のツリーでは非常に遅いです (基本的にすべてのツリーをトラバースし、各ノードのブランチを比較して重複を削除する必要があります)、削除したいと思います。代わりに否定的な結果をキャッシュしたい。
この問題の良い解決策を知っている人はいますか? 肯定的な比較結果だけでなく、否定的な結果もキャッシュする必要があります。
アップデート:
私のために働く私の現在の解決策は次のとおりです。
def memoize_hash_and_eq(cls):
"This decorator should be applied to the class."
def union(key1, key2):
nonlocal union_find
if key1 is not key2:
key1_leader = union_find(key1)
key2_leader = union_find(key2)
key1_leader._memoize_hash_and_eq__leader = key2_leader
try:
key2_leader._memoize_hash_and_eq__splits = key1_leader._memoize_hash_and_eq__splits
del key1_leader._memoize_hash_and_eq__splits
except AttributeError:
pass
def union_find(key):
leader = key
while True:
try:
leader = leader._memoize_hash_and_eq__leader
except AttributeError:
break
if leader is not key:
key._memoize_hash_and_eq__leader = leader
try:
leader.__splits = key._memoize_hash_and_eq__splits
del key._memoize_hash_and_eq__splits
except AttributeError:
pass
return leader
def split(key1, key2):
nonlocal union_find
key1_leader = union_find(key1)
key2_leader = union_find(key2)
try:
key1_leader._memoize_hash_and_eq__splits.add(key2_leader)
except AttributeError:
try:
key2_leader._memoize_hash_and_eq__splits.add(key1_leader)
except AttributeError:
try:
key1_leader._memoize_hash_and_eq__splits = set()
key1_leader._memoize_hash_and_eq__splits.add(key2_leader)
except (AttributeError, TypeError):
pass
def split_find(key1, key2):
nonlocal union_find
key1_leader = union_find(key1)
key2_leader = union_find(key2)
try:
split_leaders = key2_leader._memoize_hash_and_eq__splits
for k in [_k for _k in split_leaders]:
split_leaders.add(union_find(k))
if key1_leader in split_leaders:
return True
except (AttributeError, TypeError):
pass
try:
split_leaders = key1_leader._memoize_hash_and_eq__splits
for k in [_k for _k in split_leaders]:
split_leaders.add(union_find(k))
if key2_leader in split_leaders:
return True
except (AttributeError, TypeError):
pass
return False
def memoized_hash(self):
return original_hash(union_find(self))
original_hash = cls.__hash__
cls.__hash__ = memoized_hash
def memoized_equivalence(self, other):
if self is other:
return True
if union_find(self) is union_find(other):
return True
if split_find(self, other):
return False
result = original_equivalence(self, other)
if result is NotImplemented:
return result
elif result:
union(self, other)
else:
split(self, other)
return result
original_equivalence = cls.__eq__
cls.__eq__ = memoized_equivalence
return cls
これにより、eq と hash の両方が高速化されます。