hamster 0.1.8 → 0.1.11
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/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
|