radix_tree 1.0.0.dev

Sign up to get free protection for your applications and to get access to all the features.
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: []