immutable 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -3,15 +3,6 @@ immutable - immutable data structures for Ruby
3
3
 
4
4
  This project aims to provide immutable data structures for Ruby.
5
5
 
6
- Currently, immutable provides the following classes and modules:
7
-
8
- * Immutable::Foldable
9
- * Immutable::List
10
- * Immutable::Map
11
- * Immutable::Promise
12
- * Immutable::Stream
13
- * Immutable::Queue
14
-
15
6
  Install
16
7
  ========================
17
8
 
@@ -0,0 +1,59 @@
1
+ require "benchmark"
2
+ require_relative "../lib/immutable/deque"
3
+ require_relative "../lib/immutable/map"
4
+
5
+ TIMES = 100000
6
+ key_size = 10
7
+
8
+ def head(deque)
9
+ TIMES.times do
10
+ deque.head
11
+ end
12
+ end
13
+
14
+ def last(deque)
15
+ TIMES.times do
16
+ deque.last
17
+ end
18
+ end
19
+
20
+ def tail(deque)
21
+ TIMES.times do
22
+ deque.tail
23
+ end
24
+ end
25
+
26
+ def init(deque)
27
+ TIMES.times do
28
+ deque.init
29
+ end
30
+ end
31
+
32
+ def cons(deque)
33
+ TIMES.times do
34
+ deque.cons(0)
35
+ end
36
+ end
37
+
38
+ def snoc(deque)
39
+ TIMES.times do
40
+ deque.snoc(0)
41
+ end
42
+ end
43
+
44
+ def run(bm, deque, num, method)
45
+ bm.report("#{method} for #{num}-elements deque") do
46
+ send(method, deque)
47
+ end
48
+ end
49
+
50
+ Benchmark.bmbm do |bm|
51
+ deques = [100, 1000, 10000].inject(Immutable::Map.empty) { |m, n|
52
+ m.insert(n, (1..n).inject(Immutable::Deque.empty, &:snoc))
53
+ }
54
+ for method in [:head, :last, :tail, :init, :cons, :snoc]
55
+ for num, deque in deques
56
+ run(bm, deque, num, method)
57
+ end
58
+ end
59
+ end
@@ -1,8 +1,8 @@
1
- require 'benchmark'
2
- require 'avl_tree'
3
- require 'red_black_tree'
4
- require 'immutable/map'
5
- require 'openssl'
1
+ require "benchmark"
2
+ require "avl_tree"
3
+ require "red_black_tree"
4
+ require_relative "../lib/immutable/map"
5
+ require "openssl"
6
6
 
7
7
  #random = Random.new(0)
8
8
 
@@ -0,0 +1,73 @@
1
+ require 'benchmark'
2
+ require 'avl_tree'
3
+ require 'red_black_tree'
4
+ require 'immutable/map'
5
+ require 'atomic'
6
+
7
+
8
+ class LockBoundFib
9
+ def initialize(map = RedBlackTree.new)
10
+ @memo = map
11
+ @mutex = Mutex.new
12
+ end
13
+
14
+ def [](n)
15
+ @mutex.synchronize { @memo[n] } ||
16
+ if n < 2
17
+ n
18
+ else
19
+ self[n - 2] + self[n - 1]
20
+ end.tap { |x|
21
+ @mutex.synchronize {
22
+ @memo[n] = x
23
+ }
24
+ }
25
+ end
26
+ end
27
+
28
+ class LockFreeFib
29
+ def initialize(map = Immutable::Map.empty)
30
+ @memo = Atomic.new(map)
31
+ end
32
+
33
+ def [](n)
34
+ @memo.value[n] ||
35
+ if n < 2
36
+ n
37
+ else
38
+ self[n - 2] + self[n - 1]
39
+ end.tap { |x|
40
+ @memo.update { |map|
41
+ map.insert(n, x)
42
+ }
43
+ }
44
+ end
45
+ end
46
+
47
+ TIMES = 5
48
+ N = 10000
49
+
50
+ Benchmark.bmbm do |bm|
51
+ bm.report("lock-bound") do
52
+ TIMES.times do
53
+ lock_bound_fib = LockBoundFib.new(RedBlackTree.new)
54
+ threads = 4.times.map {
55
+ Thread.start {
56
+ lock_bound_fib[N]
57
+ }
58
+ }
59
+ threads.each(&:join)
60
+ end
61
+ end
62
+ bm.report("lock-free") do
63
+ TIMES.times do
64
+ lock_free_fib = LockFreeFib.new(Immutable::Map.empty)
65
+ threads = 4.times.map {
66
+ Thread.start {
67
+ lock_free_fib[N]
68
+ }
69
+ }
70
+ threads.each(&:join)
71
+ end
72
+ end
73
+ end
@@ -1,6 +1,6 @@
1
- require 'benchmark'
2
- require 'immutable/queue'
3
- require 'immutable/map'
1
+ require "benchmark"
2
+ require_relative "../lib/immutable/queue"
3
+ require_relative "../lib/immutable/map"
4
4
 
5
5
  TIMES = 100000
6
6
  key_size = 10
@@ -2,8 +2,10 @@
2
2
  module Immutable
3
3
  end
4
4
 
5
- require "immutable/list"
6
- require "immutable/map"
7
- require "immutable/promise"
8
- require "immutable/stream"
9
- require "immutable/queue"
5
+ require_relative "immutable/list"
6
+ require_relative "immutable/map"
7
+ require_relative "immutable/promise"
8
+ require_relative "immutable/stream"
9
+ require_relative "immutable/queue"
10
+ require_relative "immutable/output_restricted_deque"
11
+ require_relative "immutable/deque"
@@ -0,0 +1,344 @@
1
+ require_relative "headable"
2
+
3
+ module Immutable
4
+ module Consable
5
+ include Headable
6
+
7
+ module ClassMethods
8
+ # Creates a new +Consable+ object populated with the given objects.
9
+ #
10
+ # @param [Array<Object>] elements the elements of the +Consable+
11
+ # object.
12
+ # @return [Consable] the new +Consable+ object.
13
+ def [](*elements)
14
+ from_array(elements)
15
+ end
16
+
17
+ # Converts the given array to a +Consable+ object.
18
+ #
19
+ # @param [Array, #reverse_each] ary the array to convert.
20
+ # @return [Consable] the +Consable+ object converted from +ary+.
21
+ def from_array(ary)
22
+ ary.reverse_each.inject(empty) { |x, y|
23
+ Cons(y, x)
24
+ }
25
+ end
26
+
27
+ # Converts the given Enumerable object to a +Consable+ object.
28
+ #
29
+ # @param [#inject] enum the Enumerable object to convert.
30
+ # @return [Cons] the +Consable+ object converted from +enum+.
31
+ def from_enum(enum)
32
+ enum.inject(empty) { |x, y|
33
+ Cons(y, x)
34
+ }.reverse
35
+ end
36
+
37
+ # Builds a +Consable+ object from the seed value +e+ and the given
38
+ # block. The block takes a seed value and returns +nil+ if the seed
39
+ # should unfold to the empty +Consable+ object, or returns +[a, b]+,
40
+ # where +a+ is the head of the +Consable+ object and +b+ is the next
41
+ # seed from which to unfold the tail. For example:
42
+ #
43
+ # xs = List.unfoldr(3) { |x| x == 0 ? nil : [x, x - 1] }
44
+ # p xs #=> List[3, 2, 1]
45
+ #
46
+ # +unfoldr+ is the dual of +foldr+.
47
+ #
48
+ # @param [Object] e the seed value.
49
+ # @return [Consable] the +Consable+ object built from the seed value
50
+ # and the block.
51
+ def unfoldr(e, &block)
52
+ x = yield(e)
53
+ if x.nil?
54
+ empty
55
+ else
56
+ y, z = x
57
+ Cons(y, unfoldr(z, &block))
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def Cons(x, y)
64
+ y.cons(x)
65
+ end
66
+ end
67
+
68
+ def self.included(c)
69
+ c.extend(ClassMethods)
70
+ end
71
+
72
+ # Adds a new element at the head of +self+. A class including
73
+ # +Immutable::Consable+ must implement this method.
74
+ #
75
+ # @param [Object] x the element to add.
76
+ # @return [Consable] a new +Consable+ object.
77
+ def cons(x)
78
+ raise NotImplementedError
79
+ end
80
+
81
+ # Same as {#cons}. This method just calls {#cons}.
82
+ #
83
+ # @param [Object] x the element to add.
84
+ # @return [Consable] a new +Consable+ object.
85
+ def unshift(x)
86
+ cons(x)
87
+ end
88
+
89
+ # Same as {#cons}. This method just calls {#cons}.
90
+ #
91
+ # @param [Object] x the element to add.
92
+ # @return [Consable] a new +Consable+ object.
93
+ def prepend(x)
94
+ cons(x)
95
+ end
96
+
97
+ # Appends two +Consable+ objects +self+ and +xs+.
98
+ #
99
+ # @param [Consable] xs the +Consable+ object to append.
100
+ # @return [Consable] a new +Consable+ object.
101
+ def +(xs)
102
+ foldr(xs) { |y, ys| Cons(y, ys) }
103
+ end
104
+
105
+ # Returns the +Consable+ object obtained by applying the given block to
106
+ # each element in +self+.
107
+ #
108
+ # @return [Consable] the obtained +Consable+ object.
109
+ def map(&block)
110
+ rev_map(&block).reverse
111
+ end
112
+
113
+ # Returns the +Consable+ object obtained by applying the given block to
114
+ # each element in +self+ in reverse order.
115
+ #
116
+ # @return [Consable] the obtained +Consable+ object.
117
+ def rev_map
118
+ foldl(empty) { |xs, x| Cons(yield(x), xs) }
119
+ end
120
+
121
+ # Returns the elements of +self+ in reverse order.
122
+ #
123
+ # @return [Consable] the reversed +Consable+ object.
124
+ def reverse
125
+ foldl(empty) { |x, y| Cons(y, x) }
126
+ end
127
+
128
+ # Returns a new +Consable+ object obtained by inserting +sep+ in between
129
+ # the elements of +self+.
130
+ #
131
+ # @param [Object] sep the object to insert between elements.
132
+ # @return [Consable] the new +Consable+ object.
133
+ def intersperse(sep)
134
+ if empty?
135
+ empty
136
+ else
137
+ Cons(head, tail.prepend_to_all(sep))
138
+ end
139
+ end
140
+
141
+ # Returns a new +Consable+ object obtained by inserting +xs+ in between
142
+ # the +Consable+ objects in +self+ and concatenates the result.
143
+ # +xss.intercalate(xs)+ is equivalent to +xss.intersperse(xs).flatten+.
144
+ #
145
+ # @param [Consable] xs the +Consable+ object to insert between
146
+ # +Consable+ objects.
147
+ # @return [Consable] the new +Consable+ object.
148
+ def intercalate(xs)
149
+ intersperse(xs).flatten
150
+ end
151
+
152
+ # Transposes the rows and columns of +self+. For example:
153
+ #
154
+ # p List[List[1, 2, 3], List[4, 5, 6]].transpose
155
+ # #=> List[List[1, 4], List[2, 5], List[3, 6]]
156
+ #
157
+ # @return [Consable] the transposed +Consable+ object.
158
+ def transpose
159
+ if empty?
160
+ empty
161
+ else
162
+ if head.empty?
163
+ tail.transpose
164
+ else
165
+ t = tail.filter { |x| !x.empty? }
166
+ Cons(Cons(head.head, t.map(&:head)),
167
+ Cons(head.tail, t.map(&:tail)).transpose)
168
+ end
169
+ end
170
+ end
171
+
172
+ # Returns the +Consable+ object of all subsequences of +self+.
173
+ #
174
+ # @return [Consable<Consable>] the subsequences of +self+.
175
+ def subsequences
176
+ Cons(empty, nonempty_subsequences)
177
+ end
178
+
179
+ # Concatenates a +Consable+ object of +Consable+ objects.
180
+ #
181
+ # @return [Consable] the concatenated +Consable+ object.
182
+ def flatten
183
+ foldr(empty) { |x, xs| x + xs }
184
+ end
185
+
186
+ alias concat flatten
187
+
188
+ # Returns the +Consable+ object obtained by concatenating the results of
189
+ # the given block for each element in +self+.
190
+ #
191
+ # @return [Consable] the obtained +Consable+ object.
192
+ def flat_map
193
+ foldr(empty) { |x, xs| yield(x) + xs }
194
+ end
195
+
196
+ alias concat_map flat_map
197
+ alias bind flat_map
198
+
199
+ # Returns the first +n+ elements of +self+, or all the elements of
200
+ # +self+ if +n > self.length+.
201
+ #
202
+ # @param [Integer] n the number of elements to take.
203
+ # @return [Consable] the first +n+ elements of +self+.
204
+ def take(n)
205
+ if empty?
206
+ empty
207
+ else
208
+ if n <= 0
209
+ empty
210
+ else
211
+ Cons(head, tail.take(n - 1))
212
+ end
213
+ end
214
+ end
215
+
216
+ # Returns the suffix of +self+ after the first +n+ elements, or
217
+ # an empty +Consable+ object if +n > self.length+.
218
+ #
219
+ # @param [Integer] n the number of elements to drop.
220
+ # @return [Consable] the suffix of +self+ after the first +n+ elements.
221
+ def drop(n)
222
+ if empty?
223
+ empty
224
+ else
225
+ if n > 0
226
+ tail.drop(n - 1)
227
+ else
228
+ self
229
+ end
230
+ end
231
+ end
232
+
233
+ # Returns the longest prefix of the elements of +self+ for which +block+
234
+ # evaluates to true.
235
+ #
236
+ # @return [Consable] the prefix of the elements of +self+.
237
+ def take_while(&block)
238
+ if empty?
239
+ empty
240
+ else
241
+ if yield(head)
242
+ Cons(head, tail.take_while(&block))
243
+ else
244
+ empty
245
+ end
246
+ end
247
+ end
248
+
249
+ # Returns the suffix remaining after
250
+ # +self.take_while(&block)+.
251
+ #
252
+ # @return [Consable] the suffix of the elements of +self+.
253
+ def drop_while(&block)
254
+ if empty?
255
+ empty
256
+ else
257
+ if yield(head)
258
+ tail.drop_while(&block)
259
+ else
260
+ self
261
+ end
262
+ end
263
+ end
264
+
265
+ # Returns the elements in +self+ for which the given block evaluates to
266
+ # true.
267
+ #
268
+ # @return [Consable] the elements that satisfies the condition.
269
+ def filter
270
+ foldr(empty) { |x, xs|
271
+ if yield(x)
272
+ Cons(x, xs)
273
+ else
274
+ xs
275
+ end
276
+ }
277
+ end
278
+
279
+ # Takes zero or more lists and returns a new +Consable+ object in which
280
+ # each element is an array of the corresponding elements of +self+ and
281
+ # the input +Consable+ objects.
282
+ #
283
+ # @param [Array<Consable>] xss the input +Consable+ objects.
284
+ # @return [Consable] the new +Consable+ object.
285
+ def zip(*xss)
286
+ if empty?
287
+ empty
288
+ else
289
+ heads = xss.map { |xs| xs.empty? ? nil : xs.head }
290
+ tails = xss.map { |xs| xs.empty? ? empty : xs.tail }
291
+ Cons([head, *heads], tail.zip(*tails))
292
+ end
293
+ end
294
+
295
+ # Takes zero or more +Consable+ objects and returns the +Consable+
296
+ # object obtained by applying the given block to an array of the
297
+ # corresponding elements of +self+ and the input +Consable+ objects.
298
+ # +xs.zip_with(*yss, &block)+ is equivalent to
299
+ # +xs.zip(*yss).map(&block)+.
300
+ #
301
+ # @param [Array<Consable>] xss the input +Consable+ objects.
302
+ # @return [Consable] the new +Consable+ object.
303
+ def zip_with(*xss, &block)
304
+ if empty?
305
+ empty
306
+ else
307
+ heads = xss.map { |xs| xs.null? ? nil : xs.head }
308
+ tails = xss.map { |xs| xs.null? ? empty : xs.tail }
309
+ Cons(yield(head, *heads), tail.zip_with(*tails, &block))
310
+ end
311
+ end
312
+
313
+ protected
314
+
315
+ def prepend_to_all(sep)
316
+ if empty?
317
+ empty
318
+ else
319
+ Cons(sep, Cons(head, tail.prepend_to_all(sep)))
320
+ end
321
+ end
322
+
323
+ def nonempty_subsequences
324
+ if empty?
325
+ empty
326
+ else
327
+ yss = tail.nonempty_subsequences.foldr(empty) { |xs, xss|
328
+ Cons(xs, Cons(Cons(head, xs), xss))
329
+ }
330
+ Cons(Cons(head, empty), yss)
331
+ end
332
+ end
333
+
334
+ private
335
+
336
+ def empty
337
+ self.class.empty
338
+ end
339
+
340
+ def Cons(x, y)
341
+ y.cons(x)
342
+ end
343
+ end
344
+ end