hamster 0.1.14 → 0.1.15

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 (85) hide show
  1. data/History.rdoc +6 -0
  2. data/README.rdoc +33 -16
  3. data/lib/hamster.rb +1 -0
  4. data/lib/hamster/core_ext.rb +2 -0
  5. data/lib/hamster/core_ext/enumerable.rb +23 -0
  6. data/lib/hamster/core_ext/io.rb +29 -0
  7. data/lib/hamster/list.rb +23 -0
  8. data/lib/hamster/version.rb +1 -1
  9. data/spec/hamster/core_ext/enumerable_spec.rb +34 -0
  10. data/spec/hamster/core_ext/io_spec.rb +17 -0
  11. data/spec/hamster/core_ext/io_spec.txt +3 -0
  12. data/spec/hamster/hash/all_spec.rb +2 -0
  13. data/spec/hamster/hash/any_spec.rb +2 -0
  14. data/spec/hamster/hash/construction_spec.rb +2 -0
  15. data/spec/hamster/hash/copying_spec.rb +2 -0
  16. data/spec/hamster/hash/each_spec.rb +2 -0
  17. data/spec/hamster/hash/empty_spec.rb +2 -0
  18. data/spec/hamster/hash/eql_spec.rb +2 -0
  19. data/spec/hamster/hash/filter_spec.rb +2 -0
  20. data/spec/hamster/hash/get_spec.rb +2 -0
  21. data/spec/hamster/hash/has_key_spec.rb +2 -0
  22. data/spec/hamster/hash/map_spec.rb +2 -0
  23. data/spec/hamster/hash/none_spec.rb +2 -0
  24. data/spec/hamster/hash/put_spec.rb +2 -0
  25. data/spec/hamster/hash/reduce_spec.rb +2 -0
  26. data/spec/hamster/hash/reject_spec.rb +2 -0
  27. data/spec/hamster/hash/remove_spec.rb +2 -0
  28. data/spec/hamster/hash/size_spec.rb +2 -0
  29. data/spec/hamster/list/all_spec.rb +2 -0
  30. data/spec/hamster/list/any_spec.rb +2 -0
  31. data/spec/hamster/list/append_spec.rb +65 -0
  32. data/spec/hamster/list/cadr_spec.rb +2 -0
  33. data/spec/hamster/list/cons_spec.rb +2 -0
  34. data/spec/hamster/list/construction_spec.rb +2 -0
  35. data/spec/hamster/list/copying_spec.rb +2 -0
  36. data/spec/hamster/list/drop_spec.rb +2 -0
  37. data/spec/hamster/list/drop_while_spec.rb +2 -0
  38. data/spec/hamster/list/each_spec.rb +2 -0
  39. data/spec/hamster/list/empty_spec.rb +2 -0
  40. data/spec/hamster/list/eql_spec.rb +2 -0
  41. data/spec/hamster/list/filter_spec.rb +2 -0
  42. data/spec/hamster/list/find_spec.rb +2 -0
  43. data/spec/hamster/list/head_spec.rb +2 -0
  44. data/spec/hamster/list/include_spec.rb +2 -0
  45. data/spec/hamster/list/inspect_spec.rb +2 -0
  46. data/spec/hamster/list/map_spec.rb +2 -0
  47. data/spec/hamster/list/none_spec.rb +2 -0
  48. data/spec/hamster/list/partition_spec.rb +101 -0
  49. data/spec/hamster/list/reduce_spec.rb +2 -0
  50. data/spec/hamster/list/reject_spec.rb +2 -0
  51. data/spec/hamster/list/reverse_spec.rb +43 -0
  52. data/spec/hamster/list/size_spec.rb +2 -0
  53. data/spec/hamster/list/tail_spec.rb +2 -0
  54. data/spec/hamster/list/take_spec.rb +2 -0
  55. data/spec/hamster/list/take_while_spec.rb +2 -0
  56. data/spec/hamster/list/to_a_spec.rb +2 -0
  57. data/spec/hamster/list/to_ary_spec.rb +2 -0
  58. data/spec/hamster/set/add_spec.rb +2 -0
  59. data/spec/hamster/set/all_spec.rb +2 -0
  60. data/spec/hamster/set/any_spec.rb +2 -0
  61. data/spec/hamster/set/construction_spec.rb +2 -0
  62. data/spec/hamster/set/copying_spec.rb +2 -0
  63. data/spec/hamster/set/each_spec.rb +2 -0
  64. data/spec/hamster/set/empty_spec.rb +2 -0
  65. data/spec/hamster/set/eql_spec.rb +1 -0
  66. data/spec/hamster/set/filter_spec.rb +2 -0
  67. data/spec/hamster/set/include_spec.rb +2 -0
  68. data/spec/hamster/set/map_spec.rb +2 -0
  69. data/spec/hamster/set/none_spec.rb +2 -0
  70. data/spec/hamster/set/reduce_spec.rb +2 -0
  71. data/spec/hamster/set/reject_spec.rb +2 -0
  72. data/spec/hamster/set/remove_spec.rb +2 -0
  73. data/spec/hamster/set/size_spec.rb +2 -0
  74. data/spec/hamster/set/to_a_spec.rb +2 -0
  75. data/spec/hamster/stack/construction_spec.rb +2 -0
  76. data/spec/hamster/stack/copying_spec.rb +2 -0
  77. data/spec/hamster/stack/empty_spec.rb +2 -0
  78. data/spec/hamster/stack/eql_spec.rb +2 -0
  79. data/spec/hamster/stack/inspect_spec.rb +2 -0
  80. data/spec/hamster/stack/pop_spec.rb +2 -0
  81. data/spec/hamster/stack/push_spec.rb +2 -0
  82. data/spec/hamster/stack/size_spec.rb +2 -0
  83. data/spec/hamster/stack/top_spec.rb +2 -0
  84. data/spec/spec_helper.rb +0 -2
  85. metadata +11 -2
data/History.rdoc CHANGED
@@ -1,3 +1,9 @@
1
+ === 0.1.15 / 2009-12-15
2
+
3
+ * Implemented IO#to_list.
4
+
5
+ * Implemented Enumerable#to_list.
6
+
1
7
  === 0.1.14 / 2009-12-14
2
8
 
3
9
  * List#reduce supports optional initial value.
data/README.rdoc CHANGED
@@ -6,8 +6,6 @@ Hamster started out as an implementation of Hash Array Mapped Hashes (HAMT) for
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
 
9
- require 'hamster'
10
-
11
9
  hash = Hamster.hash
12
10
 
13
11
  hash.put("Name", "Simon")
@@ -18,8 +16,6 @@ Persistent data structures have a really neat property: very efficient copy-on-w
18
16
 
19
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:
20
18
 
21
- require 'hamster'
22
-
23
19
  original = Hamster.hash
24
20
  copy = original.put("Name", "Simon")
25
21
 
@@ -28,8 +24,6 @@ Whoops! Remember, each call to <tt>#put</tt> creates an efficient copy containin
28
24
 
29
25
  The same goes for <tt>#remove</tt>:
30
26
 
31
- require 'hamster'
32
-
33
27
  original = Hamster.hash
34
28
  original = original.put("Name", "Simon")
35
29
  copy = hash.remove("Name")
@@ -47,8 +41,6 @@ Moreover, because they're immutable, you can pass them around between objects, m
47
41
 
48
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:
49
43
 
50
- require 'hamster'
51
-
52
44
  hash = Hamster.hash
53
45
  (1..10000).each { |i| hash = hash.put(i, i) } # => 0.05s
54
46
  (1..10000).each { |i| hash.get(i) } # => 0.008s
@@ -67,8 +59,6 @@ Well, yes and no. The previous comparison wasn't really fair. Sure, if all you w
67
59
 
68
60
  A more realistic comparison might look like:
69
61
 
70
- require 'hamster'
71
-
72
62
  hash = Hamster.hash
73
63
  (1..10000).each { |i| hash = hash.put(i, i) } # => 0.05s
74
64
  (1..10000).each { |i| hash.get(i) } # => 0.008s
@@ -97,6 +87,9 @@ And don't forget that even if threading isn't a concern for you, the safety prov
97
87
 
98
88
  Indeed I did.
99
89
 
90
+ === Sets
91
+
92
+
100
93
  === Lists
101
94
 
102
95
  Lists have a head--the value of the item at the head of the list--and a tail--containing the remaining items. For example:
@@ -126,17 +119,41 @@ The following code will only call <tt>prime?</tt> as many times as necessary to
126
119
 
127
120
  Compare that to the conventional equivalent which needs to calculate all possible values in the range before taking the first 3:
128
121
 
129
- (10000..1000000).select { |i| prime?(i) }[1, 3] # => 10s
122
+ (10000..1000000).select { |i| prime?(i) }[0, 3] # => 10s
130
123
 
131
- Besides <tt>Hamster.list</tt> there is <tt>Hamster.interval(from, to)</tt> and <tt>Hamster.stream { a_block }</tt>.
124
+ Besides <tt>Hamster.list</tt> there are other ways to construct lists:
132
125
 
133
- <tt>.interval</tt> (aliased as <tt>.range</tt>) creates a lazy list equivalent to a list containing all the values between <tt>from</tt> and <tt>to</tt> without actually creating a list that big.
126
+ <tt>Hamster.interval(from, to)</tt> (aliased as <tt>.range</tt>) creates a lazy list equivalent to a list containing all the values between <tt>from</tt> and <tt>to</tt> without actually creating a list that big.
134
127
 
135
- <tt>.stream</tt> allows you to creates infinite lists. Each time a new value is required, the supplied block is called.
128
+ <tt>Hamster.stream { ... }</tt> allows you to creates infinite lists. Each time a new value is required, the supplied block is called.
136
129
 
137
- === Stacks
130
+ You also get <tt>Enumerable#to_list</tt> so you can slowly transition from built-in collection classes to Hamster.
138
131
 
139
- === Sets
132
+ And finally, you also <tt>IO#to_list</tt> allowing you to lazily processes huge files. For example, imagine the following code to process a 10MB file:
133
+
134
+ File.open("my_10_mb_file.txt") do |io|
135
+ lines = []
136
+ io.each_line do |line|
137
+ break if lines.size == 10
138
+ lines << line.chomp.downcase.reverse
139
+ end
140
+ end
141
+
142
+ How many times/how long did you read the code before it became apparent what the code actually did? Now compare that to the following:
143
+
144
+ File.open("my_10_mb_file.txt") do |io|
145
+ io.map(&:chomp).map(&:downcase).map(&:reverse)[0, 10]
146
+ end
147
+
148
+ Unfortunately, though the second example reads nicely, it takes around 3 seconds to run--compared with 0.033 seconds for the first--even though we're only interested in the first 10 lines! However, using a little <tt>#to_list</tt> magic, we can get the running time down to 0.033 seconds!
149
+
150
+ File.open("my_10_mb_file.txt") do |io|
151
+ puts io.to_list.map(&:chomp).map(&:downcase).map(&:reverse).take(10)
152
+ end
153
+
154
+ 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.
155
+
156
+ === Stacks
140
157
 
141
158
  == Disclaimer
142
159
 
data/lib/hamster.rb CHANGED
@@ -2,4 +2,5 @@ require 'hamster/list'
2
2
  require 'hamster/stack'
3
3
  require 'hamster/set'
4
4
  require 'hamster/hash'
5
+ require 'hamster/core_ext'
5
6
  require 'hamster/version'
@@ -0,0 +1,2 @@
1
+ require 'hamster/core_ext/enumerable'
2
+ require 'hamster/core_ext/io'
@@ -0,0 +1,23 @@
1
+ require 'hamster/list'
2
+
3
+ module Hamster
4
+
5
+ module CoreExt
6
+
7
+ module Enumerable
8
+
9
+ def to_list
10
+ Hamster.list(self)
11
+ end
12
+
13
+ end
14
+
15
+ end
16
+
17
+ end
18
+
19
+ module Enumerable
20
+
21
+ include Hamster::CoreExt::Enumerable
22
+
23
+ end
@@ -0,0 +1,29 @@
1
+ require 'hamster/list'
2
+
3
+ module Hamster
4
+
5
+ module CoreExt
6
+
7
+ module IO
8
+
9
+ def to_list(sep = $/)
10
+ line = gets(sep)
11
+ if line
12
+ Stream.new(line) { to_list }
13
+ else
14
+ EmptyList
15
+ end
16
+
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+
25
+ class IO
26
+
27
+ include Hamster::CoreExt::IO
28
+
29
+ end
data/lib/hamster/list.rb CHANGED
@@ -164,6 +164,22 @@ module Hamster
164
164
  end
165
165
  alias_method :detect, :find
166
166
 
167
+ def partition(&block)
168
+ return self unless block_given?
169
+ EmptyList.cons(reject(&block)).cons(filter(&block))
170
+ end
171
+
172
+ def append(other)
173
+ Stream.new(head) { tail.append(other) }
174
+ end
175
+ alias_method :concat, :append
176
+ alias_method :cat, :append
177
+ alias_method :+, :append
178
+
179
+ def reverse
180
+ reduce(EmptyList) { |list, item| list.cons(item) }
181
+ end
182
+
167
183
  def eql?(other)
168
184
  return false unless other.is_a?(List)
169
185
 
@@ -290,6 +306,13 @@ module Hamster
290
306
  self
291
307
  end
292
308
 
309
+ def append(other)
310
+ other
311
+ end
312
+ alias_method :concat, :append
313
+ alias_method :cat, :append
314
+ alias_method :+, :append
315
+
293
316
  end
294
317
 
295
318
  end
@@ -1,5 +1,5 @@
1
1
  module Hamster
2
2
 
3
- VERSION = "0.1.14".freeze
3
+ VERSION = "0.1.15".freeze
4
4
 
5
5
  end
@@ -0,0 +1,34 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ require 'hamster/core_ext/enumerable'
4
+
5
+ describe Enumerable do
6
+
7
+ class TestEnumerable
8
+
9
+ include Enumerable
10
+
11
+ def initialize(*values)
12
+ @values = values
13
+ end
14
+
15
+ def each(&block)
16
+ @values.each(&block)
17
+ end
18
+
19
+ end
20
+
21
+ describe "#to_list" do
22
+
23
+ before do
24
+ enumerable = TestEnumerable.new("A", "B", "C")
25
+ @list = enumerable.to_list
26
+ end
27
+
28
+ it "returns an equivalent list" do
29
+ @list == Hamster.list("A", "B", "C")
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,17 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ require 'hamster/core_ext/io'
4
+
5
+ describe IO do
6
+
7
+ describe "#to_list" do
8
+
9
+ it "returns an equivalent list" do
10
+ File.open(File.dirname(__FILE__) + "/io_spec.txt") do |io|
11
+ io.to_list.should == Hamster.list("A\n", "B\n", "C\n")
12
+ end
13
+ end
14
+
15
+ end
16
+
17
+ end
@@ -0,0 +1,3 @@
1
+ A
2
+ B
3
+ C
@@ -1,5 +1,7 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
2
 
3
+ require 'hamster/hash'
4
+
3
5
  describe Hamster::Hash do
4
6
 
5
7
  describe "#all?" do
@@ -1,5 +1,7 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
2
 
3
+ require 'hamster/hash'
4
+
3
5
  describe Hamster::Hash do
4
6
 
5
7
  [:any?, :exist?, :exists?].each do |method|
@@ -1,5 +1,7 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
2
 
3
+ require 'hamster/hash'
4
+
3
5
  describe Hamster::Hash do
4
6
 
5
7
  describe ".hash" do
@@ -1,5 +1,7 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
2
 
3
+ require 'hamster/hash'
4
+
3
5
  describe Hamster::Hash do
4
6
 
5
7
  before do
@@ -1,5 +1,7 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
2
 
3
+ require 'hamster/hash'
4
+
3
5
  describe Hamster::Hash do
4
6
 
5
7
  describe "#each" do
@@ -1,5 +1,7 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
2
 
3
+ require 'hamster/hash'
4
+
3
5
  describe Hamster::Hash do
4
6
 
5
7
  describe "#empty?" do
@@ -1,5 +1,7 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
2
 
3
+ require 'hamster/hash'
4
+
3
5
  describe Hamster::Hash do
4
6
 
5
7
  [:eql?, :==].each do |method|
@@ -1,5 +1,7 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
2
 
3
+ require 'hamster/hash'
4
+
3
5
  describe Hamster::Hash do
4
6
 
5
7
  [:filter, :select, :find_all].each do |method|
@@ -1,5 +1,7 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
2
 
3
+ require 'hamster/hash'
4
+
3
5
  describe Hamster::Hash do
4
6
 
5
7
  [:get, :[]].each do |method|
@@ -1,5 +1,7 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
2
 
3
+ require 'hamster/hash'
4
+
3
5
  describe Hamster::Hash do
4
6
 
5
7
  [:has_key?, :key?, :include?, :member?].each do |method|
@@ -1,5 +1,7 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
2
 
3
+ require 'hamster/hash'
4
+
3
5
  describe Hamster::Hash do
4
6
 
5
7
  [:map, :collect].each do |method|
@@ -1,5 +1,7 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
2
 
3
+ require 'hamster/hash'
4
+
3
5
  describe Hamster::Hash do
4
6
 
5
7
  describe "#none?" do
@@ -1,5 +1,7 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
2
 
3
+ require 'hamster/hash'
4
+
3
5
  describe Hamster::Hash do
4
6
 
5
7
  [:put, :[]=].each do |method|
@@ -1,5 +1,7 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
2
 
3
+ require 'hamster/hash'
4
+
3
5
  describe Hamster::Hash do
4
6
 
5
7
  [:reduce, :inject, :fold].each do |method|
@@ -1,5 +1,7 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
2
 
3
+ require 'hamster/hash'
4
+
3
5
  describe Hamster::Hash do
4
6
 
5
7
  [:reject, :delete_if].each do |method|
@@ -1,5 +1,7 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
2
 
3
+ require 'hamster/hash'
4
+
3
5
  describe Hamster::Hash do
4
6
 
5
7
  describe "#remove" do
@@ -1,5 +1,7 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
2
 
3
+ require 'hamster/hash'
4
+
3
5
  describe Hamster::Hash do
4
6
 
5
7
  [:size, :length].each do |method|
@@ -1,5 +1,7 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
2
 
3
+ require 'hamster/list'
4
+
3
5
  describe Hamster::List do
4
6
 
5
7
  describe "#all?" do