SetのようなもののRubyでの表現
準備その2です.
クラスタ作成時には,クラスタ対象同士の相関値を延々と計算します.
特に階層型クラスタの場合は全ての組み合わせの距離を調べるので,当たり前なのですが,相関値の計算は重いのです.
何とか計算する回数を減らしたい.
やりたいこと
A,Bのピアソン相関値をPearson(A, B)とした場合,Pearson(B, A)も同じ値になります.
ということは,ピアソン相関値をハッシュに格納した場合,(A, B)と(B, A)のキー値を同一にできれば計算は1回で済むことになります.
では,(A, B)と(B, A)のキー値を一致させるにはどうしたらよいのでしょうか.
Setで試してみた
まず思いついたのは,集合を扱うSetライブラリです.
Setでは各要素に順序というものがなく,イケそうな感じがします.
require 'set' hash = {} set1 = Set.new ['a', 'b'] set2 = Set.new ['b', 'a'] hash[set1] = 'value' puts hash[set1] puts hash[set2]
結果は,
value
nil # キーが異なると見なされ,'value'を参照できない
ダメでした...
Hashのキーとして同一にさせるための条件
そもそも,キー値は何を使って決められているのでしょう.Rubyリファレンスマニュアルによると
キーには任意の種類のオブジェクトを用いることができますが、以下の2つのメソッドが適切に定義してある必要があります。
* Object#hash ハッシュの格納に用いられるハッシュ値の計算
* Object#eql? キーの同一性判定
とあります.
では,このhashとeql?の値を確認してみましょう.
p set1.hash == set2.hash p set1.eql?(set2)
結果は,
false false
やはり,このままでは無理そうです.
Arrayを元に実装してみる
先ほど述べたように,hashとeql?の値を同じにできればよいので,自分で実装してみます.
ここではsortしてから元のメソッドに渡すようにしてみました.
今回の条件ではSetを使わずともArrayを元にすればよさそうです.
module My class Pair < Array def initialize(*args) super # 引数が何個あっても,最初の2個だけ使ってペアにする slice!(2, (size - 2)) end alias_method :original_eql?, :eql? alias_method :original_hash, :hash def eql?(other) sort.original_eql?(other.sort) end def hash sort.original_hash end alias == eql? alias left first alias right last end end
動作確認
include My pair1 = Pair.new ['a', 'b'] pair2 = Pair.new ['b', 'a'] hash[pair1] = 'value' puts hash[pair1] puts hash[pair2] p pair1.hash == pair2.hash p pair1.eql?(pair2)
実行すると↓のようになります.
value value true true
いい感じですね.
後日談
MacPortsを使うようになってから分かったのですが,ruby 1.8.7以上であればSetでもhashやeql?が一致し,そのまま使えるようです.
気づいた時は結構ショックでした...