setで違う値を同じとみなす

タイトルの意味がわかりにくいですが、

s = set([((2, 3), 1), ((2, 4), 2), ((2, 3), 3)])
print s
set([((2, 4), 2), ((2, 3), 1), ((2, 3), 3)])

となりますが、タプルの第2項は無視して第1項だけで同じ値か否かを判定したいときにこれでは困るという場合にどうすればいいかという話です。ネットでちょっと調べたところではそのものズバリというページが無かったので書き記します。
C++STLではless(比較関数オブジェクト)を指定できますが、Pythonにはどうもそういう機能はなさそうです。その代わりにここではクラスを作ります。

class C:
    def __init__(self, a, b):
        self.a, self.b = a, b
    
    def __eq__(self, other):
        return self.a == other.a
    
    def __hash__(self):
        return self.a.__hash__()

ここでself.aに(2, 4)とかをself.bに2とかを割り当てます。クラスに__eq__と__hash__というメソッドを定義します。__eq__は等しいとみなす条件を書きます。__hash__は(たぶん)ハッシュテーブルの格納場所を指定するための整数を返します。self.aのみに依存する整数を返せばいいのですが、self.aはタプルなのでこれの__hash__を返せば整数を返したことになります。

ls = [((2, 3), 1), ((2, 4), 2), ((2, 3), 3)]
s = set(C(a, b) for a, b in ls)
for c in s:
    print c.a
(2, 3)
(2, 4)

これで、((2, 3), 1)と((2, 3), 3)は同じ値とみなされたことになります。


ところで、なぜこのようなことをしなければならなかったかというと、元々は、

(2, 3), (2, 4), (2, 3), ...

というタプルがあってここから重複は取り除くという処理を行いたいのです。しかしこのままsetを使うと順序が変わってしまいます。順序は変えたくないのです。そのために事前に通し番号を振っておき、そこで重複を取り除いて、通し番号でソートして、最後に通し番号を捨てます。