hamster 0.3.3 → 0.3.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|