immutable 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +39 -0
- data/benchmark/benchmark_map.rb +78 -0
- data/lib/immutable/list.rb +455 -0
- data/lib/immutable/map.rb +385 -0
- data/test/immutable/test_list.rb +255 -0
- data/test/immutable/test_map.rb +113 -0
- metadata +59 -0
data/README.md
ADDED
@@ -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
|
+
|