hamster 0.1.8 → 0.1.11

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