ストローク入力方式ルックアップアルゴリズムの例(Lua実装)
52148 ワード
会社の同僚は最近、グーグルのピンイン入力法のソースコードを利用して自分のピンイン入力法を実現しました.大体の考えは:すべての漢字あるいは1、2級の漢字の筆順のデータベース を探します Luaでデータベースを読み込み、trieツリーを構築 各ノードは、1ストローク を保持する.各ノードは、1つのサブノードセット を有する.各ノードに漢字の集合があり、このレベルになるとすべてのストロークからなる完全な漢字 を表す.
検索時にユーザが入力したストロークに基づいてノードを検索し、サブツリーをストローク順に巡回する サブツリーを巡ると、これらのストロークで始まるすべての漢字を与えることができますが、すぐには表示できないでしょう.だから、反復器が必要です.呼び出すたびに可能な値が与えられます.この反復器はCで実現するのは複雑ですが、Luaで実現するのは小さな意味で、直接サブツリーを巡る関数をcoroutineにカプセル化します.漢字を1つ見つけるたびにyield(漢字)
ペン順データベース
CSDNではhttp://download.csdn.net/detail/yyjlan/3766691にダウンロードできます
ダウンロードしたmdbフォーマットは、私はあまり好きではありません.Luaもあまり好きではありません.luasqlはodbcをサポートするため、mdbファイルをodbcデータソースに追加し、ロードしてsqlite 3のフォーマットに変換することができ、後で使用するのに便利で、変換コードは以下の通りです.
サブツリーの構築と取得
多くは言わないで、直接コードを見ましょう.コードは少し乱れていますが、間に合わせてみると問題ありません.コードを実行するにはLuaForWindowsをインストールする必要があります
パッケージのダウンロード
ソースパッケージとsqlite 3データベースはここでダウンロードできます.
ペン順データベース
CSDNではhttp://download.csdn.net/detail/yyjlan/3766691にダウンロードできます
ダウンロードしたmdbフォーマットは、私はあまり好きではありません.Luaもあまり好きではありません.luasqlはodbcをサポートするため、mdbファイルをodbcデータソースに追加し、ロードしてsqlite 3のフォーマットに変換することができ、後で使用するのに便利で、変換コードは以下の通りです.
1 require "luasql.odbc"
2 require "luasql.sqlite3"
3
4 odbc_env = luasql.odbc()
5
6 -- Access -> -> DSN, hzbs
7 odbc_conn = odbc_env:connect("hzbs")
8 odbc_cur = odbc_conn:execute("SELECT * FROM hzbs;")
9
10 sqlite_env = luasql.sqlite3()
11 sqlite_conn = sqlite_env:connect("hzbs.sqlite3.db")
12 sqlite_conn:execute("CREATE TABLE hzbs (id INTEGER primary key, hanzi TEXT, stroke_number INTEGER, stroke_order TEXT, unicode TEXT, gbk TEXT);")
13 sqlite_conn:setautocommit(false) -- start transaction
14
15 record = {}
16 while odbc_cur:fetch(record, "n") do
17 local id = record[1]
18 local hanzi = record[2]
19 local stroke_number = record[3]
20 local stroke_order = record[4]
21 local unicode = record[5]
22 local gbk = record[6]
23 sqlite_conn:execute("INSERT INTO hzbs(id, hanzi, stroke_number, stroke_order, unicode, gbk) VALUES(" .. id .. ",\'" .. hanzi .. "\'," .. stroke_number .. ",\'" .. stroke_order .. "\',\'" .. unicode .. "\',\'" .. gbk .. "\');")
24 end
25
26 sqlite_conn:commit() -- commit the transaction
27 sqlite_conn:close()
28
29 odbc_cur:close()
30 odbc_conn:close()
31 odbc_env:close()
サブツリーの構築と取得
多くは言わないで、直接コードを見ましょう.コードは少し乱れていますが、間に合わせてみると問題ありません.コードを実行するにはLuaForWindowsをインストールする必要があります
1 require "luasql.sqlite3"
2 require "wx"
3
4
5 function _T(s)
6 return s
7 end
8
9 -- enum stroke_t {
10 local stroke_root = 0 -- for trie root, not a valid stroke
11 local stroke_heng = 1
12 local stroke_shu = 2
13 local stroke_pie = 3
14 local stroke_na = 4
15 local stroke_zhe = 5
16 local stroke_max = 5
17 local stroke_text = {_T" ", _T" ", _T" ", _T" ", _T" "}
18 -- }
19
20 function new_node(stroke)
21 return {stroke=stroke, -- see stroke definition
22 subnodes = {}, -- next strokes
23 hanzis={} -- two or more hanzi could have the same stroke order
24 }
25 end
26
27 function new_trie()
28 return new_node(stroke_root)
29 end
30
31 -- insert hanzi and create the trie
32 function insert_hanzi(node, stroke_order, hanzi)
33 local stroke, not_found_index
34 for i = 1, #stroke_order do
35 stroke = tonumber(stroke_order:sub(i,i))
36 if node.subnodes[stroke] then
37 node = node.subnodes[stroke]
38 else
39 not_found_index = i
40 break
41 end
42 end
43 if not_found_index then
44 for i = not_found_index, #stroke_order do
45 stroke = tonumber(stroke_order:sub(i,i))
46 node.subnodes[stroke] = new_node(stroke)
47 node = node.subnodes[stroke]
48 end
49 end
50 table.insert(node.hanzis, hanzi)
51 end
52
53 -- strokes ,
54 function find_node(root, strokes)
55 local node = root
56
57 if #strokes < 1 then
58 return nil
59 end
60
61 for i, stroke in ipairs(strokes) do
62 if node.subnodes[stroke] then
63 node = node.subnodes[stroke]
64 else
65 return nil
66 end
67 end
68 return node
69 end
70
71 function db_to_trie(db_name)
72 local env = luasql.sqlite3()
73 local conn = env:connect(db_name)
74 local cur = conn:execute("SELECT hanzi,stroke_order FROM hzbs;")
75 local trie = new_trie()
76
77 record = {}
78 while cur:fetch(record, "a") do
79 insert_hanzi(trie, record.stroke_order, record.hanzi)
80 end
81
82 cur:close()
83 conn:close()
84 env:close()
85
86 return trie
87 end
88
89 function get_hanzi_enumerator(root)
90 local traverse
91
92 traverse = function(node)
93 for i = 1, #node.hanzis do
94 coroutine.yield(node.hanzis[i])
95 end
96
97 for stroke = 1, stroke_max do
98 if node.subnodes[stroke] then
99 traverse(node.subnodes[stroke])
100 end
101 end
102 end
103 local co = coroutine.create(function () traverse(root) end)
104
105 return (function ()
106 local ret, hanzi = coroutine.resume(co)
107 if not ret then -- already stopped
108 return nil
109 elseif hanzi == nil then -- the last call, no yield and no return value
110 return nil
111 else
112 return hanzi
113 end
114 end)
115 end
116
117 ---------------------------------------------------------------
118 -- GUI
119 local new_id = (function ()
120 local id = wx.wxID_HIGHEST
121 return (function ()
122 id = id + 1
123 return id
124 end)
125 end)()
126
127 dialog = wx.wxDialog(wx.NULL, new_id(), _T"Lua ",
128 wx.wxDefaultPosition, wx.wxDefaultSize)
129 panel = wx.wxPanel(dialog, wx.wxID_ANY)
130 local main_sizer = wx.wxBoxSizer(wx.wxVERTICAL)
131
132 --
133 local stroke_label = wx.wxStaticText(panel, new_id(), _T" ")
134 local heng_button = wx.wxButton(panel, stroke_heng, stroke_text[stroke_heng])
135 local shu_button = wx.wxButton(panel, stroke_shu, stroke_text[stroke_shu])
136 local pie_button = wx.wxButton(panel, stroke_pie, stroke_text[stroke_pie])
137 local na_button = wx.wxButton(panel, stroke_na, stroke_text[stroke_na])
138 local zhe_button = wx.wxButton(panel, stroke_zhe, stroke_text[stroke_zhe])
139
140 local button_sizer = wx.wxBoxSizer(wx.wxHORIZONTAL)
141 button_sizer:Add(stroke_label, 0, wx.wxALIGN_LEFT+wx.wxALL, 5)
142 button_sizer:Add(heng_button, 0, wx.wxALIGN_LEFT+wx.wxEXPAND+wx.wxALL, 5)
143 button_sizer:Add(shu_button, 0, wx.wxALIGN_LEFT+wx.wxEXPAND+wx.wxALL, 5)
144 button_sizer:Add(pie_button, 0, wx.wxALIGN_LEFT+wx.wxEXPAND+wx.wxALL, 5)
145 button_sizer:Add(na_button, 0, wx.wxALIGN_LEFT+wx.wxEXPAND+wx.wxALL, 5)
146 button_sizer:Add(zhe_button, 0, wx.wxALIGN_LEFT+wx.wxEXPAND+wx.wxALL, 5)
147
148 main_sizer:Add(button_sizer, 0, wx.wxALIGN_LEFT+wx.wxEXPAND+wx.wxALL, 5)
149
150 --
151 local input_label = wx.wxStaticText(panel, new_id(), _T" ")
152 local input_textctrl = wx.wxTextCtrl(panel, new_id(), "",
153 wx.wxDefaultPosition, wx.wxDefaultSize, wx.wxTE_READONLY)
154 local input_backspace_button = wx.wxButton(panel, new_id(), _T" ")
155 local input_clear_button = wx.wxButton(panel, wx.wxID_CANCEL, _T" ")
156
157 local input_sizer = wx.wxBoxSizer(wx.wxHORIZONTAL)
158 input_sizer:Add(input_label, 0, wx.wxALIGN_LEFT+wx.wxALL, 5)
159 input_sizer:Add(input_textctrl, 1, wx.wxALIGN_LEFT+wx.wxEXPAND+wx.wxALL, 5)
160 input_sizer:Add(input_backspace_button, 0, wx.wxALL, 5)
161 input_sizer:Add(input_clear_button, 0, wx.wxALL, 5)
162 main_sizer:Add(input_sizer, 1, wx.wxALIGN_LEFT+wx.wxEXPAND+wx.wxALL, 5)
163
164 --
165 local candidate_label = wx.wxStaticText(panel, new_id(), _T" ")
166 local candidate_sizer = wx.wxBoxSizer(wx.wxHORIZONTAL)
167 candidate_sizer:Add(candidate_label, 0, wx.wxALIGN_LEFT+wx.wxALL, 5)
168
169 local candidate_number = 5
170 function create_candidate_btn(num)
171 local textctrls = {}
172 for i= 1, num do
173 textctrls[i] = wx.wxButton(panel, new_id(), "")
174 candidate_sizer:Add(textctrls[i], 1, wx.wxALIGN_LEFT+wx.wxALL+wx.wxEXPAND, 5)
175 end
176 textctrls.start_id = textctrls[1]:GetId()
177 textctrls.end_id = textctrls.start_id + candidate_number - 1
178 return textctrls
179 end
180 local candidate_textctrls = create_candidate_btn(candidate_number)
181 main_sizer:Add(candidate_sizer, 1, wx.wxALIGN_LEFT+wx.wxALL+wx.wxEXPAND, 5)
182
183 --
184 local output_textctrl = wx.wxTextCtrl(panel, new_id(), "", wx.wxDefaultPosition,
185 wx.wxSize(0, 100), wx.wxTE_MULTILINE)
186 local output_sizer = wx.wxBoxSizer(wx.wxHORIZONTAL)
187 output_sizer:Add(output_textctrl, 1, wx.wxALIGN_LEFT+wx.wxEXPAND+wx.wxALL, 5)
188 main_sizer:Add(output_sizer, 0, wx.wxALIGN_LEFT+wx.wxEXPAND+wx.wxALL, 0)
189
190 main_sizer:SetSizeHints(dialog)
191 dialog:SetSizer(main_sizer)
192
193 -- ,
194 dialog:Connect(wx.wxEVT_CLOSE_WINDOW,
195 function (event)
196 dialog:Destroy()
197 event:Skip()
198 end)
199
200 --
201 local trie = db_to_trie("hzbs.sqlite3.db")
202
203 -- stroke
204 input_strokes = {}
205 get_next_candidate = nil
206
207 function update_candidate()
208 if get_next_candidate == nil then
209 for _,textctrl in ipairs(candidate_textctrls) do
210 textctrl:SetLabel("")
211 end
212 else
213 for _,textctrl in ipairs(candidate_textctrls) do
214 local hanzi = get_next_candidate()
215 if hanzi then
216 textctrl:SetLabel(hanzi)
217 else
218 textctrl:SetLabel("")
219 end
220 end
221 end
222 end
223
224 function update_input()
225 local text = {}
226 for _,stroke in ipairs(input_strokes) do
227 table.insert(text, stroke_text[stroke])
228 end
229
230 input_textctrl:SetValue(table.concat(text, " "))
231 end
232
233 function insert_stroke(stroke)
234 table.insert(input_strokes, stroke);
235 local node = find_node(trie, input_strokes)
236 if node == nil then
237 table.remove(input_strokes) --
238 -- BEEP
239 else
240 get_next_candidate = get_hanzi_enumerator(node)
241 update_input()
242 update_candidate()
243 end
244 end
245
246 function remove_stroke()
247 table.remove(input_strokes)
248 local node = find_node(trie, input_strokes)
249 if node == nil then
250 get_next_candidate = nil
251 else
252 get_next_candidate = get_hanzi_enumerator(node)
253 end
254
255 update_input()
256 update_candidate()
257 end
258
259 function clear_stroke()
260 input_strokes = {}
261 get_next_candidate = nil
262 update_input()
263 update_candidate()
264 end
265
266 dialog:Connect(wx.wxID_ANY, wx.wxEVT_COMMAND_BUTTON_CLICKED,
267 function(event)
268 local id = event:GetId()
269 if id <= stroke_max then
270 insert_stroke(id)
271 elseif id >= candidate_textctrls.start_id and id <= candidate_textctrls.end_id then
272 output_textctrl:AppendText(candidate_textctrls[id-candidate_textctrls.start_id+1]:GetLabel())
273 clear_stroke()
274 elseif id == input_backspace_button:GetId() then
275 remove_stroke()
276 elseif id == input_clear_button:GetId() then
277 clear_stroke()
278 end
279 end)
280
281 dialog:Connect(wx.wxID_ANY, wx.wxEVT_KEY_DOWN, function (event)
282 local key = event:GetKeyCode()
283 local callbacks = { }
284 callbacks[wx.WXK_NUMPAD7] = function ()
285 insert_stroke(stroke_heng)
286 end
287 callbacks[wx.WXK_NUMPAD8] = function ()
288 insert_stroke(stroke_shu)
289 end
290 callbacks[wx.WXK_NUMPAD9] = function ()
291 insert_stroke(stroke_pie)
292 end
293 callbacks[wx.WXK_NUMPAD4] = function ()
294 insert_stroke(stroke_na)
295 end
296 callbacks[wx.WXK_NUMPAD5] = function ()
297 insert_stroke(stroke_zhe)
298 end
299 callbacks[wx.WXK_BACK] = function ()
300 remove_stroke()
301 end
302 for i = 1, candidate_number do
303 callbacks[i - 1 + string.byte("1")] = function ()
304 output_textctrl:AppendText(candidate_textctrls[i]:GetLabel())
305 clear_stroke()
306 end
307 end
308
309 if callbacks[key] then
310 callbacks[key]()
311 end
312 end)
313
314 -- wxwindgets , ,
315 function process_children_keydown_event(parent, processer)
316 local wnd
317 local wlist = parent:GetChildren()
318
319 for i = 0, wlist:GetCount()-1 do
320 wnd = wlist:Item(i):GetData():DynamicCast("wxWindow")
321 wnd:SetNextHandler(processer)
322 process_children_keydown_event(wnd, processer)
323 end
324 end
325
326 process_children_keydown_event(dialog, dialog)
327
328
329 dialog:Centre()
330 dialog:Show(true)
331 input_textctrl:SetFocus() --
332
333 wx.wxGetApp():MainLoop()
パッケージのダウンロード
ソースパッケージとsqlite 3データベースはここでダウンロードできます.