hamster 0.2.7 → 0.2.8
Sign up to get free protection for your applications and to get access to all the features.
- data/History.rdoc +18 -0
- data/README.rdoc +107 -79
- data/lib/hamster/hash.rb +0 -1
- data/lib/hamster/set.rb +46 -12
- data/lib/hamster/version.rb +1 -1
- data/spec/hamster/core_ext/array_spec.rb +1 -1
- data/spec/hamster/core_ext/enumerable_spec.rb +1 -1
- data/spec/hamster/core_ext/io_spec.rb +1 -1
- data/spec/hamster/hash/all_spec.rb +1 -1
- data/spec/hamster/hash/any_spec.rb +1 -1
- data/spec/hamster/hash/construction_spec.rb +1 -1
- data/spec/hamster/hash/copying_spec.rb +1 -1
- data/spec/hamster/hash/delete_spec.rb +1 -1
- data/spec/hamster/hash/each_spec.rb +1 -1
- data/spec/hamster/hash/empty_spec.rb +1 -1
- data/spec/hamster/hash/eql_spec.rb +1 -1
- data/spec/hamster/hash/filter_spec.rb +1 -1
- data/spec/hamster/hash/find_spec.rb +1 -1
- data/spec/hamster/hash/get_spec.rb +1 -1
- data/spec/hamster/hash/has_key_spec.rb +1 -1
- data/spec/hamster/hash/inspect_spec.rb +1 -1
- data/spec/hamster/hash/map_spec.rb +1 -1
- data/spec/hamster/hash/none_spec.rb +1 -1
- data/spec/hamster/hash/put_spec.rb +23 -27
- data/spec/hamster/hash/reduce_spec.rb +1 -1
- data/spec/hamster/hash/remove_spec.rb +1 -1
- data/spec/hamster/hash/size_spec.rb +1 -1
- data/spec/hamster/hash/uniq_spec.rb +1 -1
- data/spec/hamster/list/all_spec.rb +1 -1
- data/spec/hamster/list/any_spec.rb +1 -1
- data/spec/hamster/list/append_spec.rb +1 -1
- data/spec/hamster/list/at_spec.rb +1 -1
- data/spec/hamster/list/break_spec.rb +1 -1
- data/spec/hamster/list/cadr_spec.rb +1 -1
- data/spec/hamster/list/chunk_spec.rb +1 -1
- data/spec/hamster/list/clear_spec.rb +1 -1
- data/spec/hamster/list/combinations_spec.rb +1 -1
- data/spec/hamster/list/compact_spec.rb +1 -1
- data/spec/hamster/list/cons_spec.rb +1 -1
- data/spec/hamster/list/construction_spec.rb +1 -1
- data/spec/hamster/list/copying_spec.rb +1 -1
- data/spec/hamster/list/count_spec.rb +1 -1
- data/spec/hamster/list/cycle_spec.rb +1 -1
- data/spec/hamster/list/drop_spec.rb +1 -1
- data/spec/hamster/list/drop_while_spec.rb +1 -1
- data/spec/hamster/list/each_slice_spec.rb +1 -1
- data/spec/hamster/list/each_spec.rb +1 -1
- data/spec/hamster/list/elem_index_spec.rb +1 -1
- data/spec/hamster/list/elem_indices_spec.rb +1 -1
- data/spec/hamster/list/empty_spec.rb +1 -1
- data/spec/hamster/list/eql_spec.rb +1 -1
- data/spec/hamster/list/filter_spec.rb +1 -1
- data/spec/hamster/list/find_index_spec.rb +1 -1
- data/spec/hamster/list/find_indices_spec.rb +1 -1
- data/spec/hamster/list/find_spec.rb +1 -1
- data/spec/hamster/list/flatten_spec.rb +1 -1
- data/spec/hamster/list/grep_spec.rb +1 -1
- data/spec/hamster/list/group_by_spec.rb +1 -1
- data/spec/hamster/list/head_spec.rb +1 -1
- data/spec/hamster/list/include_spec.rb +1 -1
- data/spec/hamster/list/init_spec.rb +1 -1
- data/spec/hamster/list/inits_spec.rb +1 -1
- data/spec/hamster/list/inspect_spec.rb +1 -1
- data/spec/hamster/list/intersperse_spec.rb +1 -1
- data/spec/hamster/list/join_spec.rb +1 -1
- data/spec/hamster/list/last_spec.rb +1 -1
- data/spec/hamster/list/map_spec.rb +1 -1
- data/spec/hamster/list/maximum_spec.rb +1 -1
- data/spec/hamster/list/minimum_spec.rb +1 -1
- data/spec/hamster/list/none_spec.rb +1 -1
- data/spec/hamster/list/one_spec.rb +1 -1
- data/spec/hamster/list/partition_spec.rb +1 -1
- data/spec/hamster/list/product_spec.rb +1 -1
- data/spec/hamster/list/reduce_spec.rb +1 -1
- data/spec/hamster/list/remove_spec.rb +1 -1
- data/spec/hamster/list/reverse_spec.rb +1 -1
- data/spec/hamster/list/size_spec.rb +1 -1
- data/spec/hamster/list/slice_spec.rb +1 -1
- data/spec/hamster/list/sorting_spec.rb +1 -1
- data/spec/hamster/list/span_spec.rb +1 -1
- data/spec/hamster/list/split_at_spec.rb +1 -1
- data/spec/hamster/list/sum_spec.rb +1 -1
- data/spec/hamster/list/tail_spec.rb +1 -1
- data/spec/hamster/list/tails_spec.rb +1 -1
- data/spec/hamster/list/take_spec.rb +1 -1
- data/spec/hamster/list/take_while_spec.rb +1 -1
- data/spec/hamster/list/to_a_spec.rb +1 -1
- data/spec/hamster/list/to_ary_spec.rb +1 -1
- data/spec/hamster/list/to_list_spec.rb +1 -1
- data/spec/hamster/list/union_spec.rb +2 -2
- data/spec/hamster/list/uniq_spec.rb +1 -1
- data/spec/hamster/list/zip_spec.rb +1 -1
- data/spec/hamster/set/add_spec.rb +1 -1
- data/spec/hamster/set/all_spec.rb +1 -1
- data/spec/hamster/set/any_spec.rb +1 -1
- data/spec/hamster/set/clear_spec.rb +1 -1
- data/spec/hamster/set/compact_spec.rb +1 -1
- data/spec/hamster/set/construction_spec.rb +1 -1
- data/spec/hamster/set/copying_spec.rb +1 -1
- data/spec/hamster/set/count_spec.rb +1 -1
- data/spec/hamster/set/delete_spec.rb +1 -1
- data/spec/hamster/set/difference_spec.rb +37 -0
- data/spec/hamster/set/each_spec.rb +1 -1
- data/spec/hamster/set/empty_spec.rb +1 -1
- data/spec/hamster/set/eql_spec.rb +1 -1
- data/spec/hamster/set/exclusion_spec.rb +38 -0
- data/spec/hamster/set/filter_spec.rb +1 -1
- data/spec/hamster/set/find_spec.rb +1 -1
- data/spec/hamster/set/grep_spec.rb +1 -1
- data/spec/hamster/set/group_by_spec.rb +1 -1
- data/spec/hamster/set/head_spec.rb +1 -1
- data/spec/hamster/set/include_spec.rb +1 -1
- data/spec/hamster/set/inspect_spec.rb +1 -1
- data/spec/hamster/set/intersection_spec.rb +46 -0
- data/spec/hamster/set/join_spec.rb +1 -1
- data/spec/hamster/set/map_spec.rb +1 -1
- data/spec/hamster/set/maximum_spec.rb +1 -1
- data/spec/hamster/set/minimum_spec.rb +1 -1
- data/spec/hamster/set/none_spec.rb +1 -1
- data/spec/hamster/set/one_spec.rb +1 -1
- data/spec/hamster/set/partition_spec.rb +1 -1
- data/spec/hamster/set/product_spec.rb +1 -1
- data/spec/hamster/set/reduce_spec.rb +1 -1
- data/spec/hamster/set/remove_spec.rb +1 -1
- data/spec/hamster/set/size_spec.rb +1 -1
- data/spec/hamster/set/sorting_spec.rb +1 -1
- data/spec/hamster/set/subset_spec.rb +39 -0
- data/spec/hamster/set/sum_spec.rb +1 -1
- data/spec/hamster/set/superset_spec.rb +39 -0
- data/spec/hamster/set/to_a_spec.rb +1 -1
- data/spec/hamster/set/to_list_spec.rb +1 -1
- data/spec/hamster/set/to_set_spec.rb +1 -1
- data/spec/hamster/set/union_spec.rb +45 -0
- data/spec/hamster/set/uniq_spec.rb +1 -1
- data/spec/hamster/stack/clear_spec.rb +1 -1
- data/spec/hamster/stack/construction_spec.rb +1 -1
- data/spec/hamster/stack/copying_spec.rb +1 -1
- data/spec/hamster/stack/empty_spec.rb +1 -1
- data/spec/hamster/stack/eql_spec.rb +1 -1
- data/spec/hamster/stack/inspect_spec.rb +1 -1
- data/spec/hamster/stack/pop_spec.rb +1 -1
- data/spec/hamster/stack/push_spec.rb +1 -1
- data/spec/hamster/stack/size_spec.rb +1 -1
- data/spec/hamster/stack/to_a_spec.rb +1 -1
- data/spec/hamster/stack/to_list_spec.rb +1 -1
- data/spec/hamster/stack/top_spec.rb +1 -1
- data/spec/hamster/trie/remove_spec.rb +1 -1
- data/spec/hamster/tuple/copying_spec.rb +1 -1
- data/spec/hamster/tuple/eql_spec.rb +1 -1
- data/spec/hamster/tuple/first_spec.rb +1 -1
- data/spec/hamster/tuple/inspect_spec.rb +1 -1
- data/spec/hamster/tuple/last_spec.rb +1 -1
- data/spec/hamster/tuple/to_a_spec.rb +1 -1
- data/spec/hamster/tuple/to_ary_spec.rb +1 -1
- metadata +8 -2
data/History.rdoc
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
=== 0.2.8 / 2010-02-10
|
2
|
+
|
3
|
+
* Implement Set#exclusion (aliased as #^) as equivalent to ((a | b) - (a & b)).
|
4
|
+
|
5
|
+
* Implement Set#subset?
|
6
|
+
|
7
|
+
* Implement Set#superset?
|
8
|
+
|
9
|
+
* Implement Set#difference (aliased as #diff, #subtract, and #-).
|
10
|
+
|
11
|
+
* Implement Set#intersection (aliased as #intersect and #&).
|
12
|
+
|
13
|
+
* Implement Set#union (aliased as #| and #+).
|
14
|
+
|
15
|
+
* Implement Set#union (aliased as #| and #+).
|
16
|
+
|
17
|
+
* Remove Hash#[]= as its useless.
|
18
|
+
|
1
19
|
=== 0.2.7 / 2010-02-08
|
2
20
|
|
3
21
|
* Fixed bug with Hamster.interval (and Hamster.range) not handling Strings correctly.
|
data/README.rdoc
CHANGED
@@ -1,113 +1,93 @@
|
|
1
1
|
= Hamster - Efficient, Immutable, Thread-Safe Collection classes for Ruby
|
2
2
|
|
3
|
-
|
3
|
+
GitHub: http://github.com/harukizaemon/bundler
|
4
|
+
Gemcutter: http://gemcutter/gems/hamster
|
5
|
+
email: haruki.zaemon@gmail.com
|
6
|
+
IRC: #haruki_zaemon on freenode
|
4
7
|
|
5
|
-
==
|
8
|
+
== Introduction
|
6
9
|
|
7
|
-
|
10
|
+
Hamster started out as an implementation of Hash Array Mapped Trees (HAMT) for Ruby (see http://lamp.epfl.ch/papers/idealhashtrees.pdf) and has since expanded to include implementations of other Persistent Data Structures (see http://en.wikipedia.org/wiki/Persistent_data_structure) including Set, List, and Stack.
|
8
11
|
|
9
|
-
|
12
|
+
Hamster collections are immutable. Whenever you modify a Hamster collection, the original is preserved and a modified copy is returned. This makes them inherently thread-safe and sharable. (For an interesting perspective on why immutability itself is inherently a good thing, you might like to take a look at Matthias Felleisen's Function Objects presentation: http://www.ccs.neu.edu/home/matthias/Presentations/ecoop2004.pdf)
|
10
13
|
|
11
|
-
|
12
|
-
hash.has_key?("Name") # => false
|
13
|
-
hash.get("Name") # => nil
|
14
|
+
Hamster collection classes remain space efficient by making use of some very well understood and very simple techniques that enable sharing between copies.
|
14
15
|
|
15
|
-
|
16
|
+
Hamster collections are almost always closed under a given operation. That is, whereas Ruby's collection methods always return arrays, Hamster collections will return an instance of the same class wherever possible.
|
16
17
|
|
17
|
-
|
18
|
+
And lastly, Hamster lists are lazy -- where Ruby's language constructs permit -- making it possible to, among other things, process "infinitely large" lists. (Note: Ruby 1.9 supports a form of laziness using Enumerator however they're implemented using Fibers which unfortunately can't be shared across threads.)
|
18
19
|
|
19
|
-
|
20
|
-
copy = original.put("Name", "Simon")
|
20
|
+
== Installation
|
21
21
|
|
22
|
-
|
23
|
-
copy.get("Name") # => "Simon"
|
22
|
+
Hamster is distributed as a gem via gemcutter (http://gemcutter.org/gems/hamster) or as source via GitHub (http://github.com/harukizaemon/hamster).
|
24
23
|
|
25
|
-
|
24
|
+
Installation via the gem is easy:
|
26
25
|
|
27
|
-
|
28
|
-
original = original.put("Name", "Simon")
|
29
|
-
copy = hash.remove("Name")
|
26
|
+
> gem install hamster
|
30
27
|
|
31
|
-
|
32
|
-
copy.get("Name") # => nil
|
28
|
+
Once installed, all that remains is to make the collection classes available in your code:
|
33
29
|
|
34
|
-
|
30
|
+
require 'hamster'
|
35
31
|
|
36
|
-
|
32
|
+
If you prefer, you can instead require individual classes as necessary:
|
37
33
|
|
38
|
-
|
34
|
+
require 'hamster/hash'
|
35
|
+
require 'hamster/set'
|
36
|
+
require 'hamster/list'
|
37
|
+
require 'hamster/stack'
|
39
38
|
|
40
|
-
==
|
39
|
+
== Examples
|
41
40
|
|
42
|
-
|
41
|
+
Most Hamster classes support an API that resembles their standard library counterpart with the caveat that any modification returns a new instance. What follows are some simple examples to help illustrate this point.
|
43
42
|
|
44
|
-
|
45
|
-
(1..10000).each { |i| hash = hash.put(i, i) } # => 0.05s
|
46
|
-
(1..10000).each { |i| hash.get(i) } # => 0.008s
|
43
|
+
=== Hash
|
47
44
|
|
48
|
-
|
45
|
+
Constructing a Hamster Hash is almost as simple as a regular one:
|
49
46
|
|
50
|
-
|
51
|
-
(1..10000).each { |i| hash[i] = i } # => 0.004s
|
52
|
-
(1..10000).each { |i| hash[i] } # => 0.001s
|
47
|
+
simon = Hamster.hash(:name => "Simon", :gender => :male) # => {:name => "Simon", :gender => :male}
|
53
48
|
|
54
|
-
|
49
|
+
Accessing the contents will be familiar to you:
|
55
50
|
|
56
|
-
|
51
|
+
simon[:name] # => "Simon"
|
52
|
+
simon.get(:gender) # => :male
|
57
53
|
|
58
|
-
|
54
|
+
Updating the contents is a little different than you are used to:
|
59
55
|
|
60
|
-
|
56
|
+
james = simon.put(:name, "James") # => {:name => "James", :gender => :male}
|
57
|
+
simon # => {:name => "Simon", :gender => :male}
|
58
|
+
james[:name] # => "James"
|
59
|
+
simon[:name] # => "Simon"
|
61
60
|
|
62
|
-
|
63
|
-
(1..10000).each { |i| hash = hash.put(i, i) } # => 0.05s
|
64
|
-
(1..10000).each { |i| hash.get(i) } # => 0.008s
|
61
|
+
As you can see, updating the hash returned a copy leaving the original intact. Similarly, deleting a key returns yet another copy:
|
65
62
|
|
66
|
-
|
63
|
+
male = simon.delete(:name) # => {:gender => :male}
|
64
|
+
simon # => {:name => "Simon", :gender => :male}
|
65
|
+
male.has_key?(:name) # => false
|
66
|
+
simon.has_key?(:name) # => true
|
67
67
|
|
68
|
-
|
69
|
-
(1..10000).each { |i|
|
70
|
-
hash = hash.dup
|
71
|
-
hash[i] = i
|
72
|
-
} # => 19.8s
|
68
|
+
Hamster's hash doesn't provide an assignment (<tt>#[]=</tt>) method. The reason for this is simple yet irritating: Ruby assignment methods always return the assigned value, no matter what the method itself returns. For example:
|
73
69
|
|
74
|
-
|
70
|
+
{}[:name] = "Simon" # => "Simon"
|
75
71
|
|
76
|
-
|
72
|
+
Because of this, the returned copy would be lost thus making the construct useless.
|
77
73
|
|
78
|
-
|
74
|
+
=== Set
|
79
75
|
|
80
|
-
|
76
|
+
=== List
|
81
77
|
|
82
|
-
|
83
|
-
|
84
|
-
And don't forget that even if threading isn't a concern for you, the safety provided by immutability is worth it, not to mention the lazy implementations.
|
85
|
-
|
86
|
-
== But doesn't Ruby 1.9 now support lazy behaviour using Enumerators?
|
87
|
-
|
88
|
-
Sure does but they're implemented using Fibers which can't be shared across threads. All Hamster types are inherently thread-safe and sharable.
|
89
|
-
|
90
|
-
Moreover, Ruby's Enumerable module always returns an array -- calling <tt>Set#filter</tt> returns an <tt>Array</tt> -- whereas Hamster classes are almost always closed under a given operation. That is, Calling <tt>#filter</tt> on <tt>Set</tt> will return a <tt>Set</tt>, on a <tt>List</tt> will return a <tt>List</tt>, etc.
|
91
|
-
|
92
|
-
== So, you mentioned Sets, Lists, and Stacks?
|
93
|
-
|
94
|
-
Indeed I did.
|
95
|
-
|
96
|
-
=== Lists
|
97
|
-
|
98
|
-
Lists have a head--the value of the item at the head of the list--and a tail--containing the remaining items. For example:
|
78
|
+
Lists have a head -- the value of the item at the head of the list -- and a tail -- containing the remaining items. For example:
|
99
79
|
|
100
80
|
list = Hamster.list(1, 2, 3)
|
101
81
|
|
102
82
|
list.head # => 1
|
103
83
|
list.tail # => Hamster.list(2, 3)
|
104
84
|
|
105
|
-
To add to a list you use <tt>#cons</tt>:
|
85
|
+
To add to a list, you use <tt>#cons</tt>:
|
106
86
|
|
107
87
|
original = Hamster.list(1, 2, 3)
|
108
88
|
copy = original.cons(0) # => Hamster.list(0, 1, 2, 3)
|
109
89
|
|
110
|
-
Notice how modifying a list actually returns a new list. That's because Hamster lists are immutable. Thankfully, just like Hamster
|
90
|
+
Notice how modifying a list actually returns a new list. That's because Hamster lists are immutable. Thankfully, just like Hamster Set and Hash, they're also very efficient at making copies!
|
111
91
|
|
112
92
|
Lists are, where possible, lazy. That is, they try to defer processing items until absolutely necessary. For example, given a crude function to detect prime numbers:
|
113
93
|
|
@@ -130,14 +110,18 @@ Besides <tt>Hamster.list</tt> there are other ways to construct lists:
|
|
130
110
|
|
131
111
|
<tt>Hamster.stream { ... }</tt> allows you to creates infinite lists. Each time a new value is required, the supplied block is called. For example, to generate a list of integers you could do:
|
132
112
|
|
133
|
-
count =
|
113
|
+
count = 0
|
134
114
|
integers = Hamster.stream { count += 1 }
|
135
115
|
|
136
116
|
<tt>Hamster.repeat(x)</tt> creates an infinite list with x the value for every element.
|
137
117
|
|
138
118
|
<tt>Hamster.replicate(n, x)</tt> creates a list of size n with x the value for every element.
|
139
119
|
|
140
|
-
<tt>Hamster.iterate(x) { ... }</tt> creates an infinite list where the first item is calculated by applying the block on the initial argument, the second item by applying the function on the previous result and so on. For example, a simpler way to generate a list of integers would be:
|
120
|
+
<tt>Hamster.iterate(x) { |x| ... }</tt> creates an infinite list where the first item is calculated by applying the block on the initial argument, the second item by applying the function on the previous result and so on. For example, a simpler way to generate a list of integers would be:
|
121
|
+
|
122
|
+
integers = Hamster.iterate(1) { |i| i + 1 }
|
123
|
+
|
124
|
+
or even more succinctly:
|
141
125
|
|
142
126
|
integers = Hamster.iterate(1, &:succ)
|
143
127
|
|
@@ -167,24 +151,68 @@ Unfortunately, though the second example reads nicely, it takes around 13 second
|
|
167
151
|
|
168
152
|
How is this even possible? It's possible because <tt>IO#to_list</tt> creates a lazy list whereby each line is only ever read and processed as needed, in effect converting it to the first example without all the syntactic, imperative, noise.
|
169
153
|
|
170
|
-
===
|
171
|
-
|
172
|
-
=== Sets
|
154
|
+
=== Stack
|
173
155
|
|
174
156
|
== Disclaimer
|
175
157
|
|
176
158
|
Hamster started out as a spike to prove a point and has since morphed into something I actually use. My primary concern has been to round out the functionality with good test coverage and clean, readable code.
|
177
159
|
|
178
|
-
Performance is pretty good--especially with lazy lists--but there are some things which may blow the stack due to a lack of Tail-Call-Optimisation in Ruby.
|
160
|
+
Performance is pretty good -- especially with lazy lists -- but there are some things which may blow the stack due to a lack of Tail-Call-Optimisation in Ruby.
|
179
161
|
|
180
|
-
Documentation is sparse but I've tried as best I can to write specs that read as documentation. I've also tried to alias methods as their <tt>Enumerable</tt> equivalents where possible to
|
162
|
+
Documentation is sparse but I've tried as best I can to write specs that read as documentation. I've also tried to alias methods as their <tt>Enumerable</tt> equivalents where possible to ease code migration.
|
181
163
|
|
182
|
-
==
|
164
|
+
== Reporting bugs
|
183
165
|
|
184
|
-
|
166
|
+
Please report all bugs on the GitHub issue tracker located at: http://github.com/harukizaemon/hamster/issues
|
185
167
|
|
186
|
-
|
168
|
+
== But I still don't understand why I should care?
|
187
169
|
|
188
|
-
|
170
|
+
As mentioned earlier, persistent data structures 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 memory in the process.
|
171
|
+
|
172
|
+
Even if threading isn't a concern, because they're immutable, you can pass them around between objects, methods, and functions in the same thread and never worry about data corruption; no more defensive calls to <tt>#dup</tt>!
|
173
|
+
|
174
|
+
=== OK, that sounds mildly interesting. What's the downside--there's always a downside?
|
175
|
+
|
176
|
+
There's a potential performance hit when compared with MRI's built-in, native, hand-crafted C-code implementation of Hash. For example:
|
177
|
+
|
178
|
+
hash = Hamster.hash
|
179
|
+
(1..10000).each { |i| hash = hash.put(i, i) } # => 0.05s
|
180
|
+
(1..10000).each { |i| hash.get(i) } # => 0.008s
|
181
|
+
|
182
|
+
versus
|
183
|
+
|
184
|
+
hash = {}
|
185
|
+
(1..10000).each { |i| hash[i] = i } # => 0.004s
|
186
|
+
(1..10000).each { |i| hash[i] } # => 0.001s
|
187
|
+
|
188
|
+
=== That seems pretty bad?
|
189
|
+
|
190
|
+
Well, yes and no. The previous comparison wasn't really fair. Sure, if all you want to do is replace your existing uses of Hash in single-threaded environments then don't even bother. However, if you need something that can be used efficiently in concurrent environments where multiple threads are accessing--reading AND writing--the contents things get much better.
|
191
|
+
|
192
|
+
=== Do you have a better example?
|
193
|
+
|
194
|
+
A more realistic comparison might look like:
|
195
|
+
|
196
|
+
hash = Hamster.hash
|
197
|
+
(1..10000).each { |i| hash = hash.put(i, i) } # => 0.05s
|
198
|
+
(1..10000).each { |i| hash.get(i) } # => 0.008s
|
199
|
+
|
200
|
+
versus
|
201
|
+
|
202
|
+
hash = {}
|
203
|
+
(1..10000).each { |i|
|
204
|
+
hash = hash.dup
|
205
|
+
hash[i] = i
|
206
|
+
} # => 19.8s
|
207
|
+
|
208
|
+
(1..10000).each { |i| hash[i] } # => 0.001s
|
209
|
+
|
210
|
+
What's even better -- or worse depending on your perspective -- is that after all that, the native Hash version still isn't thread-safe and still requires some synchronisation around it slowing it down even further and.
|
211
|
+
|
212
|
+
The Hamster version on the other hand was unchanged from the original whilst remaining inherently thread-safe, and 3 orders of magnitude faster.
|
213
|
+
|
214
|
+
=== Sure, but as you say, you still need synchronisation so why bother with the copying?
|
215
|
+
|
216
|
+
Well, I could show you one but I'd have to re-write/wrap most Hash methods to make them generic, or at the very least write some application-specific code that synchronised using a <tt>Mutex</tt> and ... well ... it's hard, I always make mistakes, I always end up with weird edge cases and race conditions so, I'll leave that as an exercise for you :)
|
189
217
|
|
190
|
-
|
218
|
+
And don't forget that even if threading isn't a concern for you, the safety provided by immutability alone is worth it, not to mention the lazy implementations.
|
data/lib/hamster/hash.rb
CHANGED
data/lib/hamster/set.rb
CHANGED
@@ -44,11 +44,8 @@ module Hamster
|
|
44
44
|
|
45
45
|
def delete(item)
|
46
46
|
trie = @trie.delete(item)
|
47
|
-
if trie.equal?(@trie)
|
48
|
-
|
49
|
-
else
|
50
|
-
self.class.new(trie)
|
51
|
-
end
|
47
|
+
return self if trie.equal?(@trie)
|
48
|
+
self.class.new(trie)
|
52
49
|
end
|
53
50
|
|
54
51
|
def each
|
@@ -75,13 +72,9 @@ module Hamster
|
|
75
72
|
def filter
|
76
73
|
return self unless block_given?
|
77
74
|
trie = @trie.filter { |entry| yield(entry.key) }
|
78
|
-
if trie.equal?(@trie)
|
79
|
-
|
80
|
-
|
81
|
-
EmptySet
|
82
|
-
else
|
83
|
-
self.class.new(trie)
|
84
|
-
end
|
75
|
+
return self if trie.equal?(@trie)
|
76
|
+
return EmptySet if trie.empty?
|
77
|
+
self.class.new(trie)
|
85
78
|
end
|
86
79
|
def_delegator :self, :filter, :select
|
87
80
|
def_delegator :self, :filter, :find_all
|
@@ -164,6 +157,47 @@ module Hamster
|
|
164
157
|
to_a.join(sep)
|
165
158
|
end
|
166
159
|
|
160
|
+
def union(other)
|
161
|
+
trie = other.reduce(@trie) do |trie, item|
|
162
|
+
next trie if trie.has_key?(item)
|
163
|
+
trie.put(item, nil)
|
164
|
+
end
|
165
|
+
return self if trie.equal?(@trie)
|
166
|
+
self.class.new(trie)
|
167
|
+
end
|
168
|
+
def_delegator :self, :union, :|
|
169
|
+
def_delegator :self, :union, :+
|
170
|
+
|
171
|
+
def intersection(other)
|
172
|
+
trie = @trie.filter { |entry| other.include?(entry.key) }
|
173
|
+
return self if trie.equal?(@trie)
|
174
|
+
self.class.new(trie)
|
175
|
+
end
|
176
|
+
def_delegator :self, :intersection, :intersect
|
177
|
+
def_delegator :self, :intersection, :&
|
178
|
+
|
179
|
+
def difference(other)
|
180
|
+
trie = @trie.filter { |entry| !other.include?(entry.key) }
|
181
|
+
return self if trie.equal?(@trie)
|
182
|
+
self.class.new(trie)
|
183
|
+
end
|
184
|
+
def_delegator :self, :difference, :diff
|
185
|
+
def_delegator :self, :difference, :subtract
|
186
|
+
def_delegator :self, :difference, :-
|
187
|
+
|
188
|
+
def exclusion(other)
|
189
|
+
((self | other) - (self & other))
|
190
|
+
end
|
191
|
+
def_delegator :self, :exclusion, :^
|
192
|
+
|
193
|
+
def subset?(other)
|
194
|
+
all? { |item| other.include?(item) }
|
195
|
+
end
|
196
|
+
|
197
|
+
def superset?(other)
|
198
|
+
other.subset?(self)
|
199
|
+
end
|
200
|
+
|
167
201
|
def compact
|
168
202
|
remove(&:nil?)
|
169
203
|
end
|
data/lib/hamster/version.rb
CHANGED