hamster 0.1.8 → 0.1.11

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.
Files changed (69) hide show
  1. data/README.rdoc +34 -10
  2. data/lib/hamster.rb +0 -1
  3. data/lib/hamster/hash.rb +77 -20
  4. data/lib/hamster/list.rb +189 -52
  5. data/lib/hamster/set.rb +78 -23
  6. data/lib/hamster/stack.rb +13 -14
  7. data/lib/hamster/trie.rb +13 -4
  8. data/lib/hamster/version.rb +1 -1
  9. data/spec/hamster/hash/all_spec.rb +53 -0
  10. data/spec/hamster/hash/any_spec.rb +62 -0
  11. data/spec/hamster/hash/construction_spec.rb +21 -5
  12. data/spec/hamster/hash/copying_spec.rb +6 -10
  13. data/spec/hamster/hash/each_spec.rb +6 -18
  14. data/spec/hamster/hash/empty_spec.rb +9 -5
  15. data/spec/hamster/hash/eql_spec.rb +17 -22
  16. data/spec/hamster/hash/filter_spec.rb +61 -0
  17. data/spec/hamster/hash/get_spec.rb +24 -12
  18. data/spec/hamster/hash/has_key_spec.rb +17 -13
  19. data/spec/hamster/hash/map_spec.rb +66 -0
  20. data/spec/hamster/hash/none_spec.rb +62 -0
  21. data/spec/hamster/hash/put_spec.rb +17 -73
  22. data/spec/hamster/hash/reduce_spec.rb +58 -0
  23. data/spec/hamster/hash/reject_spec.rb +57 -0
  24. data/spec/hamster/hash/remove_spec.rb +13 -85
  25. data/spec/hamster/hash/size_spec.rb +25 -0
  26. data/spec/hamster/list/cadr_spec.rb +37 -0
  27. data/spec/hamster/list/construction_spec.rb +46 -6
  28. data/spec/hamster/list/copying_spec.rb +13 -11
  29. data/spec/hamster/list/drop_spec.rb +29 -0
  30. data/spec/hamster/list/drop_while_spec.rb +39 -0
  31. data/spec/hamster/list/each_spec.rb +24 -24
  32. data/spec/hamster/list/empty_spec.rb +15 -6
  33. data/spec/hamster/list/eql_spec.rb +27 -7
  34. data/spec/hamster/list/filter_spec.rb +51 -0
  35. data/spec/hamster/list/head_spec.rb +27 -0
  36. data/spec/hamster/list/include_spec.rb +37 -0
  37. data/spec/hamster/list/lazy_spec.rb +21 -0
  38. data/spec/hamster/list/map_spec.rb +21 -19
  39. data/spec/hamster/list/reduce_spec.rb +27 -15
  40. data/spec/hamster/list/reject_spec.rb +47 -0
  41. data/spec/hamster/list/size_spec.rb +31 -0
  42. data/spec/hamster/list/tail_spec.rb +27 -0
  43. data/spec/hamster/list/take_spec.rb +29 -0
  44. data/spec/hamster/list/take_while_spec.rb +45 -0
  45. data/spec/hamster/set/add_spec.rb +49 -0
  46. data/spec/hamster/set/all_spec.rb +61 -0
  47. data/spec/hamster/set/any_spec.rb +61 -0
  48. data/spec/hamster/set/construction_spec.rb +3 -3
  49. data/spec/hamster/set/copying_spec.rb +21 -0
  50. data/spec/hamster/set/each_spec.rb +36 -0
  51. data/spec/hamster/set/empty_spec.rb +21 -0
  52. data/spec/hamster/set/eql_spec.rb +31 -0
  53. data/spec/hamster/set/filter_spec.rb +61 -0
  54. data/spec/hamster/set/include_spec.rb +29 -0
  55. data/spec/hamster/set/map_spec.rb +66 -0
  56. data/spec/hamster/set/none_spec.rb +61 -0
  57. data/spec/hamster/set/reduce_spec.rb +58 -0
  58. data/spec/hamster/set/reject_spec.rb +57 -0
  59. data/spec/hamster/set/remove_spec.rb +45 -0
  60. data/spec/hamster/set/size_spec.rb +25 -0
  61. data/spec/hamster/stack/copying_spec.rb +1 -1
  62. data/spec/hamster/stack/empty_spec.rb +2 -2
  63. data/spec/hamster/stack/eql_spec.rb +15 -4
  64. data/spec/hamster/stack/push_spec.rb +6 -26
  65. data/spec/hamster/trie/remove_spec.rb +117 -0
  66. metadata +39 -7
  67. data/TODO +0 -1
  68. data/spec/hamster/list/accessor_spec.rb +0 -26
  69. data/spec/hamster/list/car_spec.rb +0 -17
@@ -1,6 +1,6 @@
1
1
  = Hamster
2
2
 
3
- Hamster started out as an implementation of Hash Array Mapped Hashs (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
+ Hamster started out as an implementation of Hash Array Mapped Hashes (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.
4
4
 
5
5
  == Huh?
6
6
 
@@ -8,7 +8,7 @@ Persistent data structures have a really neat property: very efficient copy-on-w
8
8
 
9
9
  require 'hamster'
10
10
 
11
- hash = Hamster::Hash.new
11
+ hash = Hamster.hash
12
12
 
13
13
  hash.put("Name", "Simon")
14
14
  hash.has_key?("Name") # => false
@@ -20,8 +20,8 @@ Whoops! Remember, each call to <tt>#put</tt> creates an efficient copy containin
20
20
 
21
21
  require 'hamster'
22
22
 
23
- original = Hamster::Hash.new
24
- copy = hash.put("Name", "Simon")
23
+ original = Hamster.hash
24
+ copy = original.put("Name", "Simon")
25
25
 
26
26
  original.get("Name") # => nil
27
27
  copy.get("Name") # => "Simon"
@@ -30,10 +30,10 @@ The same goes for <tt>#remove</tt>:
30
30
 
31
31
  require 'hamster'
32
32
 
33
- original = Hamster::Hash.new
34
- original = hash.put("Name", "Simon")
33
+ original = Hamster.hash
34
+ original = original.put("Name", "Simon")
35
35
  copy = hash.remove("Name")
36
-
36
+
37
37
  original.get("Name") # => Simon
38
38
  copy.get("Name") # => nil
39
39
 
@@ -47,7 +47,7 @@ There's a potential performance hit when compared with MRI's built-in, native, h
47
47
 
48
48
  require 'hamster'
49
49
 
50
- hash = Hamster::Hash.new
50
+ hash = Hamster.hash
51
51
  (1..10000).each { |i| hash = hash.put(i, i) } # => 0.05s
52
52
  (1..10000).each { |i| hash.get(i) } # => 0.008s
53
53
 
@@ -67,7 +67,7 @@ A more realistic comparison might look like:
67
67
 
68
68
  require 'hamster'
69
69
 
70
- hash = Hamster::Hash.new
70
+ hash = Hamster.hash
71
71
  (1..10000).each { |i| hash = hash.put(i, i) } # => 0.05s
72
72
  (1..10000).each { |i| hash.get(i) } # => 0.008s
73
73
 
@@ -89,4 +89,28 @@ The <tt>Hamster::Hash</tt> version on the other hand was unchanged from the orig
89
89
 
90
90
  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 :)
91
91
 
92
- == And that, my friends, is why you might want to use one :)
92
+ And that, my friends, is why you might want to use one :)
93
+
94
+ == So, you mentioned Sets, Lists, and Stacks?
95
+
96
+ Indeed I did.
97
+
98
+ === Lists
99
+
100
+ list = Hamster.list(1, 2, 3)
101
+
102
+ list.head # => 1
103
+ list.tail # => Hamster.list(2, 3)
104
+
105
+ def prime?(n)
106
+ 2.upto(Math.sqrt(n).round) { |i| return false if n % i == 0 }
107
+ true
108
+ end
109
+
110
+ Hamster.interval(10000, 1000000).filter { |i| prime?(i) }.take(3)
111
+
112
+ === Stacks
113
+
114
+ === Sets
115
+
116
+ === Hash
@@ -1,4 +1,3 @@
1
- require 'hamster/trie'
2
1
  require 'hamster/list'
3
2
  require 'hamster/stack'
4
3
  require 'hamster/set'
@@ -1,72 +1,129 @@
1
+ require 'hamster/trie'
2
+
1
3
  module Hamster
2
4
 
3
- class Hash
5
+ def self.hash(pairs = {})
6
+ pairs.reduce(Hash.new) { |hash, pair| hash.put(pair.first, pair.last) }
7
+ end
4
8
 
5
- def self.[](pairs)
6
- pairs.reduce(self.new) { |hash, pair| hash.put(pair.first, pair.last) }
7
- end
9
+ class Hash
8
10
 
9
11
  def initialize(trie = Trie.new)
10
12
  @trie = trie
11
13
  end
12
14
 
13
- # Returns the number of key-value pairs in the hash.
14
15
  def size
15
16
  @trie.size
16
17
  end
18
+ alias_method :length, :size
17
19
 
18
- # Returns <tt>true</tt> if the hash contains no key-value pairs.
19
20
  def empty?
20
21
  @trie.empty?
21
22
  end
22
23
 
23
- # Returns <tt>true</tt> if the given key is present in the hash.
24
24
  def has_key?(key)
25
25
  @trie.has_key?(key)
26
26
  end
27
+ alias_method :key?, :has_key?
28
+ alias_method :include?, :has_key?
29
+ alias_method :member?, :has_key?
27
30
 
28
- # Retrieves the value corresponding to the given key. If not found, returns <tt>nil</tt>.
29
31
  def get(key)
30
32
  entry = @trie.get(key)
31
33
  if entry
32
34
  entry.value
33
35
  end
34
36
  end
37
+ alias_method :[], :get
35
38
 
36
- # Returns a copy of <tt>self</tt> with the given value associated with the key.
37
39
  def put(key, value)
38
40
  self.class.new(@trie.put(key, value))
39
41
  end
42
+ alias_method :[]=, :put
40
43
 
41
- # Returns a copy of <tt>self</tt> with the given key/value pair removed. If not found, returns <tt>self</tt>.
42
44
  def remove(key)
43
- copy = @trie.remove(key)
44
- if !copy.equal?(@trie)
45
- self.class.new(copy)
45
+ trie = @trie.remove(key)
46
+ if !trie.equal?(@trie)
47
+ self.class.new(trie)
46
48
  else
47
49
  self
48
50
  end
49
51
  end
50
52
 
51
- # Calls <tt>block</tt> once for each entry in the hash, passing the key-value pair as parameters.
52
- # Returns <tt>self</tt>
53
53
  def each
54
54
  block_given? or return enum_for(__method__)
55
- @trie.each { |entry| yield entry.key, entry.value }
55
+ @trie.each { |entry| yield(entry.key, entry.value) }
56
56
  self
57
57
  end
58
58
 
59
- # Returns <tt>true</tt> if . <tt>eql?</tt> is synonymous with <tt>==</tt>
59
+ def map
60
+ block_given? or return enum_for(:each)
61
+ if empty?
62
+ self
63
+ else
64
+ self.class.new(@trie.reduce(Trie.new) { |trie, entry| trie.put(*yield(entry.key, entry.value)) })
65
+ end
66
+ end
67
+ alias_method :collect, :map
68
+
69
+ def reduce(memo)
70
+ block_given? or return memo
71
+ @trie.reduce(memo) { |memo, entry| yield(memo, entry.key, entry.value) }
72
+ end
73
+ alias_method :inject, :reduce
74
+
75
+ def filter
76
+ block_given? or return enum_for(__method__)
77
+ trie = @trie.filter { |entry| yield(entry.key, entry.value) }
78
+ if !trie.equal?(@trie)
79
+ self.class.new(trie)
80
+ else
81
+ self
82
+ end
83
+ end
84
+ alias_method :select, :filter
85
+
86
+ def reject
87
+ block_given? or return enum_for(__method__)
88
+ select { |key, value| !yield(key, value) }
89
+ end
90
+
91
+ def any?
92
+ if block_given?
93
+ each { |key, value| return true if yield(key, value) }
94
+ else
95
+ each { |pair| return true if pair }
96
+ end
97
+ false
98
+ end
99
+
100
+ def all?
101
+ if block_given?
102
+ each { |key, value| return false unless yield(key, value) }
103
+ else
104
+ each { |pair| return false unless pair }
105
+ end
106
+ true
107
+ end
108
+
109
+ def none?
110
+ if block_given?
111
+ each { |key, value| return false if yield(key, value) }
112
+ else
113
+ each { |pair| return false if pair }
114
+ end
115
+ true
116
+ end
117
+
60
118
  def eql?(other)
61
119
  other.is_a?(self.class) && @trie.eql?(other.instance_eval{@trie})
62
120
  end
63
- alias :== :eql?
121
+ alias_method :==, :eql?
64
122
 
65
- # Returns <tt>self</tt>
66
123
  def dup
67
124
  self
68
125
  end
69
- alias :clone :dup
126
+ alias_method :clone, :dup
70
127
 
71
128
  end
72
129
 
@@ -1,89 +1,125 @@
1
1
  module Hamster
2
2
 
3
- class List
3
+ class << self
4
4
 
5
- def self.[](*items)
6
- list = self.new
7
- items.reverse_each { |item| list = list.cons(item) }
8
- list
5
+ def list(*items)
6
+ items.reverse.reduce(EmptyList) { |list, item| list.cons(item) }
9
7
  end
10
8
 
11
- def initialize(head = nil, tail = self)
12
- @head = head
13
- @tail = tail
9
+ def interval(from, to)
10
+ if from > to
11
+ EmptyList
12
+ else
13
+ Stream.new(from) { interval(from.succ, to) }
14
+ end
14
15
  end
16
+ alias_method :range, :interval
17
+
18
+ end
19
+
20
+ module List
15
21
 
16
- # Returns <tt>true</tt> if the list contains no items.
17
22
  def empty?
18
- @tail.equal?(self)
23
+ false
19
24
  end
20
25
 
21
- # Returns the number of items in the list.
22
26
  def size
23
- if empty?
24
- 0
25
- else
26
- @tail.size + 1
27
- end
27
+ tail.size + 1
28
28
  end
29
+ alias_method :length, :size
29
30
 
30
- # Returns the first item.
31
- def head
32
- @head
31
+ def cons(item)
32
+ Sequence.new(item, self)
33
33
  end
34
34
 
35
- # Returns a copy of <tt>self</tt> without the first item.
36
- def tail
37
- @tail
35
+ def each(&block)
36
+ block_given? or return self
37
+ yield(head)
38
+ tail.each(&block)
39
+ nil
38
40
  end
39
41
 
40
- # Returns a copy of <tt>self</tt> with it as the head.
41
- def cons(item)
42
- self.class.new(item, self)
42
+ def map(&block)
43
+ block_given? or return self
44
+ Stream.new(yield(head)) { tail.map(&block) }
43
45
  end
44
46
 
45
- # Calls <tt>block</tt> once for each item in the list, passing the item as the only parameter.
46
- # Returns <tt>self</tt>
47
- def each
48
- block_given? or return enum_for(__method__)
49
- unless empty?
50
- yield(@head)
51
- @tail.each { |item| yield(item) }
47
+ def reduce(memo, &block)
48
+ block_given? or return memo
49
+ tail.reduce(yield(memo, head), &block)
50
+ end
51
+ alias_method :inject, :reduce
52
+
53
+ def filter(&block)
54
+ block_given? or return self
55
+ if yield(head)
56
+ Stream.new(head) { tail.filter(&block) }
57
+ else
58
+ tail.filter(&block)
52
59
  end
53
- self
54
60
  end
61
+ alias_method :select, :filter
55
62
 
56
- # Returns <tt>true</tt> if . <tt>eql?</tt> is synonymous with <tt>==</tt>
57
- def eql?(other)
58
- return true if other.equal?(self)
59
- return false unless other.is_a?(self.class)
60
- return true if other.empty? && empty?
61
- return other.head == head && other.tail.eql?(tail)
63
+ def reject(&block)
64
+ block_given? or return self
65
+ if yield(head)
66
+ tail.reject(&block)
67
+ else
68
+ Stream.new(head) { tail.reject(&block) }
69
+ end
62
70
  end
63
- alias :== :eql?
64
71
 
65
- # Returns <tt>self</tt>
66
- def dup
67
- self
72
+ def take_while(&block)
73
+ block_given? or return self
74
+ if yield(head)
75
+ Stream.new(head) { tail.take_while(&block) }
76
+ else
77
+ EmptyList
78
+ end
68
79
  end
69
- alias :clone :dup
70
80
 
71
- def map
72
- if empty?
81
+ def drop_while(&block)
82
+ block_given? or return self
83
+ if yield(head)
84
+ tail.drop_while(&block)
85
+ else
73
86
  self
87
+ end
88
+ end
89
+
90
+ def take(number)
91
+ if number > 0
92
+ Stream.new(head) { tail.take(number - 1) }
74
93
  else
75
- @tail.map { |item| yield(item) }.cons(yield(@head))
94
+ EmptyList
76
95
  end
77
96
  end
78
97
 
79
- def reduce(memo)
80
- if empty?
81
- memo
98
+ def drop(number)
99
+ if number > 0
100
+ tail.drop(number - 1)
82
101
  else
83
- @tail.reduce(yield(memo, @head)) { |memo, item| yield(memo, item) }
102
+ self
84
103
  end
85
104
  end
86
105
 
106
+ def include?(item)
107
+ item == head || tail.include?(item)
108
+ end
109
+ alias_method :member?, :include?
110
+
111
+ def eql?(other)
112
+ return true if other.equal?(self)
113
+ return false unless other.is_a?(List)
114
+ other.head == head && other.tail.eql?(tail)
115
+ end
116
+ alias_method :==, :eql?
117
+
118
+ def dup
119
+ self
120
+ end
121
+ alias_method :clone, :dup
122
+
87
123
  private
88
124
 
89
125
  def method_missing(name, *args, &block)
@@ -99,7 +135,7 @@ module Hamster
99
135
  # identify the series of car and cdr operations that is performed by the function. The order in which the 'a's and
100
136
  # 'd's appear is the inverse of the order in which the corresponding operations are performed.
101
137
  def accessor(sequence)
102
- sequence.split(//).reverse!.inject(self) do |memo, char|
138
+ sequence.split(//).reverse!.reduce(self) do |memo, char|
103
139
  case char
104
140
  when "a" then memo.head
105
141
  when "d" then memo.tail
@@ -109,4 +145,105 @@ module Hamster
109
145
 
110
146
  end
111
147
 
148
+ class Sequence
149
+
150
+ include List
151
+
152
+ attr_reader :head, :tail
153
+
154
+ def initialize(head, tail)
155
+ @head = head
156
+ @tail = tail
157
+ end
158
+
159
+ end
160
+
161
+ class Stream
162
+
163
+ include List
164
+
165
+ attr_reader :head
166
+
167
+ def initialize(head, &tail)
168
+ @head = head
169
+ @tail = tail
170
+ end
171
+
172
+ def tail
173
+ @tail.call
174
+ end
175
+
176
+ end
177
+
178
+ module EmptyList
179
+
180
+ class << self
181
+
182
+ include List
183
+
184
+ def head
185
+ nil
186
+ end
187
+
188
+ def tail
189
+ self
190
+ end
191
+
192
+ def empty?
193
+ true
194
+ end
195
+
196
+ def size
197
+ 0
198
+ end
199
+ alias_method :length, :size
200
+
201
+ def each
202
+ block_given? or return self
203
+ nil
204
+ end
205
+
206
+ def map
207
+ self
208
+ end
209
+
210
+ def reduce(memo)
211
+ memo
212
+ end
213
+ alias_method :inject, :reduce
214
+
215
+ def filter
216
+ self
217
+ end
218
+ alias_method :select, :filter
219
+
220
+ def reject
221
+ self
222
+ end
223
+
224
+ def take_while
225
+ self
226
+ end
227
+
228
+ def drop_while
229
+ self
230
+ end
231
+
232
+ def take(number)
233
+ self
234
+ end
235
+
236
+ def drop(number)
237
+ self
238
+ end
239
+
240
+ def include?(item)
241
+ false
242
+ end
243
+ alias_method :member?, :include?
244
+
245
+ end
246
+
247
+ end
248
+
112
249
  end