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.
- data/History.rdoc +6 -0
- data/README.rdoc +33 -16
- data/lib/hamster.rb +1 -0
- data/lib/hamster/core_ext.rb +2 -0
- data/lib/hamster/core_ext/enumerable.rb +23 -0
- data/lib/hamster/core_ext/io.rb +29 -0
- data/lib/hamster/list.rb +23 -0
- data/lib/hamster/version.rb +1 -1
- data/spec/hamster/core_ext/enumerable_spec.rb +34 -0
- data/spec/hamster/core_ext/io_spec.rb +17 -0
- data/spec/hamster/core_ext/io_spec.txt +3 -0
- data/spec/hamster/hash/all_spec.rb +2 -0
- data/spec/hamster/hash/any_spec.rb +2 -0
- data/spec/hamster/hash/construction_spec.rb +2 -0
- data/spec/hamster/hash/copying_spec.rb +2 -0
- data/spec/hamster/hash/each_spec.rb +2 -0
- data/spec/hamster/hash/empty_spec.rb +2 -0
- data/spec/hamster/hash/eql_spec.rb +2 -0
- data/spec/hamster/hash/filter_spec.rb +2 -0
- data/spec/hamster/hash/get_spec.rb +2 -0
- data/spec/hamster/hash/has_key_spec.rb +2 -0
- data/spec/hamster/hash/map_spec.rb +2 -0
- data/spec/hamster/hash/none_spec.rb +2 -0
- data/spec/hamster/hash/put_spec.rb +2 -0
- data/spec/hamster/hash/reduce_spec.rb +2 -0
- data/spec/hamster/hash/reject_spec.rb +2 -0
- data/spec/hamster/hash/remove_spec.rb +2 -0
- data/spec/hamster/hash/size_spec.rb +2 -0
- data/spec/hamster/list/all_spec.rb +2 -0
- data/spec/hamster/list/any_spec.rb +2 -0
- data/spec/hamster/list/append_spec.rb +65 -0
- data/spec/hamster/list/cadr_spec.rb +2 -0
- data/spec/hamster/list/cons_spec.rb +2 -0
- data/spec/hamster/list/construction_spec.rb +2 -0
- data/spec/hamster/list/copying_spec.rb +2 -0
- data/spec/hamster/list/drop_spec.rb +2 -0
- data/spec/hamster/list/drop_while_spec.rb +2 -0
- data/spec/hamster/list/each_spec.rb +2 -0
- data/spec/hamster/list/empty_spec.rb +2 -0
- data/spec/hamster/list/eql_spec.rb +2 -0
- data/spec/hamster/list/filter_spec.rb +2 -0
- data/spec/hamster/list/find_spec.rb +2 -0
- data/spec/hamster/list/head_spec.rb +2 -0
- data/spec/hamster/list/include_spec.rb +2 -0
- data/spec/hamster/list/inspect_spec.rb +2 -0
- data/spec/hamster/list/map_spec.rb +2 -0
- data/spec/hamster/list/none_spec.rb +2 -0
- data/spec/hamster/list/partition_spec.rb +101 -0
- data/spec/hamster/list/reduce_spec.rb +2 -0
- data/spec/hamster/list/reject_spec.rb +2 -0
- data/spec/hamster/list/reverse_spec.rb +43 -0
- data/spec/hamster/list/size_spec.rb +2 -0
- data/spec/hamster/list/tail_spec.rb +2 -0
- data/spec/hamster/list/take_spec.rb +2 -0
- data/spec/hamster/list/take_while_spec.rb +2 -0
- data/spec/hamster/list/to_a_spec.rb +2 -0
- data/spec/hamster/list/to_ary_spec.rb +2 -0
- data/spec/hamster/set/add_spec.rb +2 -0
- data/spec/hamster/set/all_spec.rb +2 -0
- data/spec/hamster/set/any_spec.rb +2 -0
- data/spec/hamster/set/construction_spec.rb +2 -0
- data/spec/hamster/set/copying_spec.rb +2 -0
- data/spec/hamster/set/each_spec.rb +2 -0
- data/spec/hamster/set/empty_spec.rb +2 -0
- data/spec/hamster/set/eql_spec.rb +1 -0
- data/spec/hamster/set/filter_spec.rb +2 -0
- data/spec/hamster/set/include_spec.rb +2 -0
- data/spec/hamster/set/map_spec.rb +2 -0
- data/spec/hamster/set/none_spec.rb +2 -0
- data/spec/hamster/set/reduce_spec.rb +2 -0
- data/spec/hamster/set/reject_spec.rb +2 -0
- data/spec/hamster/set/remove_spec.rb +2 -0
- data/spec/hamster/set/size_spec.rb +2 -0
- data/spec/hamster/set/to_a_spec.rb +2 -0
- data/spec/hamster/stack/construction_spec.rb +2 -0
- data/spec/hamster/stack/copying_spec.rb +2 -0
- data/spec/hamster/stack/empty_spec.rb +2 -0
- data/spec/hamster/stack/eql_spec.rb +2 -0
- data/spec/hamster/stack/inspect_spec.rb +2 -0
- data/spec/hamster/stack/pop_spec.rb +2 -0
- data/spec/hamster/stack/push_spec.rb +2 -0
- data/spec/hamster/stack/size_spec.rb +2 -0
- data/spec/hamster/stack/top_spec.rb +2 -0
- data/spec/spec_helper.rb +0 -2
- metadata +11 -2
data/History.rdoc
CHANGED
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) }[
|
122
|
+
(10000..1000000).select { |i| prime?(i) }[0, 3] # => 10s
|
130
123
|
|
131
|
-
Besides <tt>Hamster.list</tt> there
|
124
|
+
Besides <tt>Hamster.list</tt> there are other ways to construct lists:
|
132
125
|
|
133
|
-
<tt
|
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
|
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
|
-
|
130
|
+
You also get <tt>Enumerable#to_list</tt> so you can slowly transition from built-in collection classes to Hamster.
|
138
131
|
|
139
|
-
|
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
@@ -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
|
data/lib/hamster/version.rb
CHANGED
@@ -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
|