immutable 0.2.0 → 0.3.0

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.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