immutable 0.1.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.
@@ -0,0 +1,39 @@
1
+ immutable - immutable data structures for Ruby
2
+ ==============================================
3
+
4
+ This project aims to provide immutable data structures for Ruby.
5
+
6
+ Currently, immutable provides the following classes:
7
+
8
+ * Immutable::List
9
+ * Immutable::Map
10
+
11
+ Install
12
+ ========================
13
+
14
+ $ gem install immutable
15
+
16
+ License
17
+ ========================
18
+ (The MIT License)
19
+
20
+ Copyright (c) 2012 Shugo Maeda
21
+
22
+ Permission is hereby granted, free of charge, to any person obtaining
23
+ a copy of this software and associated documentation files (the
24
+ 'Software'), to deal in the Software without restriction, including
25
+ without limitation the rights to use, copy, modify, merge, publish,
26
+ distribute, sublicense, and/or sell copies of the Software, and to
27
+ permit persons to whom the Software is furnished to do so, subject to
28
+ the following conditions:
29
+
30
+ The above copyright notice and this permission notice shall be
31
+ included in all copies or substantial portions of the Software.
32
+
33
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
34
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
35
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
36
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
37
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
38
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
39
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,78 @@
1
+ require 'benchmark'
2
+ require 'avl_tree'
3
+ require 'red_black_tree'
4
+ require 'immutable/map'
5
+ require 'openssl'
6
+
7
+ #random = Random.new(0)
8
+
9
+ TIMES = 100000
10
+ key_size = 10
11
+
12
+ def aset(h, keys)
13
+ keys.each do |k|
14
+ h[k] = 1
15
+ end
16
+ end
17
+
18
+ def aref(h, keys)
19
+ keys.each do |k|
20
+ h[k]
21
+ end
22
+ end
23
+
24
+ def delete(h, keys)
25
+ keys.each do |k|
26
+ h.delete(k)
27
+ end
28
+ end
29
+
30
+ def map_insert(h, keys)
31
+ keys.inject(h) { |m, k|
32
+ m.insert(k, 1)
33
+ }
34
+ end
35
+
36
+ def map_delete(h, keys)
37
+ keys.inject(h) { |m, k|
38
+ m.delete(k)
39
+ }
40
+ end
41
+
42
+ def run(bm, h, keys)
43
+ name = h.class.name
44
+ bm.report("#{name} aset") do
45
+ aset(h, keys)
46
+ end
47
+ bm.report("#{name} aref") do
48
+ aref(h, keys)
49
+ end
50
+ bm.report("#{name} delete") do
51
+ delete(h, keys)
52
+ end
53
+ end
54
+
55
+ def map_run(bm, h, keys)
56
+ name = h.class.name
57
+ bm.report("#{name} insert") do
58
+ h = map_insert(h, keys)
59
+ end
60
+ bm.report("#{name} aref") do
61
+ aref(h, keys)
62
+ end
63
+ bm.report("#{name} delete") do
64
+ h = map_delete(h, keys)
65
+ end
66
+ end
67
+
68
+ keys = []
69
+ TIMES.times do
70
+ keys << OpenSSL::Random.random_bytes(key_size)
71
+ end
72
+
73
+ Benchmark.bmbm do |bm|
74
+ run(bm, Hash.new, keys)
75
+ run(bm, AVLTree.new, keys)
76
+ run(bm, RedBlackTree.new, keys)
77
+ map_run(bm, Immutable::Map.empty, keys)
78
+ end
@@ -0,0 +1,455 @@
1
+ # -*- tailcall-optimization: true; trace-instruction: false -*-
2
+
3
+ module Immutable
4
+ # <code>Immutable::List</code> represents an immutable list.
5
+ #
6
+ # <code>Immutable::List</code> is an abstract class and
7
+ # <code>Immutable::List.[]</code> should be used instead of
8
+ # <code>Immutable::List.new</code>. For example:
9
+ #
10
+ # include Immutable
11
+ # p List[] #=> List[]
12
+ # p List[1, 2] #=> List[1, 2]
13
+ #
14
+ # <code>Immutable::Nil</code> represents an empty list, and
15
+ # <code>Immutable::Cons</code> represents a cons cell, which is a node in
16
+ # a list. For example:
17
+ #
18
+ # p Nil #=> List[]
19
+ # p Cons[1, Cons[2, Nil]] #=> List[1, 2]
20
+ class List
21
+ class EmptyError < StandardError
22
+ def initialize(msg = "list is empty")
23
+ super(msg)
24
+ end
25
+ end
26
+
27
+ # Returns a new list populated with the given objects.
28
+ def self.[](*args)
29
+ from_array(args)
30
+ end
31
+
32
+ # Converts the given array +ary+ to a list.
33
+ def self.from_array(ary)
34
+ ary.reverse_each.inject(Nil) { |x, y|
35
+ Cons.new(y, x)
36
+ }
37
+ end
38
+
39
+ # Converts +enum+ to a list. +enum+ should respond to <code>each</code>.
40
+ def self.from_enum(enum)
41
+ enum.inject(Nil) { |x, y|
42
+ Cons.new(y, x)
43
+ }.reverse
44
+ end
45
+
46
+ # Appends two lists +self+ and +xs+.
47
+ def +(xs)
48
+ foldr(xs) { |y, ys| Cons[y, ys] }
49
+ end
50
+
51
+ # Returns the first element of +self+. If +self+ is empty,
52
+ # <code>Immutable::List::EmptyError</code> is raised.
53
+ def head
54
+ raise ScriptError, "this method should be overriden"
55
+ end
56
+
57
+ # Returns the first element of +self+. If +self+ is empty,
58
+ # <code>Immutable::List::EmptyError</code> is raised.
59
+ def first
60
+ head
61
+ end
62
+
63
+ # Returns the last element of +self+. If +self+ is empty,
64
+ # <code>Immutable::List::EmptyError</code> is raised.
65
+ def last
66
+ raise ScriptError, "this method should be overriden"
67
+ end
68
+
69
+ # Returns the element after the head of +self+. If +self+ is empty,
70
+ # <code>Immutable::List::EmptyError</code> is raised.
71
+ def tail
72
+ raise ScriptError, "this method should be overriden"
73
+ end
74
+
75
+ # Returns all the element of +self+ except the last one.
76
+ # If +self+ is empty, <code>Immutable::List::EmptyError</code> is
77
+ # raised.
78
+ def init
79
+ raise ScriptError, "this method should be overriden"
80
+ end
81
+
82
+ # Returns <code>true</code> if +self+ is empty.
83
+ def empty?
84
+ raise ScriptError, "this method should be overriden"
85
+ end
86
+
87
+ # Returns <code>true</code> if +self+ is empty.
88
+ def null?
89
+ empty?
90
+ end
91
+
92
+ # Returns the number of elements in +self+. May be zero.
93
+ def length
94
+ foldl(0) { |x, y| x + 1 }
95
+ end
96
+
97
+ alias size length
98
+
99
+ # Returns the list obtained by applying the given block to each element
100
+ # in +self+.
101
+ def map
102
+ foldr(Nil) { |x, xs| Cons[yield(x), xs] }
103
+ end
104
+
105
+ # Returns the elements of +self+ in reverse order.
106
+ def reverse
107
+ foldl(Nil) { |x, y| Cons[y, x] }
108
+ end
109
+
110
+ # Inserts +xs+ in between the lists in +self+.
111
+ def intersperse(xs)
112
+ raise ScriptError, "this method should be overriden"
113
+ end
114
+
115
+ # Inserts +xs+ in between the lists in +self+ and concatenates the
116
+ # result.
117
+ # <code>xss.intercalate(xs)</code> is equivalent to
118
+ # <code>xss.intersperse(xs).flatten</code>.
119
+ def intercalate(xs)
120
+ intersperse(xs).flatten
121
+ end
122
+
123
+ # Transposes the rows and columns of +self+. For example:
124
+ #
125
+ # p List[List[1, 2, 3], List[4, 5, 6]].transpose
126
+ # #=> List[List[1, 4], List[2, 5], List[3, 6]]
127
+ def transpose
128
+ raise ScriptError, "this method should be overriden"
129
+ end
130
+
131
+ # Reduces +self+ using +block+ from right to left. +e+ is used as the
132
+ # starting value. For example:
133
+ #
134
+ # List[1, 2, 3].foldr(9) { |x, y| x + y } #=> 1 - (2 - (3 - 9)) = -7
135
+ def foldr(e, &block)
136
+ raise ScriptError, "this method should be overriden"
137
+ end
138
+
139
+ # Reduces +self+ using +block+ from right to left. If +self+ is empty,
140
+ # <code>Immutable::List::EmptyError</code> is raised.
141
+ def foldr1(&block)
142
+ raise ScriptError, "this method should be overriden"
143
+ end
144
+
145
+ # Reduces +self+ using +block+ from left to right. +e+ is used as the
146
+ # starting value. For example:
147
+ #
148
+ # List[1, 2, 3].foldl(9) { |x, y| x + y } #=> ((9 - 1) - 2) - 3 = 3
149
+ def foldl(e, &block)
150
+ raise ScriptError, "this method should be overriden"
151
+ end
152
+
153
+ # Reduces +self+ using +block+ from left to right. If +self+ is empty,
154
+ # <code>Immutable::List::EmptyError</code> is raised.
155
+ def foldl1(&block)
156
+ raise ScriptError, "this method should be overriden"
157
+ end
158
+
159
+ # Concatenates a list of lists.
160
+ def flatten
161
+ foldr(Nil) { |x, xs| x + xs }
162
+ end
163
+
164
+ # An alias of <code>flatten</code>.
165
+ alias concat flatten
166
+
167
+ # Returns the list obtained by concatenating the results of the given
168
+ # block for each element in +self+.
169
+ def flat_map
170
+ foldr(Nil) { |x, xs| yield(x) + xs }
171
+ end
172
+
173
+ # An alias of <code>flat_map</code>.
174
+ alias concat_map flat_map
175
+
176
+ # An alias of <code>flat_map</code>.
177
+ alias bind flat_map
178
+
179
+ # Computes the sum of the numbers in +self+.
180
+ def sum
181
+ foldl(0, &:+)
182
+ end
183
+
184
+ # Computes the product of the numbers in +self+.
185
+ def product
186
+ foldl(1, &:*)
187
+ end
188
+
189
+ # Builds a list from the seed value +e+ and the given block. The block
190
+ # takes a seed value and returns <code>nil</code> if the seed should
191
+ # unfold to the empty list, or returns <code>[a, b]</code>, where
192
+ # <code>a</code> is the head of the list and <code>b</code> is the next
193
+ # seed from which to unfold the tail. For example:
194
+ #
195
+ # xs = List.unfoldr(3) { |x| x == 0 ? nil : [x, x - 1] }
196
+ # p xs #=> List[3, 2, 1]
197
+ #
198
+ # <code>unfoldr</code> is the dual of <code>foldr</code>.
199
+ def self.unfoldr(e, &block)
200
+ x = yield(e)
201
+ if x.nil?
202
+ Nil
203
+ else
204
+ y, z = x
205
+ Cons[y, unfoldr(z, &block)]
206
+ end
207
+ end
208
+
209
+ # Returns the first +n+ elements of +self+, or +self+ itself if
210
+ # <code>n > self.length</code>.
211
+ def take(n)
212
+ raise ScriptError, "this method should be overriden"
213
+ end
214
+
215
+
216
+ # Returns the suffix of +self+ after the first +n+ elements, or
217
+ # <code>List[]</code> if <code>n > self.length</code>.
218
+ def drop(n)
219
+ raise ScriptError, "this method should be overriden"
220
+ end
221
+
222
+ # Returns the longest prefix of the elements of +self+ for which +block+
223
+ # evaluates to true.
224
+ def take_while(&block)
225
+ raise ScriptError, "this method should be overriden"
226
+ end
227
+
228
+ # Returns the suffix remaining after
229
+ # <code>self.take_while(&block)</code>.
230
+ def drop_while(&block)
231
+ raise ScriptError, "this method should be overriden"
232
+ end
233
+
234
+ # Returns the first element in +self+ for which the given block
235
+ # evaluates to true. If such an element is not found, it
236
+ # returns <code>nil</code>.
237
+ def find(&block)
238
+ raise ScriptError, "this method should be overriden"
239
+ end
240
+
241
+ # Returns the elements in +self+ for which the given block evaluates to
242
+ # true.
243
+ def filter
244
+ foldr(Nil) { |x, xs|
245
+ if yield(x)
246
+ Cons[x, xs]
247
+ else
248
+ xs
249
+ end
250
+ }
251
+ end
252
+ end
253
+
254
+ Nil = List.new
255
+
256
+ class Cons < List
257
+ # Creates a list obtained by prepending +head+ to the list +tail+.
258
+ def self.[](head, tail = Nil)
259
+ self.new(head, tail)
260
+ end
261
+
262
+ # Creates a list obtained by prepending +head+ to the list +tail+.
263
+ def initialize(head, tail = Nil)
264
+ @head = head
265
+ @tail = tail
266
+ end
267
+
268
+ def Nil.head
269
+ raise EmptyError
270
+ end
271
+
272
+ attr_reader :head
273
+
274
+ def Nil.last
275
+ raise EmptyError
276
+ end
277
+
278
+ def last
279
+ if @tail.empty?
280
+ @head
281
+ else
282
+ @tail.last
283
+ end
284
+ end
285
+
286
+ def Nil.tail
287
+ raise EmptyError
288
+ end
289
+
290
+ attr_reader :tail
291
+
292
+ def Nil.init
293
+ raise EmptyError
294
+ end
295
+
296
+ def init
297
+ if @tail.empty?
298
+ Nil
299
+ else
300
+ Cons[@head, @tail.init]
301
+ end
302
+ end
303
+
304
+ def Nil.empty?
305
+ true
306
+ end
307
+
308
+ def empty?
309
+ false
310
+ end
311
+
312
+ def Nil.foldr(e)
313
+ e
314
+ end
315
+
316
+ def foldr(e, &block)
317
+ yield(@head, @tail.foldr(e, &block))
318
+ end
319
+
320
+ def Nil.foldr1
321
+ raise EmptyError
322
+ end
323
+
324
+ def foldr1(&block)
325
+ if @tail.empty?
326
+ @head
327
+ else
328
+ yield(@head, @tail.foldr1(&block))
329
+ end
330
+ end
331
+
332
+ def Nil.foldl(e)
333
+ e
334
+ end
335
+
336
+ def foldl(e, &block)
337
+ @tail.foldl(yield(e, @head), &block)
338
+ end
339
+
340
+ def Nil.foldl1
341
+ raise EmptyError
342
+ end
343
+
344
+ def foldl1(&block)
345
+ @tail.foldl(@head, &block)
346
+ end
347
+
348
+ def ==(xs)
349
+ if xs.empty?
350
+ false
351
+ else
352
+ @head == xs.head && @tail == xs.tail
353
+ end
354
+ end
355
+
356
+ def Nil.inspect
357
+ "List[]"
358
+ end
359
+
360
+ def inspect
361
+ "List[" + @head.inspect +
362
+ @tail.foldl("") {|x, y| x + ", " + y.inspect } + "]"
363
+ end
364
+
365
+ def Nil.intersperse(sep)
366
+ Nil
367
+ end
368
+
369
+ def intersperse(sep)
370
+ Cons[@head, @tail.prepend_to_all(sep)]
371
+ end
372
+
373
+ def Nil.prepend_to_all(sep)
374
+ Nil
375
+ end
376
+
377
+ def prepend_to_all(sep)
378
+ Cons[sep, Cons[@head, @tail.prepend_to_all(sep)]]
379
+ end
380
+
381
+ def Nil.transpose
382
+ Nil
383
+ end
384
+
385
+ def transpose
386
+ if @head == Nil
387
+ @tail.transpose
388
+ else
389
+ tail = @tail.filter { |x| !x.empty? }
390
+ Cons[Cons[@head.head, tail.map(&:head)],
391
+ Cons[@head.tail, tail.map(&:tail)].transpose]
392
+ end
393
+ end
394
+
395
+ def Nil.take(n)
396
+ Nil
397
+ end
398
+
399
+ def take(n)
400
+ if n <= 0
401
+ Nil
402
+ else
403
+ Cons[@head, @tail.take(n - 1)]
404
+ end
405
+ end
406
+
407
+ def Nil.take_while
408
+ Nil
409
+ end
410
+
411
+ def take_while(&block)
412
+ if yield(@head)
413
+ Cons[@head, @tail.take_while(&block)]
414
+ else
415
+ Nil
416
+ end
417
+ end
418
+
419
+ def Nil.drop(n)
420
+ Nil
421
+ end
422
+
423
+ def drop(n)
424
+ if n > 0
425
+ @tail.drop(n - 1)
426
+ else
427
+ self
428
+ end
429
+ end
430
+
431
+ def Nil.drop_while
432
+ Nil
433
+ end
434
+
435
+ def drop_while(&block)
436
+ if yield(@head)
437
+ @tail.drop_while(&block)
438
+ else
439
+ self
440
+ end
441
+ end
442
+
443
+ def Nil.find
444
+ nil
445
+ end
446
+
447
+ def find(&block)
448
+ if yield(@head)
449
+ @head
450
+ else
451
+ @tail.find(&block)
452
+ end
453
+ end
454
+ end
455
+ end
@@ -0,0 +1,385 @@
1
+ # -*- tailcall-optimization: true; trace-instruction: false -*-
2
+ # ported from http://www.cs.kent.ac.uk/people/staff/smk/redblack/Untyped.hs
3
+
4
+ module Immutable
5
+ # <code>Immutable::Map</code> represents an immutable map from keys to
6
+ # values.
7
+ #
8
+ # <code>Immutable::Map</code> is an abstract class and
9
+ # <code>Immutable::Map.[]</code> should be used instead of
10
+ # <code>Immutable::Map.new</code>. For example:
11
+ #
12
+ # include Immutable
13
+ # p Map[] #=> Map[]
14
+ # p Map[a: 1, b: 2] #=> Map[:a => 1, :b => 2]
15
+ #
16
+ # <code>Immutable::Map#insert</code> inserts a key/value pair and
17
+ # returns a new <code>Immutable::Map</code>. The original map never be
18
+ # changed by <code>Immutable::Map#insert</code>. For example:
19
+ #
20
+ # m = Map[a: 1]
21
+ # p m #=> Map[:a => 1]
22
+ # m2 = m.insert(:b, 2)
23
+ # p m2 #=> Map[:a => 1, :b => 2]
24
+ # p m #=> Map[:a => 1]
25
+ class Map
26
+ class InvarianceViolationError < StandardError
27
+ end
28
+
29
+ # Returns an empty map.
30
+ def self.empty
31
+ Leaf
32
+ end
33
+
34
+ # Returns a map that has only one pair whose key is +key+ and whose
35
+ # value is +value+.
36
+ def self.singleton(key, value)
37
+ Leaf.insert(key, value)
38
+ end
39
+
40
+ # Returns a map that has the same key/value pairs as the
41
+ # <code>Hash</code> object +h+.
42
+ def self.[](h = {})
43
+ h.inject(Leaf) { |m, (k, v)| m.insert(k, v) }
44
+ end
45
+
46
+ # Inserts +key+/+value+ in +self+.
47
+ def insert(key, value)
48
+ ins(key, value).make_black
49
+ end
50
+
51
+ # Returns the value at +key+ in +self+, or <code>nil</code> if +key+
52
+ # isn't in +self+.
53
+ def [](key)
54
+ raise ScriptError, "this method should be overriden"
55
+ end
56
+
57
+ # Deletes +key+ and its value from +self+.
58
+ def delete(key)
59
+ m = del(key)
60
+ if m.empty?
61
+ m
62
+ else
63
+ m.make_black
64
+ end
65
+ end
66
+
67
+ def inspect
68
+ "Map[" + foldr_with_key("") { |k, v, s|
69
+ x = k.inspect + " => " + v.inspect
70
+ if s.empty?
71
+ x
72
+ else
73
+ x + ", " + s
74
+ end
75
+ } + "]"
76
+ end
77
+
78
+ # Folds the values in +self+ from right to left.
79
+ def foldr(e)
80
+ foldr_with_key(e) { |k, v, x| yield(v, x) }
81
+ end
82
+
83
+ # Folds the values in +self+ from left to right.
84
+ def foldl(e)
85
+ foldl_with_key(e) { |x, k, v| yield(x, v) }
86
+ end
87
+
88
+ # Maps the given block over all values in +self+.
89
+ def map
90
+ map_with_key { |k, v| yield(v) }
91
+ end
92
+
93
+ # Maps the given block over all keys and values in +self+.
94
+ def map_with_key
95
+ foldr_with_key(List[]) { |k, v, xs| Cons[yield(k, v), xs] }
96
+ end
97
+
98
+ # :nodoc:
99
+ Leaf = Map.new
100
+
101
+ def Leaf.empty?
102
+ true
103
+ end
104
+
105
+ def Leaf.red?
106
+ false
107
+ end
108
+
109
+ def Leaf.black?
110
+ true
111
+ end
112
+
113
+ def Leaf.[](key)
114
+ nil
115
+ end
116
+
117
+ def Leaf.ins(key, value)
118
+ RedFork[Leaf, key, value, Leaf]
119
+ end
120
+
121
+ def Leaf.del(key)
122
+ Leaf
123
+ end
124
+
125
+ def Leaf.each
126
+ end
127
+
128
+ class Fork < Map #:nodoc:
129
+ attr_reader :left, :key, :value, :right
130
+
131
+ def initialize(left, key, value, right)
132
+ @left, @key, @value, @right = left, key, value, right
133
+ end
134
+
135
+ def self.extract(val)
136
+ accept_self_instance_only(val)
137
+ [val.left, val.key, val.value, val.right]
138
+ end
139
+
140
+ class << self
141
+ alias [] new
142
+ end
143
+
144
+ def empty?
145
+ false
146
+ end
147
+
148
+ def [](key)
149
+ if key < self.key
150
+ left[key]
151
+ elsif key > self.key
152
+ right[key]
153
+ else
154
+ value
155
+ end
156
+ end
157
+
158
+ def del(key)
159
+ if key < self.key
160
+ del_left(left, self.key, self.value, right, key)
161
+ elsif key > self.key
162
+ del_right(left, self.key, self.value, right, key)
163
+ else
164
+ app(left, right)
165
+ end
166
+ end
167
+
168
+ def each(&block)
169
+ left.each(&block)
170
+ yield key, value
171
+ right.each(&block)
172
+ end
173
+
174
+ def Leaf.foldr_with_key(e)
175
+ e
176
+ end
177
+
178
+ def foldr_with_key(e, &block)
179
+ r = @right.foldr_with_key(e, &block)
180
+ @left.foldr_with_key(yield(@key, @value, r), &block)
181
+ end
182
+
183
+ def Leaf.foldl_with_key(e)
184
+ e
185
+ end
186
+
187
+ def foldl_with_key(e, &block)
188
+ l = @left.foldl_with_key(e, &block)
189
+ @right.foldl_with_key(yield(l, @key, @value), &block)
190
+ end
191
+
192
+ private
193
+
194
+ def balance(left, key, value, right)
195
+ # balance (T R a x b) y (T R c z d) = T R (T B a x b) y (T B c z d)
196
+ if left.red? && right.red?
197
+ RedFork[left.make_black, key, value, right.make_black]
198
+ # balance (T R (T R a x b) y c) z d = T R (T B a x b) y (T B c z d)
199
+ elsif left.red? && left.left.red?
200
+ RedFork[
201
+ left.left.make_black, left.key, left.value,
202
+ BlackFork[left.right, key, value, right]
203
+ ]
204
+ # balance (T R a x (T R b y c)) z d = T R (T B a x b) y (T B c z d)
205
+ elsif left.red? && left.right.red?
206
+ RedFork[
207
+ BlackFork[left.left, left.key, left.value, left.right.left],
208
+ left.right.key, left.right.value,
209
+ BlackFork[left.right.right, key, value, right]
210
+ ]
211
+ # balance a x (T R b y (T R c z d)) = T R (T B a x b) y (T B c z d)
212
+ elsif right.red? && right.right.red?
213
+ RedFork[
214
+ BlackFork[left, key, value, right.left],
215
+ right.key, right.value, right.right.make_black
216
+ ]
217
+ # balance a x (T R (T R b y c) z d) = T R (T B a x b) y (T B c z d)
218
+ elsif right.red? && right.left.red?
219
+ RedFork[
220
+ BlackFork[left, key, value, right.left.left],
221
+ right.left.key, right.left.value,
222
+ BlackFork[right.left.right, right.key, right.value, right.right]
223
+ ]
224
+ # balance a x b = T B a x b
225
+ else
226
+ BlackFork[left, key, value, right]
227
+ end
228
+ end
229
+
230
+ def del_left(left, key, value, right, del_key)
231
+ if left.black?
232
+ bal_left(left.del(del_key), key, value, right)
233
+ else
234
+ RedFork[left.del(del_key), key, value, right]
235
+ end
236
+ end
237
+
238
+ def del_right(left, key, value, right, del_key)
239
+ if right.black?
240
+ bal_right(left, key, value, right.del(del_key))
241
+ else
242
+ RedFork[left, key, value, right.del(del_key)]
243
+ end
244
+ end
245
+
246
+ def bal_left(left, key, value, right)
247
+ if left.red?
248
+ RedFork[left.make_black, key, value, right]
249
+ elsif right.black?
250
+ balance(left, key, value, right.make_red)
251
+ elsif right.red? && right.left.black?
252
+ RedFork[
253
+ BlackFork[left, key, value, right.left.left],
254
+ right.left.key, right.left.value,
255
+ balance(right.left.right, right.key, right.value,
256
+ sub1(right.right))
257
+ ]
258
+ else
259
+ raise ScriptError, "should not reach here"
260
+ end
261
+ end
262
+
263
+ def bal_right(left, key, value, right)
264
+ if right.red?
265
+ RedFork[left, key, value, right.make_black]
266
+ elsif left.black?
267
+ balance(left.make_red, key, value, right)
268
+ elsif left.red? && left.right.black?
269
+ RedFork[
270
+ balance(sub1(left.left), left.key, left.value, left.right.left),
271
+ left.right.key, left.right.value,
272
+ BlackFork[left.right.right, key, value, right]
273
+ ]
274
+ else
275
+ raise ScriptError, "should not reach here"
276
+ end
277
+ end
278
+
279
+ def sub1(node)
280
+ if node.black?
281
+ node.make_red
282
+ else
283
+ raise InvarianceViolationError, "invariance violation"
284
+ end
285
+ end
286
+
287
+ def app(left, right)
288
+ if left.empty?
289
+ right
290
+ elsif right.empty?
291
+ left
292
+ elsif left.red? && right.red?
293
+ m = app(left.right, right.left)
294
+ if m.red?
295
+ RedFork[
296
+ RedFork[left.left, left.key, left.value, m.left],
297
+ m.key, m.value,
298
+ RedFork[m.right, right.key, right.value, right.right]
299
+ ]
300
+ else
301
+ RedFork[
302
+ left.left, left.key, left.value,
303
+ RedFork[m, right.key, right.value, right.right]
304
+ ]
305
+ end
306
+ elsif left.black? && right.black?
307
+ m = app(left.right, right.left)
308
+ if m.red?
309
+ RedFork[
310
+ BlackFork[left.left, left.key, left.value, m.left],
311
+ m.key, m.value,
312
+ BlackFork[m.right, right.key, right.value, right.right]
313
+ ]
314
+ else
315
+ bal_left(left.left, left.key, left.value,
316
+ BlackFork[m, right.key, right.value, right.right])
317
+ end
318
+ elsif right.red?
319
+ RedFork[app(left, right.left), right.key, right.value,
320
+ right.right]
321
+ elsif left.red?
322
+ RedFork[left.left, left.key, left.value, app(left.right, right)]
323
+ else
324
+ raise ScriptError, "should not reach here"
325
+ end
326
+ end
327
+ end
328
+
329
+ class RedFork < Fork #:nodoc:
330
+ def red?
331
+ true
332
+ end
333
+
334
+ def black?
335
+ false
336
+ end
337
+
338
+ def make_red
339
+ self
340
+ end
341
+
342
+ def make_black
343
+ BlackFork[left, key, value, right]
344
+ end
345
+
346
+ def ins(key, value)
347
+ if key < self.key
348
+ RedFork[left.ins(key, value), self.key, self.value, right]
349
+ elsif key > self.key
350
+ RedFork[left, self.key, self.value, right.ins(key, value)]
351
+ else
352
+ RedFork[left, key, value, right]
353
+ end
354
+ end
355
+ end
356
+
357
+ class BlackFork < Fork #:nodoc:
358
+ def red?
359
+ false
360
+ end
361
+
362
+ def black?
363
+ true
364
+ end
365
+
366
+ def make_red
367
+ RedFork[left, key, value, right]
368
+ end
369
+
370
+ def make_black
371
+ self
372
+ end
373
+
374
+ def ins(key, value)
375
+ if key < self.key
376
+ balance(left.ins(key, value), self.key, self.value, right)
377
+ elsif key > self.key
378
+ balance(left, self.key, self.value, right.ins(key, value))
379
+ else
380
+ BlackFork[left, key, value, right]
381
+ end
382
+ end
383
+ end
384
+ end
385
+ end
@@ -0,0 +1,255 @@
1
+ require "test/unit"
2
+ require "immutable/list"
3
+
4
+ module Immutable
5
+ class TestList < Test::Unit::TestCase
6
+ def test_s_from_array
7
+ assert_equal(List[], List.from_array([]))
8
+ assert_equal(List[1, 2, 3], List.from_array([1, 2, 3]))
9
+ end
10
+
11
+ def test_s_from_enum
12
+ assert_equal(List[], List.from_enum([]))
13
+ assert_equal(List[1, 2, 3], List.from_enum(1..3))
14
+ assert_equal(List["a", "b", "c"], List.from_enum("abc".chars))
15
+ end
16
+
17
+ def test_head
18
+ assert_raise(List::EmptyError) do
19
+ List[].head
20
+ end
21
+ assert_equal(1, List[1].head)
22
+ assert_equal(1, List[1, 2, 3].head)
23
+ end
24
+
25
+ def test_last
26
+ assert_raise(List::EmptyError) do
27
+ List[].last
28
+ end
29
+ assert_equal(1, List[1].last)
30
+ assert_equal(3, List[1, 2, 3].last)
31
+ end
32
+
33
+ def test_tail
34
+ assert_raise(List::EmptyError) do
35
+ List[].tail
36
+ end
37
+ assert_equal(List[], List[1].tail)
38
+ assert_equal(List[2, 3], List[1, 2, 3].tail)
39
+ end
40
+
41
+ def test_init
42
+ assert_raise(List::EmptyError) do
43
+ List[].init
44
+ end
45
+ assert_equal(List[], List[1].init)
46
+ assert_equal(List[1, 2], List[1, 2, 3].init)
47
+ end
48
+
49
+ def test_empty?
50
+ assert(List[].empty?)
51
+ assert(!List[1].empty?)
52
+ assert(!List[1, 2, 3].empty?)
53
+ end
54
+
55
+ def test_foldr
56
+ assert_equal(0, List[].foldr(0, &:+))
57
+ assert_equal(123, List[].foldr(123, &:+))
58
+
59
+ assert_equal(6, List[1, 2, 3].foldr(0, &:+))
60
+ # 1 - (2 - (3 - 10))
61
+ assert_equal(-8, List[1, 2, 3].foldr(10, &:-))
62
+ end
63
+
64
+ def test_foldr1
65
+ assert_raise(List::EmptyError) do
66
+ List[].foldr1(&:+)
67
+ end
68
+ assert_equal(1, List[1].foldr1(&:+))
69
+ assert_equal(3, List[1, 2].foldr1(&:+))
70
+ assert_equal(6, List[1, 2, 3].foldr1(&:+))
71
+ assert_equal(2, List[1, 2, 3].foldr1(&:-))
72
+ end
73
+
74
+ def test_foldl
75
+ assert_equal(0, List[].foldl(0, &:+))
76
+ assert_equal(123, List[].foldl(123, &:+))
77
+
78
+ assert_equal(6, List[1, 2, 3].foldl(0, &:+))
79
+ # ((10 - 1) - 2) - 3
80
+ assert_equal(4, List[1, 2, 3].foldl(10, &:-))
81
+ end
82
+
83
+ def test_foldl1
84
+ assert_raise(List::EmptyError) do
85
+ List[].foldl1(&:+)
86
+ end
87
+ assert_equal(1, List[1].foldl1(&:+))
88
+ assert_equal(3, List[1, 2].foldl1(&:+))
89
+ assert_equal(6, List[1, 2, 3].foldl1(&:+))
90
+ assert_equal(-4, List[1, 2, 3].foldl1(&:-))
91
+ end
92
+
93
+ def test_eq
94
+ assert(List[] == List[])
95
+ assert(List[] != List[1])
96
+ assert(List[1] != List[])
97
+ assert(List[1] == List[1])
98
+ assert(List[1] != List[2])
99
+ assert(List["foo"] == List["foo"])
100
+ assert(List["foo"] != List["bar"])
101
+ assert(List[1, 2, 3] == List[1, 2, 3])
102
+ assert(List[1, 2, 3] != List[1, 2])
103
+ assert(List[1, 2, 3] != List[1, 2, 3, 4])
104
+ assert(List[List[1, 2], List[3, 4]] == List[List[1, 2], List[3, 4]])
105
+ assert(List[List[1, 2], List[3, 4]] != List[List[1, 2], List[3]])
106
+ end
107
+
108
+ def test_inspect
109
+ assert_equal('List[]', List[].inspect)
110
+ assert_equal('List[1]', List[1].inspect)
111
+ assert_equal('List["foo"]', List["foo"].inspect)
112
+ assert_equal('List[1, 2, 3]', List[1, 2, 3].inspect)
113
+ assert_equal('List[List[1, 2], List[3, 4]]',
114
+ List[List[1, 2], List[3, 4]].inspect)
115
+ end
116
+
117
+ def test_length
118
+ assert_equal(0, List[].length)
119
+ assert_equal(1, List[1].length)
120
+ assert_equal(3, List[1, 2, 3].length)
121
+ assert_equal(100, List[*(1..100)].length)
122
+ end
123
+
124
+ def test_plus
125
+ assert_equal(List[], List[] + List[])
126
+ assert_equal(List[1, 2, 3], List[] + List[1, 2, 3])
127
+ assert_equal(List[1, 2, 3], List[1, 2, 3] + List[])
128
+ assert_equal(List[1, 2, 3], List[1] + List[2, 3])
129
+ assert_equal(List[1, 2, 3], List[1, 2] + List[3])
130
+ end
131
+
132
+ def test_flatten
133
+ assert_equal(List[], List[].flatten)
134
+ assert_equal(List[1], List[List[1]].flatten)
135
+ assert_equal(List[List[1]], List[List[List[1]]].flatten)
136
+ assert_equal(List[1, 2, 3], List[List[1, 2], List[3]].flatten)
137
+ assert_equal(List[1, 2, 3], List[List[1], List[2], List[3]].flatten)
138
+ end
139
+
140
+ def test_map
141
+ assert_equal(List[], List[].map(&:to_s))
142
+ assert_equal(List["1", "2", "3"], List[1, 2, 3].map(&:to_s))
143
+ end
144
+
145
+ def test_flat_map
146
+ assert_equal(List[], List[].flat_map {})
147
+ assert_equal(List["1", "2", "3"], List[1, 2, 3].map(&:to_s))
148
+ end
149
+
150
+ def test_find
151
+ assert_equal(nil, List[].find(&:odd?))
152
+ assert_equal(1, List[1, 2, 3, 4, 5].find(&:odd?))
153
+ assert_equal(2, List[1, 2, 3, 4, 5].find(&:even?))
154
+ end
155
+
156
+ def test_filter
157
+ assert_equal(List[], List[].filter(&:odd?))
158
+ assert_equal(List[1, 3, 5], List[1, 2, 3, 4, 5].filter(&:odd?))
159
+ assert_equal(List[2, 4], List[1, 2, 3, 4, 5].filter(&:even?))
160
+ end
161
+
162
+ def test_take
163
+ assert_equal(List[], List[].take(1))
164
+ assert_equal(List[], List[1, 2, 3].take(0))
165
+ assert_equal(List[], List[1, 2, 3].take(-1))
166
+ assert_equal(List[1], List[1, 2, 3].take(1))
167
+ assert_equal(List[1, 2], List[1, 2, 3].take(2))
168
+ assert_equal(List[1, 2, 3], List[1, 2, 3].take(3))
169
+ assert_equal(List[1, 2, 3], List[1, 2, 3].take(4))
170
+ end
171
+
172
+ def test_take_while
173
+ assert_equal(List[], List[].take_while { true })
174
+ assert_equal(List[], List[1, 2, 3].take_while { |x| x < 1 })
175
+ assert_equal(List[1], List[1, 2, 3].take_while { |x| x < 2 })
176
+ assert_equal(List[1, 2], List[1, 2, 3].take_while { |x| x < 3 })
177
+ assert_equal(List[1, 2, 3], List[1, 2, 3].take_while { |x| x < 4 })
178
+ end
179
+
180
+ def test_drop
181
+ assert_equal(List[], List[].drop(1))
182
+ assert_equal(List[1, 2, 3], List[1, 2, 3].drop(0))
183
+ assert_equal(List[1, 2, 3], List[1, 2, 3].drop(-1))
184
+ assert_equal(List[2, 3], List[1, 2, 3].drop(1))
185
+ assert_equal(List[3], List[1, 2, 3].drop(2))
186
+ assert_equal(List[], List[1, 2, 3].drop(3))
187
+ assert_equal(List[], List[1, 2, 3].drop(4))
188
+ end
189
+
190
+ def test_drop_while
191
+ assert_equal(List[], List[].drop_while { false })
192
+ assert_equal(List[1, 2, 3], List[1, 2, 3].drop_while { |x| x < 1 })
193
+ assert_equal(List[2, 3], List[1, 2, 3].drop_while { |x| x < 2 })
194
+ assert_equal(List[3], List[1, 2, 3].drop_while { |x| x < 3 })
195
+ assert_equal(List[], List[1, 2, 3].drop_while { |x| x < 4 })
196
+ end
197
+
198
+ def test_reverse
199
+ assert_equal(List[], List[].reverse)
200
+ assert_equal(List[1], List[1].reverse)
201
+ assert_equal(List[2, 1], List[1, 2].reverse)
202
+ assert_equal(List[3, 2, 1], List[1, 2, 3].reverse)
203
+ end
204
+
205
+ def test_intersperse
206
+ assert_equal(List[], List[].intersperse(0))
207
+ assert_equal(List[1], List[1].intersperse(0))
208
+ assert_equal(List[1, 0, 2], List[1, 2].intersperse(0))
209
+ assert_equal(List[1, 0, 2, 0, 3], List[1, 2, 3].intersperse(0))
210
+ end
211
+
212
+ def test_intercalate
213
+ assert_equal(List[], List[].intercalate(List[0]))
214
+ assert_equal(List[1], List[List[1]].intercalate(List[0]))
215
+ xs = List[List[1, 2], List[3, 4], List[5, 6]].intercalate(List[0])
216
+ assert_equal(List[1, 2, 0, 3, 4, 0, 5, 6], xs)
217
+ end
218
+
219
+ def test_transpose
220
+ assert_equal(List[], List[].transpose)
221
+ assert_equal(List[], List[List[]].transpose)
222
+ assert_equal(List[List[1], List[2]], List[List[1, 2]].transpose)
223
+ assert_equal(List[List[1, 4], List[2, 5], List[3, 6]],
224
+ List[List[1, 2, 3], List[4, 5, 6]].transpose)
225
+ assert_equal(List[List[1, 4], List[2, 5], List[3]],
226
+ List[List[1, 2, 3], List[4, 5]].transpose)
227
+ end
228
+
229
+ def test_sum
230
+ assert_equal(0, List[].sum)
231
+ assert_equal(1, List[1].sum)
232
+ assert_equal(10, List[1, 2, 3, 4].sum)
233
+ end
234
+
235
+ def test_product
236
+ assert_equal(1, List[].product)
237
+ assert_equal(1, List[1].product)
238
+ assert_equal(24, List[1, 2, 3, 4].product)
239
+ end
240
+
241
+ def test_s_unfoldr
242
+ xs = List.unfoldr(3) { |x| x == 0 ? nil : [x, x - 1] }
243
+ assert_equal(List[3, 2, 1], xs)
244
+ xs = List.unfoldr("foo,bar,baz") { |x|
245
+ if x.empty?
246
+ nil
247
+ else
248
+ y = x.slice(/([^,]*),?/, 1)
249
+ [y, $']
250
+ end
251
+ }
252
+ assert_equal(List["foo", "bar", "baz"], xs)
253
+ end
254
+ end
255
+ end
@@ -0,0 +1,113 @@
1
+ require "test/unit"
2
+ require "immutable/map"
3
+
4
+ module Immutable
5
+ class TestMap < Test::Unit::TestCase
6
+ def test_empty?
7
+ assert(Map[].empty?)
8
+ assert(!Map[a: 1].empty?)
9
+ assert(!Map[a: 1, b: 2].empty?)
10
+ end
11
+
12
+ def test_inspect
13
+ assert_equal("Map[]", Map.empty.inspect)
14
+ assert_equal("Map[:a => 1, :b => 2]", Map[a: 1, b: 2].inspect)
15
+ end
16
+
17
+ def test_insert
18
+ 5.times do
19
+ map = (1..100).to_a.shuffle.inject(Map.empty) { |m, k|
20
+ m.insert(k, k.to_s)
21
+ }
22
+ for i in 1..100
23
+ assert_equal(i.to_s, map[i])
24
+ end
25
+ end
26
+ end
27
+
28
+ def test_delete
29
+ 5.times do
30
+ keys = (1..100).to_a.shuffle
31
+ map = keys.inject(Map.empty) { |m, k|
32
+ m.insert(k, k.to_s)
33
+ }
34
+ keys.shuffle!
35
+ deleted_keys = keys.shift(20)
36
+ map2 = deleted_keys.inject(map) { |m, k|
37
+ m.delete(k)
38
+ }
39
+ for i in 1..100
40
+ assert_equal(i.to_s, map[i])
41
+ end
42
+ keys.each do |k|
43
+ assert_equal(k.to_s, map2[k])
44
+ end
45
+ deleted_keys.each do |k|
46
+ assert_equal(nil, map2[k])
47
+ end
48
+ end
49
+ end
50
+
51
+ def test_foldr
52
+ xs = Map[].foldr(List[]) { |v, ys| Cons[v, ys] }
53
+ assert_equal(List[], xs)
54
+
55
+ xs = Map[a: 1, c: 3, b: 2].foldr(List[]) { |v, ys| Cons[v, ys] }
56
+ assert_equal(List[1, 2, 3], xs)
57
+ end
58
+
59
+ def test_foldl
60
+ xs = Map[].foldl(List[]) { |ys, v| Cons[v, ys] }
61
+ assert_equal(List[], xs)
62
+
63
+ xs = Map[a: 1, c: 3, b: 2].foldl(List[]) { |ys, v| Cons[v, ys] }
64
+ assert_equal(List[3, 2, 1], xs)
65
+ end
66
+
67
+ def test_foldl_with_key
68
+ xs = Map[].foldl_with_key(List[]) { |ys, k, v| Cons[[k, v], ys] }
69
+ assert_equal(List[], xs)
70
+
71
+ xs = Map[a: 1, c: 3, b: 2].foldl_with_key(List[]) { |ys, k, v|
72
+ Cons[[k, v], ys]
73
+ }
74
+ assert_equal(List[[:c, 3], [:b, 2], [:a, 1]], xs)
75
+ end
76
+
77
+ def test_foldr_with_key
78
+ xs = Map[].foldr_with_key(List[]) { |k, v, ys| Cons[[k, v], ys] }
79
+ assert_equal(List[], xs)
80
+
81
+ xs = Map[a: 1, c: 3, b: 2].foldr_with_key(List[]) { |k, v, ys|
82
+ Cons[[k, v], ys]
83
+ }
84
+ assert_equal(List[[:a, 1], [:b, 2], [:c, 3]], xs)
85
+ end
86
+
87
+ def test_foldl_with_key
88
+ xs = Map[].foldl_with_key(List[]) { |ys, k, v| Cons[[k, v], ys] }
89
+ assert_equal(List[], xs)
90
+
91
+ xs = Map[a: 1, c: 3, b: 2].foldl_with_key(List[]) { |ys, k, v|
92
+ Cons[[k, v], ys]
93
+ }
94
+ assert_equal(List[[:c, 3], [:b, 2], [:a, 1]], xs)
95
+ end
96
+
97
+ def test_map
98
+ xs = Map[].map { |v| v.to_s }
99
+ assert_equal(List[], xs)
100
+
101
+ xs = Map[a: 1, c: 3, b: 2].map { |v| v.to_s }
102
+ assert_equal(List["1", "2", "3"], xs)
103
+ end
104
+
105
+ def test_map_with_key
106
+ xs = Map[].map_with_key { |k, v| [k, v].join(":") }
107
+ assert_equal(List[], xs)
108
+
109
+ xs = Map[a: 1, c: 3, b: 2].map_with_key { |k, v| [k, v].join(":") }
110
+ assert_equal(List["a:1", "b:2", "c:3"], xs)
111
+ end
112
+ end
113
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: immutable
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - Shugo Maeda
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2012-04-09 00:00:00 Z
14
+ dependencies: []
15
+
16
+ description:
17
+ email: shugo@ruby-lang.org
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - lib/immutable/map.rb
26
+ - lib/immutable/list.rb
27
+ - benchmark/benchmark_map.rb
28
+ - test/immutable/test_list.rb
29
+ - test/immutable/test_map.rb
30
+ - README.md
31
+ homepage: http://github.com/shugo/immutable
32
+ licenses: []
33
+
34
+ post_install_message:
35
+ rdoc_options: []
36
+
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: "0"
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ none: false
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: "0"
51
+ requirements: []
52
+
53
+ rubyforge_project:
54
+ rubygems_version: 1.8.15
55
+ signing_key:
56
+ specification_version: 3
57
+ summary: Immutable data structures for Ruby
58
+ test_files: []
59
+