Rubyのhashメソッドをきちんと実装するとある1つの方法
前回のエントリの続き。こんなmoduleを作ってみました。includeするだけでまともなhashが使えるようになります。
module Hashable require "digest/sha1" def hash class_name = self.class.to_s hash_str = class_name hash_str.concat("={") if not defined?(@@hashable_hash_use_vars) then @@hashable_hash_use_vars = hash_use_instance_vars() end @@hashable_hash_use_vars.each_with_index do |var_name_symbol, i| hash_str.concat(",") if 0 < i hash_str.concat(var_name_symbol.to_s) hash_str.concat("=") hash_str.concat(self.instance_variable_get(var_name_symbol).to_s) end hash_str.concat("}") if not defined?(@@hashable_hash_xor_num) then digest = Digest::SHA1.digest(class_name) digest_first64bit = digest[0, 64/8] @@hashable_hash_xor_num = digest_first64bit.unpack("Q").first # extract unsigned 64bit Fixnum end return @@hashable_hash_xor_num ^ hash_str.hash end def eql?(obj) return false if !obj.kind_of?(self.class) obj_vars = {} obj.instance_eval do hash_use_instance_vars().each do |sym| obj_vars[sym] = self.instance_variable_get(sym) end end my_ivars = hash_use_instance_vars() return false if obj_vars.keys.sort != my_ivars.sort obj_vars.each do |sym, val| return false if val != self.instance_variable_get(sym) end return true end private def hash_use_instance_vars self.instance_variables end end
コードはパフォーマンスを少しでもよくするため冗長になってます。簡潔にするとかわりに30~35%ぐらい遅くなります。Hashableで下のようなシンプルなモデルのhash値を計算した場合、だいたいString#hashと比べて10倍ぐらい遅いです。
基本的なアイディアとしては前回のエントリ(Rubyのhashメソッドをきちんと実装するには?)を踏襲していますが、排他的論理和に使うビットマスクをクラス名をSHA1したものを使って自動化しています。意外と再利用性がありそうだったのでモジュールにしました。
以下のようなパターンの場合、うまく動かないかもしれません。
2点目について補足すると、基本的にリフレクションでインスタンス変数を全部取り出してきているので、等値性に関係ないインスタンス変数があとから増えちゃう場合はそれが考慮されない、という意味です。こういう場合の抜け道もいちおう簡単なものを用意してありまして、hash_use_instance_varsメソッドを以下のようにオーバーライドするとよいです。(別の要求として、あとからインスタンス変数が増えるのもhash値に織り込んで計算させたい場合は@@hashable_hash_use_varsクラス変数へのキャッシュを外してもいいかもしれません。)
# @a, @bに基づいて等値性を判断するのだけど@hogeが邪魔をしてしまうのを回避 class Hoge include Hashable attr_accessor :a attr_accessor :b def initialize(a, b) @a, @b = a, b end def hoge @hoge = "hoge" puts @hoge end private def hash_use_instance_vars [:@a, :@b] end end
ちなみにこのHashableの実装はString#hashに依存しているのですが、String#hashは同じ文字列に対しても、同じrubyプロセス内では何回評価しても同じ値ですが、コマンドを複数回実行すると毎回違う値が出てきます。従って、まずありえないとは思いますがString#hashの出力や、Hashableのhashを使って求めたハッシュ値を永続化して、同一でないプロセス上で他のインスタンスのハッシュ値と比較したりすると変なことになるはずです。
追記:ちらばり具合に関する定量的評価を取ったのを忘れていました、以下折り畳んで追記
続きを読む交通事故に遭った
車同士の右直事故で、自分は直進車でした。
- 車が横転して裏返しになった, 中で死んでたら後味悪すぎると思ったけど大事には至らなくてよかった...
- やっぱり事故の瞬間がスローモーションになるというのは本当だった
- 現場に長時間拘束されて寒かった、何人もの警察官に状況を何度も何度も説明してすごく大変だった...
- しばらく震えが止まらなかった
任意保険には入ってるし、基本的には相手の過失割合の方が圧倒的に高いのだけど、乗ってた車が非常に古いこともあって全損の可能性もあるのでもしかしたら車なしライフになってしまうかも... 不幸。
詳しいことはオフラインでお尋ねください。というかこの日記?ブログ?を呼んでる人でオフラインの知り合いがいるのかどうかは知らないけど...
なぜRubyを使うのか
最近なぜPerlを使うのか?という問いに対するアンサーエントリーがいろんなところで見られますが、これは各言語のユーザがやってみるとその言語のポジティブな部分が見れていいのではないかと思いますので僕もLLでは一番気に入っているRubyをなぜ気に入っているのか、を書きたいと思います。
文法
これは好みの問題なので、全ての人が他の言語よりRubyに魅力を感じる要因にはなりえないと思いますが、僕はRubyの文法が気に入っています。Perlはデフォルトでグローバル変数だったり、Pythonはインデントを閉じなかったり、というのが個人的にはあまり好きじゃなくて消去法的にRubyを使っているのかもしれません。
- メソッドチェインによる日本語っぽい記述: タイプが楽
- メソッド呼び出しでカッコを省略できる: タイプが楽
- 柔軟性: 強力なメタプログラミングが可能(時には害にもなり得るとは思いますが)
- 素直なローカル変数スコープ, 変数名先頭記号によるわかりやすいスコープ
- ブロックつきメソッド呼び出し(メソッドにdo endをつけて呼び出す)
- 純粋なオブジェクト指向言語であること
RubyGems
PerlのCPANに対するRubyのソレは今まではRAA(Ruby Application Archive)でしたが、最近はgemコマンドで簡単にライブラリがインストールできるのがすごく便利なので、みんなこちらのRubyGemsを使っているイメージですね。
LinuxやMacなど環境に関係なく動いてくれるところもうれしいです。優秀なパッケージマネージャーがあることでツールとしての魅力が高まっていると思います。
とろけるチキン問題
ネット上でなりすまされるのを防ぐにはどうすればいいんだろう。
有名人は認証済みアカウントとかあるっぽいけど、それほど有名じゃない個人や団体、組織・会社などでは自分のプレゼンスをそこまで高く評価してないから誰もなりすますなんて思わないよね、と思ってると思うのだけど、されてる可能性は十分あるわけで。
とろけるチキンに絡まれた: http://togetter.com/li/93253
とろけるチキン、実際に店舗に行ってみた: http://togetter.com/li/93481
とろけるチキン騒動第二幕 〜炎上マーケティング?なりすましの嫌がらせ?〜: http://togetter.com/li/93614
30byte FizzBuzz問題チャレンジ
i=0;loop{i+=1;m="";m<<"Fizz" if (0==i%3);m<<"Buzz" if (0==i%5);puts(m!=""?m:i)}
79が限界でした。(5分で挫折)
ちょっとがんばったら75になった。そして71になった。
# 75 i=0;loop{i+=1;m=""<<(i%3==0?"Fizz":"")<<(i%5==0?"Buzz":"");puts(m!=""?m:i)} # 71 i=0;loop{i+=1;m=(i%3==0?"Fizz":"")<<(i%5==0?"Buzz":"");puts(m!=""?m:i)}
Perlだと70でできた。bare word卑怯だなー。
for(;;){$i++;$m=($i%3==0?Fizz:"").($i%5==0?Buzz:"");print $m||$i,"\n"}
しかし、30byteには達しそうもないので、僕はたぶんYahooではエンジニアとして働けないのでしょう。無念。
Rubyで数値の配列からバイナリ構造体をつくるには
1個前のエントリの対応で。
data = [0xA1, 0xB2, 0xC3] data.pack("C*")
Rubyでバイナリデータをヘキサダンプするには?
バイナリデータをコンソールに出力して確認したいときとかなどにたまに使う。
bin = "\xa1\xb2\xc3" p bin.unpack("H*").first #=> "a1b2c3"
firstのあとにupcaseメソッドをチェインすれば大文字になる("A1B2C3"のように)