やりたいこと
ペア(やセット)に対して値(やオブジェクト)を設定したい場合があります。
(例)
- ニューラルネットワーク計算での2ユニット間の「重み」
- ソーシャルネットワークにおけるユーザ間の距離とか
一致や比較について特別な要件がある場合は、ペアやグループを保持する専用クラスを作って、equals()やhashcode()をoverrideするようにし、comparatorも設定し、その専用クラスをMapのキーにする、という感じだと思います。
しかし、ただペアやセットを、別の何かと一対一対応させたいだけなら、Listをキーにすれば済み(順不同にする工夫は必要)、それもよくやられていると思います。
今回は、それをGroovyでやってみると、値を登録/取得するコードがシンプルで気持ちいいという話です。
実装例
複数キー対応マップクラス:
class MultiKeyMap extends HashMap<List,Object>{
@Override
Object put(List key, Object value) {
super.put(key.sort(false), value)
}
@Override
Object get(Object key) { // get(List key)とすると呼ばれなくなってしまう
super.get((key as List).sort(false))
}
}
使用例:
class Neuron{} // ネットワークのニューロン(ユニット)、中身は省略
があるとして、
def w = new MultiKeyMap() // ニューロン間の重み(ペアごとに設定される)マップ
def n1 = new Neuron()
def n2 = new Neuron()
def n3 = new Neuron()
w[n1,n2] = 0.2 // 値をセット
println w[n1,n2] // 取得できる
assert w[n1,n2] == w[n2,n1] // もちろん順不同
.
w[n1,n2,n3] = 0.5 // 3要素以上の組でもOK
println w[n1,n2,n3]
ポイント
Groovyのマップにおいて、mapObj[a,b,c]
は、mapObj[[a,b,c]]
と同じで、
つまり「[a,b,c]というリストをキーにしてmapObjから値を取得する」動きになります。
複数キーマップとして使い勝手のよい最小表現は、
- mapObj[a,b] ← 今回はこれ
- mapObj[a][b] // 二次元配列、numpyの行列とか
- mapObj(a,b)
かなと思うのですが、1.の形は比較的簡単に実現できて便利だな、というところです。
※2.も実装できないことはないのですが、速度やメモリに無駄な部分が出てくる予感です。
また、numpyの行列が一層便利だと思う点は、ワイルドカードを使って、w[a][*](aを含むすべてのペアの値を返す)のような書き方ができることです。