セットの違いを行うときに最後の要素を無視するPythonの方法


11

私が2つ持っているとしましょうset()

a = {('1', '2', '3', 'a'), ('1', '2', '4', 'a'), ('1', '2', '5', 'b')}
b = {('1', '2', '3', 'b'), ('1', '2', '4', 'b'), ('1', '2', '6', 'b')}

今、私がしたいことは、セットの違いを見つけることですがb \ a、すべてのタプルの最後の要素を無視します。つまり、次のようなことをするだけです。

a = {('1', '2', '3'), ('1', '2', '4'), ('1', '2', '5')}
b = {('1', '2', '3'), ('1', '2', '4'), ('1', '2', '6')}

In[1]: b - a
Out[1]: {('1', '2', '6')}

期待される出力:

b \ a = {('1', '2', '6', 'b')}

各セットを手動で反復してそれぞれに対してチェックする必要なしにこれを達成する明白な/ pythonicな方法はありますtuple[:3]か?


3
私の最初の考えは、それらをクラスにして、比較演算子を定義することです
Kenny Ostrom

2
set差分操作をサブクラス化して上書きします。私が知っているすぐに使用できるソリューションはなく、存在するかどうか疑問です。
Ev。Kounis

セットには「key = ...」や(sort(..)のような)似たものはありません。タプルは不変でハッシュ可能であり、ハッシュに基づいて比較されます。1つの要素を削除すると、ハッシュが無効になります。したがって、いいえ-不可能です。あなたが値を必要としない場合は、3部構成のセットを作成することができますaa = { t[:3] for t in a }
パトリックArtner

2
@ AK47 2つのセットSとTの(セット)差はS∖Tと表記され、Tの要素ではないSの要素で構成されるセットを意味します。x∈S∖T⟺x∈S∧x∉T
Grajdeanu Alex。

tuple差分演算子をサブクラス化してオーバーライド
Pynchia

回答:


10

タプルの通常のハッシュ動作をオーバーライドする独自のクラスを作成する方法は次のとおりです。

a_data = [('1', '2', '3', 'a'), ('1', '2', '4', 'a'), ('1', '2', '5', 'b')]
b_data = [('1', '2', '3', 'b'), ('1', '2', '4', 'b'), ('1', '2', '6', 'b')]

class HashableIgnoresLastElement(tuple):
    def __eq__(self, other):
        return self[:-1] == other[:-1]

    def __hash__(self):
        return hash(self[:-1])

a = set(map(HashableIgnoresLastElement, a_data))
b = set(map(HashableIgnoresLastElement, b_data))

print(b - a)

出力あり

{('1', '2', '6', 'b')}

タプルのセットの動作を変更するには、タプルのハッシュ方法を変更する必要があります。

ここから、

オブジェクトは、その存続期間中に決して変更されない(__hash__()メソッドが必要)ハッシュ値があり、他のオブジェクトと比較できる(メソッドが必要)場合、ハッシュ可能__eq__()です。等しいと比較するハッシュ可能なオブジェクトは、同じハッシュ値を持つ必要があります。

これらのデータ構造はハッシュ値を内部で使用するため、ハッシュ可能性により、オブジェクトがディクショナリキーおよびセットメンバーとして使用可能になります。

したがって、ハッシュが最後の要素を無視するようにするには、dunderメソッド__eq____hash__適切にオーバーロードする必要があります。私たちがしなければならないのは、最後の要素を切り取ってから、通常の適切なメソッドに委譲するだけなので、これはそれほど難しいことにはなりませんtuple

参考文献:


1
すごくすっきり!これがどのように機能するかについても少し説明できますか?このソリューションを読み通す人にとっては価値があるかもしれません。
Grajdeanu Alex。

@GrajdeanuAlex。簡単な説明を追加しました:)。実際には、演算子のオーバーロードのビットと断片を組み合わせ、Pythonでハッシュがどのように機能するかを示しています。
Izaak van Dongen

2

ここでは、1つのアプローチを定義しab最も単純な解決策はインデックス付けを意味するように思えるので、セットではなくリストして示しますb

a = [('1', '2', '3', 'a'), ('1', '2', '4', 'a'), ('1', '2', '5', 'b')]
b = [('1', '2', '3', 'b'), ('1', '2', '4', 'b'), ('1', '2', '6', 'b')]

# reconstruct the sets of tuples removing the last elements
a_ = {tuple(t) for *t, _ in a}
b_ = [tuple(t) for *t, _ in b]

# index b based on whether an element in a_
[b[ix] for ix, j in enumerate(b_) if j not in a_]
# [('1', '2', '6', 'b')]

1
ルックアップにセットを使用しているため、これは私が誤解していない場合はO(n)です。Izaak van Dongenの答えは@konradの方がはるかにエレガントだと思いますが
yatu

1
あなたは完全に正しいです。リストの使用(および列挙)は私を捨てましたが、もちろん、セットの違いも最初のセットを反復する必要があります。
Konrad Rudolph

1

セットは正常に動作します。正しく機能しないのはデータです。それらが異なるように見えても実際には同じである場合は、希望どおりに動作するデータ型を定義します。その後、セットはそれ自体でうまく機能します。

class thing:
    def __init__(self, a, b, c, d):
        self.a, self.b, self.c, self.d = a, b, c, d

    def __repr__(self):
        return (str((self.a, self.b, self.c, self.d)))

    def __hash__(self):
        return hash((self.a, self.b, self.c))

    def __eq__(self, other):
        return self.a == other.a and self.b == other.b and self.c == other.c       

a = {thing('1', '2', '3', 'a'), thing('1', '2', '4', 'a'), thing('1', '2', '5', 'b')}
b = {thing('1', '2', '3', 'b'), thing('1', '2', '4', 'b'), thing('1', '2', '6', 'b')}
print (b - a)

{( '1'、 '2'、 '6'、 'b')}


3
あなたはタプルに関して定義__repr____hash__ましたが、__eq__。ここでもタプルを使用する方が短いのではないでしょうか?実際、こことスライスでスライスを使用し__hash__て、コードをさらに短くすることができます。
Konrad Rudolph

はい、タプルをサブクラス化するだけで、質問の大きな改善が得られました。
ケニー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.