hamster 0.2.7 → 0.2.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (155) hide show
  1. data/History.rdoc +18 -0
  2. data/README.rdoc +107 -79
  3. data/lib/hamster/hash.rb +0 -1
  4. data/lib/hamster/set.rb +46 -12
  5. data/lib/hamster/version.rb +1 -1
  6. data/spec/hamster/core_ext/array_spec.rb +1 -1
  7. data/spec/hamster/core_ext/enumerable_spec.rb +1 -1
  8. data/spec/hamster/core_ext/io_spec.rb +1 -1
  9. data/spec/hamster/hash/all_spec.rb +1 -1
  10. data/spec/hamster/hash/any_spec.rb +1 -1
  11. data/spec/hamster/hash/construction_spec.rb +1 -1
  12. data/spec/hamster/hash/copying_spec.rb +1 -1
  13. data/spec/hamster/hash/delete_spec.rb +1 -1
  14. data/spec/hamster/hash/each_spec.rb +1 -1
  15. data/spec/hamster/hash/empty_spec.rb +1 -1
  16. data/spec/hamster/hash/eql_spec.rb +1 -1
  17. data/spec/hamster/hash/filter_spec.rb +1 -1
  18. data/spec/hamster/hash/find_spec.rb +1 -1
  19. data/spec/hamster/hash/get_spec.rb +1 -1
  20. data/spec/hamster/hash/has_key_spec.rb +1 -1
  21. data/spec/hamster/hash/inspect_spec.rb +1 -1
  22. data/spec/hamster/hash/map_spec.rb +1 -1
  23. data/spec/hamster/hash/none_spec.rb +1 -1
  24. data/spec/hamster/hash/put_spec.rb +23 -27
  25. data/spec/hamster/hash/reduce_spec.rb +1 -1
  26. data/spec/hamster/hash/remove_spec.rb +1 -1
  27. data/spec/hamster/hash/size_spec.rb +1 -1
  28. data/spec/hamster/hash/uniq_spec.rb +1 -1
  29. data/spec/hamster/list/all_spec.rb +1 -1
  30. data/spec/hamster/list/any_spec.rb +1 -1
  31. data/spec/hamster/list/append_spec.rb +1 -1
  32. data/spec/hamster/list/at_spec.rb +1 -1
  33. data/spec/hamster/list/break_spec.rb +1 -1
  34. data/spec/hamster/list/cadr_spec.rb +1 -1
  35. data/spec/hamster/list/chunk_spec.rb +1 -1
  36. data/spec/hamster/list/clear_spec.rb +1 -1
  37. data/spec/hamster/list/combinations_spec.rb +1 -1
  38. data/spec/hamster/list/compact_spec.rb +1 -1
  39. data/spec/hamster/list/cons_spec.rb +1 -1
  40. data/spec/hamster/list/construction_spec.rb +1 -1
  41. data/spec/hamster/list/copying_spec.rb +1 -1
  42. data/spec/hamster/list/count_spec.rb +1 -1
  43. data/spec/hamster/list/cycle_spec.rb +1 -1
  44. data/spec/hamster/list/drop_spec.rb +1 -1
  45. data/spec/hamster/list/drop_while_spec.rb +1 -1
  46. data/spec/hamster/list/each_slice_spec.rb +1 -1
  47. data/spec/hamster/list/each_spec.rb +1 -1
  48. data/spec/hamster/list/elem_index_spec.rb +1 -1
  49. data/spec/hamster/list/elem_indices_spec.rb +1 -1
  50. data/spec/hamster/list/empty_spec.rb +1 -1
  51. data/spec/hamster/list/eql_spec.rb +1 -1
  52. data/spec/hamster/list/filter_spec.rb +1 -1
  53. data/spec/hamster/list/find_index_spec.rb +1 -1
  54. data/spec/hamster/list/find_indices_spec.rb +1 -1
  55. data/spec/hamster/list/find_spec.rb +1 -1
  56. data/spec/hamster/list/flatten_spec.rb +1 -1
  57. data/spec/hamster/list/grep_spec.rb +1 -1
  58. data/spec/hamster/list/group_by_spec.rb +1 -1
  59. data/spec/hamster/list/head_spec.rb +1 -1
  60. data/spec/hamster/list/include_spec.rb +1 -1
  61. data/spec/hamster/list/init_spec.rb +1 -1
  62. data/spec/hamster/list/inits_spec.rb +1 -1
  63. data/spec/hamster/list/inspect_spec.rb +1 -1
  64. data/spec/hamster/list/intersperse_spec.rb +1 -1
  65. data/spec/hamster/list/join_spec.rb +1 -1
  66. data/spec/hamster/list/last_spec.rb +1 -1
  67. data/spec/hamster/list/map_spec.rb +1 -1
  68. data/spec/hamster/list/maximum_spec.rb +1 -1
  69. data/spec/hamster/list/minimum_spec.rb +1 -1
  70. data/spec/hamster/list/none_spec.rb +1 -1
  71. data/spec/hamster/list/one_spec.rb +1 -1
  72. data/spec/hamster/list/partition_spec.rb +1 -1
  73. data/spec/hamster/list/product_spec.rb +1 -1
  74. data/spec/hamster/list/reduce_spec.rb +1 -1
  75. data/spec/hamster/list/remove_spec.rb +1 -1
  76. data/spec/hamster/list/reverse_spec.rb +1 -1
  77. data/spec/hamster/list/size_spec.rb +1 -1
  78. data/spec/hamster/list/slice_spec.rb +1 -1
  79. data/spec/hamster/list/sorting_spec.rb +1 -1
  80. data/spec/hamster/list/span_spec.rb +1 -1
  81. data/spec/hamster/list/split_at_spec.rb +1 -1
  82. data/spec/hamster/list/sum_spec.rb +1 -1
  83. data/spec/hamster/list/tail_spec.rb +1 -1
  84. data/spec/hamster/list/tails_spec.rb +1 -1
  85. data/spec/hamster/list/take_spec.rb +1 -1
  86. data/spec/hamster/list/take_while_spec.rb +1 -1
  87. data/spec/hamster/list/to_a_spec.rb +1 -1
  88. data/spec/hamster/list/to_ary_spec.rb +1 -1
  89. data/spec/hamster/list/to_list_spec.rb +1 -1
  90. data/spec/hamster/list/union_spec.rb +2 -2
  91. data/spec/hamster/list/uniq_spec.rb +1 -1
  92. data/spec/hamster/list/zip_spec.rb +1 -1
  93. data/spec/hamster/set/add_spec.rb +1 -1
  94. data/spec/hamster/set/all_spec.rb +1 -1
  95. data/spec/hamster/set/any_spec.rb +1 -1
  96. data/spec/hamster/set/clear_spec.rb +1 -1
  97. data/spec/hamster/set/compact_spec.rb +1 -1
  98. data/spec/hamster/set/construction_spec.rb +1 -1
  99. data/spec/hamster/set/copying_spec.rb +1 -1
  100. data/spec/hamster/set/count_spec.rb +1 -1
  101. data/spec/hamster/set/delete_spec.rb +1 -1
  102. data/spec/hamster/set/difference_spec.rb +37 -0
  103. data/spec/hamster/set/each_spec.rb +1 -1
  104. data/spec/hamster/set/empty_spec.rb +1 -1
  105. data/spec/hamster/set/eql_spec.rb +1 -1
  106. data/spec/hamster/set/exclusion_spec.rb +38 -0
  107. data/spec/hamster/set/filter_spec.rb +1 -1
  108. data/spec/hamster/set/find_spec.rb +1 -1
  109. data/spec/hamster/set/grep_spec.rb +1 -1
  110. data/spec/hamster/set/group_by_spec.rb +1 -1
  111. data/spec/hamster/set/head_spec.rb +1 -1
  112. data/spec/hamster/set/include_spec.rb +1 -1
  113. data/spec/hamster/set/inspect_spec.rb +1 -1
  114. data/spec/hamster/set/intersection_spec.rb +46 -0
  115. data/spec/hamster/set/join_spec.rb +1 -1
  116. data/spec/hamster/set/map_spec.rb +1 -1
  117. data/spec/hamster/set/maximum_spec.rb +1 -1
  118. data/spec/hamster/set/minimum_spec.rb +1 -1
  119. data/spec/hamster/set/none_spec.rb +1 -1
  120. data/spec/hamster/set/one_spec.rb +1 -1
  121. data/spec/hamster/set/partition_spec.rb +1 -1
  122. data/spec/hamster/set/product_spec.rb +1 -1
  123. data/spec/hamster/set/reduce_spec.rb +1 -1
  124. data/spec/hamster/set/remove_spec.rb +1 -1
  125. data/spec/hamster/set/size_spec.rb +1 -1
  126. data/spec/hamster/set/sorting_spec.rb +1 -1
  127. data/spec/hamster/set/subset_spec.rb +39 -0
  128. data/spec/hamster/set/sum_spec.rb +1 -1
  129. data/spec/hamster/set/superset_spec.rb +39 -0
  130. data/spec/hamster/set/to_a_spec.rb +1 -1
  131. data/spec/hamster/set/to_list_spec.rb +1 -1
  132. data/spec/hamster/set/to_set_spec.rb +1 -1
  133. data/spec/hamster/set/union_spec.rb +45 -0
  134. data/spec/hamster/set/uniq_spec.rb +1 -1
  135. data/spec/hamster/stack/clear_spec.rb +1 -1
  136. data/spec/hamster/stack/construction_spec.rb +1 -1
  137. data/spec/hamster/stack/copying_spec.rb +1 -1
  138. data/spec/hamster/stack/empty_spec.rb +1 -1
  139. data/spec/hamster/stack/eql_spec.rb +1 -1
  140. data/spec/hamster/stack/inspect_spec.rb +1 -1
  141. data/spec/hamster/stack/pop_spec.rb +1 -1
  142. data/spec/hamster/stack/push_spec.rb +1 -1
  143. data/spec/hamster/stack/size_spec.rb +1 -1
  144. data/spec/hamster/stack/to_a_spec.rb +1 -1
  145. data/spec/hamster/stack/to_list_spec.rb +1 -1
  146. data/spec/hamster/stack/top_spec.rb +1 -1
  147. data/spec/hamster/trie/remove_spec.rb +1 -1
  148. data/spec/hamster/tuple/copying_spec.rb +1 -1
  149. data/spec/hamster/tuple/eql_spec.rb +1 -1
  150. data/spec/hamster/tuple/first_spec.rb +1 -1
  151. data/spec/hamster/tuple/inspect_spec.rb +1 -1
  152. data/spec/hamster/tuple/last_spec.rb +1 -1
  153. data/spec/hamster/tuple/to_a_spec.rb +1 -1
  154. data/spec/hamster/tuple/to_ary_spec.rb +1 -1
  155. metadata +8 -2
@@ -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.
@@ -1,113 +1,93 @@
1
1
  = Hamster - Efficient, Immutable, Thread-Safe Collection classes for Ruby
2
2
 
3
- 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) such as Sets, Lists, Stacks, etc.
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
- == What are persistent data structures?
8
+ == Introduction
6
9
 
7
- Persistent data structures have a really neat property: very efficient copy-on-write operations. That allows you to create immutable data-structures that only need copying when something changes. For example:
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
- hash = Hamster.hash
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
- hash.put("Name", "Simon")
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
- == Double Huh? That's not much use!
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
- Whoops! Unlike Ruby's standard library, each call to <tt>Hamster::hash#put</tt> creates an efficient copy containing the modifications, leaving the original unmodified. Thus, all Hamster classes follow Command-Query-Seperation (see http://martinfowler.com/bliki/CommandQuerySeparation.html) and return the modified copy of themselves after any mutating operation. Let's try that again:
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
- original = Hamster.hash
20
- copy = original.put("Name", "Simon")
20
+ == Installation
21
21
 
22
- original.get("Name") # => nil
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
- The same goes for <tt>#remove</tt>:
24
+ Installation via the gem is easy:
26
25
 
27
- original = Hamster.hash
28
- original = original.put("Name", "Simon")
29
- copy = hash.remove("Name")
26
+ > gem install hamster
30
27
 
31
- original.get("Name") # => Simon
32
- copy.get("Name") # => nil
28
+ Once installed, all that remains is to make the collection classes available in your code:
33
29
 
34
- == Oh, I get it. Cool. But I still don't understand why I should care?
30
+ require 'hamster'
35
31
 
36
- 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. Moreover, because they're immutable, you can pass them around between objects, methods, and functions and never worry about data corruption; no more defensive calls to <tt>collection.dup</tt>!
32
+ If you prefer, you can instead require individual classes as necessary:
37
33
 
38
- For an interesting read on why immutability is a good thing, take a look at Matthias Felleisen's Function Objects presnetation (http://www.ccs.neu.edu/home/matthias/Presentations/ecoop2004.pdf).
34
+ require 'hamster/hash'
35
+ require 'hamster/set'
36
+ require 'hamster/list'
37
+ require 'hamster/stack'
39
38
 
40
- == OK, that sounds mildly interesting. What's the downside--there's always a downside?
39
+ == Examples
41
40
 
42
- There's a potential performance hit when compared with MRI's built-in, native, hand-crafted C-code implementation of <tt>Hash</tt>. For example:
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
- hash = Hamster.hash
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
- versus
45
+ Constructing a Hamster Hash is almost as simple as a regular one:
49
46
 
50
- hash = {}
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
- == That seems pretty bad?
49
+ Accessing the contents will be familiar to you:
55
50
 
56
- Well, yes and no. The previous comparison wasn't really fair. Sure, if all you want to do is replace your existing uses of <tt>Hash</tt> 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.
51
+ simon[:name] # => "Simon"
52
+ simon.get(:gender) # => :male
57
53
 
58
- == Do you have a better example?
54
+ Updating the contents is a little different than you are used to:
59
55
 
60
- A more realistic comparison might look like:
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
- hash = Hamster.hash
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
- versus
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
- hash = {}
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
- (1..10000).each { |i| hash[i] } # => 0.001s
70
+ {}[:name] = "Simon" # => "Simon"
75
71
 
76
- Impressive huh? What's even better is--or worse depending on your perspective--is that after all that, the native <tt>Hash</tt> version still isn't thread-safe and still requires some synchronisation around it slowing it down even further.
72
+ Because of this, the returned copy would be lost thus making the construct useless.
77
73
 
78
- The <tt>Hamster::Hash</tt> version on the other hand was unchanged from the original whilst remaining inherently thread-safe, and 3 orders of magnitude faster.
74
+ === Set
79
75
 
80
- == Sure, but as you say, you still need synchronisation so why bother with the copying?
76
+ === List
81
77
 
82
- Well, I could show you one but I'd have to re-write--or at least wrap--most <tt>Hash</tt> methods to make it generic, or at 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 :)
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 <tt>Set</tt> and <tt>Hash</tt>, they're also very efficient at making copies!
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 = 1
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
- === Stacks
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 make it easier for people to migrate code.
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
- == Installation
164
+ == Reporting bugs
183
165
 
184
- Hamster is distributed as a gem via gemcutter (http://gemcutter.org/gems/hamster) or as source via GitHub (http://github.com/harukizaemon/hamster).
166
+ Please report all bugs on the GitHub issue tracker located at: http://github.com/harukizaemon/hamster/issues
185
167
 
186
- Installation via the gem is easy:
168
+ == But I still don't understand why I should care?
187
169
 
188
- > gem install hamster
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
- I'll leave it up to you to install from source :)
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.
@@ -42,7 +42,6 @@ module Hamster
42
42
  def put(key, value)
43
43
  self.class.new(@trie.put(key, value))
44
44
  end
45
- def_delegator :self, :put, :[]=
46
45
 
47
46
  def delete(key)
48
47
  trie = @trie.delete(key)
@@ -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
- self
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
- self
80
- elsif trie.empty?
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
@@ -1,5 +1,5 @@
1
1
  module Hamster
2
2
 
3
- VERSION = "0.2.7".freeze
3
+ VERSION = "0.2.8".freeze
4
4
 
5
5
  end
@@ -1,4 +1,4 @@
1
- require File.expand_path('../../spec_helper', File.dirname(__FILE__))
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
2
 
3
3
  require 'hamster/core_ext/enumerable'
4
4
 
@@ -1,4 +1,4 @@
1
- require File.expand_path('../../spec_helper', File.dirname(__FILE__))
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
2
 
3
3
  require 'hamster/core_ext/enumerable'
4
4
 
@@ -1,4 +1,4 @@
1
- require File.expand_path('../../spec_helper', File.dirname(__FILE__))
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
2
 
3
3
  require 'hamster/core_ext/io'
4
4
 
@@ -1,4 +1,4 @@
1
- require File.expand_path('../../spec_helper', File.dirname(__FILE__))
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
2
 
3
3
  require 'hamster/hash'
4
4
 
@@ -1,4 +1,4 @@
1
- require File.expand_path('../../spec_helper', File.dirname(__FILE__))
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
2
 
3
3
  require 'hamster/hash'
4
4
 
@@ -1,4 +1,4 @@
1
- require File.expand_path('../../spec_helper', File.dirname(__FILE__))
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
2
 
3
3
  require 'hamster/hash'
4
4
 
@@ -1,4 +1,4 @@
1
- require File.expand_path('../../spec_helper', File.dirname(__FILE__))
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
2
 
3
3
  require 'hamster/hash'
4
4
 
@@ -1,4 +1,4 @@
1
- require File.expand_path('../../spec_helper', File.dirname(__FILE__))
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
2
 
3
3
  require 'hamster/hash'
4
4
 
@@ -1,4 +1,4 @@
1
- require File.expand_path('../../spec_helper', File.dirname(__FILE__))
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
2
 
3
3
  require 'hamster/hash'
4
4
 
@@ -1,4 +1,4 @@
1
- require File.expand_path('../../spec_helper', File.dirname(__FILE__))
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
2
 
3
3
  require 'hamster/hash'
4
4
 
@@ -1,4 +1,4 @@
1
- require File.expand_path('../../spec_helper', File.dirname(__FILE__))
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
2
 
3
3
  require 'hamster/hash'
4
4