radix_tree 1.0.0.dev

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/README ADDED
@@ -0,0 +1,34 @@
1
+ radix_tree - Naive implementation of Radix Tree for Ruby
2
+ Copyright (C) 2012 Hiroshi Nakamura <nahi@ruby-lang.org>
3
+
4
+ My intention is using Radix Tree instead of Hash for parsing external input
5
+ to avoid DoS via Algorithmic Complexity Attacks.
6
+
7
+
8
+ == Performance
9
+
10
+ * 25 times slower for 10 bytes key insertion
11
+ * 20 times slower for 10 bytes key retrieval
12
+
13
+
14
+ == TODO
15
+
16
+ Implement following features for utilizing strength of Radix Tree.
17
+ * find predecessor
18
+ * find successor
19
+ * find_all by start string
20
+ * delete_all by start string
21
+
22
+
23
+ == Author
24
+
25
+ Name:: Hiroshi Nakamura
26
+ E-mail:: nahi@ruby-lang.org
27
+ Project web site:: http://github.com/nahi/radix_tree
28
+
29
+
30
+ == License
31
+
32
+ This program is copyrighted free software by Hiroshi Nakamura. You can
33
+ redistribute it and/or modify it under the same terms of Ruby's license;
34
+ either the dual license version in 2003, or any later version.
data/lib/radix_tree.rb ADDED
@@ -0,0 +1,214 @@
1
+ # Naive implementation of Radix Tree for avoiding DoS via Algorithmic
2
+ # Complexity Attacks.
3
+ #
4
+ # 25 times slower for 10 bytes key insertion
5
+ # 20 times slower for 10 bytes key retrieval
6
+ #
7
+ # TODO: Implement following features for utilizing strength of Radix Tree.
8
+ # * find predecessor
9
+ # * find successor
10
+ # * find_all by start string
11
+ # * delete_all by start string
12
+ class RadixTree
13
+ include Enumerable
14
+
15
+ class Node
16
+ UNDEFINED = Object.new
17
+
18
+ attr_reader :key, :value
19
+ attr_reader :children
20
+
21
+ def initialize(key, value = UNDEFINED, children = nil)
22
+ @key, @value, @children = key, value, children
23
+ end
24
+
25
+ def empty?
26
+ @children.nil? and @value == UNDEFINED
27
+ end
28
+
29
+ def size
30
+ count = @value != UNDEFINED ? 1 : 0
31
+ if @children
32
+ @children.inject(count) { |r, (k, v)| r + v.size }
33
+ else
34
+ count
35
+ end
36
+ end
37
+
38
+ def each(prefix, &block)
39
+ prefix += @key
40
+ if @value != UNDEFINED
41
+ block.call(prefix, @value)
42
+ end
43
+ if @children
44
+ @children.each do |key, child|
45
+ child.each(prefix, &block)
46
+ end
47
+ end
48
+ end
49
+
50
+ def keys(prefix)
51
+ collect(prefix) { |k, v| k }
52
+ end
53
+
54
+ def values(prefix)
55
+ collect(prefix) { |k, v| v }
56
+ end
57
+
58
+ def store(key, value)
59
+ if @key == key
60
+ @value = value
61
+ else
62
+ index = head_match_length(key)
63
+ if index == @key.bytesize
64
+ push(key[index..-1], value)
65
+ else
66
+ split(index)
67
+ store(key, value) # search again
68
+ end
69
+ end
70
+ end
71
+
72
+ def retrieve(key)
73
+ return UNDEFINED unless @children
74
+ key = child_key(key)
75
+ if child = find_child(key)
76
+ if child.key == key
77
+ child.value
78
+ else
79
+ child.retrieve(key)
80
+ end
81
+ else
82
+ UNDEFINED
83
+ end
84
+ end
85
+
86
+ def delete(key)
87
+ return nil unless @children
88
+ key = child_key(key)
89
+ if child = find_child(key)
90
+ if child.key == key
91
+ value, child.value = child.value, UNDEFINED
92
+ else
93
+ value = child.delete(key)
94
+ end
95
+ delete_child(child) if value and child.children.nil?
96
+ value
97
+ end
98
+ end
99
+
100
+ protected
101
+
102
+ def value=(value)
103
+ @value = value
104
+ end
105
+
106
+ private
107
+
108
+ def collect(prefix)
109
+ pool = []
110
+ each(prefix) do |key, value|
111
+ pool << yield(key, value)
112
+ end
113
+ pool
114
+ end
115
+
116
+ def push(key, value)
117
+ if child = find_child(key)
118
+ child.store(key, value)
119
+ else
120
+ add_child(Node.new(key, value))
121
+ end
122
+ end
123
+
124
+ def split(index)
125
+ @key, new_key = @key[0, index], @key[index..-1]
126
+ child = Node.new(new_key, @value, @children)
127
+ @value, @children = UNDEFINED, nil
128
+ add_child(child)
129
+ end
130
+
131
+ def child_key(key)
132
+ index = head_match_length(key)
133
+ key[index..-1]
134
+ end
135
+
136
+ def head_match_length(check)
137
+ 0.upto([check.bytesize, @key.bytesize].min) do |index|
138
+ return index if check[index] != @key[index]
139
+ end
140
+ raise 'assert check != @key'
141
+ end
142
+
143
+ def find_child(key)
144
+ @children[key[0]] if @children
145
+ end
146
+
147
+ def add_child(child)
148
+ @children ||= {}
149
+ @children[child.key[0]] = child
150
+ end
151
+
152
+ def delete_child(child)
153
+ @children.delete(child.key[0])
154
+ @children = nil if @children.empty?
155
+ end
156
+ end
157
+
158
+ DEFAULT = Object.new
159
+
160
+ def initialize(default = DEFAULT, &block)
161
+ raise ArgumentError, 'wrong number of arguments' if block && default != DEFAULT
162
+ @root = Node.new('')
163
+ @default = default
164
+ @default_proc = block
165
+ end
166
+
167
+ def empty?
168
+ @root.empty?
169
+ end
170
+
171
+ def size
172
+ @root.size
173
+ end
174
+
175
+ def each(&block)
176
+ @root.each('', &block)
177
+ end
178
+
179
+ def keys
180
+ @root.keys('')
181
+ end
182
+
183
+ def values
184
+ @root.values('')
185
+ end
186
+
187
+ def []=(key, value)
188
+ @root.store(key, value)
189
+ end
190
+
191
+ def key?(key)
192
+ @root.retrieve(key) != Node::UNDEFINED
193
+ end
194
+ alias has_key? key?
195
+
196
+ def [](key)
197
+ value = @root.retrieve(key)
198
+ if value == Node::UNDEFINED
199
+ if @default != DEFAULT
200
+ @default
201
+ elsif @default_proc
202
+ @default_proc.call
203
+ else
204
+ nil
205
+ end
206
+ else
207
+ value
208
+ end
209
+ end
210
+
211
+ def delete(key)
212
+ @root.delete(key)
213
+ end
214
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,2 @@
1
+ require "test/unit"
2
+ require "radix_tree"
@@ -0,0 +1,141 @@
1
+ require File.expand_path('./helper', File.dirname(__FILE__))
2
+
3
+ class TestRadixTree < Test::Unit::TestCase
4
+ def test_aref_nil
5
+ h = RadixTree.new
6
+ h['abc'] = 1
7
+ assert_equal nil, h['def']
8
+ end
9
+
10
+ def test_aref_empty
11
+ h = RadixTree.new
12
+ assert_equal nil, h['']
13
+ end
14
+
15
+ def test_aref_single
16
+ h = RadixTree.new
17
+ h['abc'] = 1
18
+ assert_equal 1, h['abc']
19
+ end
20
+
21
+ def test_aref_double
22
+ h = RadixTree.new
23
+ h['abc'] = 1
24
+ h['def'] = 2
25
+ assert_equal 1, h['abc']
26
+ assert_equal 2, h['def']
27
+ end
28
+
29
+ def test_aset_override
30
+ h = RadixTree.new
31
+ h['abc'] = 1
32
+ h['abc'] = 2
33
+ assert_equal 2, h['abc']
34
+ end
35
+
36
+ def test_split
37
+ h = RadixTree.new
38
+ h['abcd'] = 1
39
+ assert_equal 1, h['abcd']
40
+ h['abce'] = 2
41
+ assert_equal 1, h['abcd']
42
+ assert_equal 2, h['abce']
43
+ h['abd'] = 3
44
+ assert_equal 1, h['abcd']
45
+ assert_equal 2, h['abce']
46
+ assert_equal 3, h['abd']
47
+ h['ac'] = 4
48
+ assert_equal 1, h['abcd']
49
+ assert_equal 2, h['abce']
50
+ assert_equal 3, h['abd']
51
+ assert_equal 4, h['ac']
52
+ end
53
+
54
+ def test_split_and_assign
55
+ h = RadixTree.new
56
+ h['ab'] = 1
57
+ h['a'] = 2
58
+ assert_equal 1, h['ab']
59
+ assert_equal 2, h['a']
60
+ end
61
+
62
+ def test_push
63
+ h = RadixTree.new
64
+ assert_equal 0, h.size
65
+ h['a'] = 1
66
+ assert_equal 1, h['a']
67
+ h['ab'] = 2
68
+ assert_equal 1, h['a']
69
+ assert_equal 2, h['ab']
70
+ h['abc'] = 3
71
+ assert_equal 1, h['a']
72
+ assert_equal 2, h['ab']
73
+ assert_equal 3, h['abc']
74
+ h['abd'] = 4
75
+ assert_equal 1, h['a']
76
+ assert_equal 2, h['ab']
77
+ assert_equal 3, h['abc']
78
+ assert_equal 4, h['abd']
79
+ h['ac'] = 5
80
+ assert_equal 1, h['a']
81
+ assert_equal 2, h['ab']
82
+ assert_equal 3, h['abc']
83
+ assert_equal 4, h['abd']
84
+ assert_equal 5, h['ac']
85
+ h['b'] = 6
86
+ assert_equal 1, h['a']
87
+ assert_equal 2, h['ab']
88
+ assert_equal 3, h['abc']
89
+ assert_equal 4, h['abd']
90
+ assert_equal 5, h['ac']
91
+ assert_equal 6, h['b']
92
+ assert_equal ['a', 'ab', 'abc', 'abd', 'ac', 'b'].sort, h.keys.sort
93
+ assert_equal 6, h.size
94
+ end
95
+
96
+ def test_delete
97
+ h = RadixTree.new
98
+ h['a'] = 1
99
+ h['ab'] = 2
100
+ h['abc'] = 3
101
+ h['abd'] = 4
102
+ h['ac'] = 5
103
+ h['b'] = 6
104
+ assert_equal 6, h.size
105
+ assert_equal nil, h.delete('XXX')
106
+ # delete leaf
107
+ assert_equal 4, h.delete('abd')
108
+ assert_equal 5, h.size
109
+ assert_equal 1, h['a']
110
+ assert_equal 2, h['ab']
111
+ assert_equal 3, h['abc']
112
+ assert_equal nil, h['abd']
113
+ assert_equal 5, h['ac']
114
+ assert_equal 6, h['b']
115
+ # delete single leaf node
116
+ assert_equal 2, h.delete('ab')
117
+ assert_equal 4, h.size
118
+ assert_equal 1, h['a']
119
+ assert_equal nil, h['ab']
120
+ assert_equal 3, h['abc']
121
+ assert_equal nil, h['abd']
122
+ assert_equal 5, h['ac']
123
+ assert_equal 6, h['b']
124
+ # delete multiple leaf node
125
+ assert_equal 1, h.delete('a')
126
+ assert_equal 3, h.size
127
+ assert_equal nil, h['a']
128
+ assert_equal nil, h['ab']
129
+ assert_equal 3, h['abc']
130
+ assert_equal nil, h['abd']
131
+ assert_equal 5, h['ac']
132
+ assert_equal 6, h['b']
133
+ assert_equal ['abc', 'ac', 'b'].sort, h.keys.sort
134
+ # delete rest
135
+ assert_equal 3, h.delete('abc')
136
+ assert_equal 5, h.delete('ac')
137
+ assert_equal 6, h.delete('b')
138
+ assert_equal 0, h.size
139
+ assert h.empty?
140
+ end
141
+ end
metadata ADDED
@@ -0,0 +1,48 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: radix_tree
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0.dev
5
+ prerelease: 6
6
+ platform: ruby
7
+ authors:
8
+ - Hiroshi Nakamura
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-05 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description:
15
+ email: nahi@ruby-lang.org
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/radix_tree.rb
21
+ - test/test_radix_tree.rb
22
+ - test/helper.rb
23
+ - README
24
+ homepage: http://github.com/nahi/radix_tree
25
+ licenses: []
26
+ post_install_message:
27
+ rdoc_options: []
28
+ require_paths:
29
+ - lib
30
+ required_ruby_version: !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ! '>='
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ required_rubygems_version: !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ! '>'
40
+ - !ruby/object:Gem::Version
41
+ version: 1.3.1
42
+ requirements: []
43
+ rubyforge_project:
44
+ rubygems_version: 1.8.11
45
+ signing_key:
46
+ specification_version: 3
47
+ summary: Naive implementation of Radix Tree for Ruby
48
+ test_files: []