hamster 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,11 @@
1
+ === 0.1.2 / 2009-10-23
2
+
3
+ * Fixed all but one outstanding issue with #remove
4
+
5
+ === 0.1.1 / 2009-10-23
6
+
7
+ * Added #remove
8
+
1
9
  === 0.1.0 / 2009-10-21
2
10
 
3
11
  * Initial version
@@ -1,15 +1,15 @@
1
1
  = Hamster
2
2
 
3
- Hash Array Mapped Tries (HAMT) for Ruby (See http://lamp.epfl.ch/papers/idealhashtrees.pdf).
3
+ Hash Array Mapped Tries (HAMT) for Ruby (see http://lamp.epfl.ch/papers/idealhashtrees.pdf).
4
4
 
5
5
  Why do you care?
6
6
 
7
- HAMTs are hash tables with one really neat property: their structure enables you to perform very efficient write-on-copy. For example:
7
+ HAMTs are hash tables with one really neat property: their structure enables you to perform very efficient copy-on-write operations. For example:
8
8
 
9
9
  trie = Hamster::Trie.new
10
10
 
11
11
  trie.put("Name", "Simon")
12
- trie.get("Name") # => nil
12
+ trie.get("Name") # => nil
13
13
 
14
14
  Huh? That's not much use!
15
15
 
@@ -18,7 +18,7 @@ Remember, each instance of a trie is immutable. #put creates an efficient copy c
18
18
  trie = Hamster::Trie.new
19
19
 
20
20
  trie = trie.put("Name", "Simon")
21
- trie.get("Name") # => "Simon"
21
+ trie.get("Name") # => "Simon"
22
22
 
23
23
  The same goes for remove:
24
24
 
@@ -27,12 +27,18 @@ The same goes for remove:
27
27
  trie = trie.put("Name", "Simon")
28
28
  trie = trie.put("Gender", "Male")
29
29
  trie = trie.remove("Name")
30
- trie.get("Name") # => nil
31
- trie.get("Gender") # => "Male"
30
+ trie.get("Name") # => nil
31
+ trie.get("Gender") # => "Male"
32
32
 
33
33
  So tell me again why I care?
34
34
 
35
- As mentioned earlier, HAMTs perform a copy whenever they are modified means that there is never a chance that two threads could be modifying the same instance at any one time. And the fact that they are very efficient copies means you don't need to worry about using up gobs of heap space.
35
+ As mentioned earlier, HAMTs perform a copy whenever they are modified meaning there is never any chance that two threads could be modifying the same instance at any one time. And, because they are very efficient copies, you don't need to worry about using up gobs of heap space in the process.
36
+
37
+ Thats nice but I don't really have multi-threading issues.
38
+
39
+ OK, how about transactional memory:
40
+
41
+ Need an example
36
42
 
37
43
  So what's the downside?
38
44
 
@@ -77,38 +77,38 @@ module Hamster
77
77
 
78
78
  # Returns a copy of <tt>self</tt> with the given key/value pair removed. If not found, returns <tt>self</tt>.
79
79
  def remove(key)
80
+ remove!(key) || self
81
+ end
82
+
83
+ protected
84
+
85
+ def put!(key, value)
86
+ @entries[index_for(key)] = Entry.new(key, value)
87
+ self
88
+ end
89
+
90
+ def remove!(key)
80
91
  index = index_for(key)
81
92
  entry = @entries[index]
82
93
  child = @children[index]
83
94
  if entry && entry.has_key?(key)
84
- # TODO: Probably should "pull up" a child entry
85
95
  entries = @entries.dup
86
96
  entries[index] = nil
87
- self.class.new(@significant_bits, entries, @children)
97
+ self.class.new(@significant_bits, entries, @children) unless size == 1
88
98
  elsif child
89
- new_child = child.remove(key)
99
+ new_child = child.remove!(key)
90
100
  if new_child != child
91
- # TODO: Probably should "prune" empty children
92
101
  children = @children.dup
93
102
  children[index] = new_child
94
103
  self.class.new(@significant_bits, @entries, children)
95
104
  end
96
- end || self
97
- end
98
-
99
- protected
100
-
101
- def put!(key, value)
102
- @entries[index_for(key)] = Entry.new(key, value)
103
- self
105
+ end
104
106
  end
105
107
 
106
108
  private
107
109
 
108
110
  def index_for(key)
109
- key.hash.abs & 31
110
- # puts "#{key}##{key.object_id}:#{key.hash}"
111
- # (key.hash.abs >> @significant_bits) & 31
111
+ (key.hash.abs >> @significant_bits) & 31
112
112
  end
113
113
 
114
114
  end
@@ -1,5 +1,5 @@
1
1
  module Hamster
2
2
 
3
- VERSION = "0.1.1".freeze
3
+ VERSION = "0.1.2".freeze
4
4
 
5
5
  end
@@ -0,0 +1,51 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ module Hamster
4
+
5
+ describe Trie do
6
+
7
+ describe "#each" do
8
+
9
+ before do
10
+ @trie = Trie.new
11
+ @expected_pairs = { "A" => "aye", "B" => "bee", "C" => "sea" }
12
+ @expected_pairs.each do |key, value|
13
+ @trie = @trie.put(key, value)
14
+ end
15
+ end
16
+
17
+ describe "with a block (internal iteration)" do
18
+
19
+ it "returns self" do
20
+ @trie.each {}.should == @trie
21
+ end
22
+
23
+ it "yields all key value pairs" do
24
+ actual_pairs = {}
25
+ @trie.each do |key, value|
26
+ actual_pairs[key] = value
27
+ end
28
+ actual_pairs.should == @expected_pairs
29
+ end
30
+
31
+ end
32
+
33
+ describe "with no block (external iteration)" do
34
+
35
+ it "returns an enumerator over all key value pairs" do
36
+ actual_pairs = {}
37
+ enum = @trie.each
38
+ loop do
39
+ key, value = enum.next
40
+ actual_pairs[key] = value
41
+ end
42
+ actual_pairs.should == @expected_pairs
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1,22 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ module Hamster
4
+
5
+ describe Trie do
6
+
7
+ describe "#empty?" do
8
+
9
+ it "initially returns true" do
10
+ Trie.new.should be_empty
11
+ end
12
+
13
+ it "returns false once items have been added" do
14
+ trie = Trie.new.put("A", "aye")
15
+ trie.should_not be_empty
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,13 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ module Hamster
4
+
5
+ describe Trie do
6
+
7
+ it "is Enumerable" do
8
+ Trie.ancestors.should include(Enumerable)
9
+ end
10
+
11
+ end
12
+
13
+ end
@@ -0,0 +1,26 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ module Hamster
4
+
5
+ describe Trie do
6
+
7
+ describe "#get" do
8
+
9
+ before do
10
+ @trie = Trie.new
11
+ @trie = @trie.put("A", "aye")
12
+ end
13
+
14
+ it "returns the value for an existing key" do
15
+ @trie.get("A").should == "aye"
16
+ end
17
+
18
+ it "returns nil for a non-existing key" do
19
+ @trie.get("B").should be_nil
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+
26
+ end
@@ -0,0 +1,25 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ module Hamster
4
+
5
+ describe Trie do
6
+
7
+ describe "#has_key?" do
8
+
9
+ before do
10
+ @trie = Trie.new.put("A", "aye")
11
+ end
12
+
13
+ it "returns true for an existing key" do
14
+ @trie.has_key?("A").should be_true
15
+ end
16
+
17
+ it "returns false for a non-existing key" do
18
+ @trie.has_key?("B").should be_false
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,97 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ module Hamster
4
+
5
+ describe Trie do
6
+
7
+ describe "#put" do
8
+
9
+ describe "with a key/value pair that already exists" do
10
+
11
+ before do
12
+ @original = Trie.new.put("A", "aye").put("B", "bee")
13
+ @copy = @original.put("A", "yes")
14
+ end
15
+
16
+ it "returns a modified copy" do
17
+ @copy.should_not === @original
18
+ end
19
+
20
+ describe "the original" do
21
+
22
+ it "still has the original key/value pairs" do
23
+ @original.get("A").should == "aye"
24
+ @original.get("B").should == "bee"
25
+ end
26
+
27
+ it "still has the original size" do
28
+ @original.size.should == 2
29
+ end
30
+
31
+ end
32
+
33
+ describe "the modified copy" do
34
+
35
+ it "has the new key/value pairs" do
36
+ @copy.get("A").should == "yes"
37
+ @copy.get("B").should == "bee"
38
+ end
39
+
40
+ it "has the original size" do
41
+ @copy.size == 2
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+
48
+ describe "with a key/value pair that doesn't exist" do
49
+
50
+ before do
51
+ @original = Trie.new.put("A", "aye")
52
+ @copy = @original.put("B", "bee")
53
+ end
54
+
55
+ it "returns a modified copy" do
56
+ @copy.should_not === @original
57
+ end
58
+
59
+ describe "the original" do
60
+
61
+ it "still has the original key/value pairs" do
62
+ @original.get("A").should == "aye"
63
+ end
64
+
65
+ it "doesn't contain the new key/value pair" do
66
+ @original.has_key?("B").should be_false
67
+ end
68
+
69
+ it "still has the original size" do
70
+ @original.size.should == 1
71
+ end
72
+
73
+ end
74
+
75
+ describe "the modified copy" do
76
+
77
+ it "has the original key/value pairs" do
78
+ @copy.get("A").should == "aye"
79
+ end
80
+
81
+ it "has the new key/value pair" do
82
+ @copy.get("B").should == "bee"
83
+ end
84
+
85
+ it "size is increased by one" do
86
+ @copy.size.should == 2
87
+ end
88
+
89
+ end
90
+
91
+ end
92
+
93
+ end
94
+
95
+ end
96
+
97
+ end
@@ -0,0 +1,115 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ module Hamster
4
+
5
+ describe Trie do
6
+
7
+ describe "#remove" do
8
+
9
+ describe "with an existing key" do
10
+
11
+ before do
12
+ @original = Trie.new.put("A", "aye").put("B", "bee")
13
+ @copy = @original.remove("A")
14
+ end
15
+
16
+ it "returns a modified copy" do
17
+ @copy.should_not === @original
18
+ end
19
+
20
+ describe "the original" do
21
+
22
+ it "still has the original key/value pairs" do
23
+ @original.get("A").should == "aye"
24
+ @original.get("B").should == "bee"
25
+ end
26
+
27
+ it "still has the original size" do
28
+ @original.size.should == 2
29
+ end
30
+
31
+ end
32
+
33
+ describe "the modified copy" do
34
+
35
+ it "has all but the removed original key/value pairs" do
36
+ @copy.get("B").should == "bee"
37
+ end
38
+
39
+ it "doesn't have the removed key" do
40
+ @copy.has_key?("A").should be_false
41
+ end
42
+
43
+ it "has a size one less than the original" do
44
+ @copy.size.should == 1
45
+ end
46
+
47
+ end
48
+
49
+ end
50
+
51
+ describe "with non-existing keys" do
52
+
53
+ before do
54
+ @original = Trie.new.put("A", "aye")
55
+ @copy = @original.remove("missing")
56
+ end
57
+
58
+ it "returns self" do
59
+ @copy.should === @original
60
+ end
61
+
62
+ describe "the original" do
63
+
64
+ it "still has the original key/value pairs" do
65
+ @original.get("A").should == "aye"
66
+ end
67
+
68
+ it "still has the original size" do
69
+ @original.size.should == 1
70
+ end
71
+
72
+ end
73
+
74
+ end
75
+
76
+ describe "with keys of the same hash value" do
77
+
78
+ class Key
79
+ def hash; 1; end
80
+ end
81
+
82
+ def number_of_tries
83
+ ObjectSpace.garbage_collect
84
+ ObjectSpace.each_object(Trie) {}
85
+ end
86
+
87
+ before do
88
+ @a = Key.new
89
+ @b = Key.new
90
+ @original = Trie.new.put(@a, "aye").put(@b, "bee")
91
+ end
92
+
93
+ it "no longer provides access to the removed key" do
94
+ copy = @original.remove(@b)
95
+ copy.has_key?(@b).should be_false
96
+ end
97
+
98
+ it "provides access to the remaining keys" do
99
+ copy = @original.remove(@a)
100
+ copy.get(@b).should == "bee"
101
+ end
102
+
103
+ it "cleans up empty tries" do
104
+ number_of_tries_before = number_of_tries
105
+ copy = @original.remove(@b)
106
+ number_of_tries.should == number_of_tries_before + 1
107
+ end
108
+
109
+ end
110
+
111
+ end
112
+
113
+ end
114
+
115
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hamster
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Harris
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-10-24 00:00:00 +11:00
12
+ date: 2009-10-25 00:00:00 +11:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -29,7 +29,13 @@ files:
29
29
  - lib/hamster/trie.rb
30
30
  - lib/hamster/version.rb
31
31
  - lib/hamster.rb
32
- - spec/hamster/trie_spec.rb
32
+ - spec/hamster/trie/each_spec.rb
33
+ - spec/hamster/trie/empty_spec.rb
34
+ - spec/hamster/trie/enumerable_spec.rb
35
+ - spec/hamster/trie/get_spec.rb
36
+ - spec/hamster/trie/has_key_spec.rb
37
+ - spec/hamster/trie/put_spec.rb
38
+ - spec/hamster/trie/remove_spec.rb
33
39
  - spec/spec.opts
34
40
  - spec/spec_helper.rb
35
41
  - tasks/spec.rb
@@ -1,267 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
-
3
- module Hamster
4
-
5
- describe Trie do
6
-
7
- before do
8
- @expected_pairs = {}
9
- @trie = Trie.new
10
- ("A".."Z").each do |letter|
11
- @expected_pairs.store(letter, letter.downcase)
12
- @trie = @trie.put(letter, letter.downcase)
13
- end
14
- end
15
-
16
- it "is Enumerable" do
17
- Trie.is_a?(Enumerable)
18
- end
19
-
20
- describe "#empty?" do
21
-
22
- it "initially returns true" do
23
- Trie.new.should be_empty
24
- end
25
-
26
- it "returns false once items have been added" do
27
- @trie.should_not be_empty
28
- end
29
-
30
- end
31
-
32
- describe "#each" do
33
-
34
- describe "with a block (internal iteration)" do
35
-
36
- it "returns self" do
37
- @trie.each {}.should == @trie
38
-
39
- end
40
-
41
- it "yields all key value pairs" do
42
- actual_pairs = {}
43
- @trie.each do |key, value|
44
- actual_pairs[key] = value
45
- end
46
- actual_pairs.should == @expected_pairs
47
- end
48
-
49
- end
50
-
51
- describe "with no block (external iteration)" do
52
-
53
- it "returns an enumerator over all key value pairs" do
54
- actual_pairs = {}
55
- enum = @trie.each
56
- loop do
57
- key, value = enum.next
58
- actual_pairs[key] = value
59
- end
60
- actual_pairs.should == @expected_pairs
61
- end
62
-
63
- end
64
-
65
- end
66
-
67
- describe "#get" do
68
-
69
- it "returns values associated with existing keys" do
70
- @expected_pairs.each do |key, value|
71
- @trie.get(key).should == value
72
- end
73
- end
74
-
75
- it "returns nil for non-existing" do
76
- @trie.get("missing").should be_nil
77
- end
78
-
79
- end
80
-
81
- describe "#has_key?" do
82
-
83
- it "returns true for existing keys" do
84
- @expected_pairs.each_key do |key|
85
- @trie.has_key?(key).should be_true
86
- end
87
- end
88
-
89
- it "returns false for non-existing keys" do
90
- @trie.has_key?("missing").should be_false
91
- end
92
-
93
- end
94
-
95
- describe "#put" do
96
-
97
- describe "with key/value pairs that already exists" do
98
-
99
- before do
100
- @copy = @trie.put("J", "jay")
101
- end
102
-
103
- it "returns a modified copy" do
104
- @copy.should_not === @trie
105
- end
106
-
107
- describe "the original" do
108
-
109
- it "still has the original key/value pairs" do
110
- @expected_pairs.each do |key, value|
111
- @trie.get(key).should == value
112
- end
113
- end
114
-
115
- it "still has the original size" do
116
- @trie.size.should == @expected_pairs.size
117
- end
118
-
119
- end
120
-
121
- describe "the modified copy" do
122
-
123
- it "has the new key/value pair" do
124
- @copy.get("J").should == "jay"
125
- end
126
-
127
- it "has the original size" do
128
- @trie.size.should == @expected_pairs.size
129
- end
130
-
131
- end
132
-
133
- end
134
-
135
- describe "with key/value pairs that don't exist"
136
-
137
- before do
138
- @copy = @trie.put("missing", "in action")
139
- end
140
-
141
- it "returns a modified copy" do
142
- @copy.should_not === @trie
143
- end
144
-
145
- describe "the original" do
146
-
147
- it "still has the original key/value pairs" do
148
- @expected_pairs.each do |key, value|
149
- @trie.get(key).should == value
150
- end
151
- end
152
-
153
- it "doesn't contain the new key/value pair" do
154
- @trie.has_key?("missing").should be_false
155
- end
156
-
157
- it "still has the original size" do
158
- @trie.size.should == @expected_pairs.size
159
- end
160
-
161
- end
162
-
163
- describe "the modified copy" do
164
-
165
- it "returns values associated with existing keys" do
166
- @expected_pairs.each do |key, value|
167
- @copy.get(key).should == value
168
- end
169
- end
170
-
171
- it "has the new key/value pair" do
172
- @copy.get("missing").should == "in action"
173
- end
174
-
175
- it "size is increased by 1" do
176
- @copy.size.should == @expected_pairs.size + 1
177
- end
178
-
179
- end
180
-
181
- end
182
-
183
- describe "#remove" do
184
-
185
- it "can be used successively to remove all key/value pairs" do
186
- @expected_pairs.each do |key, value|
187
- @trie = @trie.remove(key)
188
- end
189
- @trie.should be_empty
190
- end
191
-
192
- describe "with existing keys" do
193
-
194
- before do
195
- @copy = @trie.remove("J")
196
- end
197
-
198
- it "returns a modified copy" do
199
- @copy.should_not === @trie
200
- end
201
-
202
- describe "the original" do
203
-
204
- it "still has the original key/value pairs" do
205
- @expected_pairs.each do |key, value|
206
- @trie.get(key).should == value
207
- end
208
- end
209
-
210
- it "still has the original size" do
211
- @trie.size.should == @expected_pairs.size
212
- end
213
-
214
- end
215
-
216
- describe "the modified copy" do
217
-
218
- it "doesn't have the removed key" do
219
- @copy.has_key?("J").should be_false
220
- end
221
-
222
- it "returns values associated with all but the removed key" do
223
- @expected_pairs.each do |key, value|
224
- next if key == "J"
225
- @copy.get(key).should == value
226
- end
227
- end
228
-
229
- it "has one less than the original" do
230
- @copy.size.should == @expected_pairs.size - 1
231
- end
232
-
233
- end
234
-
235
- end
236
-
237
- describe "with non-existing keys" do
238
-
239
- before do
240
- @copy = @trie.remove("missing")
241
- end
242
-
243
- it "returns self" do
244
- @copy.should === @trie
245
- end
246
-
247
- describe "the original" do
248
-
249
- it "returns values associated with existing keys" do
250
- @expected_pairs.each do |key, value|
251
- @trie.get(key).should == value
252
- end
253
- end
254
-
255
- it "has the original size" do
256
- @trie.size.should == @expected_pairs.size
257
- end
258
-
259
- end
260
-
261
- end
262
-
263
- end
264
-
265
- end
266
-
267
- end