hamster 0.1.14 → 0.1.15

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