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