7 月 02 2008
JavaのString#hashCode()をRubyで再現
Javaで作ってあるアプリをiPhoneに移植しようと思ったが、まずObjective-Cがわからないので、Rubyに移植してみようと考えた。
移植元のアプリの中でさらっとString#hashCode()を使っていて困った。
http://sdc.sun.co.jp/java/docs/j2se/1.4/ja/docs/ja/api/java/lang/String.html#hashCode()
この文字列のハッシュコードを返します。String のハッシュコードは、次の方法で計算します。
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
上記の通りの計算をしてもRubyでは同じ数値にならない。 どうもJavaのintとRubyのIntegerは挙動が違うからのようだ。
java のInteger int 最大値超えるとどうなるの<br/> http://becomehappy.orz.hm/smithlog/article.php?id=370
Javaのintは-0x80000000~0x7fffffffの間をぐるぐるループしているらしい。<br/> 最大値(2147483647)を超えたら最小値(-2147483648)に戻してやる気遣いが必要だ。
というわけで作ってみたのが下記。
hashcode_gen.rb
module EmurateJavaStringHashCode
def to_hashcode
max = 2 ** 31 - 1 # Javaのint最大値
min = -2 ** 31 # Javaのint最小値
h = 0
n = self.size
n.times do |i|
h = 31 * h + self[i]
while h <min || max <h
h = max - ( min - h ) + 1 if h <min
h = min - ( max - h ) - 1 if max <h
end
end
h
end
end
class String
include EmurateJavaStringHashCode
end
key = ARGV.first
puts key.to_hashcode
確認用のjava HashCodeGenerator.java
public static void main(String argv[]) {
System.out.println( argv[0].hashCode() );
}
}
テストスクリプト test/test_hashcode_gen.rb
$:.unshift "#{File.dirname(__FILE__)}/../"
$:.unshift File.dirname(__FILE__)
class HashCodeGenTest <Test::Unit::TestCase
def test_random
10.times do
str = [Array.new(rand(20)){rand(256).chr}.join].pack("m").chomp
j = `java HashCodeGenerater '#{str}'`
r = `ruby hashcode_gen.rb '#{str}'`
assert_equal(j,r)
end
end
end
テスト結果
Started
.
Finished in 1.478273 seconds.
1 tests, 10 assertions, 0 failures, 0 errors
ちゃんと通りました。
--
ついでにCでも書いてみた。
#include <string.h>
int strhashcode( char *word );
int main( int argc, char** argv)
{
printf("hashcode = %d\n", strhashcode(argv[1]) );
return 0;
}
int strhashcode( char *word){
int len,i,h = 0;
len = strlen(word);
for (i=0; i<len; i++){
h = 31 * h + word[i];
}
return h;
}