概要

MahoutはいろんなものをlongのIDで扱う。ので、文字列をIDに変換しないといけないシーンにけっこう出くわす。

手軽にIDと文字列を変換できる機能に、IDMigratorがある。ファイルとかDBから文字列とIDの対応表を生成してくれる機能。

変換にはハッシュ値を使用し、衝突したら仕方ないという男らしい仕様を採用している。JavaDocには「最悪、違うユーザのレコメン出しちゃうこともあるかもね」と書かれている。

でも、大丈夫。64bitでぶつかってしまうような運命の2人なら、きっと趣味も一緒のはずだから。

@CretedDate 2012/08/13
@Env Java7, Mahout0.7

MemoryIDMigrator

とりあえず手軽に使えるところで、MemoryIDMigratorを使ってみる。Collectionに入った文字列からIDへの変換表を生成してくれる。

List<String> list = Arrays.asList(
        "田中", "佐藤", "鈴木", "高橋", "伊藤",
        "山本", "渡辺", "中村", "小林", "加藤");

// Migratorの用意
MemoryIDMigrator migrator = new MemoryIDMigrator();
migrator.initialize(list);

// 文字列 → ID変換
long id = migrator.toLongID("田中");
System.out.println(id);
  //=> -3962337966239618561

// ID → 文字列変換
String name = migrator.toStr
  //=> 田中

ID→文字列の変換(toStringID)に必要な情報はMahoutのFastByIDMapで持っている。FastByIDMapはJavaのHashMapよりも少ない容量で情報が持てる子。

文字列→IDの変換(toLongID)はMapは見に行かずに、ハッシュ値を計算して返している。そのため、登録していない文字列に対してtoLongIDを実行しても普通に結果が返ってくる。

id = migrator.toLongID("藤原")
System.out.println(id);
  //=> -4224957980247652293

ハッシュ値が重複した文字列を登録した場合は、後にputした方で上書きされる。

ハッシュ値自体は、java.security.MessageDigestでgetInstance("MD5")したもの使って生成している。

FileIDMigrator

ファイルにIDになる文字列を改行区切りで羅列しておき、それを読み込むタイプのIDMigrator。

とりあえず下記のような感じのファイルを用意する。

田中
佐藤
鈴木
高橋
伊藤
山本
渡辺
中村
小林
加藤

あとはファイル名をコンストラクタで指定するだけ。

FileIDMigrator migrator = new FileIDMigrator(new File("data/ids.txt"));

long id = migrator.toLongID("田中");
System.out.println(id);
  //=> -3962337966239618561

String name = migrator.toStringID(id);
System.out.println(name);
  //=> 田中

変換の仕組み自体はMemoryIDMigratorと同じ。

MySQLJDBCIDMigratorSample

名前の通り、MySQLと連携してIDを変換する。

とりあえずDBとテーブルを用意。

mysql> create database mahout;
mysql> use mahout;
mysql> create table table_name( id_column bigint not null primary key, value_column varchar(255) not null unique );

コードを書いてみる。

// DataSourceの用意
MysqlDataSource ds = new MysqlDataSource();
ds.setServerName("localhost");
ds.setUser("scott");
ds.setPassword("tiger");
ds.setDatabaseName("mahout");

// migratorの用意
MySQLJDBCIDMigrator migrator = new MySQLJDBCIDMigrator(ds,
        "table_name", "id_column", "value_column");

// とりあえずsotreしてみる
migrator.storeMapping(migrator.toLongID("田中"), "田中");
migrator.storeMapping(migrator.toLongID("佐藤"), "佐藤");
migrator.storeMapping(migrator.toLongID("鈴木"), "鈴木");

// IDから名前を取ってみる
String name = migrator.toStringID(migrator.toLongID("田中"));
System.out.println(name);
  //=> 田中

ちゃんと取れた。

上記コマンドを実行した後のテーブルの中身。

mysql> select * from table_name;
+----------------------+--------------+
| id_column            | value_column |
+----------------------+--------------+
| -3962337966239618561 | 田中         |
| -8429178488470665793 | 佐藤         |
|  -245016868995516317 | 鈴木         |
+----------------------+--------------+

ちゃんと入っとるね。