hamster 0.1.16 → 0.1.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/History.rdoc +30 -0
  2. data/README.rdoc +12 -6
  3. data/lib/hamster/hash.rb +1 -0
  4. data/lib/hamster/list.rb +55 -38
  5. data/lib/hamster/set.rb +12 -0
  6. data/lib/hamster/version.rb +1 -1
  7. data/spec/hamster/hash/empty_spec.rb +12 -8
  8. data/spec/hamster/list/all_spec.rb +3 -7
  9. data/spec/hamster/list/any_spec.rb +3 -7
  10. data/spec/hamster/list/append_spec.rb +2 -6
  11. data/spec/hamster/list/construction_spec.rb +42 -6
  12. data/spec/hamster/list/cycle_spec.rb +51 -0
  13. data/spec/hamster/list/drop_spec.rb +2 -6
  14. data/spec/hamster/list/drop_while_spec.rb +2 -6
  15. data/spec/hamster/list/each_spec.rb +3 -7
  16. data/spec/hamster/list/empty_spec.rb +16 -12
  17. data/spec/hamster/list/eql_spec.rb +4 -9
  18. data/spec/hamster/list/filter_spec.rb +2 -6
  19. data/spec/hamster/list/find_spec.rb +2 -6
  20. data/spec/hamster/list/grep_spec.rb +83 -0
  21. data/spec/hamster/list/include_spec.rb +2 -6
  22. data/spec/hamster/list/inspect_spec.rb +2 -6
  23. data/spec/hamster/list/map_spec.rb +2 -6
  24. data/spec/hamster/list/maximum_spec.rb +3 -7
  25. data/spec/hamster/list/minimum_spec.rb +3 -7
  26. data/spec/hamster/list/none_spec.rb +2 -6
  27. data/spec/hamster/list/one_spec.rb +3 -7
  28. data/spec/hamster/list/partition_spec.rb +10 -14
  29. data/spec/hamster/list/reduce_spec.rb +2 -6
  30. data/spec/hamster/list/reject_spec.rb +2 -6
  31. data/spec/hamster/list/reverse_spec.rb +2 -6
  32. data/spec/hamster/list/size_spec.rb +2 -6
  33. data/spec/hamster/list/split_at_spec.rb +64 -0
  34. data/spec/hamster/list/take_spec.rb +2 -6
  35. data/spec/hamster/list/take_while_spec.rb +2 -6
  36. data/spec/hamster/list/to_a_spec.rb +2 -6
  37. data/spec/hamster/list/to_ary_spec.rb +2 -6
  38. data/spec/hamster/list/zip_spec.rb +50 -0
  39. data/spec/hamster/set/empty_spec.rb +20 -8
  40. data/spec/hamster/set/grep_spec.rb +62 -0
  41. data/spec/hamster/set/to_list.rb +46 -0
  42. data/spec/hamster/set/uniq_spec.rb +23 -0
  43. metadata +9 -2
@@ -1,3 +1,33 @@
1
+ === 0.1.17 / 2010-01-02
2
+
3
+ * Alias #empty? as #null?
4
+
5
+ * Implement List#split_at.
6
+
7
+ * Implement List.iterate.
8
+
9
+ * Implement List#cycle.
10
+
11
+ * Implement List.replicate.
12
+
13
+ * Implement List.repeat.
14
+
15
+ * Simplify List#take.
16
+
17
+ * Simplify any?, all?, and none?
18
+
19
+ * Re-write List#one? to be less complex.
20
+
21
+ * Add Set#to_list.
22
+
23
+ * Implement List#zip.
24
+
25
+ * Implement Set#grep.
26
+
27
+ * Implement Set#uniq (aliased as #nub).
28
+
29
+ * Implement List#grep.
30
+
1
31
  === 0.1.16 / 2009-12-30
2
32
 
3
33
  * Ensure streams cache results.
@@ -2,7 +2,7 @@
2
2
 
3
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.
4
4
 
5
- == Huh?
5
+ == What are persistent data structures?
6
6
 
7
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:
8
8
 
@@ -14,7 +14,7 @@ Persistent data structures have a really neat property: very efficient copy-on-w
14
14
 
15
15
  == Double Huh? That's not much use!
16
16
 
17
- Whoops! Remember, each call to <tt>#put</tt> creates an efficient copy containing the modifications, leaving the original unmodified. So, unlike Ruby's built-in <tt>Hash</tt> 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:
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
18
 
19
19
  original = Hamster.hash
20
20
  copy = original.put("Name", "Simon")
@@ -33,9 +33,9 @@ The same goes for <tt>#remove</tt>:
33
33
 
34
34
  == Oh, I get it. Cool. But I still don't understand why I should care?
35
35
 
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 heap space in the process.
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>!
37
37
 
38
- 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>!
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).
39
39
 
40
40
  == OK, that sounds mildly interesting. What's the downside--there's always a downside?
41
41
 
@@ -127,6 +127,12 @@ Besides <tt>Hamster.list</tt> there are other ways to construct lists:
127
127
 
128
128
  <tt>Hamster.stream { ... }</tt> allows you to creates infinite lists. Each time a new value is required, the supplied block is called.
129
129
 
130
+ <tt>Hamster.repeat(x)</tt> creates an infinite list with x the value for every element.
131
+
132
+ <tt>Hamster.replicate(n, x)</tt> creates a list of size n with x the value for every element.
133
+
134
+ <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.
135
+
130
136
  You also get <tt>Enumerable#to_list</tt> so you can slowly transition from built-in collection classes to Hamster.
131
137
 
132
138
  And finally, you get <tt>IO#to_list</tt> allowing you to lazily processes huge files. For example, imagine the following code to process a 100MB file:
@@ -159,6 +165,6 @@ How is this even possible? It's possible because <tt>IO#to_list</tt> creates a l
159
165
 
160
166
  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.
161
167
 
162
- Performance is pretty good--especially with lazy lists--but there are some things which unfortunately had to be converted from recursive to iterative due to a lack of Tail-Call-Optimisation in Ruby, making them a little less readable than I would otherwise have preferred.
168
+ Performance is pretty good--especially with lazy lists--but there are some things which unfortunately had to be converted from recursive to iterative due to a lack of Tail-Call-Optimisation in Ruby, making them a little less readable, and a little more memory hungry, than I would otherwise have preferred.
163
169
 
164
- Documentation is sparse but I've tried as best I can to write specs that reads 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.
170
+ 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.
@@ -20,6 +20,7 @@ module Hamster
20
20
  def empty?
21
21
  @trie.empty?
22
22
  end
23
+ alias_method :null?, :empty?
23
24
 
24
25
  def has_key?(key)
25
26
  @trie.has_key?(key)
@@ -21,6 +21,18 @@ module Hamster
21
21
  end
22
22
  alias_method :range, :interval
23
23
 
24
+ def repeat(item)
25
+ Sequence.new(item)
26
+ end
27
+
28
+ def replicate(number, item)
29
+ Sequence.new(item).take(number)
30
+ end
31
+
32
+ def iterate(item, &block)
33
+ Stream.new(item) { iterate(yield(item), &block) }
34
+ end
35
+
24
36
  end
25
37
 
26
38
  module List
@@ -28,6 +40,7 @@ module Hamster
28
40
  def empty?
29
41
  false
30
42
  end
43
+ alias_method :null?, :empty?
31
44
 
32
45
  def size
33
46
  reduce(0) { |memo, item| memo.succ }
@@ -100,11 +113,8 @@ module Hamster
100
113
  end
101
114
 
102
115
  def take(number)
103
- if number > 0
104
- Stream.new(head) { tail.take(number - 1) }
105
- else
106
- EmptyList
107
- end
116
+ return EmptyList unless number > 0
117
+ Stream.new(head) { tail.take(number - 1) }
108
118
  end
109
119
 
110
120
  def drop(number)
@@ -123,52 +133,33 @@ module Hamster
123
133
  alias_method :member?, :include?
124
134
 
125
135
  def any?
126
- if block_given?
127
- each { |item| return true if yield(item) }
128
- else
129
- each { |item| return true if item }
130
- end
136
+ return any? { |item| item } unless block_given?
137
+ each { |item| return true if yield(item) }
131
138
  false
132
139
  end
133
140
  alias_method :exist?, :any?
134
141
  alias_method :exists?, :any?
135
142
 
136
143
  def all?
137
- if block_given?
138
- each { |item| return false unless yield(item) }
139
- else
140
- each { |item| return false unless item }
141
- end
144
+ return all? { |item| item } unless block_given?
145
+ each { |item| return false unless yield(item) }
142
146
  true
143
147
  end
144
148
 
145
149
  def none?
146
- if block_given?
147
- each { |item| return false if yield(item) }
148
- else
149
- each { |item| return false if item }
150
- end
150
+ return none? { |item| item } unless block_given?
151
+ each { |item| return false if yield(item) }
151
152
  true
152
153
  end
153
154
 
154
- def one?
155
- found_one = false
156
- if block_given?
157
- each do |item|
158
- if yield(item)
159
- return false if found_one
160
- found_one = true
161
- end
162
- end
163
- else
164
- each do |item|
165
- if item
166
- return false if found_one
167
- found_one = true
168
- end
169
- end
155
+ def one?(&block)
156
+ return one? { |item| item } unless block_given?
157
+ list = self
158
+ while !list.empty?
159
+ return list.tail.none?(&block) if yield(list.head)
160
+ list = list.tail
170
161
  end
171
- found_one
162
+ false
172
163
  end
173
164
 
174
165
  def find
@@ -205,6 +196,22 @@ module Hamster
205
196
  end
206
197
  alias_method :max, :maximum
207
198
 
199
+ def grep(pattern, &block)
200
+ filter { |item| pattern === item }.map(&block)
201
+ end
202
+
203
+ def zip(other)
204
+ Stream.new(EmptyList.cons(other.head).cons(head)) { tail.zip(other.tail) }
205
+ end
206
+
207
+ def cycle
208
+ Stream.new(head) { tail.append(self.cycle) }
209
+ end
210
+
211
+ def split_at(number)
212
+ EmptyList.cons(drop(number)).cons(take(number))
213
+ end
214
+
208
215
  def eql?(other)
209
216
  return false unless other.is_a?(List)
210
217
 
@@ -270,7 +277,7 @@ module Hamster
270
277
 
271
278
  attr_reader :head, :tail
272
279
 
273
- def initialize(head, tail)
280
+ def initialize(head, tail = self)
274
281
  @head = head
275
282
  @tail = tail
276
283
  end
@@ -317,6 +324,7 @@ module Hamster
317
324
  def empty?
318
325
  true
319
326
  end
327
+ alias_method :null?, :empty?
320
328
 
321
329
  def map
322
330
  self
@@ -349,6 +357,15 @@ module Hamster
349
357
  alias_method :cat, :append
350
358
  alias_method :+, :append
351
359
 
360
+ def zip(other)
361
+ return super unless other.empty?
362
+ self
363
+ end
364
+
365
+ def cycle
366
+ self
367
+ end
368
+
352
369
  end
353
370
 
354
371
  end
@@ -1,4 +1,5 @@
1
1
  require 'hamster/trie'
2
+ require 'hamster/list'
2
3
 
3
4
  module Hamster
4
5
 
@@ -15,6 +16,7 @@ module Hamster
15
16
  def empty?
16
17
  @trie.empty?
17
18
  end
19
+ alias_method :null?, :empty?
18
20
 
19
21
  def size
20
22
  @trie.size
@@ -114,6 +116,10 @@ module Hamster
114
116
  true
115
117
  end
116
118
 
119
+ def grep(pattern, &block)
120
+ filter { |item| pattern === item }.map(&block)
121
+ end
122
+
117
123
  def eql?(other)
118
124
  other.is_a?(self.class) && @trie.eql?(other.instance_eval{@trie})
119
125
  end
@@ -123,12 +129,18 @@ module Hamster
123
129
  self
124
130
  end
125
131
  alias_method :clone, :dup
132
+ alias_method :uniq, :dup
133
+ alias_method :nub, :dup
126
134
 
127
135
  def to_a
128
136
  reduce([]) { |a, item| a << item }
129
137
  end
130
138
  alias_method :entries, :to_a
131
139
 
140
+ def to_list
141
+ reduce(Hamster.list) { |list, item| list.cons(item) }
142
+ end
143
+
132
144
  end
133
145
 
134
146
  end
@@ -1,5 +1,5 @@
1
1
  module Hamster
2
2
 
3
- VERSION = "0.1.16".freeze
3
+ VERSION = "0.1.17".freeze
4
4
 
5
5
  end
@@ -4,16 +4,20 @@ require 'hamster/hash'
4
4
 
5
5
  describe Hamster::Hash do
6
6
 
7
- describe "#empty?" do
7
+ [:empty?, :null?].each do |method|
8
8
 
9
- [
10
- [[], true],
11
- [["A" => "aye"], false],
12
- [["A" => "aye", "B" => "bee", "C" => "see"], false],
13
- ].each do |pairs, result|
9
+ describe "##{method}" do
10
+
11
+ [
12
+ [[], true],
13
+ [["A" => "aye"], false],
14
+ [["A" => "aye", "B" => "bee", "C" => "see"], false],
15
+ ].each do |pairs, result|
16
+
17
+ it "returns #{result} for #{pairs.inspect}" do
18
+ Hamster.hash(*pairs).send(method).should == result
19
+ end
14
20
 
15
- it "returns #{result} for #{pairs.inspect}" do
16
- Hamster.hash(*pairs).empty?.should == result
17
21
  end
18
22
 
19
23
  end
@@ -8,20 +8,16 @@ describe Hamster::List do
8
8
 
9
9
  describe "doesn't run out of stack space on a really big" do
10
10
 
11
- before do
12
- @interval = Hamster.interval(0, 10000)
13
- end
14
-
15
11
  it "stream" do
16
- @list = @interval
12
+ @list = Hamster.interval(0, 10000)
17
13
  end
18
14
 
19
15
  it "list" do
20
- @list = @interval.reduce(Hamster.list) { |list, i| list.cons(i) }
16
+ @list = (0..10000).reduce(Hamster.list) { |list, i| list.cons(i) }
21
17
  end
22
18
 
23
19
  after do
24
- @interval.all? { true }
20
+ @list.all? { true }
25
21
  end
26
22
 
27
23
  end
@@ -10,20 +10,16 @@ describe Hamster::List do
10
10
 
11
11
  describe "doesn't run out of stack space on a really big" do
12
12
 
13
- before do
14
- @interval = Hamster.interval(0, 10000)
15
- end
16
-
17
13
  it "stream" do
18
- @list = @interval
14
+ @list = Hamster.interval(0, 10000)
19
15
  end
20
16
 
21
17
  it "list" do
22
- @list = @interval.reduce(Hamster.list) { |list, i| list.cons(i) }
18
+ @list = (0..10000).reduce(Hamster.list) { |list, i| list.cons(i) }
23
19
  end
24
20
 
25
21
  after do
26
- @interval.any? { false }
22
+ @list.any? { false }
27
23
  end
28
24
 
29
25
  end
@@ -10,16 +10,12 @@ describe Hamster::List do
10
10
 
11
11
  describe "doesn't run out of stack space on a really big" do
12
12
 
13
- before do
14
- @interval = Hamster.interval(0, 10000)
15
- end
16
-
17
13
  it "stream" do
18
- @a = @b = @interval
14
+ @a = @b = Hamster.interval(0, 10000)
19
15
  end
20
16
 
21
17
  it "list" do
22
- @a = @b = @interval.reduce(Hamster.list) { |list, i| list.cons(i) }
18
+ @a = @b = (0..10000).reduce(Hamster.list) { |list, i| list.cons(i) }
23
19
  end
24
20
 
25
21
  after do
@@ -45,11 +45,11 @@ describe Hamster do
45
45
  describe "with no block" do
46
46
 
47
47
  before do
48
- @stream = Hamster.stream
48
+ @list = Hamster.stream
49
49
  end
50
50
 
51
51
  it "returns an empty list" do
52
- @stream.should == Hamster.list
52
+ @list.should == Hamster.list
53
53
  end
54
54
 
55
55
  end
@@ -58,11 +58,11 @@ describe Hamster do
58
58
 
59
59
  before do
60
60
  count = 0
61
- @stream = Hamster.stream { count += 1 }
61
+ @list = Hamster.stream { count += 1 }
62
62
  end
63
63
 
64
64
  it "repeatedly calls the block" do
65
- @stream.take(5).should == Hamster.list(1, 2, 3, 4, 5)
65
+ @list.take(5).should == Hamster.list(1, 2, 3, 4, 5)
66
66
  end
67
67
 
68
68
  end
@@ -74,15 +74,51 @@ describe Hamster do
74
74
  describe ".#{method}" do
75
75
 
76
76
  before do
77
- @interval = Hamster.send(method, "A", "D")
77
+ @list = Hamster.send(method, "A", "D")
78
78
  end
79
79
 
80
80
  it "is equivalent to a list with explicit values" do
81
- @interval.should == Hamster.list("A", "B", "C", "D")
81
+ @list.should == Hamster.list("A", "B", "C", "D")
82
82
  end
83
83
 
84
84
  end
85
85
 
86
86
  end
87
87
 
88
+ describe ".repeat" do
89
+
90
+ before do
91
+ @list = Hamster.repeat("A")
92
+ end
93
+
94
+ it "returns an infinite list with specified value for each element" do
95
+ @list.take(5).should == Hamster.list("A", "A", "A", "A", "A")
96
+ end
97
+
98
+ end
99
+
100
+ describe ".replicate" do
101
+
102
+ before do
103
+ @list = Hamster.replicate(5, "A")
104
+ end
105
+
106
+ it "returns a list with the specified value repeated the specified number of times" do
107
+ @list.should == Hamster.list("A", "A", "A", "A", "A")
108
+ end
109
+
110
+ end
111
+
112
+ describe ".iterate" do
113
+
114
+ before do
115
+ @list = Hamster.iterate(1) { |item| item * 2 }
116
+ end
117
+
118
+ it "returns 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" do
119
+ @list.take(10).should == Hamster.list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512)
120
+ end
121
+
122
+ end
123
+
88
124
  end