char_tree 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ Copyright 2012 Miguel Adolfo Barroso
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,3 @@
1
+ = CharTree
2
+
3
+ This project rocks and uses MIT-LICENSE.
@@ -0,0 +1,9 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ task :default => :test
5
+
6
+ desc "Run all tests"
7
+ task :test do
8
+ exec "rspec --color"
9
+ end
@@ -0,0 +1,2 @@
1
+ require File.expand_path('../char_tree/node', __FILE__)
2
+ require File.expand_path('../char_tree/dictionary', __FILE__)
@@ -0,0 +1,292 @@
1
+ # encoding: utf-8
2
+ module CharTree
3
+ class Dictionary
4
+ def initialize(file = false)
5
+ @callbacks = {}
6
+ @f = false
7
+ @first = nil
8
+ @size = 0
9
+ @n = 0
10
+ @results = []
11
+ @searching = false
12
+ @use_set = false
13
+ @set_chars = {}
14
+ @pattern = '*'
15
+
16
+ if file
17
+ @file = file
18
+ else
19
+ root = File.expand_path('../../..', __FILE__)
20
+ @file = "#{root}/tmp/dictionary.bda"
21
+ end
22
+ pattern
23
+ end
24
+
25
+ # File management
26
+ def file_path
27
+ @file
28
+ end
29
+
30
+ def delete
31
+ File.delete(@file) if File.file?(@file)
32
+ end
33
+
34
+ def create
35
+ return if @f
36
+ delete
37
+ @f = File.new(@file, "w+b")
38
+ n = create_node
39
+ write n
40
+ close
41
+ end
42
+
43
+ def open
44
+ return if @f
45
+ @f = File.new(@file, "r+b")
46
+ first
47
+ end
48
+
49
+ def close
50
+ @f.close if @f
51
+ @f = false
52
+ @first = nil
53
+ end
54
+
55
+ def empty?
56
+ File.zero?(@file)
57
+ end
58
+
59
+ # Callbacks
60
+ $callbacks = {}
61
+
62
+ def self.event(name, method_name)
63
+ $callbacks[name] = [] unless $callbacks[name]
64
+ $callbacks[name] << method_name
65
+ end
66
+ # Callbacks end
67
+
68
+
69
+ # Words
70
+ def add(word)
71
+ return unless @f
72
+ add_char(encode(word).split(""), @first)
73
+ end
74
+
75
+ # Search
76
+ def use_set
77
+ @use_set = true
78
+ end
79
+
80
+ def no_use_set
81
+ @use_set = false
82
+ end
83
+
84
+ def use_set?
85
+ @use_set
86
+ end
87
+
88
+ def set(chars = [])
89
+ use_set
90
+ @set_chars = {}
91
+ chars.each do |c|
92
+ c = '8' if (c == 'CH')
93
+ c = '1' if (c == 'LL')
94
+ c = '4' if (c == 'RR')
95
+ @set_chars[c] = @set_chars[c] ? @set_chars[c] + 1 : 1
96
+ end
97
+ end
98
+
99
+ def pattern(pattern = '*')
100
+ @pattern = "^#{pattern}$"
101
+ @pattern = @pattern.gsub('*', '.*')
102
+ @pattern = @pattern.gsub('?', '.')
103
+ @pattern = @pattern.gsub('CH', '8')
104
+ @pattern = @pattern.gsub('LL', '1')
105
+ @pattern = @pattern.gsub('RR', '4')
106
+ end
107
+
108
+
109
+ def search(min = 1, max = 999)
110
+ return unless @f
111
+ return if searching?
112
+ decode(search_next_chars(@first, '', min, max))
113
+ end
114
+
115
+ def search_all(min = 1, max = 999)
116
+ return unless @f
117
+ return if searching?
118
+ @searching = true
119
+ search_next_chars(@first, '', min, max)
120
+ @searching = false
121
+ end
122
+
123
+ # Results
124
+ def searching?
125
+ @searching
126
+ end
127
+
128
+ def results?
129
+ @results.count > 0
130
+ end
131
+
132
+ def total_results
133
+ @results.count
134
+ end
135
+
136
+ def next_result
137
+ @results.shift
138
+ end
139
+
140
+ private
141
+ def encode(word)
142
+ word = word.upcase
143
+ word = word.gsub('CH', '8')
144
+ word = word.gsub('LL', '1')
145
+ word = word.gsub('RR', '4')
146
+ word
147
+ end
148
+
149
+ def decode(word)
150
+ return false unless word
151
+ word = word.gsub('8', 'CH')
152
+ word = word.gsub('1', 'LL')
153
+ word = word.gsub('4', 'RR')
154
+ word
155
+ end
156
+
157
+ # Results
158
+ def add_result(word)
159
+ word = decode(word)
160
+ @results << word
161
+ run_event_for :found, word
162
+ end
163
+
164
+ # Callbacks
165
+ def run_event_for(name, *args)
166
+ return unless $callbacks[name.to_sym]
167
+ $callbacks[name.to_sym].each do |callback|
168
+ if callback.kind_of? Symbol
169
+ self.send(callback, *args)
170
+ else
171
+ self.instance_exec(*args, &callback)
172
+ end
173
+ end
174
+ end
175
+ # Callbacks end
176
+
177
+ # Nodes
178
+ def first
179
+ return if @first
180
+ @size = CharTree::SIZE
181
+ @n = File.size(@file) / @size
182
+ @first = read
183
+ end
184
+
185
+ def write(node)
186
+ @f.seek(@size*node.id-@size, IO::SEEK_SET)
187
+ @f.write node.to_b
188
+ end
189
+
190
+ def read(id = 1)
191
+ @f.seek(@size*id-@size, IO::SEEK_SET)
192
+ node = CharTree::Node.new
193
+ node.from_b(@f.read(@size))
194
+ node
195
+ end
196
+
197
+ def create_node(up = 0, up_position = 0)
198
+ @n += 1
199
+ node = CharTree::Node.new(@n, up, up_position)
200
+ end
201
+
202
+ def add_char(chars, node = @first, save = false)
203
+ c = chars.shift
204
+ node.go_for c
205
+ if chars.count > 0
206
+ if node.down > 0
207
+ return add_char(chars, read(node.down))
208
+ else
209
+ new_node = create_node(node.id, node.position)
210
+ node.down = new_node.id
211
+ add_char(chars, new_node, true)
212
+ save = true
213
+ end
214
+ else
215
+ save = true unless node.complete?
216
+ node.complete
217
+ end
218
+
219
+ write node if save
220
+ return save
221
+ end
222
+
223
+ def undo_set(char, return_value)
224
+ if use_set? && char && @set_chars[char]
225
+ @set_chars[char] = @set_chars[char] + 1
226
+ end
227
+ return_value
228
+ end
229
+
230
+ def do_set(char, ignore_wildcard = false)
231
+ if (!ignore_wildcard && @set_chars[' '] && @set_chars[' '] > 0)
232
+ @set_chars[' '] -= 1
233
+ return ' '
234
+ end
235
+ if (@set_chars[char] && @set_chars[char] > 0)
236
+ @set_chars[char] -= 1
237
+ return char
238
+ end
239
+ return false
240
+ end
241
+
242
+ def search_next_chars(node = @first, word = '', min = 1, max = 999)
243
+ return false unless node.first_char
244
+ return search_next_chars_by_alternate(node, word, min, max)
245
+ end
246
+
247
+ def search_next_chars_by_alternate(node, word, min, max, wildcard = false)
248
+ if use_set? && !wildcard
249
+ set_char = do_set(node.chr)
250
+ if set_char == ' '
251
+ position = node.position
252
+ new_word = search_next_chars_by_alternate(node, word, min, max, true)
253
+ undo_set(set_char, false)
254
+ node.go position
255
+ if new_word && !searching?
256
+ return new_word
257
+ end
258
+ set_char = do_set(node.chr, true)
259
+ end
260
+ unless set_char
261
+ return undo_set(set_char, false) unless node.next_char
262
+ return undo_set(set_char, search_next_chars_by_alternate(node, word, min, max))
263
+ end
264
+ else
265
+ set_char = false
266
+ end
267
+
268
+ current_word = word + node.chr
269
+ return undo_set(set_char, false) if current_word.length > max
270
+ if node.complete? && current_word.length >= min
271
+ if current_word.match(@pattern)
272
+ if searching?
273
+ add_result current_word
274
+ else
275
+ return undo_set(set_char, current_word)
276
+ end
277
+ end
278
+ end
279
+
280
+ if node.down?
281
+ new_word = search_next_chars(read(node.down), current_word, min, max)
282
+ if new_word && (new_word.match(@pattern) == nil)
283
+ new_word = false
284
+ end
285
+ return undo_set(set_char, new_word) if new_word
286
+ end
287
+ return undo_set(set_char, false) unless node.next_char
288
+ undo_set(set_char, false)
289
+ return search_next_chars_by_alternate(node, word, min, max, wildcard)
290
+ end
291
+ end
292
+ end
@@ -0,0 +1,230 @@
1
+ # encoding: utf-8
2
+ module CharTree
3
+
4
+ CHARS = 30
5
+ MAX = CHARS - 1
6
+ BYTES_L = 4
7
+ BYTES_C = 1
8
+ GROUPS_L = 2
9
+ GROUPS_C = 1
10
+ SIZE = 2 * BYTES_L + 1 * BYTES_C + CHARS * BYTES_L * GROUPS_L + CHARS * BYTES_C * GROUPS_C
11
+
12
+ class Node
13
+ def initialize(id = 0, up = 0, up_position = 0, complete = 0, down = 0, down_position = 0)
14
+ @current = 0
15
+ @id = id
16
+ @up = up
17
+ @up_position = up_position
18
+ @complete = [complete]*CHARS
19
+ @down = [down]*CHARS
20
+ @down_position = [down_position]*CHARS
21
+ end
22
+
23
+ def size
24
+ SIZE
25
+ end
26
+
27
+ def first
28
+ @current = 0
29
+ end
30
+
31
+ def last
32
+ @current = MAX
33
+ end
34
+
35
+ def next
36
+ @current += 1
37
+ if @current > MAX
38
+ @current = MAX
39
+ return false
40
+ end
41
+ return true
42
+ end
43
+
44
+ def next?
45
+ @current < MAX
46
+ end
47
+
48
+ def previous
49
+ @current -= 1
50
+ if @current < 0
51
+ @current = 0
52
+ return false
53
+ end
54
+ return true
55
+ end
56
+
57
+ def previous?
58
+ @current > 0
59
+ end
60
+
61
+ def current
62
+ @current
63
+ end
64
+
65
+ def position
66
+ @current
67
+ end
68
+
69
+ def next_char
70
+ previous_current = @current
71
+ return false unless next?
72
+ self.next
73
+ return true if down? || complete?
74
+ return true if next_char
75
+ @current = previous_current
76
+ return false
77
+ end
78
+
79
+ def previous_char
80
+ previous_current = @current
81
+ return false unless previous?
82
+ self.previous
83
+ return true if down? || complete?
84
+ return true if previous_char
85
+ @current = previous_current
86
+ return false
87
+ end
88
+
89
+ def first_char
90
+ previous_current = @current
91
+ self.first
92
+ return true if down? || complete?
93
+ return true if next_char
94
+ @current = previous_current
95
+ return false
96
+ end
97
+
98
+ def last_char
99
+ previous_current = @current
100
+ self.last
101
+ return true if down? || complete?
102
+ return true if previous_char
103
+ @current = previous_current
104
+ return false
105
+ end
106
+
107
+ def position_for(char)
108
+ c = char.capitalize[0]
109
+ return MAX - 3 if c == '8' # Ch
110
+ return MAX - 2 if c == '1' # Ll
111
+ return MAX - 1 if c == '4' # Rr
112
+ p = c.ord - 65
113
+ p = MAX if p > MAX
114
+ p
115
+ end
116
+
117
+ def go(position)
118
+ @current = position if position <= MAX
119
+ end
120
+
121
+ def go_for(char)
122
+ @current = position_for(char)
123
+ end
124
+
125
+ def chr
126
+ chr_for(@current)
127
+ end
128
+
129
+ def chr_for(position)
130
+ return '8' if position == MAX - 3 # Ch
131
+ return '1' if position == MAX - 2 # Ll
132
+ return '4' if position == MAX - 1 # Rr
133
+ return 'Ñ' if position == MAX
134
+ return (65+position).chr
135
+ end
136
+
137
+ def id=(id)
138
+ @id = up
139
+ end
140
+
141
+ def id
142
+ @id
143
+ end
144
+
145
+ def complete
146
+ @complete[@current] = 1
147
+ end
148
+
149
+ def not_complete
150
+ @complete[@current] = 0
151
+ end
152
+
153
+ def complete?
154
+ @complete[@current] > 0
155
+ end
156
+
157
+ def up=(up)
158
+ @up = up
159
+ end
160
+
161
+ def up
162
+ @up
163
+ end
164
+
165
+ def up_position=(up_position)
166
+ @up_position = up_position
167
+ end
168
+
169
+ def up_position
170
+ @up_position
171
+ end
172
+
173
+ def down=(down)
174
+ @down[@current] = down
175
+ end
176
+
177
+ def down
178
+ @down[@current]
179
+ end
180
+
181
+ def down?
182
+ @down[@current] > 0
183
+ end
184
+
185
+ def down_position=(down_position)
186
+ @down_position[@current] = down_position
187
+ end
188
+
189
+ def down_position
190
+ @down_position[@current]
191
+ end
192
+
193
+ def to_b
194
+ d = [@id].pack("L")
195
+ d += [@up].pack("L")
196
+ d += [@up_position].pack("C")
197
+ d += array_to_b :L, @complete
198
+ d += array_to_b :L, @down
199
+ d += array_to_b :C, @down_position
200
+ d
201
+ end
202
+
203
+ def from_b(binary)
204
+ @id = binary[BYTES_L*0..BYTES_L*1+BYTES_C*0].unpack("L")[0]
205
+ @up = binary[BYTES_L*1..BYTES_L*2+BYTES_C*0].unpack("L")[0]
206
+ @up_position = binary[BYTES_L*2..BYTES_L*2+BYTES_C*1].unpack("C")[0]
207
+ @complete = array_from_b :L, BYTES_L, binary[(BYTES_L*2+BYTES_C+CHARS*BYTES_L*0+CHARS*BYTES_C*0)..(BYTES_L*2+BYTES_C+CHARS*BYTES_L*1+CHARS*BYTES_C*0-1)]
208
+ @down = array_from_b :L, BYTES_L, binary[(BYTES_L*2+BYTES_C+CHARS*BYTES_L*1+CHARS*BYTES_C*0)..(BYTES_L*2+BYTES_C+CHARS*BYTES_L*2+CHARS*BYTES_C*0-1)]
209
+ @down_position = array_from_b :C, BYTES_C, binary[(BYTES_L*2+BYTES_C+CHARS*BYTES_L*2+CHARS*BYTES_C*0)..(BYTES_L*2+BYTES_C+CHARS*BYTES_L*2+CHARS*BYTES_C*1-1)]
210
+ end
211
+
212
+ private
213
+ def array_to_b(type, array)
214
+ r = ''
215
+ array.each {|i|
216
+ r += [i].pack(type.to_s)
217
+ }
218
+ r
219
+ end
220
+
221
+ def array_from_b(type, size, binary)
222
+ array = []
223
+ CHARS.times do |i|
224
+ array << binary[(size*i)..(size*(i+1)-1)].unpack(type.to_s)[0]
225
+ end
226
+ array
227
+ end
228
+ end
229
+ end
230
+
@@ -0,0 +1,3 @@
1
+ module CharTree
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :char_tree do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: char_tree
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Miguel Adolfo Barroso
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-02-05 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &16837060 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *16837060
25
+ description: Dictionary functions for word permutation search.
26
+ email:
27
+ - mabarroso@mabarroso.com
28
+ executables: []
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - lib/char_tree/version.rb
33
+ - lib/char_tree/node.rb
34
+ - lib/char_tree/dictionary.rb
35
+ - lib/tasks/char_tree_tasks.rake
36
+ - lib/char_tree.rb
37
+ - MIT-LICENSE
38
+ - Rakefile
39
+ - README.rdoc
40
+ homepage: https://github.com/mabarroso/char_tree
41
+ licenses: []
42
+ post_install_message:
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ none: false
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ! '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubyforge_project:
60
+ rubygems_version: 1.8.10
61
+ signing_key:
62
+ specification_version: 3
63
+ summary: Binary file dictionary for word permutation search.
64
+ test_files: []