char_tree 1.0.0

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.
@@ -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: []