hamster 0.3.3 → 0.3.4
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/History.rdoc +18 -0
- data/lib/hamster/hash.rb +10 -3
- data/lib/hamster/immutable.rb +37 -8
- data/lib/hamster/list.rb +1 -1
- data/lib/hamster/read_copy_update_hash.rb +46 -0
- data/lib/hamster/read_copy_update_set.rb +40 -0
- data/lib/hamster/read_copy_update_stack.rb +43 -0
- data/lib/hamster/set.rb +3 -2
- data/lib/hamster/stack.rb +1 -3
- data/lib/hamster/trie.rb +2 -2
- data/lib/hamster/tuple.rb +1 -2
- data/lib/hamster/version.rb +1 -1
- data/spec/hamster/hash/merge_spec.rb +36 -0
- data/spec/hamster/immutable/memoize_spec.rb +64 -0
- data/spec/hamster/set/union_spec.rb +1 -1
- metadata +8 -3
data/History.rdoc
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
=== 0.3.4 / 2010-04-29
|
2
|
+
|
3
|
+
* Reduce garbage collection by keeping a singleton instance of an empty hash.
|
4
|
+
|
5
|
+
* Added some VERY experimental Read-Copy-Update collections (no tests).
|
6
|
+
|
7
|
+
* Slightly improve performance and greatly improve the readability of #eql?/#== methods.
|
8
|
+
|
9
|
+
* Added some VERY experimental memoization for immutable objects.
|
10
|
+
|
11
|
+
* Added Set#merge as an alias for Set#union.
|
12
|
+
|
13
|
+
* Implemented Hash#merge (aliased as #+).
|
14
|
+
|
15
|
+
=== 0.3.3 / 2010-04-09
|
16
|
+
|
17
|
+
* Fixed: hash code generation had VERY poor distribution.
|
18
|
+
|
1
19
|
=== 0.3.1 / 2010-04-04
|
2
20
|
|
3
21
|
* Fixed: Immutable not required in hamster.rb
|
data/lib/hamster/hash.rb
CHANGED
@@ -6,7 +6,7 @@ require 'hamster/trie'
|
|
6
6
|
module Hamster
|
7
7
|
|
8
8
|
def self.hash(pairs = {})
|
9
|
-
pairs.reduce(
|
9
|
+
pairs.reduce(EmptyHash) { |hash, pair| hash.put(pair.first, pair.last) }
|
10
10
|
end
|
11
11
|
|
12
12
|
class Hash
|
@@ -38,7 +38,7 @@ module Hamster
|
|
38
38
|
|
39
39
|
def get(key)
|
40
40
|
entry = @trie.get(key)
|
41
|
-
|
41
|
+
entry.value if entry
|
42
42
|
end
|
43
43
|
def_delegator :self, :get, :[]
|
44
44
|
|
@@ -114,12 +114,17 @@ module Hamster
|
|
114
114
|
end
|
115
115
|
def_delegator :self, :find, :detect
|
116
116
|
|
117
|
+
def merge(other)
|
118
|
+
transform { @trie = other.reduce(@trie, &:put) }
|
119
|
+
end
|
120
|
+
def_delegator :self, :merge, :+
|
121
|
+
|
117
122
|
def keys
|
118
123
|
reduce(Hamster.set) { |keys, key, value| keys.add(key) }
|
119
124
|
end
|
120
125
|
|
121
126
|
def eql?(other)
|
122
|
-
|
127
|
+
instance_of?(other.class) && @trie.eql?(other.instance_variable_get(:@trie))
|
123
128
|
end
|
124
129
|
def_delegator :self, :eql?, :==
|
125
130
|
|
@@ -137,4 +142,6 @@ module Hamster
|
|
137
142
|
|
138
143
|
end
|
139
144
|
|
145
|
+
EmptyHash = Hash.new
|
146
|
+
|
140
147
|
end
|
data/lib/hamster/immutable.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'forwardable'
|
2
|
-
|
3
1
|
module Hamster
|
4
2
|
|
5
3
|
module Immutable
|
@@ -14,26 +12,57 @@ module Hamster
|
|
14
12
|
module ClassMethods
|
15
13
|
|
16
14
|
def new(*args)
|
17
|
-
super.
|
15
|
+
super.immutable!
|
16
|
+
end
|
17
|
+
|
18
|
+
def memoize(*names)
|
19
|
+
include MemoizeMethods unless include?(MemoizeMethods)
|
20
|
+
names.each do |name|
|
21
|
+
original_method = "__hamster_immutable_#{name}__"
|
22
|
+
alias_method original_method, name
|
23
|
+
class_eval <<-METHOD, __FILE__, __LINE__
|
24
|
+
def #{name}
|
25
|
+
if @__hamster_immutable_memory__.instance_variable_defined?(:@#{name})
|
26
|
+
@__hamster_immutable_memory__.instance_variable_get(:@#{name})
|
27
|
+
else
|
28
|
+
@__hamster_immutable_memory__.instance_variable_set(:@#{name}, #{original_method})
|
29
|
+
end
|
30
|
+
end
|
31
|
+
METHOD
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
module MemoizeMethods
|
38
|
+
|
39
|
+
def immutable!
|
40
|
+
@__hamster_immutable_memory__ = Object.new
|
41
|
+
freeze
|
18
42
|
end
|
19
43
|
|
20
44
|
end
|
21
45
|
|
22
46
|
module InstanceMethods
|
23
47
|
|
24
|
-
|
48
|
+
def immutable!
|
49
|
+
freeze
|
50
|
+
end
|
25
51
|
|
26
52
|
def immutable?
|
27
53
|
frozen?
|
28
54
|
end
|
29
55
|
|
30
|
-
alias_method :
|
31
|
-
private :
|
56
|
+
alias_method :__hamster_immutable_dup__, :dup
|
57
|
+
private :__hamster_immutable_dup__
|
32
58
|
|
33
59
|
def dup
|
34
60
|
self
|
35
61
|
end
|
36
|
-
|
62
|
+
|
63
|
+
def clone
|
64
|
+
self
|
65
|
+
end
|
37
66
|
|
38
67
|
protected
|
39
68
|
|
@@ -42,7 +71,7 @@ module Hamster
|
|
42
71
|
end
|
43
72
|
|
44
73
|
def transform(&block)
|
45
|
-
|
74
|
+
__hamster_immutable_dup__.tap { |copy| copy.instance_eval(&block) }.immutable!
|
46
75
|
end
|
47
76
|
|
48
77
|
end
|
data/lib/hamster/list.rb
CHANGED
@@ -380,7 +380,7 @@ module Hamster
|
|
380
380
|
|
381
381
|
def group_by(&block)
|
382
382
|
return group_by { |item| item } unless block_given?
|
383
|
-
reduce(
|
383
|
+
reduce(EmptyHash) do |hash, item|
|
384
384
|
key = yield(item)
|
385
385
|
hash.put(key, (hash.get(key) || EmptyList).cons(item))
|
386
386
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
require 'hamster/hash'
|
5
|
+
|
6
|
+
module Hamster
|
7
|
+
|
8
|
+
class ReadCopyUpdateHash
|
9
|
+
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@hash = EmptyHash
|
14
|
+
@lock = Mutex.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def put(key, value)
|
18
|
+
@lock.synchronize {
|
19
|
+
original_value = @hash.get(key)
|
20
|
+
@hash = @hash.put(key, value)
|
21
|
+
original_value
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete(key)
|
26
|
+
@lock.synchronize {
|
27
|
+
original_value = @hash.get(key)
|
28
|
+
@hash = @hash.delete(key)
|
29
|
+
original_value
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def eql?(other)
|
34
|
+
instance_of?(other.class) && @hash.eql?(other.instance_variable_get(:@hash))
|
35
|
+
end
|
36
|
+
def_delegator :self, :eql?, :==
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def method_missing(name, *args, &block)
|
41
|
+
@hash.send(name, *args, &block)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
require 'hamster/set'
|
5
|
+
|
6
|
+
module Hamster
|
7
|
+
|
8
|
+
class ReadCopyUpdateSet
|
9
|
+
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@set = EmptySet
|
14
|
+
@lock = Mutex.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def add(value)
|
18
|
+
@lock.synchronize { @set = @set.add(value) }
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def delete(value)
|
23
|
+
@lock.synchronize { @set = @set.delete(value) }
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def eql?(other)
|
28
|
+
instance_of?(other.class) && @set.eql?(other.instance_variable_get(:@set))
|
29
|
+
end
|
30
|
+
def_delegator :self, :eql?, :==
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def method_missing(name, *args, &block)
|
35
|
+
@set.send(name, *args, &block)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
require 'hamster/set'
|
5
|
+
|
6
|
+
module Hamster
|
7
|
+
|
8
|
+
class ReadCopyUpdateStack
|
9
|
+
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@stack = EmptyStack
|
14
|
+
@lock = Mutex.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def push(value)
|
18
|
+
@lock.synchronize { @stack = @stack.push(value) }
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def pop
|
23
|
+
@lock.synchronize {
|
24
|
+
top_value = @stack.peek
|
25
|
+
@stack = @stack.pop
|
26
|
+
top_value
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def eql?(other)
|
31
|
+
instance_of?(other.class) && @stack.eql?(other.instance_variable_get(:@stack))
|
32
|
+
end
|
33
|
+
def_delegator :self, :eql?, :==
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def method_missing(name, *args, &block)
|
38
|
+
@stack.send(name, *args, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
data/lib/hamster/set.rb
CHANGED
@@ -191,6 +191,7 @@ module Hamster
|
|
191
191
|
end
|
192
192
|
def_delegator :self, :union, :|
|
193
193
|
def_delegator :self, :union, :+
|
194
|
+
def_delegator :self, :union, :merge
|
194
195
|
|
195
196
|
def intersection(other)
|
196
197
|
trie = @trie.filter { |entry| other.include?(entry.key) }
|
@@ -233,7 +234,7 @@ module Hamster
|
|
233
234
|
|
234
235
|
def group_by(&block)
|
235
236
|
return group_by { |item| item } unless block_given?
|
236
|
-
reduce(
|
237
|
+
reduce(EmptyHash) do |hash, item|
|
237
238
|
key = yield(item)
|
238
239
|
hash.put(key, (hash.get(key) || EmptySet).add(item))
|
239
240
|
end
|
@@ -244,7 +245,7 @@ module Hamster
|
|
244
245
|
end
|
245
246
|
|
246
247
|
def eql?(other)
|
247
|
-
|
248
|
+
instance_of?(other.class) && @trie.eql?(other.instance_variable_get(:@trie))
|
248
249
|
end
|
249
250
|
def_delegator :self, :eql?, :==
|
250
251
|
|
data/lib/hamster/stack.rb
CHANGED
@@ -51,9 +51,7 @@ module Hamster
|
|
51
51
|
end
|
52
52
|
|
53
53
|
def eql?(other)
|
54
|
-
|
55
|
-
return false unless other.class.equal?(self.class)
|
56
|
-
@list.eql?(other.instance_eval{@list})
|
54
|
+
instance_of?(other.class) && @list.eql?(other.instance_variable_get(:@list))
|
57
55
|
end
|
58
56
|
def_delegator :self, :eql?, :==
|
59
57
|
|
data/lib/hamster/trie.rb
CHANGED
@@ -91,8 +91,8 @@ module Hamster
|
|
91
91
|
|
92
92
|
# Returns <tt>true</tt> if . <tt>eql?</tt> is synonymous with <tt>==</tt>
|
93
93
|
def eql?(other)
|
94
|
-
|
95
|
-
return false unless
|
94
|
+
return true if equal?(other)
|
95
|
+
return false unless instance_of?(other.class) && size == other.size
|
96
96
|
each do |entry|
|
97
97
|
return false unless other.include?(entry.key, entry.value)
|
98
98
|
end
|
data/lib/hamster/tuple.rb
CHANGED
@@ -24,8 +24,7 @@ module Hamster
|
|
24
24
|
|
25
25
|
def eql?(other)
|
26
26
|
return true if other.equal?(self)
|
27
|
-
|
28
|
-
@items.eql?(other.instance_eval{@items})
|
27
|
+
instance_of?(other.class) && @items.eql?(other.instance_variable_get(:@items))
|
29
28
|
end
|
30
29
|
def_delegator :self, :eql?, :==
|
31
30
|
|
data/lib/hamster/version.rb
CHANGED
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.expand_path('../../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
require 'hamster/hash'
|
4
|
+
|
5
|
+
describe Hamster::Hash do
|
6
|
+
|
7
|
+
[:merge, :+].each do |method|
|
8
|
+
|
9
|
+
describe "##{method}" do
|
10
|
+
|
11
|
+
[
|
12
|
+
[[], [], []],
|
13
|
+
[["A" => "aye"], [], ["A" => "aye"]],
|
14
|
+
[["A" => "aye"], ["A" => "bee"], ["A" => "bee"]],
|
15
|
+
[["A" => "aye"], ["B" => "bee"], ["A" => "aye", "B" => "bee"]],
|
16
|
+
].each do |a, b, expected|
|
17
|
+
|
18
|
+
describe "for #{a.inspect} and #{b.inspect}" do
|
19
|
+
|
20
|
+
before do
|
21
|
+
@result = Hamster.hash(*a).send(method, Hamster.hash(*b))
|
22
|
+
end
|
23
|
+
|
24
|
+
it "returns #{expected.inspect}" do
|
25
|
+
@result.should == Hamster.hash(*expected)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require File.expand_path('../../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
require 'hamster/immutable'
|
4
|
+
|
5
|
+
describe Hamster::Immutable do
|
6
|
+
|
7
|
+
describe "#memoize" do
|
8
|
+
|
9
|
+
class Fixture
|
10
|
+
include Hamster::Immutable
|
11
|
+
|
12
|
+
def initialize(&block)
|
13
|
+
@block = block
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
@block.call
|
18
|
+
end
|
19
|
+
memoize :call
|
20
|
+
|
21
|
+
def copy
|
22
|
+
transform {}
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
before do
|
28
|
+
@count = 0
|
29
|
+
@fixture = Fixture.new { @count += 1 }
|
30
|
+
@fixture.call
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should still freezes be immutable" do
|
34
|
+
@fixture.should be_immutable
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "when called multiple times" do
|
38
|
+
|
39
|
+
before do
|
40
|
+
@fixture.call
|
41
|
+
end
|
42
|
+
|
43
|
+
it "a memoized method will only be evaluated once" do
|
44
|
+
@count.should == 1
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "when making a copy" do
|
50
|
+
|
51
|
+
before do
|
52
|
+
@copy = @fixture.copy
|
53
|
+
@copy.call
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should clear all memory" do
|
57
|
+
@count.should == 2
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 3
|
8
|
-
-
|
9
|
-
version: 0.3.
|
8
|
+
- 4
|
9
|
+
version: 0.3.4
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Simon Harris
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-04-
|
17
|
+
date: 2010-04-29 00:00:00 +10:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -62,6 +62,9 @@ files:
|
|
62
62
|
- lib/hamster/hash.rb
|
63
63
|
- lib/hamster/immutable.rb
|
64
64
|
- lib/hamster/list.rb
|
65
|
+
- lib/hamster/read_copy_update_hash.rb
|
66
|
+
- lib/hamster/read_copy_update_set.rb
|
67
|
+
- lib/hamster/read_copy_update_stack.rb
|
65
68
|
- lib/hamster/set.rb
|
66
69
|
- lib/hamster/sorter.rb
|
67
70
|
- lib/hamster/stack.rb
|
@@ -91,6 +94,7 @@ files:
|
|
91
94
|
- spec/hamster/hash/inspect_spec.rb
|
92
95
|
- spec/hamster/hash/keys_spec.rb
|
93
96
|
- spec/hamster/hash/map_spec.rb
|
97
|
+
- spec/hamster/hash/merge_spec.rb
|
94
98
|
- spec/hamster/hash/none_spec.rb
|
95
99
|
- spec/hamster/hash/put_spec.rb
|
96
100
|
- spec/hamster/hash/reduce_spec.rb
|
@@ -99,6 +103,7 @@ files:
|
|
99
103
|
- spec/hamster/hash/uniq_spec.rb
|
100
104
|
- spec/hamster/immutable/copying_spec.rb
|
101
105
|
- spec/hamster/immutable/immutable_spec.rb
|
106
|
+
- spec/hamster/immutable/memoize_spec.rb
|
102
107
|
- spec/hamster/immutable/new_spec.rb
|
103
108
|
- spec/hamster/immutable/transform_spec.rb
|
104
109
|
- spec/hamster/immutable/transform_unless_spec.rb
|