hamster 0.1.8 → 0.1.11
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +34 -10
- data/lib/hamster.rb +0 -1
- data/lib/hamster/hash.rb +77 -20
- data/lib/hamster/list.rb +189 -52
- data/lib/hamster/set.rb +78 -23
- data/lib/hamster/stack.rb +13 -14
- data/lib/hamster/trie.rb +13 -4
- data/lib/hamster/version.rb +1 -1
- data/spec/hamster/hash/all_spec.rb +53 -0
- data/spec/hamster/hash/any_spec.rb +62 -0
- data/spec/hamster/hash/construction_spec.rb +21 -5
- data/spec/hamster/hash/copying_spec.rb +6 -10
- data/spec/hamster/hash/each_spec.rb +6 -18
- data/spec/hamster/hash/empty_spec.rb +9 -5
- data/spec/hamster/hash/eql_spec.rb +17 -22
- data/spec/hamster/hash/filter_spec.rb +61 -0
- data/spec/hamster/hash/get_spec.rb +24 -12
- data/spec/hamster/hash/has_key_spec.rb +17 -13
- data/spec/hamster/hash/map_spec.rb +66 -0
- data/spec/hamster/hash/none_spec.rb +62 -0
- data/spec/hamster/hash/put_spec.rb +17 -73
- data/spec/hamster/hash/reduce_spec.rb +58 -0
- data/spec/hamster/hash/reject_spec.rb +57 -0
- data/spec/hamster/hash/remove_spec.rb +13 -85
- data/spec/hamster/hash/size_spec.rb +25 -0
- data/spec/hamster/list/cadr_spec.rb +37 -0
- data/spec/hamster/list/construction_spec.rb +46 -6
- data/spec/hamster/list/copying_spec.rb +13 -11
- data/spec/hamster/list/drop_spec.rb +29 -0
- data/spec/hamster/list/drop_while_spec.rb +39 -0
- data/spec/hamster/list/each_spec.rb +24 -24
- data/spec/hamster/list/empty_spec.rb +15 -6
- data/spec/hamster/list/eql_spec.rb +27 -7
- data/spec/hamster/list/filter_spec.rb +51 -0
- data/spec/hamster/list/head_spec.rb +27 -0
- data/spec/hamster/list/include_spec.rb +37 -0
- data/spec/hamster/list/lazy_spec.rb +21 -0
- data/spec/hamster/list/map_spec.rb +21 -19
- data/spec/hamster/list/reduce_spec.rb +27 -15
- data/spec/hamster/list/reject_spec.rb +47 -0
- data/spec/hamster/list/size_spec.rb +31 -0
- data/spec/hamster/list/tail_spec.rb +27 -0
- data/spec/hamster/list/take_spec.rb +29 -0
- data/spec/hamster/list/take_while_spec.rb +45 -0
- data/spec/hamster/set/add_spec.rb +49 -0
- data/spec/hamster/set/all_spec.rb +61 -0
- data/spec/hamster/set/any_spec.rb +61 -0
- data/spec/hamster/set/construction_spec.rb +3 -3
- data/spec/hamster/set/copying_spec.rb +21 -0
- data/spec/hamster/set/each_spec.rb +36 -0
- data/spec/hamster/set/empty_spec.rb +21 -0
- data/spec/hamster/set/eql_spec.rb +31 -0
- data/spec/hamster/set/filter_spec.rb +61 -0
- data/spec/hamster/set/include_spec.rb +29 -0
- data/spec/hamster/set/map_spec.rb +66 -0
- data/spec/hamster/set/none_spec.rb +61 -0
- data/spec/hamster/set/reduce_spec.rb +58 -0
- data/spec/hamster/set/reject_spec.rb +57 -0
- data/spec/hamster/set/remove_spec.rb +45 -0
- data/spec/hamster/set/size_spec.rb +25 -0
- data/spec/hamster/stack/copying_spec.rb +1 -1
- data/spec/hamster/stack/empty_spec.rb +2 -2
- data/spec/hamster/stack/eql_spec.rb +15 -4
- data/spec/hamster/stack/push_spec.rb +6 -26
- data/spec/hamster/trie/remove_spec.rb +117 -0
- metadata +39 -7
- data/TODO +0 -1
- data/spec/hamster/list/accessor_spec.rb +0 -26
- data/spec/hamster/list/car_spec.rb +0 -17
data/README.rdoc
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
= Hamster
|
2
2
|
|
3
|
-
Hamster started out as an implementation of Hash Array Mapped
|
3
|
+
Hamster started out as an implementation of Hash Array Mapped Hashes (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
5
|
== Huh?
|
6
6
|
|
@@ -8,7 +8,7 @@ Persistent data structures have a really neat property: very efficient copy-on-w
|
|
8
8
|
|
9
9
|
require 'hamster'
|
10
10
|
|
11
|
-
hash = Hamster
|
11
|
+
hash = Hamster.hash
|
12
12
|
|
13
13
|
hash.put("Name", "Simon")
|
14
14
|
hash.has_key?("Name") # => false
|
@@ -20,8 +20,8 @@ Whoops! Remember, each call to <tt>#put</tt> creates an efficient copy containin
|
|
20
20
|
|
21
21
|
require 'hamster'
|
22
22
|
|
23
|
-
original = Hamster
|
24
|
-
copy =
|
23
|
+
original = Hamster.hash
|
24
|
+
copy = original.put("Name", "Simon")
|
25
25
|
|
26
26
|
original.get("Name") # => nil
|
27
27
|
copy.get("Name") # => "Simon"
|
@@ -30,10 +30,10 @@ The same goes for <tt>#remove</tt>:
|
|
30
30
|
|
31
31
|
require 'hamster'
|
32
32
|
|
33
|
-
original = Hamster
|
34
|
-
original =
|
33
|
+
original = Hamster.hash
|
34
|
+
original = original.put("Name", "Simon")
|
35
35
|
copy = hash.remove("Name")
|
36
|
-
|
36
|
+
|
37
37
|
original.get("Name") # => Simon
|
38
38
|
copy.get("Name") # => nil
|
39
39
|
|
@@ -47,7 +47,7 @@ There's a potential performance hit when compared with MRI's built-in, native, h
|
|
47
47
|
|
48
48
|
require 'hamster'
|
49
49
|
|
50
|
-
hash = Hamster
|
50
|
+
hash = Hamster.hash
|
51
51
|
(1..10000).each { |i| hash = hash.put(i, i) } # => 0.05s
|
52
52
|
(1..10000).each { |i| hash.get(i) } # => 0.008s
|
53
53
|
|
@@ -67,7 +67,7 @@ A more realistic comparison might look like:
|
|
67
67
|
|
68
68
|
require 'hamster'
|
69
69
|
|
70
|
-
hash = Hamster
|
70
|
+
hash = Hamster.hash
|
71
71
|
(1..10000).each { |i| hash = hash.put(i, i) } # => 0.05s
|
72
72
|
(1..10000).each { |i| hash.get(i) } # => 0.008s
|
73
73
|
|
@@ -89,4 +89,28 @@ The <tt>Hamster::Hash</tt> version on the other hand was unchanged from the orig
|
|
89
89
|
|
90
90
|
Well, I could show you one but I'd have to re-write--or at least wrap--most <tt>Hash</tt> methods to make it generic, or at least write some application-specific code that synchronised using a <tt>Mutex</tt> and ... well ... it's hard, I always make mistakes, I always end up with weird edge cases and race conditions so, I'll leave that as an exercise for you :)
|
91
91
|
|
92
|
-
|
92
|
+
And that, my friends, is why you might want to use one :)
|
93
|
+
|
94
|
+
== So, you mentioned Sets, Lists, and Stacks?
|
95
|
+
|
96
|
+
Indeed I did.
|
97
|
+
|
98
|
+
=== Lists
|
99
|
+
|
100
|
+
list = Hamster.list(1, 2, 3)
|
101
|
+
|
102
|
+
list.head # => 1
|
103
|
+
list.tail # => Hamster.list(2, 3)
|
104
|
+
|
105
|
+
def prime?(n)
|
106
|
+
2.upto(Math.sqrt(n).round) { |i| return false if n % i == 0 }
|
107
|
+
true
|
108
|
+
end
|
109
|
+
|
110
|
+
Hamster.interval(10000, 1000000).filter { |i| prime?(i) }.take(3)
|
111
|
+
|
112
|
+
=== Stacks
|
113
|
+
|
114
|
+
=== Sets
|
115
|
+
|
116
|
+
=== Hash
|
data/lib/hamster.rb
CHANGED
data/lib/hamster/hash.rb
CHANGED
@@ -1,72 +1,129 @@
|
|
1
|
+
require 'hamster/trie'
|
2
|
+
|
1
3
|
module Hamster
|
2
4
|
|
3
|
-
|
5
|
+
def self.hash(pairs = {})
|
6
|
+
pairs.reduce(Hash.new) { |hash, pair| hash.put(pair.first, pair.last) }
|
7
|
+
end
|
4
8
|
|
5
|
-
|
6
|
-
pairs.reduce(self.new) { |hash, pair| hash.put(pair.first, pair.last) }
|
7
|
-
end
|
9
|
+
class Hash
|
8
10
|
|
9
11
|
def initialize(trie = Trie.new)
|
10
12
|
@trie = trie
|
11
13
|
end
|
12
14
|
|
13
|
-
# Returns the number of key-value pairs in the hash.
|
14
15
|
def size
|
15
16
|
@trie.size
|
16
17
|
end
|
18
|
+
alias_method :length, :size
|
17
19
|
|
18
|
-
# Returns <tt>true</tt> if the hash contains no key-value pairs.
|
19
20
|
def empty?
|
20
21
|
@trie.empty?
|
21
22
|
end
|
22
23
|
|
23
|
-
# Returns <tt>true</tt> if the given key is present in the hash.
|
24
24
|
def has_key?(key)
|
25
25
|
@trie.has_key?(key)
|
26
26
|
end
|
27
|
+
alias_method :key?, :has_key?
|
28
|
+
alias_method :include?, :has_key?
|
29
|
+
alias_method :member?, :has_key?
|
27
30
|
|
28
|
-
# Retrieves the value corresponding to the given key. If not found, returns <tt>nil</tt>.
|
29
31
|
def get(key)
|
30
32
|
entry = @trie.get(key)
|
31
33
|
if entry
|
32
34
|
entry.value
|
33
35
|
end
|
34
36
|
end
|
37
|
+
alias_method :[], :get
|
35
38
|
|
36
|
-
# Returns a copy of <tt>self</tt> with the given value associated with the key.
|
37
39
|
def put(key, value)
|
38
40
|
self.class.new(@trie.put(key, value))
|
39
41
|
end
|
42
|
+
alias_method :[]=, :put
|
40
43
|
|
41
|
-
# Returns a copy of <tt>self</tt> with the given key/value pair removed. If not found, returns <tt>self</tt>.
|
42
44
|
def remove(key)
|
43
|
-
|
44
|
-
if !
|
45
|
-
self.class.new(
|
45
|
+
trie = @trie.remove(key)
|
46
|
+
if !trie.equal?(@trie)
|
47
|
+
self.class.new(trie)
|
46
48
|
else
|
47
49
|
self
|
48
50
|
end
|
49
51
|
end
|
50
52
|
|
51
|
-
# Calls <tt>block</tt> once for each entry in the hash, passing the key-value pair as parameters.
|
52
|
-
# Returns <tt>self</tt>
|
53
53
|
def each
|
54
54
|
block_given? or return enum_for(__method__)
|
55
|
-
@trie.each { |entry| yield
|
55
|
+
@trie.each { |entry| yield(entry.key, entry.value) }
|
56
56
|
self
|
57
57
|
end
|
58
58
|
|
59
|
-
|
59
|
+
def map
|
60
|
+
block_given? or return enum_for(:each)
|
61
|
+
if empty?
|
62
|
+
self
|
63
|
+
else
|
64
|
+
self.class.new(@trie.reduce(Trie.new) { |trie, entry| trie.put(*yield(entry.key, entry.value)) })
|
65
|
+
end
|
66
|
+
end
|
67
|
+
alias_method :collect, :map
|
68
|
+
|
69
|
+
def reduce(memo)
|
70
|
+
block_given? or return memo
|
71
|
+
@trie.reduce(memo) { |memo, entry| yield(memo, entry.key, entry.value) }
|
72
|
+
end
|
73
|
+
alias_method :inject, :reduce
|
74
|
+
|
75
|
+
def filter
|
76
|
+
block_given? or return enum_for(__method__)
|
77
|
+
trie = @trie.filter { |entry| yield(entry.key, entry.value) }
|
78
|
+
if !trie.equal?(@trie)
|
79
|
+
self.class.new(trie)
|
80
|
+
else
|
81
|
+
self
|
82
|
+
end
|
83
|
+
end
|
84
|
+
alias_method :select, :filter
|
85
|
+
|
86
|
+
def reject
|
87
|
+
block_given? or return enum_for(__method__)
|
88
|
+
select { |key, value| !yield(key, value) }
|
89
|
+
end
|
90
|
+
|
91
|
+
def any?
|
92
|
+
if block_given?
|
93
|
+
each { |key, value| return true if yield(key, value) }
|
94
|
+
else
|
95
|
+
each { |pair| return true if pair }
|
96
|
+
end
|
97
|
+
false
|
98
|
+
end
|
99
|
+
|
100
|
+
def all?
|
101
|
+
if block_given?
|
102
|
+
each { |key, value| return false unless yield(key, value) }
|
103
|
+
else
|
104
|
+
each { |pair| return false unless pair }
|
105
|
+
end
|
106
|
+
true
|
107
|
+
end
|
108
|
+
|
109
|
+
def none?
|
110
|
+
if block_given?
|
111
|
+
each { |key, value| return false if yield(key, value) }
|
112
|
+
else
|
113
|
+
each { |pair| return false if pair }
|
114
|
+
end
|
115
|
+
true
|
116
|
+
end
|
117
|
+
|
60
118
|
def eql?(other)
|
61
119
|
other.is_a?(self.class) && @trie.eql?(other.instance_eval{@trie})
|
62
120
|
end
|
63
|
-
|
121
|
+
alias_method :==, :eql?
|
64
122
|
|
65
|
-
# Returns <tt>self</tt>
|
66
123
|
def dup
|
67
124
|
self
|
68
125
|
end
|
69
|
-
|
126
|
+
alias_method :clone, :dup
|
70
127
|
|
71
128
|
end
|
72
129
|
|
data/lib/hamster/list.rb
CHANGED
@@ -1,89 +1,125 @@
|
|
1
1
|
module Hamster
|
2
2
|
|
3
|
-
class
|
3
|
+
class << self
|
4
4
|
|
5
|
-
def
|
6
|
-
list
|
7
|
-
items.reverse_each { |item| list = list.cons(item) }
|
8
|
-
list
|
5
|
+
def list(*items)
|
6
|
+
items.reverse.reduce(EmptyList) { |list, item| list.cons(item) }
|
9
7
|
end
|
10
8
|
|
11
|
-
def
|
12
|
-
|
13
|
-
|
9
|
+
def interval(from, to)
|
10
|
+
if from > to
|
11
|
+
EmptyList
|
12
|
+
else
|
13
|
+
Stream.new(from) { interval(from.succ, to) }
|
14
|
+
end
|
14
15
|
end
|
16
|
+
alias_method :range, :interval
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
module List
|
15
21
|
|
16
|
-
# Returns <tt>true</tt> if the list contains no items.
|
17
22
|
def empty?
|
18
|
-
|
23
|
+
false
|
19
24
|
end
|
20
25
|
|
21
|
-
# Returns the number of items in the list.
|
22
26
|
def size
|
23
|
-
|
24
|
-
0
|
25
|
-
else
|
26
|
-
@tail.size + 1
|
27
|
-
end
|
27
|
+
tail.size + 1
|
28
28
|
end
|
29
|
+
alias_method :length, :size
|
29
30
|
|
30
|
-
|
31
|
-
|
32
|
-
@head
|
31
|
+
def cons(item)
|
32
|
+
Sequence.new(item, self)
|
33
33
|
end
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
|
35
|
+
def each(&block)
|
36
|
+
block_given? or return self
|
37
|
+
yield(head)
|
38
|
+
tail.each(&block)
|
39
|
+
nil
|
38
40
|
end
|
39
41
|
|
40
|
-
|
41
|
-
|
42
|
-
|
42
|
+
def map(&block)
|
43
|
+
block_given? or return self
|
44
|
+
Stream.new(yield(head)) { tail.map(&block) }
|
43
45
|
end
|
44
46
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
47
|
+
def reduce(memo, &block)
|
48
|
+
block_given? or return memo
|
49
|
+
tail.reduce(yield(memo, head), &block)
|
50
|
+
end
|
51
|
+
alias_method :inject, :reduce
|
52
|
+
|
53
|
+
def filter(&block)
|
54
|
+
block_given? or return self
|
55
|
+
if yield(head)
|
56
|
+
Stream.new(head) { tail.filter(&block) }
|
57
|
+
else
|
58
|
+
tail.filter(&block)
|
52
59
|
end
|
53
|
-
self
|
54
60
|
end
|
61
|
+
alias_method :select, :filter
|
55
62
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
63
|
+
def reject(&block)
|
64
|
+
block_given? or return self
|
65
|
+
if yield(head)
|
66
|
+
tail.reject(&block)
|
67
|
+
else
|
68
|
+
Stream.new(head) { tail.reject(&block) }
|
69
|
+
end
|
62
70
|
end
|
63
|
-
alias :== :eql?
|
64
71
|
|
65
|
-
|
66
|
-
|
67
|
-
|
72
|
+
def take_while(&block)
|
73
|
+
block_given? or return self
|
74
|
+
if yield(head)
|
75
|
+
Stream.new(head) { tail.take_while(&block) }
|
76
|
+
else
|
77
|
+
EmptyList
|
78
|
+
end
|
68
79
|
end
|
69
|
-
alias :clone :dup
|
70
80
|
|
71
|
-
def
|
72
|
-
|
81
|
+
def drop_while(&block)
|
82
|
+
block_given? or return self
|
83
|
+
if yield(head)
|
84
|
+
tail.drop_while(&block)
|
85
|
+
else
|
73
86
|
self
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def take(number)
|
91
|
+
if number > 0
|
92
|
+
Stream.new(head) { tail.take(number - 1) }
|
74
93
|
else
|
75
|
-
|
94
|
+
EmptyList
|
76
95
|
end
|
77
96
|
end
|
78
97
|
|
79
|
-
def
|
80
|
-
if
|
81
|
-
|
98
|
+
def drop(number)
|
99
|
+
if number > 0
|
100
|
+
tail.drop(number - 1)
|
82
101
|
else
|
83
|
-
|
102
|
+
self
|
84
103
|
end
|
85
104
|
end
|
86
105
|
|
106
|
+
def include?(item)
|
107
|
+
item == head || tail.include?(item)
|
108
|
+
end
|
109
|
+
alias_method :member?, :include?
|
110
|
+
|
111
|
+
def eql?(other)
|
112
|
+
return true if other.equal?(self)
|
113
|
+
return false unless other.is_a?(List)
|
114
|
+
other.head == head && other.tail.eql?(tail)
|
115
|
+
end
|
116
|
+
alias_method :==, :eql?
|
117
|
+
|
118
|
+
def dup
|
119
|
+
self
|
120
|
+
end
|
121
|
+
alias_method :clone, :dup
|
122
|
+
|
87
123
|
private
|
88
124
|
|
89
125
|
def method_missing(name, *args, &block)
|
@@ -99,7 +135,7 @@ module Hamster
|
|
99
135
|
# identify the series of car and cdr operations that is performed by the function. The order in which the 'a's and
|
100
136
|
# 'd's appear is the inverse of the order in which the corresponding operations are performed.
|
101
137
|
def accessor(sequence)
|
102
|
-
sequence.split(//).reverse!.
|
138
|
+
sequence.split(//).reverse!.reduce(self) do |memo, char|
|
103
139
|
case char
|
104
140
|
when "a" then memo.head
|
105
141
|
when "d" then memo.tail
|
@@ -109,4 +145,105 @@ module Hamster
|
|
109
145
|
|
110
146
|
end
|
111
147
|
|
148
|
+
class Sequence
|
149
|
+
|
150
|
+
include List
|
151
|
+
|
152
|
+
attr_reader :head, :tail
|
153
|
+
|
154
|
+
def initialize(head, tail)
|
155
|
+
@head = head
|
156
|
+
@tail = tail
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
class Stream
|
162
|
+
|
163
|
+
include List
|
164
|
+
|
165
|
+
attr_reader :head
|
166
|
+
|
167
|
+
def initialize(head, &tail)
|
168
|
+
@head = head
|
169
|
+
@tail = tail
|
170
|
+
end
|
171
|
+
|
172
|
+
def tail
|
173
|
+
@tail.call
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
module EmptyList
|
179
|
+
|
180
|
+
class << self
|
181
|
+
|
182
|
+
include List
|
183
|
+
|
184
|
+
def head
|
185
|
+
nil
|
186
|
+
end
|
187
|
+
|
188
|
+
def tail
|
189
|
+
self
|
190
|
+
end
|
191
|
+
|
192
|
+
def empty?
|
193
|
+
true
|
194
|
+
end
|
195
|
+
|
196
|
+
def size
|
197
|
+
0
|
198
|
+
end
|
199
|
+
alias_method :length, :size
|
200
|
+
|
201
|
+
def each
|
202
|
+
block_given? or return self
|
203
|
+
nil
|
204
|
+
end
|
205
|
+
|
206
|
+
def map
|
207
|
+
self
|
208
|
+
end
|
209
|
+
|
210
|
+
def reduce(memo)
|
211
|
+
memo
|
212
|
+
end
|
213
|
+
alias_method :inject, :reduce
|
214
|
+
|
215
|
+
def filter
|
216
|
+
self
|
217
|
+
end
|
218
|
+
alias_method :select, :filter
|
219
|
+
|
220
|
+
def reject
|
221
|
+
self
|
222
|
+
end
|
223
|
+
|
224
|
+
def take_while
|
225
|
+
self
|
226
|
+
end
|
227
|
+
|
228
|
+
def drop_while
|
229
|
+
self
|
230
|
+
end
|
231
|
+
|
232
|
+
def take(number)
|
233
|
+
self
|
234
|
+
end
|
235
|
+
|
236
|
+
def drop(number)
|
237
|
+
self
|
238
|
+
end
|
239
|
+
|
240
|
+
def include?(item)
|
241
|
+
false
|
242
|
+
end
|
243
|
+
alias_method :member?, :include?
|
244
|
+
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
|
112
249
|
end
|