今さらferret
WEBサイトをクロールして集めたデータの検索にgroonga(rroonga)を使っていたが、32bitのWindowsという、推奨されない環境のため、何万件もデータ突っ込んだら、エラーでどうにもならなくなったので、代わりに使えそうなferretを試してみた。solr-rubyにはそのうち挑戦したい。
rubyはrubyinstaller + devkitのやつを使っているので、普通にインストールできた。
gem install ferret
1.9でビルドできるようにしてくれたのはgithubにあるようだが、文字コードとかには対応してないので、実際に使うには、処理をかませる必要がありそう。
gem install sdsykes-ferret
一通り使い方を勉強してみた。
require 'ferret' include Ferret require 'strscan' class MyAnalyzer < Analysis::Analyzer def token_stream(field, str) case field when :content MyTokenizer.new(str) else Analysis::WhiteSpaceTokenizer.new(str) end end end class MyTokenizer attr_reader :text def initialize(str) self.text = str end def text=(str) @text = str @scnr = StringScanner.new(str) end def next c = @scnr.getch c ? Analysis::Token.new(c, @scnr.pos - c.bytesize, @scnr.pos) : nil end end fis = Index::FieldInfos.new(:store => :yes, :index => :yes, :term_vector => :no) fis.add_field(:year) fis.add_field(:content, :term_vector => :with_positions_offsets) index = Index::Index.new( :id_field => :year, #:key => :year, :path => "index.db", #:create => true, :fieldinfos => fis, :analyzer => MyAnalyzer.new ) index << {:year => "0710", :content => '平城京に都を移す。'} index << {:year => "0743", :content => '墾田永年私財法が出される。'} index << {:year => "0784", :content => '長岡京に都を移す。'} index << {:year => "0794", :content => '平安京に都を移す。'} index << {:year => "0797", :content => '坂上田村麻呂が征夷大将軍になる。'} p index[0].load #=> {:year=>"0710", :content=>"平城京に都を移す。"} p index["0743"].load #=> {:year=>"0743", :content=>"墾田永年私財法が出される。"} # 検索する query = "+都 +year:>0750" total = index.search_each(query, :sort => "year DESC") {|idx, score| data = index[idx].load hi = index.highlight(query, idx, :field => :content, :pre_tag=>"<", :post_tag=>">") p [data[:year], hi, score] } #=> ["0794", ["平安京に<都>を移す。"], 0.404452621936798] #=> ["0784", ["長岡京に<都>を移す。"], 0.404452621936798]
自分の使い方だと、MeCabでは検索漏れが多くなるので、bi-gramのTokenizer適当に作ればすぐに使えそうな感じ。
KyotoCabinetをrubyで使う (キーをたどる)
require 'kyotocabinet' db_hash = KyotoCabinet::DB.new db_hash.open("*") db_hash["apple"] = "りんご" db_hash["lemon"] = "檸檬" db_hash["orange"] = "オレンジ" db_hash["grape"] = "ぶどう" # 全ての項目を辿る each each_pair each_key each_value db_hash.each {|k,v| p [k,v] } #=> ["orange", "オレンジ"] #=> ["apple", "りんご"] #=> ["grape", "ぶどう"] #=> ["lemon", "檸檬"] db_hash.each {|k,v| p [k,v] break # (注意) breakしない } db_hash.each_key {|k| p k } # 配列で来る #=> ["orange"] #=> ["apple"] #=> ["grape"] #=> ["lemon"] db_hash.each_value {|v| p v } # 配列で来る #=> ["オレンジ"] #=> ["りんご"] #=> ["ぶどう"] #=> ["檸檬"] # カーソル cur = db_hash.cursor cur.jump # カーソルを先頭に移動 while true k,v = cur.get(true) # 取得してカーソルを次に移動 break if !k p [k,v] end cur.disable # 後始末? p cur #=> #<KyotoCabinet::Cursor:(disabled)> # 後始末付き db_hash.cursor_process {|cur| cur.jump while true k,v = cur.get(true) break if !k p [k,v] end } # キーの検索 db_hash["apricot"] = "アプリコット" p db_hash.match_prefix("ap") #=> ["apricot", "apple"] 前方一致 p db_hash.match_regex("ap") #=> ["apricot", "apple", "grape"] p db_hash.match_regex("ap", 2) #=> ["apricot", "apple"] 2個まで取得 p db_hash.match_regex("(.)\\1") #=> ["apple"] # ツリー db_tree = KyotoCabinet::DB.new db_tree.open("%") db_tree["あひる"] = 0 db_tree["あせくさい"] = 1 db_tree["あかさか"] = 2 db_tree["あかばね"] = 3 db_tree["あくましんかん"] = 4 # ツリーはキーに並び順あり db_tree.each_key {|k,x| p k } #=> "あかさか" #=> "あかばね" #=> "あくましんかん" #=> "あせくさい" #=> "あひる" # ツリーは逆順にさかのぼれる db_tree.cursor_process {|cur| cur.jump_back # カーソルを最後に移動 while true k,v = cur.get # 取得。カーソル位置はそのまま cur.step_back # カーソルを前に戻す break if !k p [k,v] end } #=> ["あひる", "0"] #=> ["あせくさい", "1"] #=> ["あくましんかん", "4"] #=> ["あかばね", "3"] #=> ["あかさか", "2"] # 文字順じゃなく、数値順にする db_tree_n = KyotoCabinet::DB.new db_tree_n.open("%#rcomp=dec") db_tree_n["1212"] = 1 db_tree_n["123"] = 1 db_tree_n["11111"] = 1 db_tree_n.each_key {|k,x| p k } #=> "123" #=> "1212" #=> "11111"
KyotoCabinetをrubyで使う(基本)
require 'kyotocabinet' db = KyotoCabinet::DB.new db.open("*") # オンメモリハッシュDB # 項目の設定 set db.set("apple", "りんご") db["banana"] = "ばなーな" # 項目の取得 get p db["apple"] #=> "りんご" p db.get("banana") #=> "ばなーな" p db["grape"] #=> nil # 項目の数 p db.count #=> 2 # 項目の追加 add (すでにあるものは変更されない) p db.add("orange", "オレンジ") #=> true p db.add("banana", "バナナ") #=> false p db["banana"] #=> "ばなーな" # 項目の変更 replace p db.replace("banana", "ばにゃにゃ") #=> true p db.replace("grape", "ぶどう") #=> false # 項目の削除 remove seize p db.remove("apple") #=> true p db.remove("lemon") #=> false p db["apple"] #=> nil p db.seize("orange") #=> "オレンジ" p db.seize("lemon") #=> nil p db["orange"] #=> nil # 値に追加 append db.append("banana", "★") db.append("lemon", "★") p db["banana"] #=> "ばにゃにゃ★" p db["lemon"] #=> "★" # 全部削除 clear db.clear p db.count #=> 0
KyotoCabinetをrubyで使う前に
まずはKyotoCabinetのデータベースを知らねば。
参照
mixi Engineers' Blog » 京都収納棚紅玉束縛: Rubyで簡単、DBプログラミング
http://alpha.mixi.co.jp/blog/?p=1795
データベースの種類
- | prototype hash database |
+ | prototype tree database |
: | stash database |
* | cache hash database |
% | cache tree database |
kch | file hash database |
kct | file tree database |
kcd | directory hash databas |
kcf | directory tree database |
kcx | plain text database |
チューニングパラメータ
db = KyotoCabinet::DB.new db.open('items.kch#opts=c#zcomp=lzo', KyotoCabinet::DB::OWRITER | KyotoCabinet::DB::OCREATE)
- | + | : | * | % | kch | kct | kcd | kcf | kcx | ||
log | ログファイルのパスを指定する。 "-": 標準出力 "+": 標準エラー出力 |
o | o | o | o | o | o | o | o | o | o |
---|---|---|---|---|---|---|---|---|---|---|---|
logkinds | "debug", "info", "warn", or "error" | o | o | o | o | o | o | o | o | o | o |
logpx | prefix of each log message | o | o | o | o | o | o | o | o | o | o |
opts | "s": use 32-bit addressing "l": use linear collision chaining "c": compress each record |
o | o | o | o | o | o | ||||
zcomp | "zlib": ZLIB raw compressor "def": ZLIB deflate compressor "gz": ZLIB gzip compressor "lzo": LZO compressor "lzma": LZMA compressor "arc": Arcfour cipher |
o | o | o | o | o | o | ||||
zkey | the cipher key of the compressor | o | o | o | o | o | o | ||||
bnum | ハッシュ表のバケット数。デフォルトは100万くらい。格納するレコードの総数の2倍くらいがオススメ。 | o | o | o | o | o | |||||
capcnt | 格納するレコード数の上限。溢れたはLRUなものから削除される。デフォルトは制限なし。 | o | |||||||||
capsiz | 格納するレコードサイズの合計の上限。溢れた分はLRUなものから削除される。デフォルトは制限なし。 | o | |||||||||
psiz | B+木のページサイズ。デフォルトは8192バイト。4096でもOK。 | o | o | o | |||||||
rcomp | B+木の比較関数。”lex”(lexical)か “dec”(decimal)。デフォルトはlexical。 "lex": lexical comparator "dec": decimal comparator "lexdesc": lexical descending comparator "decdesc": decimal descending comparator |
o | o | o | |||||||
pccap | ページキャッシュのサイズの合計の上限。デフォルトは64MB。 | o | o | o | |||||||
apow | レコードのアラインメント。2の冪で指定。デフォルトは3(ハッシュ)か8(ツリー)。 | o | o | ||||||||
fpow | フリーブロックプールのサイズ。2の冪で指定。デフォルトは10。 | o | o | ||||||||
msiz | mmapする領域のサイズ。デフォルトは64MB。 | o | o | ||||||||
dfunit | 動的デフラグの単位。デフォルトはなし。動的デフラグをしたい場合は8くらいがオススメ。 | o | o |
connection mode
OWRITER | as a writer |
---|---|
OREADER | as a reader |
The following may be added to the writer mode by bitwise-or:
OCREATE | it creates a new database if the file does not exist |
---|---|
OTRUNCATE | it creates a new database regardless if the file exists |
OAUTOTRAN | each updating operation is performed in implicit transaction |
OAUTOSYNC | each updating operation is followed by implicit synchronization with the file system |
The following may be added to both of the reader mode and the writer mode by bitwise-or:
ONOLOCK | it opens the database file without file locking |
---|---|
OTRYLOCK | locking is performed without blocking |
ONOREPAIR | the database file is not repaired implicitly even if file destruction is detected |
KyotoCabinetのruby拡張をmingwでビルド
ちょっと試してみたかっただけなのに、わからないことばかりで苦労した。速攻で忘れるだろうから、書き残しておく。
参考サイト
blog.k11i.biz: [メモ]MinGW/MSYS 環境で Kyoto Cabinet Core & Java バインディングをビルドする
http://blog.k11i.biz/2010/12/mingwmsys-kyoto-cabinet-core-java.html
Ruby BDB - |▽ ̄)ノ なページ再帰 - livedoor Wiki(ウィキ)
http://wiki.livedoor.jp/niloufar/d/Ruby%20BDB#content_11_10
1.準備
Mingwのインストール
rubyinstaller の devkit は使わずに、Mingw(mingw-get-inst-20110802.exe)をインストールした。こっちのほうが便利そうだったので。
□C++Compiler
□MSYS Basic System
□MinGW Developer ToolKit
↑にチェックしてインストール。本当に何が必要なのかはわかってない。
msys.batを起動すると、ホームディレクトリが作られるのでそこで作業することにする。(環境変数のHOMEが設定してあれば、そこ。)
必要なライブラリ等をダウンロード
KyotoCabinet (http://fallabs.com/kyotocabinet/)
・kyotocabinet-1.2.70.tar.gz
・kyotocabinet-ruby-1.27.tar.gz
zlib
・http://sourceforge.jp/projects/sfnet_libpng/downloads/zlib/1.2.5/zlib125.zip/
regex
・http://sourceforge.net/projects/mingw/files/UserContributed/regex/mingw-regex-2.5.1/mingw-libgnurx-2.5.1-src.tar.gz/
2. Kyoto Cabinet のビルド・インストール
この辺のことはちょっとわからなかったので、何度もgoogleで検索して何とかビルドできた。
configure.in を編集
pthreadは使わないみたいなので、configure.in の該当部分をコメントアウトして、autoconf → configure再作成。
245|#AC_CHECK_LIB(pthread, main) 270|#AC_CHECK_HEADER(pthread.h, true, AC_MSG_ERROR([pthread.h is required]))
libstdc++.aをコピー
libstdc++.dll.aをリンクしないように、libstdc++.aをもってくる。本当はどうするのがよいのかわからなかった。
_WIN32_WINNT=0x0500の定義も必要。
ochuki@aho ~/kyotocabinet-1.2.70 $ autoconf $ cp /mingw/lib/gcc/mingw32/4.5.2/libstdc++.a libstdc++.a $ export CPPFLAGS=-D_WIN32_WINNT=0x0500 $ ./configure --prefix=/mingw --enable-static $ make $ make install
3. KyotoCabinet の ruby拡張をビルド・インストール
1.8と1.9で何か違うようだが、深く考えないことにする。
kyotocabinet.cc の修正
参考サイトにあるように、修正する。
extconf.rb の修正
extconf.rb の $LDFLAGS に -static-libgcc を追加。何か変なのがリンクされちゃったので。
$LDFLAGS = "#{$LDFLAGS} -L. #{kcldflags} -static-libgcc"
libstdc++.aをコピー
libstdc++.dll.aをリンクしないように、libstdc++.aをもってくる。わからないので、ここでもコピーして対処した。
まずは1.9から
ochuki@aho ~/kyotocabinet-ruby-1.27 $ cp /mingw/lib/gcc/mingw32/4.5.2/libstdc++.a libstdc++.a $ ruby -v ruby 1.9.2p290 (2011-07-09) [i386-mingw32] $ ruby extconf.rb $ make $ make install $ ruby test.rb 105 tests were all ok
大丈夫そうな予感。
続いて1.8。パスが通ってない。
ochuki@aho ~/kyotocabinet-ruby-1.27 $ c:/dev/lang/ruby187/bin/ruby -v ruby 1.8.7 (2011-06-30 patchlevel 352) [i386-mingw32] $ c:/dev/lang/ruby187/bin/ruby extconf.rb $ make $ make install $ c:/dev/lang/ruby187/bin/ruby test.rb 28/105 tests failed
テストの失敗は、スレッドの関係ですかね。
まとめ
結局の所、rubyしか使えない私にはよくわからない世界でした。
あいうえお
柿九毛子