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