hamster 0.3.3 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -6,7 +6,7 @@ require 'hamster/trie'
6
6
  module Hamster
7
7
 
8
8
  def self.hash(pairs = {})
9
- pairs.reduce(Hash.new) { |hash, pair| hash.put(pair.first, pair.last) }
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
- return entry.value if entry
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
- other.is_a?(self.class) && @trie.eql?(other.instance_eval{@trie})
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
@@ -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.freeze
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
- extend Forwardable
48
+ def immutable!
49
+ freeze
50
+ end
25
51
 
26
52
  def immutable?
27
53
  frozen?
28
54
  end
29
55
 
30
- alias_method :__copy__, :dup
31
- private :__copy__
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
- def_delegator :self, :dup, :clone
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
- __copy__.tap { |copy| copy.instance_eval(&block) }.freeze
74
+ __hamster_immutable_dup__.tap { |copy| copy.instance_eval(&block) }.immutable!
46
75
  end
47
76
 
48
77
  end
@@ -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(Hamster::Hash.new) do |hash, item|
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
@@ -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(Hamster::Hash.new) do |hash, item|
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
- other.is_a?(self.class) && @trie.eql?(other.instance_eval{@trie})
248
+ instance_of?(other.class) && @trie.eql?(other.instance_variable_get(:@trie))
248
249
  end
249
250
  def_delegator :self, :eql?, :==
250
251
 
@@ -51,9 +51,7 @@ module Hamster
51
51
  end
52
52
 
53
53
  def eql?(other)
54
- return true if other.equal?(self)
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
 
@@ -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
- # return true if other.equal?(self)
95
- return false unless other.is_a?(self.class) && other.size == size
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
@@ -24,8 +24,7 @@ module Hamster
24
24
 
25
25
  def eql?(other)
26
26
  return true if other.equal?(self)
27
- return false unless other.class.equal?(self.class)
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
 
@@ -1,5 +1,5 @@
1
1
  module Hamster
2
2
 
3
- VERSION = "0.3.3".freeze
3
+ VERSION = "0.3.4".freeze
4
4
 
5
5
  end
@@ -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
@@ -4,7 +4,7 @@ require 'hamster/set'
4
4
 
5
5
  describe Hamster::Set do
6
6
 
7
- [:union, :|, :+].each do |method|
7
+ [:union, :|, :+, :merge].each do |method|
8
8
 
9
9
  describe "##{method}" do
10
10
 
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 3
8
- - 3
9
- version: 0.3.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-09 00:00:00 +10:00
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