igo-ruby 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +62 -13
- data/VERSION +1 -1
- data/lib/igo-ruby.rb +3 -2
- data/lib/igo/dictionary.rb +147 -141
- data/lib/igo/tagger.rb +1 -0
- data/lib/igo/trie.rb +162 -153
- data/lib/igo/util.rb +82 -79
- data/lib/igo/version.rb +17 -0
- data/test/test.rb +9 -2
- metadata +22 -49
data/README.rdoc
CHANGED
@@ -1,19 +1,68 @@
|
|
1
1
|
= igo-ruby
|
2
|
+
igo-rubyはJavaおよびCommon Lispで実装された形態素解析器 Igo[http://igo.sourceforge.jp] のRuby実装です。
|
2
3
|
|
3
|
-
|
4
|
+
igo-rubyでは、 Igo[http://igo.sourceforge.jp] と同一の解析用辞書ファイルを使用します。
|
5
|
+
従って Igo[http://igo.sourceforge.jp] の機能を使用して解析用辞書ファイルを生成する必要があります。
|
4
6
|
|
5
|
-
==
|
6
|
-
|
7
|
-
|
8
|
-
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
9
|
-
* Fork the project
|
10
|
-
* Start a feature/bugfix branch
|
11
|
-
* Commit and push until you are happy with your contribution
|
12
|
-
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
13
|
-
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
7
|
+
== インストール方法
|
8
|
+
コマンドプロンプトより以下を実行してください。
|
9
|
+
$ gem install igo-ruby
|
14
10
|
|
15
|
-
==
|
11
|
+
== 解析用辞書ファイルの生成
|
12
|
+
{Igoのインストール/使い方}[http://igo.sourceforge.jp/index.html#usage] を参照してください。
|
13
|
+
|
14
|
+
== サンプル
|
15
|
+
=== 形態素解析
|
16
|
+
require 'rubygems'
|
17
|
+
require 'igo-ruby'
|
18
|
+
tagger = Igo::Tagger.new('../../ipadic') # 解析用辞書のディレクトリを指定
|
19
|
+
|
20
|
+
t = tagger.parse('吾輩は猫である。名前はまだ無い。')
|
21
|
+
t.each{|m|
|
22
|
+
puts "#{m.surface} #{m.feature} #{m.start}"
|
23
|
+
}
|
24
|
+
|
25
|
+
# 実行結果
|
26
|
+
吾輩 名詞,代名詞,一般,*,*,*,吾輩,ワガハイ,ワガハイ 0
|
27
|
+
は 助詞,係助詞,*,*,*,*,は,ハ,ワ 2
|
28
|
+
猫 名詞,一般,*,*,*,*,猫,ネコ,ネコ 3
|
29
|
+
で 助動詞,*,*,*,特殊・ダ,連用形,だ,デ,デ 4
|
30
|
+
ある 助動詞,*,*,*,五段・ラ行アル,基本形,ある,アル,アル 5
|
31
|
+
。 記号,句点,*,*,*,*,。,。,。 7
|
32
|
+
名前 名詞,一般,*,*,*,*,名前,ナマエ,ナマエ 8
|
33
|
+
は 助詞,係助詞,*,*,*,*,は,ハ,ワ 10
|
34
|
+
まだ 副詞,助詞類接続,*,*,*,*,まだ,マダ,マダ 11
|
35
|
+
無い 形容詞,自立,*,*,形容詞・アウオ段,基本形,無い,ナイ,ナイ 13
|
36
|
+
。 記号,句点,*,*,*,*,。,。,。 15
|
37
|
+
|
38
|
+
=== 分かち書き
|
39
|
+
require 'rubygems'
|
40
|
+
require 'igo-ruby'
|
41
|
+
|
42
|
+
tagger = Igo::Tagger.new('../../ipadic') # 解析用辞書のディレクトリを指定
|
43
|
+
t = tagger.wakati('どこで生れたかとんと見当がつかぬ。')
|
44
|
+
puts t.join(' ')
|
45
|
+
|
46
|
+
# 実行結果
|
47
|
+
どこ で 生れ た か とんと 見当 が つか ぬ 。
|
16
48
|
|
17
|
-
|
18
|
-
|
49
|
+
=== ウェブアプリ例
|
50
|
+
* {igo-ruby.heroku.com}[http://igo-ruby.heroku.com/]
|
19
51
|
|
52
|
+
== 付録
|
53
|
+
=== 公開場所
|
54
|
+
* RubyGems
|
55
|
+
* igo-ruby[https://rubygems.org/gems/igo-ruby]
|
56
|
+
* ソース(github)
|
57
|
+
* {kyow/igo-ruby}[https://github.com/kyow/igo-ruby]
|
58
|
+
|
59
|
+
=== 参照
|
60
|
+
* Igo
|
61
|
+
1. {Igo - Java形態素解析器}[http://igo.sourceforge.jp/index.html]
|
62
|
+
2. {Igo}[http://sourceforge.jp/projects/igo/releases/]
|
63
|
+
* Igo-python
|
64
|
+
1. {igo-python 0.3a}[http://pypi.python.org/pypi/igo-python/0.3a]
|
65
|
+
2. {Igo Japanease morphological analyzer for python}[https://launchpad.net/igo-python/]
|
66
|
+
|
67
|
+
== Copyright
|
68
|
+
Copyright (c) kyow, 2010. See LICENSE.txt for further details.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.3
|
data/lib/igo-ruby.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
+
# coding: utf-8
|
1
2
|
#
|
2
3
|
#= 形態素解析エンジンIgoのRuby実装
|
3
4
|
#解析結果がほぼMeCab互換の形態素解析エンジン"Igo"のRuby実装
|
4
5
|
#
|
5
|
-
#Copyright:: Copyright (
|
6
|
+
#Copyright:: Copyright (c) kyow, 2010
|
6
7
|
#Authors:: K.Nishi
|
7
8
|
#License:: MIT License ただし、使用する辞書のライセンスに関しては、辞書配布元のそれに準ずる
|
8
9
|
#
|
@@ -28,7 +29,6 @@
|
|
28
29
|
$:.unshift(File.dirname(__FILE__))
|
29
30
|
|
30
31
|
require 'nkf'
|
31
|
-
require 'jcode'
|
32
32
|
require 'kconv'
|
33
33
|
|
34
34
|
#
|
@@ -36,4 +36,5 @@ require 'kconv'
|
|
36
36
|
#
|
37
37
|
module Igo
|
38
38
|
autoload :Tagger, 'igo/tagger'
|
39
|
+
autoload :Version, 'igo/version'
|
39
40
|
end
|
data/lib/igo/dictionary.rb
CHANGED
@@ -1,179 +1,185 @@
|
|
1
|
-
|
1
|
+
# coding: utf-8
|
2
|
+
#= 辞書クラス群
|
2
3
|
|
3
|
-
|
4
|
-
#
|
5
|
-
#
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
4
|
+
module Igo
|
5
|
+
#
|
6
|
+
# Viterbiアルゴリズムで使用されるノードクラス
|
7
|
+
#
|
8
|
+
class ViterbiNode
|
9
|
+
attr_accessor :cost, :prev, :word_id, :start, :length, :left_id, :right_id, :is_space
|
10
|
+
def initialize(word_id, start, length, left_id, right_id, is_space)
|
11
|
+
@cost = 0 # 始点からノードまでの総コスト
|
12
|
+
@prev = nil # コスト最小の前方のノードへのリンク
|
13
|
+
@word_id = word_id # 単語ID
|
14
|
+
@start = start # 入力テキスト内での形態素の開始位置
|
15
|
+
@length = length # 形態素の表層形の長さ(文字数)
|
16
|
+
@left_id = left_id # 左文脈ID
|
17
|
+
@right_id = right_id # 右文脈ID
|
18
|
+
@is_space = is_space # 形態素の文字種(文字カテゴリ)が空白かどうか
|
19
|
+
end
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
+
def self.make_BOSEOS
|
22
|
+
return ViterbiNode.new(0, 0, 0, 0, 0, false)
|
23
|
+
end
|
21
24
|
end
|
22
|
-
end
|
23
25
|
|
24
|
-
class CharCategory
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
26
|
+
class CharCategory
|
27
|
+
def initialize(data_dir)
|
28
|
+
@categories = CharCategory.read_categories(data_dir)
|
29
|
+
fmis = FileMappedInputStream.new(data_dir + "/code2category")
|
30
|
+
@char2id = fmis.get_int_array(fmis.size / 4 / 2)
|
31
|
+
@eql_masks = fmis.get_int_array(fmis.size / 4 /2)
|
32
|
+
fmis.close
|
33
|
+
end
|
32
34
|
|
33
|
-
|
34
|
-
|
35
|
-
|
35
|
+
def category(code)
|
36
|
+
return @categories[@char2id[code]]
|
37
|
+
end
|
36
38
|
|
37
|
-
|
38
|
-
|
39
|
-
|
39
|
+
def compatible?(code1, code2)
|
40
|
+
return (@eql_masks[code1] & @eql_masks[code2]) != 0
|
41
|
+
end
|
40
42
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
43
|
+
def self.read_categories(data_dir)
|
44
|
+
data = FileMappedInputStream::get_int_array(data_dir + "/char.category")
|
45
|
+
size = data.size / 4
|
46
|
+
ary = []
|
47
|
+
for i in 0 .. (size - 1)
|
48
|
+
ary.push(Category.new(data[i * 4], data[i * 4 + 1], data[i * 4 + 2] == 1, data[i * 4 + 3] == 1))
|
49
|
+
end
|
50
|
+
return ary
|
51
|
+
end
|
49
52
|
end
|
50
|
-
end
|
51
53
|
|
52
|
-
class Category
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
54
|
+
class Category
|
55
|
+
attr_reader :id, :length, :invoke, :group
|
56
|
+
def initialize(i, l, iv, g)
|
57
|
+
@id = i
|
58
|
+
@length = l
|
59
|
+
@invoke = iv
|
60
|
+
@group = g
|
61
|
+
end
|
59
62
|
end
|
60
|
-
end
|
61
63
|
|
62
|
-
#
|
63
|
-
# 形態素の連接コスト表クラス
|
64
|
-
#
|
65
|
-
class Matrix
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
64
|
+
#
|
65
|
+
# 形態素の連接コスト表クラス
|
66
|
+
#
|
67
|
+
class Matrix
|
68
|
+
# コンストラクタ
|
69
|
+
# data_dir:: 辞書ファイルのディレクトリパス
|
70
|
+
def initialize(data_dir)
|
71
|
+
fmis = FileMappedInputStream.new(data_dir + "/matrix.bin")
|
72
|
+
@left_size = fmis.get_int
|
73
|
+
@right_size = fmis.get_int
|
74
|
+
@matrix = fmis.get_short_array(@left_size * @right_size)
|
75
|
+
fmis.close
|
76
|
+
end
|
75
77
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
78
|
+
# 形態素同士の連接コストを求める
|
79
|
+
# left_id:: 左文脈ID
|
80
|
+
# right_id:: 右文脈ID
|
81
|
+
def link_cost(left_id, right_id)
|
82
|
+
return @matrix[right_id * @right_size + left_id]
|
83
|
+
end
|
81
84
|
end
|
82
|
-
end
|
83
85
|
|
84
|
-
#
|
85
|
-
# 未知語の検索を行うクラス
|
86
|
-
#
|
87
|
-
class Unknown
|
86
|
+
#
|
87
|
+
# 未知語の検索を行うクラス
|
88
|
+
#
|
89
|
+
class Unknown
|
88
90
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
91
|
+
# コンストラクタ
|
92
|
+
#data_dir:: 辞書ファイルのディレクトリパス
|
93
|
+
def initialize(data_dir)
|
94
|
+
# 文字カテゴリ管理クラス
|
95
|
+
@category = CharCategory.new(data_dir)
|
94
96
|
|
95
|
-
|
96
|
-
|
97
|
-
|
97
|
+
# 文字カテゴリが空白の文字のID
|
98
|
+
@space_id = @category.category(' '.unpack("U*")[0]).id
|
99
|
+
end
|
98
100
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
101
|
+
# 検索
|
102
|
+
#text::
|
103
|
+
#start::
|
104
|
+
#wdic::
|
105
|
+
#result::
|
106
|
+
def search(text, start, wdic, result)
|
107
|
+
txt = text.unpack("U*")
|
108
|
+
length = txt.size
|
109
|
+
ch = txt[start]
|
110
|
+
ct = @category.category(ch)
|
105
111
|
|
106
|
-
|
107
|
-
|
108
|
-
|
112
|
+
if !result.empty? and !ct.invoke
|
113
|
+
return
|
114
|
+
end
|
109
115
|
|
110
|
-
|
111
|
-
|
116
|
+
is_space = (ct.id == @space_id)
|
117
|
+
limit = [length, ct.length + start].min
|
112
118
|
|
113
|
-
|
114
|
-
|
119
|
+
for i in start..(limit - 1)
|
120
|
+
wdic.search_from_trie_id(ct.id, start, (i - start) + 1, is_space, result)
|
115
121
|
|
116
|
-
|
117
|
-
|
122
|
+
if((i + 1) != limit and !(@category.compatible?(ch, text[i + 1])))
|
123
|
+
return
|
124
|
+
end
|
118
125
|
end
|
119
|
-
end
|
120
126
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
127
|
+
if ct.group and limit < length
|
128
|
+
for i in limit..(length - 1)
|
129
|
+
if not @category.compatible?(ch, txt[i])
|
130
|
+
wdic.search_from_trie_id(ct.id, start, i - start, is_space, result)
|
131
|
+
return
|
132
|
+
end
|
126
133
|
end
|
134
|
+
wdic.search_from_trie_id(ct.id, start, length - start, is_space, result)
|
127
135
|
end
|
128
|
-
wdic.search_from_trie_id(ct.id, start, length - start, is_space, result)
|
129
136
|
end
|
130
137
|
end
|
131
|
-
end
|
132
138
|
|
133
|
-
class WordDic
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
139
|
+
class WordDic
|
140
|
+
# コンストラクタ
|
141
|
+
#data_dir:: 辞書ファイルのディレクトリパス
|
142
|
+
def initialize(data_dir)
|
143
|
+
@trie = Searcher.new(data_dir + "/word2id")
|
144
|
+
@data = FileMappedInputStream.get_string(data_dir + "/word.dat")
|
145
|
+
@indices = FileMappedInputStream.get_int_array(data_dir + "/word.ary.idx")
|
140
146
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
147
|
+
fmis = FileMappedInputStream.new(data_dir + "/word.inf")
|
148
|
+
word_count = fmis.size / (4 + 2 + 2 + 2)
|
149
|
+
@data_offsets = fmis.get_int_array(word_count) # 単語の素性データの開始位置
|
150
|
+
@left_ids = fmis.get_short_array(word_count) # 単語の左文脈ID
|
151
|
+
@right_ids = fmis.get_short_array(word_count) # 単語の右文脈ID
|
152
|
+
@costs = fmis.get_short_array(word_count) # 単語のコスト
|
153
|
+
fmis.close
|
154
|
+
end
|
149
155
|
|
150
|
-
|
151
|
-
|
152
|
-
|
156
|
+
def cost(word_id)
|
157
|
+
return @costs[word_id]
|
158
|
+
end
|
153
159
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
160
|
+
def search(text, start, result)
|
161
|
+
indices = @indices
|
162
|
+
left_ids = @left_ids
|
163
|
+
right_ids = @right_ids
|
158
164
|
|
159
|
-
|
160
|
-
|
165
|
+
@trie.each_common_prefix(text, start, Proc.new { |start, offset, trie_id|
|
166
|
+
ed = @indices[trie_id + 1]
|
161
167
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
168
|
+
for i in indices[trie_id]..(ed - 1)
|
169
|
+
result.push(ViterbiNode.new(i, start, offset, @left_ids[i], right_ids[i], false))
|
170
|
+
end
|
171
|
+
})
|
172
|
+
end
|
167
173
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
174
|
+
def search_from_trie_id(trie_id, start, word_length, is_space, result)
|
175
|
+
ed = @indices[trie_id + 1]
|
176
|
+
for i in @indices[trie_id]..(ed - 1)
|
177
|
+
result.push(ViterbiNode.new(i, start, word_length, @left_ids[i], @right_ids[i], is_space))
|
178
|
+
end
|
172
179
|
end
|
173
|
-
end
|
174
180
|
|
175
|
-
|
176
|
-
|
181
|
+
def word_data(word_id)
|
182
|
+
return @data.slice(@data_offsets[word_id]*2..@data_offsets[word_id + 1]*2 - 1)
|
183
|
+
end
|
177
184
|
end
|
178
185
|
end
|
179
|
-
|
data/lib/igo/tagger.rb
CHANGED
data/lib/igo/trie.rb
CHANGED
@@ -1,201 +1,210 @@
|
|
1
|
+
# coding: utf-8
|
1
2
|
require 'igo/util'
|
2
3
|
|
4
|
+
#
|
5
|
+
#Stringクラスの拡張
|
6
|
+
#
|
3
7
|
class String
|
8
|
+
# 文字列がパラメタの接頭辞で開始するかどうかを返却する
|
9
|
+
#prefix:: 接頭辞
|
10
|
+
#return:: true - 接頭辞で開始する
|
4
11
|
def starts_with?(prefix)
|
5
12
|
prefix = prefix.to_s
|
6
13
|
self[0, prefix.length] == prefix
|
7
14
|
end
|
8
15
|
end
|
9
16
|
|
10
|
-
|
11
|
-
|
12
|
-
#
|
13
|
-
class Node
|
17
|
+
module Igo
|
18
|
+
|
14
19
|
#
|
15
|
-
#
|
20
|
+
#DoubleArrayのノード用の定数などが定義されているクラス
|
16
21
|
#
|
17
|
-
class
|
18
|
-
#
|
19
|
-
|
20
|
-
|
22
|
+
class Node
|
23
|
+
#
|
24
|
+
#BASEノード用のメソッドが定義されているクラス
|
25
|
+
#
|
26
|
+
class Base
|
27
|
+
#BASEノードに格納するID値をエンコードする
|
28
|
+
def self.ids(nid)
|
29
|
+
return (-1 * nid) - 1
|
30
|
+
end
|
21
31
|
end
|
22
|
-
end
|
23
32
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
33
|
+
#
|
34
|
+
#CHECKノード用の定数が定義されているクラス
|
35
|
+
#
|
36
|
+
class Chck
|
37
|
+
#文字列の終端文字コード
|
38
|
+
#この文字はシステムにより予約されており、辞書内の形態素の表層形および解析対象テキストに含まれていた場合の動作は未定義
|
39
|
+
TERMINATE_CODE = 0
|
40
|
+
#文字列の終端を表す文字定数
|
41
|
+
TERMINATE_CHAR = TERMINATE_CODE.chr
|
42
|
+
#CHECKノードが未使用であることを示す文字コード
|
43
|
+
#この文字はシステムにより予約されており、辞書内の形態素の表層形および解析対象テキストに含まれていた場合の動作は未定義
|
44
|
+
VACANT_CODE = 1
|
45
|
+
#使用可能な文字の最大値
|
46
|
+
CODE_LIMIT = 0xffff
|
47
|
+
end
|
38
48
|
end
|
39
|
-
end
|
40
49
|
|
41
|
-
#
|
42
|
-
#文字列を文字のストリームとして扱うためのクラス
|
43
|
-
#* readメソッドで個々の文字を順に読み込み、文字列の終端に達した場合にはNode::Chck::TERMINATE_CODEが返される。
|
44
|
-
#
|
45
|
-
class KeyStream
|
46
|
-
|
47
|
-
def initialize(key, start = 0)
|
48
|
-
@s = key
|
49
|
-
@cur = start
|
50
|
-
@len = key.unpack("U*").size
|
51
|
-
end
|
52
|
-
|
53
|
-
def compare_to(ks)
|
54
|
-
return rest.compare_to(ks.rest)
|
55
|
-
end
|
50
|
+
#
|
51
|
+
#文字列を文字のストリームとして扱うためのクラス
|
52
|
+
#* readメソッドで個々の文字を順に読み込み、文字列の終端に達した場合にはNode::Chck::TERMINATE_CODEが返される。
|
53
|
+
#
|
54
|
+
class KeyStream
|
56
55
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
s = @s
|
62
|
-
c = @cur
|
63
|
-
if @len - c < len
|
64
|
-
return false
|
56
|
+
def initialize(key, start = 0)
|
57
|
+
@s = key
|
58
|
+
@cur = start
|
59
|
+
@len = key.unpack("U*").size
|
65
60
|
end
|
66
|
-
|
67
|
-
|
68
|
-
return (
|
69
|
-
else
|
70
|
-
[word].pack("U*").starts_with?(prefix.slice(beg, len-beg))
|
61
|
+
|
62
|
+
def compare_to(ks)
|
63
|
+
return rest.compare_to(ks.rest)
|
71
64
|
end
|
72
|
-
end
|
73
65
|
|
74
|
-
|
75
|
-
|
76
|
-
|
66
|
+
#このメソッドは動作的には、rest().starts_with?(prefix.substring(beg, len))と等価。
|
67
|
+
#ほんの若干だが、パフォーマンスを改善するために導入。
|
68
|
+
#簡潔性のためになくしても良いかもしれない。
|
69
|
+
def start_with(prefix, beg, len)
|
70
|
+
s = @s
|
71
|
+
c = @cur
|
72
|
+
if @len - c < len
|
73
|
+
return false
|
74
|
+
end
|
75
|
+
word = s.unpack("U*")[c]
|
76
|
+
if word.nil?
|
77
|
+
return (prefix.slice(beg, len-beg) == nil)
|
78
|
+
else
|
79
|
+
[word].pack("U*").starts_with?(prefix.slice(beg, len-beg))
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def rest
|
84
|
+
return @s.slice(@cur, @s.length)
|
85
|
+
end
|
77
86
|
|
78
|
-
|
87
|
+
def read
|
79
88
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
89
|
+
if eos?
|
90
|
+
return Node::Chck::TERMINATE_CODE
|
91
|
+
else
|
92
|
+
r = @s.unpack("U*")[@cur]
|
93
|
+
result = [r].pack("U*")
|
94
|
+
@cur += 1
|
95
|
+
return r
|
96
|
+
end
|
87
97
|
end
|
88
|
-
end
|
89
98
|
|
90
|
-
|
91
|
-
|
99
|
+
def eos?
|
100
|
+
return (@cur == @len) ? true : false
|
101
|
+
end
|
92
102
|
end
|
93
|
-
end
|
94
103
|
|
95
|
-
#
|
96
|
-
# DoubleArray検索用のクラス
|
97
|
-
#
|
98
|
-
class Searcher
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
104
|
+
#
|
105
|
+
# DoubleArray検索用のクラス
|
106
|
+
#
|
107
|
+
class Searcher
|
108
|
+
#保存されているDoubleArrayを読み込んで、このクラスのインスタンスを作成する
|
109
|
+
#path:: DoubleArrayが保存されているファイルのパス
|
110
|
+
def initialize(path)
|
111
|
+
fmis = FileMappedInputStream.new(path)
|
112
|
+
node_size = fmis.get_int()
|
113
|
+
tind_size = fmis.get_int()
|
114
|
+
tail_size = fmis.get_int()
|
115
|
+
@key_set_size = tind_size
|
116
|
+
@begs = fmis.get_int_array(tind_size)
|
117
|
+
@base = fmis.get_int_array(node_size)
|
118
|
+
@lens = fmis.get_short_array(tind_size)
|
119
|
+
@chck = fmis.get_char_array(node_size)
|
120
|
+
@tail = fmis.get_string(tail_size)
|
121
|
+
fmis.close
|
122
|
+
end
|
114
123
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
124
|
+
#DoubleArrayに格納されているキーの数を返却
|
125
|
+
#return:: DoubleArrayに格納されているキーの数
|
126
|
+
def size
|
127
|
+
return @key_set_size
|
128
|
+
end
|
120
129
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
130
|
+
#キーを検索する
|
131
|
+
#key:: 検索対象のキー文字列
|
132
|
+
#return:: キーが見つかった場合はそのIDを、見つからなかった場合は-1を返す
|
133
|
+
def search(key)
|
134
|
+
base = @base
|
135
|
+
chck = @chck
|
136
|
+
node = @base[0]
|
137
|
+
kin = KeyStream.new(key)
|
129
138
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
139
|
+
while true
|
140
|
+
code = kin.read
|
141
|
+
idx = node + code
|
142
|
+
node = base[idx]
|
134
143
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
144
|
+
if(chck[idx] == code)
|
145
|
+
if(node >= 0)
|
146
|
+
next
|
147
|
+
elsif(kin.eos? or key_exists?(kin, node))
|
148
|
+
return Node::Base.ids(node)
|
149
|
+
end
|
150
|
+
return -1
|
140
151
|
end
|
141
|
-
return -1
|
142
152
|
end
|
143
153
|
end
|
144
|
-
end
|
145
154
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
155
|
+
#common-prefix検索を行う
|
156
|
+
#* 条件に一致するキーが見つかる度に、callback.callメソッドが呼び出される
|
157
|
+
#key:: 検索対象のキー文字列
|
158
|
+
#start:: 検索対象となるキー文字列の最初の添字
|
159
|
+
#callback:: 一致を検出した場合に呼び出されるコールバックメソッド
|
160
|
+
def each_common_prefix(key, start, callback)
|
161
|
+
base = @base
|
162
|
+
chck = @chck
|
163
|
+
node = @base[0]
|
164
|
+
offset = -1
|
165
|
+
kin = KeyStream.new(key, start)
|
157
166
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
167
|
+
while true
|
168
|
+
code = kin.read
|
169
|
+
offset += 1
|
170
|
+
terminal_index = node
|
162
171
|
|
163
|
-
|
164
|
-
|
172
|
+
if(chck[terminal_index] == Node::Chck::TERMINATE_CODE)
|
173
|
+
callback.call(start, offset, Node::Base.ids(base[terminal_index]))
|
165
174
|
|
166
|
-
|
167
|
-
|
175
|
+
if(code == Node::Chck::TERMINATE_CODE)
|
176
|
+
return
|
177
|
+
end
|
168
178
|
end
|
169
|
-
end
|
170
179
|
|
171
|
-
|
172
|
-
|
180
|
+
idx = node + code
|
181
|
+
node = base[idx]
|
173
182
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
183
|
+
if(chck[idx] == code)
|
184
|
+
if(node >= 0)
|
185
|
+
next
|
186
|
+
else
|
187
|
+
call_if_key_including(kin, node, start, offset, callback)
|
188
|
+
end
|
179
189
|
end
|
190
|
+
return
|
180
191
|
end
|
181
|
-
return
|
182
192
|
end
|
183
|
-
end
|
184
193
|
|
185
|
-
|
194
|
+
private
|
186
195
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
196
|
+
def call_if_key_including(kin, node, start, offset, callback)
|
197
|
+
node_id = Node::Base.ids(node)
|
198
|
+
if(kin.start_with(@tail, @begs[node_id], @lens[node_id]))
|
199
|
+
callback.call(start, offset + @lens[node_id] + 1, node_id)
|
200
|
+
end
|
191
201
|
end
|
192
|
-
end
|
193
202
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
203
|
+
def key_exists?(kin, node)
|
204
|
+
nid = Node.Base.ids(node)
|
205
|
+
beg = @begs[nid]
|
206
|
+
s = @tail.slice(beg, beg + @lens[nid])
|
207
|
+
return kin.rest == s ? true : false
|
208
|
+
end
|
199
209
|
end
|
200
210
|
end
|
201
|
-
|
data/lib/igo/util.rb
CHANGED
@@ -1,93 +1,96 @@
|
|
1
|
-
#
|
1
|
+
# coding: utf-8
|
2
|
+
#= ファイルユーティリティ
|
2
3
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
#
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
4
|
+
module Igo
|
5
|
+
#
|
6
|
+
#=== ファイルにマッピングされた入力ストリーム
|
7
|
+
# ファイルからバイナリデータを取得する場合、必ずこのクラスが使用される。
|
8
|
+
#
|
9
|
+
class FileMappedInputStream
|
10
|
+
# 入力ストリームの初期化
|
11
|
+
# path:: 入力ファイルのパス
|
12
|
+
def initialize(path)
|
13
|
+
@path = path
|
14
|
+
@cur = 0
|
15
|
+
@file = open(path, "rb")
|
16
|
+
end
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
# int値で読み取り
|
19
|
+
def get_int()
|
20
|
+
return @file.read(4).unpack("i*")[0]
|
21
|
+
end
|
22
|
+
|
23
|
+
# int配列で読み取り
|
24
|
+
# count:: 読み取りカウント
|
25
|
+
def get_int_array(count)
|
26
|
+
return @file.read(count * 4).unpack("i*")
|
27
|
+
end
|
20
28
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
29
|
+
# int配列で読み取り
|
30
|
+
# path:: 入力ファイルのパス
|
31
|
+
def self.get_int_array(path)
|
32
|
+
fmis = FileMappedInputStream.new(path)
|
33
|
+
array = fmis.get_int_array((File::stat(path).size)/4)
|
34
|
+
fmis.close
|
35
|
+
return array
|
36
|
+
end
|
26
37
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
fmis.close
|
33
|
-
return array
|
34
|
-
end
|
38
|
+
# short配列で読み取り
|
39
|
+
# count:: 読み取りカウント
|
40
|
+
def get_short_array(count)
|
41
|
+
return @file.read(count * 2).unpack("s*")
|
42
|
+
end
|
35
43
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
44
|
+
# char配列で読み取り
|
45
|
+
# count:: 読み取りカウント
|
46
|
+
def get_char_array(count)
|
47
|
+
return @file.read(count * 2).unpack("S!*")
|
48
|
+
end
|
41
49
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
50
|
+
# stringで読み取り
|
51
|
+
# count:: 読み取りカウント
|
52
|
+
def get_string(count)
|
53
|
+
return @file.read(count * 2)
|
54
|
+
end
|
47
55
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
# stringで読み取り
|
55
|
-
# path:: 入力ファイル
|
56
|
-
def self.get_string(path)
|
57
|
-
fmis = FileMappedInputStream.new(path)
|
58
|
-
str = fmis.get_string((File::stat(path).size)/2)
|
59
|
-
fmis.close
|
56
|
+
# stringで読み取り
|
57
|
+
# path:: 入力ファイル
|
58
|
+
def self.get_string(path)
|
59
|
+
fmis = FileMappedInputStream.new(path)
|
60
|
+
str = fmis.get_string((File::stat(path).size)/2)
|
61
|
+
fmis.close
|
60
62
|
|
61
|
-
|
62
|
-
|
63
|
+
return str
|
64
|
+
end
|
63
65
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
66
|
+
# 入力ファイルのサイズを返却する
|
67
|
+
def size
|
68
|
+
return File::stat(@path).size
|
69
|
+
end
|
68
70
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
71
|
+
# 入力ストリームを閉じる
|
72
|
+
#* newした場合、必ずcloseを呼ぶこと
|
73
|
+
def close
|
74
|
+
@file.close
|
75
|
+
end
|
74
76
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
77
|
+
# char配列で読み取り
|
78
|
+
# path:: 入力ファイル
|
79
|
+
def self.get_char_array(path)
|
80
|
+
fmis = FileMappedInputStream.new(path)
|
81
|
+
array = fmis.get_char_array(fmis.size / 2)
|
82
|
+
fmis.close
|
83
|
+
return array
|
84
|
+
end
|
83
85
|
|
84
|
-
|
86
|
+
private
|
85
87
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
88
|
+
# ファイルマップ
|
89
|
+
#* 現在、不使用
|
90
|
+
def map(size)
|
91
|
+
@file.pos = @cur
|
92
|
+
@cur += size
|
93
|
+
return @file.read(size)
|
94
|
+
end
|
92
95
|
end
|
93
|
-
end
|
96
|
+
end
|
data/lib/igo/version.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module Igo
|
3
|
+
#
|
4
|
+
#バージョンクラス
|
5
|
+
#
|
6
|
+
class Version
|
7
|
+
#igo-rubyのRubyGemsバージョンを出力する
|
8
|
+
def self.igo_ruby
|
9
|
+
version_file = File.dirname(__FILE__) + '/../../VERSION'
|
10
|
+
version = ""
|
11
|
+
open(version_file) { |igo_ruby_version|
|
12
|
+
version = igo_ruby_version.gets
|
13
|
+
}
|
14
|
+
return version
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/test/test.rb
CHANGED
@@ -1,9 +1,16 @@
|
|
1
|
+
# coding: utf-8
|
1
2
|
require 'rubygems'
|
2
3
|
require 'igo-ruby'
|
3
|
-
|
4
|
+
#require File.dirname(__FILE__) + '/../lib/igo-ruby'
|
5
|
+
|
6
|
+
puts "version -> #{Igo::Version.igo_ruby}"
|
7
|
+
|
8
|
+
tagger = Igo::Tagger.new(File.dirname(__FILE__) + '/../../ipadic')
|
4
9
|
t = tagger.parse('吾輩は猫である。名前はまだ無い。')
|
10
|
+
puts "parse ->"
|
5
11
|
t.each{|m|
|
6
12
|
puts "#{m.surface} #{m.feature} #{m.start}"
|
7
13
|
}
|
14
|
+
puts "wakati ->"
|
8
15
|
t = tagger.wakati('どこで生れたかとんと見当がつかぬ。')
|
9
|
-
puts t.join(' ')
|
16
|
+
puts t.join(' ')
|
metadata
CHANGED
@@ -1,13 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: igo-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 1
|
9
|
-
- 2
|
10
|
-
version: 0.1.2
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.3
|
11
6
|
platform: ruby
|
12
7
|
authors:
|
13
8
|
- K.Nishi
|
@@ -15,71 +10,53 @@ autorequire:
|
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
12
|
|
18
|
-
date:
|
13
|
+
date: 2011-02-10 00:00:00 +09:00
|
19
14
|
default_executable:
|
20
15
|
dependencies:
|
21
16
|
- !ruby/object:Gem::Dependency
|
22
|
-
|
23
|
-
|
17
|
+
name: rspec
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
19
|
none: false
|
25
20
|
requirements:
|
26
21
|
- - ~>
|
27
22
|
- !ruby/object:Gem::Version
|
28
|
-
hash: 11
|
29
|
-
segments:
|
30
|
-
- 2
|
31
|
-
- 1
|
32
|
-
- 0
|
33
23
|
version: 2.1.0
|
34
|
-
name: rspec
|
35
|
-
requirement: *id001
|
36
24
|
type: :development
|
37
|
-
- !ruby/object:Gem::Dependency
|
38
25
|
prerelease: false
|
39
|
-
version_requirements:
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
30
|
none: false
|
41
31
|
requirements:
|
42
32
|
- - ~>
|
43
33
|
- !ruby/object:Gem::Version
|
44
|
-
hash: 23
|
45
|
-
segments:
|
46
|
-
- 1
|
47
|
-
- 0
|
48
|
-
- 0
|
49
34
|
version: 1.0.0
|
50
|
-
name: bundler
|
51
|
-
requirement: *id002
|
52
35
|
type: :development
|
53
|
-
- !ruby/object:Gem::Dependency
|
54
36
|
prerelease: false
|
55
|
-
version_requirements:
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: jeweler
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
56
41
|
none: false
|
57
42
|
requirements:
|
58
43
|
- - ~>
|
59
44
|
- !ruby/object:Gem::Version
|
60
|
-
hash: 1
|
61
|
-
segments:
|
62
|
-
- 1
|
63
|
-
- 5
|
64
|
-
- 1
|
65
45
|
version: 1.5.1
|
66
|
-
name: jeweler
|
67
|
-
requirement: *id003
|
68
46
|
type: :development
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
47
|
prerelease: false
|
71
|
-
version_requirements:
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: rcov
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
72
52
|
none: false
|
73
53
|
requirements:
|
74
54
|
- - ">="
|
75
55
|
- !ruby/object:Gem::Version
|
76
|
-
hash: 3
|
77
|
-
segments:
|
78
|
-
- 0
|
79
56
|
version: "0"
|
80
|
-
name: rcov
|
81
|
-
requirement: *id004
|
82
57
|
type: :development
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: *id004
|
83
60
|
description: "\n Ruby port of Igo Japanese morphological analyzer. Igo-ruby needs Igo's binary dictionary files.\n These files created by Java programs.\n See: http://igo.sourceforge.jp/\n "
|
84
61
|
email: 24signals@gmail.com
|
85
62
|
executables: []
|
@@ -101,6 +78,7 @@ files:
|
|
101
78
|
- lib/igo/tagger.rb
|
102
79
|
- lib/igo/trie.rb
|
103
80
|
- lib/igo/util.rb
|
81
|
+
- lib/igo/version.rb
|
104
82
|
- spec/igo-ruby_spec.rb
|
105
83
|
- spec/spec_helper.rb
|
106
84
|
- test/test.rb
|
@@ -120,7 +98,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
120
98
|
requirements:
|
121
99
|
- - ">="
|
122
100
|
- !ruby/object:Gem::Version
|
123
|
-
hash:
|
101
|
+
hash: 2664330083952194465
|
124
102
|
segments:
|
125
103
|
- 0
|
126
104
|
version: "0"
|
@@ -129,16 +107,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
129
107
|
requirements:
|
130
108
|
- - ">"
|
131
109
|
- !ruby/object:Gem::Version
|
132
|
-
hash: 23
|
133
|
-
segments:
|
134
|
-
- 1
|
135
|
-
- 3
|
136
|
-
- 6
|
137
110
|
version: 1.3.6
|
138
111
|
requirements: []
|
139
112
|
|
140
113
|
rubyforge_project:
|
141
|
-
rubygems_version: 1.
|
114
|
+
rubygems_version: 1.5.0
|
142
115
|
signing_key:
|
143
116
|
specification_version: 3
|
144
117
|
summary: Ruby port of Igo Japanese morphological analyzer.
|