igo-ruby 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Rakefile +8 -1
- data/VERSION +1 -1
- data/lib/igo-ruby.rb +30 -0
- data/lib/igo/dictionary.rb +36 -44
- data/lib/igo/tagger.rb +102 -113
- data/lib/igo/trie.rb +37 -39
- data/lib/igo/util.rb +28 -9
- data/test/test.rb +1 -1
- metadata +9 -7
data/Rakefile
CHANGED
@@ -16,7 +16,11 @@ Jeweler::Tasks.new do |gem|
|
|
16
16
|
gem.homepage = "http://github.com/kyow/igo-ruby"
|
17
17
|
gem.license = "MIT"
|
18
18
|
gem.summary = %Q{Ruby port of Igo Japanese morphological analyzer.}
|
19
|
-
gem.description = %Q{
|
19
|
+
gem.description = %Q{
|
20
|
+
Ruby port of Igo Japanese morphological analyzer. Igo-ruby needs Igo's binary dictionary files.
|
21
|
+
These files created by Java programs.
|
22
|
+
See: http://igo.sourceforge.jp/
|
23
|
+
}
|
20
24
|
gem.email = "24signals@gmail.com"
|
21
25
|
gem.authors = ["K.Nishi"]
|
22
26
|
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
@@ -26,6 +30,7 @@ Jeweler::Tasks.new do |gem|
|
|
26
30
|
|
27
31
|
gem.files = Rake::FileList.new('lib/**/*.rb', '[A-Z]*')
|
28
32
|
gem.required_rubygems_version = ">1.3.6"
|
33
|
+
gem.rdoc_options << '-c UTF-8' << '-S' << '-U'
|
29
34
|
end
|
30
35
|
Jeweler::RubygemsDotOrgTasks.new
|
31
36
|
|
@@ -50,4 +55,6 @@ Rake::RDocTask.new do |rdoc|
|
|
50
55
|
rdoc.title = "igo-ruby #{version}"
|
51
56
|
rdoc.rdoc_files.include('README*')
|
52
57
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
58
|
+
|
59
|
+
rdoc.options << '-c UTF-8' << '-S' << '-U'
|
53
60
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.1
|
data/lib/igo-ruby.rb
CHANGED
@@ -1,9 +1,39 @@
|
|
1
|
+
#
|
2
|
+
#= 形態素解析エンジンIgoのRuby実装
|
3
|
+
#解析結果がほぼMeCab互換の形態素解析エンジン"Igo"のRuby実装
|
4
|
+
#
|
5
|
+
#Copyright:: Copyright (C) K.Nishi, 2010. All rights reserved.
|
6
|
+
#Authors:: K.Nishi
|
7
|
+
#License:: MIT License ただし、使用する辞書のライセンスに関しては、辞書配布元のそれに準ずる
|
8
|
+
#
|
9
|
+
#== 注意
|
10
|
+
#igo-rubyには辞書ファイルを生成する機能はありません。
|
11
|
+
#Igoで生成した辞書ファイルを使用してください。
|
12
|
+
#
|
13
|
+
#== 公開
|
14
|
+
#* RubyGems
|
15
|
+
# * igo-ruby[https://rubygems.org/gems/igo-ruby]
|
16
|
+
#* ソース(github)
|
17
|
+
# * {kyow/igo-ruby}[https://github.com/kyow/igo-ruby]
|
18
|
+
#
|
19
|
+
#== 参照
|
20
|
+
#* Igo
|
21
|
+
# 1. {Igo - Java形態素解析器}[http://igo.sourceforge.jp/index.html]
|
22
|
+
# 2. {Igo}[http://sourceforge.jp/projects/igo/releases/]
|
23
|
+
#* Igo-python
|
24
|
+
# 1. {igo-python 0.3a}[http://pypi.python.org/pypi/igo-python/0.3a]
|
25
|
+
# 2. {Igo Japanease morphological analyzer for python}[https://launchpad.net/igo-python/]
|
26
|
+
#
|
27
|
+
|
1
28
|
$:.unshift(File.dirname(__FILE__))
|
2
29
|
|
3
30
|
require 'nkf'
|
4
31
|
require 'jcode'
|
5
32
|
require 'kconv'
|
6
33
|
|
34
|
+
#
|
35
|
+
#== Igoモジュール
|
36
|
+
#
|
7
37
|
module Igo
|
8
38
|
autoload :Tagger, 'igo/tagger'
|
9
39
|
end
|
data/lib/igo/dictionary.rb
CHANGED
@@ -1,21 +1,19 @@
|
|
1
|
-
|
2
|
-
#require 'util'
|
3
|
-
#require 'nkf'
|
4
|
-
|
5
|
-
# 辞書
|
1
|
+
#辞書クラス群
|
6
2
|
|
3
|
+
#
|
4
|
+
# Viterbiアルゴリズムで使用されるノードクラス
|
5
|
+
#
|
7
6
|
class ViterbiNode
|
8
7
|
attr_accessor :cost, :prev, :word_id, :start, :length, :left_id, :right_id, :is_space
|
9
8
|
def initialize(word_id, start, length, left_id, right_id, is_space)
|
10
|
-
@cost = 0
|
11
|
-
@prev = nil
|
12
|
-
@word_id = word_id
|
13
|
-
@start = start
|
14
|
-
@length = length
|
15
|
-
@left_id = left_id
|
16
|
-
@right_id = right_id
|
17
|
-
@is_space = is_space
|
18
|
-
# puts "==viterbinode #{word_id} #{start} #{length} #{left_id} #{right_id} #{is_space}"
|
9
|
+
@cost = 0 # 始点からノードまでの総コスト
|
10
|
+
@prev = nil # コスト最小の前方のノードへのリンク
|
11
|
+
@word_id = word_id # 単語ID
|
12
|
+
@start = start # 入力テキスト内での形態素の開始位置
|
13
|
+
@length = length # 形態素の表層形の長さ(文字数)
|
14
|
+
@left_id = left_id # 左文脈ID
|
15
|
+
@right_id = right_id # 右文脈ID
|
16
|
+
@is_space = is_space # 形態素の文字種(文字カテゴリ)が空白かどうか
|
19
17
|
end
|
20
18
|
|
21
19
|
def self.make_BOSEOS
|
@@ -37,7 +35,6 @@ class CharCategory
|
|
37
35
|
end
|
38
36
|
|
39
37
|
def compatible?(code1, code2)
|
40
|
-
# puts @eql_masks[code1] & @eql_masks[code2]
|
41
38
|
return (@eql_masks[code1] & @eql_masks[code2]) != 0
|
42
39
|
end
|
43
40
|
|
@@ -59,11 +56,15 @@ class Category
|
|
59
56
|
@length = l
|
60
57
|
@invoke = iv
|
61
58
|
@group = g
|
62
|
-
# puts "==category #{i} #{l} #{iv} #{g}"
|
63
59
|
end
|
64
60
|
end
|
65
61
|
|
62
|
+
#
|
63
|
+
# 形態素の連接コスト表クラス
|
64
|
+
#
|
66
65
|
class Matrix
|
66
|
+
# コンストラクタ
|
67
|
+
# data_dir:: 辞書ファイルのディレクトリパス
|
67
68
|
def initialize(data_dir)
|
68
69
|
fmis = FileMappedInputStream.new(data_dir + "/matrix.bin")
|
69
70
|
@left_size = fmis.get_int
|
@@ -72,40 +73,44 @@ class Matrix
|
|
72
73
|
fmis.close
|
73
74
|
end
|
74
75
|
|
76
|
+
# 形態素同士の連接コストを求める
|
77
|
+
# left_id:: 左文脈ID
|
78
|
+
# right_id:: 右文脈ID
|
75
79
|
def link_cost(left_id, right_id)
|
76
80
|
return @matrix[right_id * @right_size + left_id]
|
77
81
|
end
|
78
82
|
end
|
79
83
|
|
84
|
+
#
|
85
|
+
# 未知語の検索を行うクラス
|
86
|
+
#
|
80
87
|
class Unknown
|
88
|
+
|
89
|
+
# コンストラクタ
|
90
|
+
#data_dir:: 辞書ファイルのディレクトリパス
|
81
91
|
def initialize(data_dir)
|
92
|
+
# 文字カテゴリ管理クラス
|
82
93
|
@category = CharCategory.new(data_dir)
|
94
|
+
|
95
|
+
# 文字カテゴリが空白の文字のID
|
83
96
|
@space_id = @category.category(' '.unpack("U*")[0]).id
|
84
97
|
end
|
85
98
|
|
99
|
+
# 検索
|
86
100
|
def search(text, start, wdic, result)
|
87
101
|
txt = text.unpack("U*")
|
88
102
|
length = txt.size
|
89
103
|
ch = txt[start]
|
90
104
|
ct = @category.category(ch)
|
91
105
|
|
92
|
-
# puts "Unknown.search ch=#{ch} length=#{length} start=#{start}"
|
93
|
-
# p ct
|
94
|
-
# p result
|
95
|
-
# p ct.invoke
|
96
106
|
if !result.empty? and !ct.invoke
|
97
|
-
# puts "result return"
|
98
107
|
return
|
99
108
|
end
|
100
|
-
# puts "---i"
|
101
109
|
|
102
110
|
is_space = (ct.id == @space_id)
|
103
111
|
limit = [length, ct.length + start].min
|
104
112
|
|
105
|
-
# puts "limit = #{limit} #{length} #{ct.length}"
|
106
|
-
|
107
113
|
for i in start..(limit - 1)
|
108
|
-
# puts "[a]"
|
109
114
|
wdic.search_from_trie_id(ct.id, start, (i - start) + 1, is_space, result)
|
110
115
|
|
111
116
|
if((i + 1) != limit and !(@category.compatible?(ch, text[i + 1])))
|
@@ -114,23 +119,20 @@ class Unknown
|
|
114
119
|
end
|
115
120
|
|
116
121
|
if ct.group and limit < length
|
117
|
-
# puts "[b]"
|
118
122
|
for i in limit..(length - 1)
|
119
|
-
# puts "[c] COMPATIBLE? #{ch} #{txt[i + 1]}"
|
120
|
-
|
121
123
|
if not @category.compatible?(ch, txt[i])
|
122
|
-
# puts "[d] #{i} #{start}"
|
123
124
|
wdic.search_from_trie_id(ct.id, start, i - start, is_space, result)
|
124
125
|
return
|
125
126
|
end
|
126
127
|
end
|
127
|
-
# puts "[e] #{length} #{start}"
|
128
128
|
wdic.search_from_trie_id(ct.id, start, length - start, is_space, result)
|
129
129
|
end
|
130
130
|
end
|
131
131
|
end
|
132
132
|
|
133
133
|
class WordDic
|
134
|
+
# コンストラクタ
|
135
|
+
#data_dir:: 辞書ファイルのディレクトリパス
|
134
136
|
def initialize(data_dir)
|
135
137
|
@trie = Searcher.new(data_dir + "/word2id")
|
136
138
|
@data = FileMappedInputStream.get_string(data_dir + "/word.dat")
|
@@ -138,10 +140,10 @@ class WordDic
|
|
138
140
|
|
139
141
|
fmis = FileMappedInputStream.new(data_dir + "/word.inf")
|
140
142
|
word_count = fmis.size / (4 + 2 + 2 + 2)
|
141
|
-
@data_offsets = fmis.get_int_array(word_count)
|
142
|
-
@left_ids = fmis.get_short_array(word_count)
|
143
|
-
@right_ids = fmis.get_short_array(word_count)
|
144
|
-
@costs = fmis.get_short_array(word_count)
|
143
|
+
@data_offsets = fmis.get_int_array(word_count) # 単語の素性データの開始位置
|
144
|
+
@left_ids = fmis.get_short_array(word_count) # 単語の左文脈ID
|
145
|
+
@right_ids = fmis.get_short_array(word_count) # 単語の右文脈ID
|
146
|
+
@costs = fmis.get_short_array(word_count) # 単語のコスト
|
145
147
|
fmis.close
|
146
148
|
end
|
147
149
|
|
@@ -171,17 +173,7 @@ class WordDic
|
|
171
173
|
end
|
172
174
|
|
173
175
|
def word_data(word_id)
|
174
|
-
# s = UTFConverter.utf16to8(@data)
|
175
|
-
|
176
|
-
# st = format("%x", @data_offsets[word_id] * 2)
|
177
|
-
# ed = format("%x", @data_offsets[word_id + 1] * 2)
|
178
|
-
|
179
|
-
# puts "WORD DATA: #{word_id} = #{st} : #{ed}"
|
180
|
-
# p s
|
181
|
-
# puts "nkf= " + NKF.nkf('-W16L0 --utf8', s)
|
182
|
-
# p [s].pack("U*")
|
183
176
|
return @data.slice(@data_offsets[word_id]*2..@data_offsets[word_id + 1]*2 - 1)
|
184
|
-
# return NKF.nkf('-W16L0 --utf8', s)
|
185
177
|
end
|
186
178
|
end
|
187
179
|
|
data/lib/igo/tagger.rb
CHANGED
@@ -1,144 +1,133 @@
|
|
1
|
+
#形態素解析と分かち書きを行う機能の実装
|
2
|
+
|
1
3
|
require 'igo/dictionary'
|
2
4
|
require 'igo/trie'
|
3
5
|
|
4
6
|
module Igo
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
7
|
+
#
|
8
|
+
#形態素クラス
|
9
|
+
#
|
10
|
+
class Morpheme
|
11
|
+
attr_accessor :surface, :feature, :start
|
12
|
+
|
13
|
+
#surface:: 形態素の表層形
|
14
|
+
#feature:: 形態素の素性
|
15
|
+
#start:: テキスト内でも形態素の出現開始位置
|
16
|
+
def initialize(surface, feature, start)
|
17
|
+
@surface = surface
|
18
|
+
@feature = feature
|
19
|
+
@start = start
|
20
|
+
end
|
13
21
|
end
|
14
22
|
|
15
|
-
#
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
23
|
+
#
|
24
|
+
#形態素解析を行うクラス
|
25
|
+
#
|
26
|
+
class Tagger
|
27
|
+
def self.__BOS_NODES
|
28
|
+
return [ViterbiNode.make_BOSEOS]
|
29
|
+
end
|
30
|
+
|
31
|
+
#dir:: 辞書ファイルのディレクトリパス
|
32
|
+
def initialize(dir)
|
33
|
+
@wdc = WordDic.new(dir)
|
34
|
+
@unk = Unknown.new(dir)
|
35
|
+
@mtx = Matrix.new(dir)
|
36
|
+
end
|
37
|
+
|
38
|
+
#形態素解析を行う
|
39
|
+
#text:: 解析対象テキスト
|
40
|
+
#result:: 解析結果の形態素が追加される配列
|
41
|
+
#return:: 解析結果の形態素配列
|
42
|
+
def parse(text, result=[])
|
43
|
+
vn = impl(text, result)
|
44
|
+
txt = text.unpack("U*")
|
45
|
+
while vn
|
46
|
+
surface = txt.slice(vn.start, vn.length).pack("U*")
|
33
47
|
|
34
|
-
|
48
|
+
s = @wdc.word_data(vn.word_id)
|
35
49
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
50
|
+
feature = NKF.nkf('-W16L0 --utf8', s)
|
51
|
+
result.push(Morpheme.new(surface, feature, vn.start))
|
52
|
+
vn = vn.prev
|
53
|
+
end
|
54
|
+
return result
|
41
55
|
end
|
42
|
-
return result
|
43
|
-
end
|
44
56
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
57
|
+
#分かち書きを行う
|
58
|
+
#text:: 分かち書きされるテキスト
|
59
|
+
#result:: 分かち書き結果の文字列が追加される配列
|
60
|
+
#return:: 分かち書き結果の文字列の配列
|
61
|
+
def wakati(text, result=[])
|
62
|
+
vn = impl(text, result)
|
63
|
+
txt = text.unpack("U*")
|
50
64
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
65
|
+
while vn
|
66
|
+
a = txt.slice(vn.start, vn.length).pack("U*")
|
67
|
+
result.push(a)
|
68
|
+
vn = vn.prev
|
69
|
+
end
|
70
|
+
return result
|
56
71
|
end
|
57
|
-
return result
|
58
|
-
end
|
59
72
|
|
60
|
-
|
73
|
+
private
|
61
74
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
# puts "len=#{len}"
|
75
|
+
def impl(text, result=[])
|
76
|
+
txs = text.unpack("U*")
|
77
|
+
len = txs.size
|
67
78
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
79
|
+
node_ary = [Tagger.__BOS_NODES]
|
80
|
+
for i in 0..(len-1)
|
81
|
+
node_ary.push([])
|
82
|
+
end
|
72
83
|
|
73
|
-
|
74
|
-
|
84
|
+
for i in 0..(len-1)
|
85
|
+
per_result = []
|
75
86
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
# puts "---WDC---"
|
81
|
-
# p per_result
|
82
|
-
@unk.search(text, i, @wdc, per_result)
|
83
|
-
# puts "---UNK---"
|
84
|
-
# p per_result
|
85
|
-
prevs = node_ary[i]
|
87
|
+
unless node_ary[i].empty?
|
88
|
+
@wdc.search(text, i, per_result)
|
89
|
+
@unk.search(text, i, @wdc, per_result)
|
90
|
+
prevs = node_ary[i]
|
86
91
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
# node_ary[i + vn.length].push(prevs)
|
95
|
-
else
|
96
|
-
# puts "#{j} is NOT space (#{i + vn.length}) i=#{i} len=#{vn.length}"
|
97
|
-
node_ary[i + vn.length].push(set_min_cost_node(vn, prevs))
|
98
|
-
# p node_ary
|
99
|
-
# node_ary[i + vn.length] + set_min_cost_node(vn, prevs)
|
92
|
+
for j in 0..(per_result.size - 1)
|
93
|
+
vn = per_result[j]
|
94
|
+
if(vn.is_space)
|
95
|
+
node_ary[i + vn.length] = prevs
|
96
|
+
else
|
97
|
+
node_ary[i + vn.length].push(set_min_cost_node(vn, prevs))
|
98
|
+
end
|
100
99
|
end
|
101
|
-
# p node_ary
|
102
100
|
end
|
103
101
|
end
|
104
|
-
end
|
105
102
|
|
106
|
-
|
103
|
+
cur = set_min_cost_node(ViterbiNode.make_BOSEOS, node_ary[len]).prev
|
107
104
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
105
|
+
# reverse
|
106
|
+
head = nil
|
107
|
+
while cur.prev
|
108
|
+
tmp = cur.prev
|
109
|
+
cur.prev = head
|
110
|
+
head = cur
|
111
|
+
cur = tmp
|
112
|
+
end
|
113
|
+
return head
|
115
114
|
end
|
116
|
-
return head
|
117
|
-
|
118
|
-
# return cur.reverse
|
119
|
-
|
120
|
-
end
|
121
115
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
# p f
|
126
|
-
|
127
|
-
vn.cost = f.cost + @mtx.link_cost(f.right_id, vn.left_id)
|
116
|
+
def set_min_cost_node(vn, prevs)
|
117
|
+
f = vn.prev = prevs[0]
|
118
|
+
vn.cost = f.cost + @mtx.link_cost(f.right_id, vn.left_id)
|
128
119
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
vn.prev = p
|
120
|
+
for i in 1..(prevs.size - 1)
|
121
|
+
p = prevs[i]
|
122
|
+
cost = p.cost + @mtx.link_cost(p.right_id, vn.left_id)
|
123
|
+
if(cost < vn.cost)
|
124
|
+
vn.cost = cost
|
125
|
+
vn.prev = p
|
126
|
+
end
|
137
127
|
end
|
128
|
+
vn.cost += @wdc.cost(vn.word_id)
|
129
|
+
return vn
|
138
130
|
end
|
139
|
-
vn.cost += @wdc.cost(vn.word_id)
|
140
|
-
return vn
|
141
131
|
end
|
142
|
-
end
|
143
132
|
|
144
133
|
end
|
data/lib/igo/trie.rb
CHANGED
@@ -7,21 +7,41 @@ class String
|
|
7
7
|
end
|
8
8
|
end
|
9
9
|
|
10
|
+
#
|
11
|
+
#DoubleArrayのノード用の定数などが定義されているクラス
|
12
|
+
#
|
10
13
|
class Node
|
14
|
+
#
|
15
|
+
#BASEノード用のメソッドが定義されているクラス
|
16
|
+
#
|
11
17
|
class Base
|
18
|
+
#BASEノードに格納するID値をエンコードする
|
12
19
|
def self.ids(nid)
|
13
20
|
return (-1 * nid) - 1
|
14
21
|
end
|
15
22
|
end
|
16
23
|
|
24
|
+
#
|
25
|
+
#CHECKノード用の定数が定義されているクラス
|
26
|
+
#
|
17
27
|
class Chck
|
28
|
+
#文字列の終端文字コード
|
29
|
+
#この文字はシステムにより予約されており、辞書内の形態素の表層形および解析対象テキストに含まれていた場合の動作は未定義
|
18
30
|
TERMINATE_CODE = 0
|
31
|
+
#文字列の終端を表す文字定数
|
19
32
|
TERMINATE_CHAR = TERMINATE_CODE.chr
|
33
|
+
#CHECKノードが未使用であることを示す文字コード
|
34
|
+
#この文字はシステムにより予約されており、辞書内の形態素の表層形および解析対象テキストに含まれていた場合の動作は未定義
|
20
35
|
VACANT_CODE = 1
|
36
|
+
#使用可能な文字の最大値
|
21
37
|
CODE_LIMIT = 0xffff
|
22
38
|
end
|
23
39
|
end
|
24
40
|
|
41
|
+
#
|
42
|
+
#文字列を文字のストリームとして扱うためのクラス
|
43
|
+
#* readメソッドで個々の文字を順に読み込み、文字列の終端に達した場合にはNode::Chck::TERMINATE_CODEが返される。
|
44
|
+
#
|
25
45
|
class KeyStream
|
26
46
|
|
27
47
|
def initialize(key, start = 0)
|
@@ -34,22 +54,21 @@ class KeyStream
|
|
34
54
|
return rest.compare_to(ks.rest)
|
35
55
|
end
|
36
56
|
|
57
|
+
#このメソッドは動作的には、rest().starts_with?(prefix.substring(beg, len))と等価。
|
58
|
+
#ほんの若干だが、パフォーマンスを改善するために導入。
|
59
|
+
#簡潔性のためになくしても良いかもしれない。
|
37
60
|
def start_with(prefix, beg, len)
|
38
61
|
s = @s
|
39
62
|
c = @cur
|
40
63
|
if @len - c < len
|
41
64
|
return false
|
42
65
|
end
|
43
|
-
# puts "c = #{c} len = #{len}"
|
44
|
-
# p s.unpack("U*")[c]
|
45
|
-
# p [s.unpack("U*")[c]].pack("U*")
|
46
66
|
word = s.unpack("U*")[c]
|
47
67
|
if word.nil?
|
48
68
|
return (prefix.slice(beg, len-beg) == nil)
|
49
69
|
else
|
50
70
|
[word].pack("U*").starts_with?(prefix.slice(beg, len-beg))
|
51
71
|
end
|
52
|
-
# return [s.unpack("U*")[c]].pack("U*").starts_with?(prefix.slice(beg, len-beg))
|
53
72
|
end
|
54
73
|
|
55
74
|
def rest
|
@@ -57,32 +76,28 @@ class KeyStream
|
|
57
76
|
end
|
58
77
|
|
59
78
|
def read
|
60
|
-
# puts "CUR=#{@cur}"
|
61
79
|
|
62
80
|
if eos?
|
63
|
-
# puts "EOS!!"
|
64
81
|
return Node::Chck::TERMINATE_CODE
|
65
82
|
else
|
66
83
|
r = @s.unpack("U*")[@cur]
|
67
|
-
# puts [r].pack("U*").tosjis
|
68
84
|
result = [r].pack("U*")
|
69
|
-
# result = @s.unpack("U*")[@cur]
|
70
85
|
@cur += 1
|
71
86
|
return r
|
72
|
-
# p = @cur
|
73
|
-
# @cur += 1
|
74
|
-
# return @s[p]
|
75
87
|
end
|
76
88
|
end
|
77
89
|
|
78
90
|
def eos?
|
79
|
-
# puts "eos? #{@cur} == #{@len}"
|
80
91
|
return (@cur == @len) ? true : false
|
81
92
|
end
|
82
93
|
end
|
83
94
|
|
95
|
+
#
|
84
96
|
# DoubleArray検索用のクラス
|
97
|
+
#
|
85
98
|
class Searcher
|
99
|
+
#保存されているDoubleArrayを読み込んで、このクラスのインスタンスを作成する
|
100
|
+
#path:: DoubleArrayが保存されているファイルのパス
|
86
101
|
def initialize(path)
|
87
102
|
fmis = FileMappedInputStream.new(path)
|
88
103
|
node_size = fmis.get_int()
|
@@ -94,20 +109,18 @@ class Searcher
|
|
94
109
|
@lens = fmis.get_short_array(tind_size)
|
95
110
|
@chck = fmis.get_char_array(node_size)
|
96
111
|
@tail = fmis.get_string(tail_size)
|
97
|
-
|
98
|
-
#p @begs[0]
|
99
|
-
#p @base[0]
|
100
|
-
#p @lens[0]
|
101
|
-
#print @tail.tosjis
|
102
|
-
#print @tail[0].tosjis
|
103
|
-
|
104
112
|
fmis.close
|
105
113
|
end
|
106
114
|
|
115
|
+
#DoubleArrayに格納されているキーの数を返却
|
116
|
+
#return:: DoubleArrayに格納されているキーの数
|
107
117
|
def size
|
108
118
|
return @key_set_size
|
109
119
|
end
|
110
120
|
|
121
|
+
#キーを検索する
|
122
|
+
#key:: 検索対象のキー文字列
|
123
|
+
#return:: キーが見つかった場合はそのIDを、見つからなかった場合は-1を返す
|
111
124
|
def search(key)
|
112
125
|
base = @base
|
113
126
|
chck = @chck
|
@@ -130,6 +143,11 @@ class Searcher
|
|
130
143
|
end
|
131
144
|
end
|
132
145
|
|
146
|
+
#common-prefix検索を行う
|
147
|
+
#* 条件に一致するキーが見つかる度に、callback.callメソッドが呼び出される
|
148
|
+
#key:: 検索対象のキー文字列
|
149
|
+
#start:: 検索対象となるキー文字列の最初の添字
|
150
|
+
#callback:: 一致を検出した場合に呼び出されるコールバックメソッド
|
133
151
|
def each_common_prefix(key, start, callback)
|
134
152
|
base = @base
|
135
153
|
chck = @chck
|
@@ -137,48 +155,29 @@ class Searcher
|
|
137
155
|
offset = -1
|
138
156
|
kin = KeyStream.new(key, start)
|
139
157
|
|
140
|
-
# puts "each_common_prefix"
|
141
158
|
while true
|
142
159
|
code = kin.read
|
143
160
|
offset += 1
|
144
161
|
terminal_index = node
|
145
|
-
# terminal_index = node + Node::Chck::TERMINATE_CODE
|
146
|
-
#puts "code #{code.tosjis}"
|
147
162
|
|
148
163
|
if(chck[terminal_index] == Node::Chck::TERMINATE_CODE)
|
149
164
|
callback.call(start, offset, Node::Base.ids(base[terminal_index]))
|
150
165
|
|
151
|
-
# puts "code -> #{code} #{Node::Chck::TERMINATE_CHAR}"
|
152
|
-
|
153
166
|
if(code == Node::Chck::TERMINATE_CODE)
|
154
|
-
# puts code
|
155
|
-
# puts "(1)"
|
156
167
|
return
|
157
168
|
end
|
158
169
|
end
|
159
170
|
|
160
|
-
# TODO
|
161
|
-
#puts "code #{code.tosjis}"
|
162
|
-
# p code
|
163
171
|
idx = node + code
|
164
172
|
node = base[idx]
|
165
173
|
|
166
|
-
# code = [code].pack('U*')
|
167
|
-
|
168
174
|
if(chck[idx] == code)
|
169
175
|
if(node >= 0)
|
170
176
|
next
|
171
177
|
else
|
172
|
-
# id = Node.Base.ids(node)
|
173
|
-
# if(kin.start_with(@tail, @begs[id], lens[id]))
|
174
|
-
# callback.call(start, offset+@lens[id]+1, id)
|
175
|
-
# end
|
176
|
-
|
177
178
|
call_if_key_including(kin, node, start, offset, callback)
|
178
179
|
end
|
179
180
|
end
|
180
|
-
# puts code
|
181
|
-
# puts "(2)"
|
182
181
|
return
|
183
182
|
end
|
184
183
|
end
|
@@ -186,7 +185,6 @@ class Searcher
|
|
186
185
|
private
|
187
186
|
|
188
187
|
def call_if_key_including(kin, node, start, offset, callback)
|
189
|
-
# puts "call_if_key_including"
|
190
188
|
node_id = Node::Base.ids(node)
|
191
189
|
if(kin.start_with(@tail, @begs[node_id], @lens[node_id]))
|
192
190
|
callback.call(start, offset + @lens[node_id] + 1, node_id)
|
data/lib/igo/util.rb
CHANGED
@@ -1,22 +1,31 @@
|
|
1
|
-
#
|
1
|
+
# ファイルユーティリティ
|
2
2
|
|
3
|
+
#
|
4
|
+
#=== ファイルにマッピングされた入力ストリーム
|
5
|
+
# ファイルからバイナリデータを取得する場合、必ずこのクラスが使用される。
|
6
|
+
#
|
3
7
|
class FileMappedInputStream
|
8
|
+
# 入力ストリームの初期化
|
9
|
+
# path:: 入力ファイルのパス
|
4
10
|
def initialize(path)
|
5
11
|
@path = path
|
6
12
|
@cur = 0
|
7
13
|
@file = open(path, "r+b")
|
8
|
-
# @file.binmode
|
9
14
|
end
|
10
15
|
|
16
|
+
# int値で読み取り
|
11
17
|
def get_int()
|
12
18
|
return @file.read(4).unpack("i*")[0]
|
13
19
|
end
|
14
20
|
|
21
|
+
# int配列で読み取り
|
22
|
+
# count:: 読み取りカウント
|
15
23
|
def get_int_array(count)
|
16
|
-
# return map(count * 4).unpack("i*")
|
17
24
|
return @file.read(count * 4).unpack("i*")
|
18
25
|
end
|
19
26
|
|
27
|
+
# int配列で読み取り
|
28
|
+
# path:: 入力ファイルのパス
|
20
29
|
def self.get_int_array(path)
|
21
30
|
fmis = FileMappedInputStream.new(path)
|
22
31
|
array = fmis.get_int_array((File::stat(path).size)/4)
|
@@ -24,22 +33,26 @@ class FileMappedInputStream
|
|
24
33
|
return array
|
25
34
|
end
|
26
35
|
|
36
|
+
# short配列で読み取り
|
37
|
+
# count:: 読み取りカウント
|
27
38
|
def get_short_array(count)
|
28
|
-
# return map(count * 2).unpack("s*")
|
29
39
|
return @file.read(count * 2).unpack("s*")
|
30
40
|
end
|
31
41
|
|
42
|
+
# char配列で読み取り
|
43
|
+
# count:: 読み取りカウント
|
32
44
|
def get_char_array(count)
|
33
|
-
# return map(count * 2).unpack("S!*")
|
34
45
|
return @file.read(count * 2).unpack("S!*")
|
35
46
|
end
|
36
47
|
|
48
|
+
# stringで読み取り
|
49
|
+
# count:: 読み取りカウント
|
37
50
|
def get_string(count)
|
38
|
-
# return map(count * 2)
|
39
|
-
# puts "read count = #{count}"
|
40
51
|
return @file.read(count * 2)
|
41
52
|
end
|
42
53
|
|
54
|
+
# stringで読み取り
|
55
|
+
# path:: 入力ファイル
|
43
56
|
def self.get_string(path)
|
44
57
|
fmis = FileMappedInputStream.new(path)
|
45
58
|
str = fmis.get_string((File::stat(path).size)/2)
|
@@ -48,14 +61,19 @@ class FileMappedInputStream
|
|
48
61
|
return str
|
49
62
|
end
|
50
63
|
|
64
|
+
# 入力ファイルのサイズを返却する
|
51
65
|
def size
|
52
66
|
return File::stat(@path).size
|
53
67
|
end
|
54
68
|
|
69
|
+
# 入力ストリームを閉じる
|
70
|
+
#* newした場合、必ずcloseを呼ぶこと
|
55
71
|
def close
|
56
72
|
@file.close
|
57
73
|
end
|
58
74
|
|
75
|
+
# char配列で読み取り
|
76
|
+
# path:: 入力ファイル
|
59
77
|
def self.get_char_array(path)
|
60
78
|
fmis = FileMappedInputStream.new(path)
|
61
79
|
array = fmis.get_char_array(fmis.size / 2)
|
@@ -65,10 +83,11 @@ class FileMappedInputStream
|
|
65
83
|
|
66
84
|
private
|
67
85
|
|
68
|
-
|
86
|
+
# ファイルマップ
|
87
|
+
#* 現在、不使用
|
88
|
+
def map(size)
|
69
89
|
@file.pos = @cur
|
70
90
|
@cur += size
|
71
91
|
return @file.read(size)
|
72
92
|
end
|
73
93
|
end
|
74
|
-
|
data/test/test.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: igo-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- K.Nishi
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-12-
|
18
|
+
date: 2010-12-12 00:00:00 +09:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -80,7 +80,7 @@ dependencies:
|
|
80
80
|
name: rcov
|
81
81
|
requirement: *id004
|
82
82
|
type: :development
|
83
|
-
description: Ruby port of Igo Japanese morphological analyzer.
|
83
|
+
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
84
|
email: 24signals@gmail.com
|
85
85
|
executables: []
|
86
86
|
|
@@ -109,8 +109,10 @@ homepage: http://github.com/kyow/igo-ruby
|
|
109
109
|
licenses:
|
110
110
|
- MIT
|
111
111
|
post_install_message:
|
112
|
-
rdoc_options:
|
113
|
-
|
112
|
+
rdoc_options:
|
113
|
+
- -c UTF-8
|
114
|
+
- -S
|
115
|
+
- -U
|
114
116
|
require_paths:
|
115
117
|
- lib
|
116
118
|
required_ruby_version: !ruby/object:Gem::Requirement
|