huge_enumerable 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,642 @@
1
+ require 'spec_helper'
2
+
3
+ describe HugeEnumerable do
4
+
5
+ let(:collection) { ('a'..'z').to_a }
6
+
7
+ subject(:enumerable) do
8
+ klass = Class.new(HugeEnumerable)
9
+ enum_collection = collection.sort
10
+ klass.define_method(:collection_size) { enum_collection.size }
11
+ klass.define_method(:fetch) { |x| enum_collection[x] }
12
+ klass.send(:public, :next_prime)
13
+ klass.send(:public, :_fetch)
14
+ klass.send(:public, :element_or_array)
15
+ klass.send(:public, :full_cycle_increment)
16
+ klass.new
17
+ end
18
+
19
+ subject(:emptied_enumerable) do
20
+ enumerable.tap do |enum|
21
+ enum.max_array_size = enum.size
22
+ enum.next_array
23
+ end
24
+ end
25
+
26
+ context ".new" do
27
+
28
+ context "with no arguments" do
29
+
30
+ it "defaults max_array_size to a Numeric" do
31
+ enumerable.max_array_size.should be_kind_of(Numeric)
32
+ end
33
+
34
+ it "defaults rng to #rand" do
35
+ enumerable.rng.should eq(enumerable.method(:rand))
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+
42
+ context "#[]" do
43
+
44
+ context "with a positive index" do
45
+ it "returns the element from the beginning of the collection at the specified index" do
46
+ enumerable[3].should eql(collection[3])
47
+ end
48
+ end
49
+
50
+ context "with a negative index" do
51
+ it "returns the element from the end of the collection at the specified index" do
52
+ enumerable[-3].should eql(collection[-3])
53
+ end
54
+ end
55
+
56
+ context "with an out of bounds index" do
57
+ it "returns nil" do
58
+ enumerable[enumerable.size + 1].should be_nil
59
+ end
60
+ end
61
+
62
+ context "with a range" do
63
+ it "returns an array of elements corresponding to indexes within the range" do
64
+ size = collection.size + 1
65
+ test_range = (-size..size).to_a
66
+ ranges = test_range.product(test_range).map { |x| (x.first..x.last) }
67
+ arrays = ranges.map { |range| [enumerable[range], collection[range]] }
68
+ arrays.each { |x| x.first.should eq(x.last) }
69
+ end
70
+ end
71
+
72
+ context "with a length" do
73
+ it "returns an array of elements corresponding to starting with index and containing a maximum of length items" do
74
+ size = collection.size + 1
75
+ test_range = (-size..size).to_a
76
+ index_lengths = test_range.product(test_range).map { |x| [x.first, x.last] }
77
+ arrays = index_lengths.map do |idx_len|
78
+ index = idx_len.first
79
+ length = idx_len.last
80
+ [enumerable[index, length], collection[index, length]]
81
+ end
82
+ arrays.each { |x| x.first.should eq(x.last) }
83
+ end
84
+ end
85
+
86
+
87
+ it "relays to #_fetch for index mapping" do
88
+ enumerable.should_receive(:_fetch).at_least(:once)
89
+ enumerable[0]
90
+ end
91
+
92
+ end
93
+
94
+ context "#each" do
95
+
96
+ it "yields #max_array_size items" do
97
+ items = []
98
+ total_items = collection.size / 5
99
+ enumerable.max_array_size = total_items
100
+ enumerable.each { |x| items << x }
101
+ items.size.should eql(total_items)
102
+ end
103
+
104
+ it "yields only the remaining items if there are fewer than #max_array_size" do
105
+ items = []
106
+ enumerable.max_array_size = collection.size + 1
107
+ enumerable.each { |x| items << x }
108
+ items.size.should eql(collection.size)
109
+ end
110
+
111
+ it "relays to #_fetch for index mapping" do
112
+ enumerable.should_receive(:_fetch).at_least(:once)
113
+ enumerable.each {}
114
+ end
115
+
116
+ end
117
+
118
+ context "#combination" do
119
+
120
+ context "with no block" do
121
+
122
+ it "returns a new HugeCombination" do
123
+ enumerable.combination(2).should be_instance_of(HugeCombination)
124
+ end
125
+
126
+ end
127
+
128
+ context "with a block" do
129
+
130
+ # This should really be calls the block from the combination since there is testing duplication here.
131
+ # However, knowing where the block is called from would need stack investigation or similar.
132
+ it "calls the block for each combination element" do
133
+ combo = enumerable.combination(2)
134
+ enumerable.max_array_size = combo.size # Hrm. Perhaps having to do this is a reason to have it call collection_each
135
+ index = 0
136
+ enumerable.combination(2) do |x|
137
+ x.should eql(combo[index])
138
+ index += 1
139
+ end
140
+ index.should eql(combo.size)
141
+ end
142
+
143
+ it "returns self" do
144
+ enumerable.combination(2) {}.should equal(enumerable)
145
+ end
146
+
147
+ end
148
+
149
+ it "uses self a dup of itself as the enumerable" do
150
+ dupped_enumerable = enumerable.dup
151
+ enumerable.stub(:dup).and_return(dupped_enumerable)
152
+ HugeCombination.should_receive(:new).with(dupped_enumerable, 2, enumerable.max_array_size, nil)
153
+ enumerable.combination(2)
154
+ end
155
+
156
+ it "uses the same max array size" do
157
+ enumerable.combination(2).max_array_size.should eql(enumerable.max_array_size)
158
+ end
159
+
160
+ it "uses the same random number generator" do
161
+ enumerable.rng = Proc.new { 1 }
162
+ enumerable.combination(2).rng.should eq(enumerable.rng)
163
+ end
164
+
165
+ it "calls reset! on the dup" do
166
+ HugeEnumerable.send(:public, :reset!)
167
+ dupped_enumerable = enumerable.dup
168
+ enumerable.stub(:dup).and_return(dupped_enumerable)
169
+ dupped_enumerable.should_receive(:reset!).and_call_original
170
+ enumerable.combination(2)
171
+ end
172
+
173
+ end
174
+
175
+
176
+ context "#max_array_size" do
177
+
178
+ context "not explicitly set" do
179
+
180
+ it "defaults to DEFAULT_MAX_ARRAY_SIZE if smaller than #collection_size" do
181
+ enumerable.stub(:collection_size).and_return(HugeEnumerable::DEFAULT_MAX_ARRAY_SIZE + 1)
182
+ enumerable.max_array_size.should eql(HugeEnumerable::DEFAULT_MAX_ARRAY_SIZE)
183
+ end
184
+
185
+ it "defaults to #collection_size if smaller than DEFAULT_MAX_ARRAY_SIZE" do
186
+ enumerable.stub(:collection_size).and_return(HugeEnumerable::DEFAULT_MAX_ARRAY_SIZE - 1)
187
+ enumerable.max_array_size.should eql(HugeEnumerable::DEFAULT_MAX_ARRAY_SIZE - 1)
188
+ end
189
+
190
+ end
191
+
192
+ end
193
+
194
+ context "#next_array" do
195
+
196
+ it "advances to the next array in the collection" do
197
+ size = collection.size / 5
198
+ enumerable.max_array_size = size
199
+ enumerable.next_array.should eql(collection[size...size*2])
200
+ end
201
+
202
+ it "changes #size" do
203
+ size = collection.size / 5
204
+ enumerable.max_array_size = size
205
+ enumerable.next_array
206
+ enumerable.size.should eql(collection.size - size)
207
+ end
208
+
209
+ end
210
+
211
+ context "#empty?" do
212
+
213
+ it "returns true if the collection has been entirely emptied by #pop, #shift, or #next_array" do
214
+ enumerable.max_array_size = collection.size
215
+ enumerable.next_array
216
+ enumerable.empty?.should be_true
217
+ end
218
+
219
+ it "returns false if the collection has been not entirely emptied by #pop, #shift, or #next_array" do
220
+ enumerable.max_array_size = collection.size - 1
221
+ enumerable.next_array
222
+ enumerable.empty?.should be_false
223
+ end
224
+
225
+ end
226
+
227
+ context "#permutation" do
228
+
229
+ context "with no block" do
230
+
231
+ it "returns a new HugePermutation" do
232
+ enumerable.permutation(2).should be_instance_of(HugePermutation)
233
+ end
234
+
235
+ end
236
+
237
+ context "with a block" do
238
+
239
+ # This should really be calls the block from the combination since there is testing duplication here.
240
+ # However, knowing where the block is called from would need stack investigation or similar.
241
+ it "calls the block for each permutation element" do
242
+ perm = enumerable.permutation(2)
243
+ enumerable.max_array_size = perm.size # Hrm. Perhaps having to do this is a reason to have it call collection_each
244
+ index = 0
245
+ enumerable.permutation(2) do |x|
246
+ x.should eql(perm[index])
247
+ index += 1
248
+ end
249
+ index.should eql(perm.size)
250
+ end
251
+
252
+ it "returns self" do
253
+ enumerable.permutation(2) {}.should equal(enumerable)
254
+ end
255
+
256
+ end
257
+
258
+ it "uses self a dup of itself as the enumerable" do
259
+ dupped_enumerable = enumerable.dup
260
+ enumerable.stub(:dup).and_return(dupped_enumerable)
261
+ HugePermutation.should_receive(:new).with(dupped_enumerable, 2, enumerable.max_array_size, nil)
262
+ enumerable.permutation(2)
263
+ end
264
+
265
+ it "uses the same max array size" do
266
+ enumerable.permutation(2).max_array_size.should eql(enumerable.max_array_size)
267
+ end
268
+
269
+ it "uses the same random number generator" do
270
+ enumerable.rng = Proc.new { 1 }
271
+ enumerable.permutation(2).rng.should eq(enumerable.rng)
272
+ end
273
+
274
+ it "calls reset! on the dup" do
275
+ HugeEnumerable.send(:public, :reset!)
276
+ dupped_enumerable = enumerable.dup
277
+ enumerable.stub(:dup).and_return(dupped_enumerable)
278
+ dupped_enumerable.should_receive(:reset!).and_call_original
279
+ enumerable.permutation(2)
280
+ end
281
+
282
+ end
283
+
284
+
285
+ context "#pop" do
286
+
287
+ context "on a non empty collection" do
288
+ context "with no parameter" do
289
+ it "returns the next element from the end of the collection" do
290
+ enumerable.pop.should eql(collection.pop)
291
+ end
292
+ end
293
+
294
+ context "with a parameter" do
295
+ it "returns an array of the next N elements from the end of the collection" do
296
+ enumerable.pop(3).should eql(collection.pop(3))
297
+ end
298
+ end
299
+
300
+ it "removes the elements from the end of the collection" do
301
+ enumerable.pop
302
+ enumerable.pop(3)
303
+ enumerable.to_a.should eql(collection[0..-5])
304
+ end
305
+
306
+ it "changes #size" do
307
+ enumerable.pop
308
+ enumerable.pop(3)
309
+ enumerable.size.should eql(collection.size - 4)
310
+ end
311
+
312
+ it "does not harm the original collection" do
313
+ original = collection.dup
314
+ enumerable.pop
315
+ enumerable.pop(3)
316
+ collection.should eql(original)
317
+ end
318
+
319
+ end
320
+
321
+ it "depends on #(private)element_or_array" do
322
+ enumerable.should_receive(:element_or_array).twice.and_call_original
323
+ enumerable.pop
324
+ enumerable.pop(3)
325
+ end
326
+
327
+ end
328
+
329
+ context "#product" do
330
+
331
+ context "with no block" do
332
+
333
+ it "returns a new HugeProduct" do
334
+ enumerable.product([]).should be_instance_of(HugeProduct)
335
+ end
336
+
337
+ end
338
+
339
+ context "with a block" do
340
+
341
+ # This should really be calls the block from the combination since there is testing duplication here.
342
+ # However, knowing where the block is called from would need stack investigation or similar.
343
+ it "calls the block for each product element" do
344
+ other_enumerable = [1, 2, 3]
345
+ prod = enumerable.product(other_enumerable)
346
+ enumerable.max_array_size = prod.size # Hrm. Perhaps having to do this is a reason to have it call collection_each
347
+ index = 0
348
+ enumerable.product(other_enumerable) do |x|
349
+ x.should eql(prod[index])
350
+ index += 1
351
+ end
352
+ index.should eql(prod.size)
353
+ end
354
+
355
+ it "returns self" do
356
+ enumerable.product([]) {}.should equal(enumerable)
357
+ end
358
+
359
+ end
360
+
361
+ it "uses self a dup of itself as the enumerable" do
362
+ dupped_enumerable = enumerable.dup
363
+ enumerable.stub(:dup).and_return(dupped_enumerable)
364
+ other_enumerable = []
365
+ HugeProduct.should_receive(:new).with(dupped_enumerable, other_enumerable, enumerable.max_array_size, nil)
366
+ enumerable.product(other_enumerable)
367
+ end
368
+
369
+ it "calls dup on the other enumerable if it is a HugeEnumerable" do
370
+ other_enumerable = HugeCollection.new([])
371
+ other_enumerable.should_receive(:dup).and_call_original
372
+ enumerable.product(other_enumerable)
373
+ end
374
+
375
+ it "calls reset on the other enumerable if it is a HugeEnumerable" do
376
+ HugeEnumerable.send(:public, :reset!)
377
+ other_enumerable = HugeCollection.new([])
378
+ dupped_enumerable = other_enumerable.dup
379
+ other_enumerable.stub(:dup).and_return(dupped_enumerable)
380
+ dupped_enumerable.should_receive(:reset!).and_call_original
381
+ enumerable.product(other_enumerable)
382
+ end
383
+
384
+ it "does not call dup on the other enumerable if it is a HugeEnumerable" do
385
+ other_enumerable = []
386
+ other_enumerable.should_not_receive(:dup)
387
+ enumerable.product(other_enumerable)
388
+ end
389
+
390
+ it "uses the same max array size" do
391
+ enumerable.product([]).max_array_size.should eql(enumerable.max_array_size)
392
+ end
393
+
394
+ it "uses the same random number generator" do
395
+ enumerable.rng = Proc.new { 1 }
396
+ enumerable.product([]).rng.should eq(enumerable.rng)
397
+ end
398
+
399
+ it "calls reset! on the dup" do
400
+ HugeEnumerable.send(:public, :reset!)
401
+ dupped_enumerable = enumerable.dup
402
+ enumerable.stub(:dup).and_return(dupped_enumerable)
403
+ dupped_enumerable.should_receive(:reset!).and_call_original
404
+ enumerable.product([])
405
+ end
406
+
407
+ end
408
+
409
+ context "#sample" do
410
+
411
+ context "on an non empty collection" do
412
+ context "with no arguments" do
413
+ it "returns a single element from the collection" do
414
+ collection.include?(enumerable.sample).should be_true
415
+ end
416
+ end
417
+
418
+ context "with size argument" do
419
+ it "returns N elements from the collection" do
420
+ samples = enumerable.sample(3)
421
+ samples.should have(3).items
422
+ samples.all? { |item| collection.include?(item) }.should be_true
423
+ end
424
+ end
425
+
426
+ it "returns elements from the collection in a pseudo random pattern" do
427
+ enumerable.sample(enumerable.size).should_not eq(collection)
428
+ end
429
+
430
+ it "visits each element exactly once before repeating" do
431
+ samples = []
432
+ enumerable.size.times { samples << enumerable.sample }
433
+ samples.uniq.should have(collection.size).items
434
+ end
435
+
436
+ it "does not reorder the original collection" do
437
+ original = collection.dup
438
+ enumerable.sample
439
+ enumerable.sample(3)
440
+ collection.should eql(original)
441
+ end
442
+
443
+ end
444
+
445
+ it "depends on #(private)element_or_array" do
446
+ enumerable.should_receive(:element_or_array).twice.and_call_original
447
+ enumerable.sample
448
+ enumerable.sample(3)
449
+ end
450
+
451
+ end
452
+
453
+ context "#shift" do
454
+
455
+ context "on a non empty collection" do
456
+ context "with no parameter" do
457
+ it "returns the next element from the beginning of the collection" do
458
+ enumerable.shift.should eql(collection.shift)
459
+ end
460
+ end
461
+
462
+ context "with a parameter" do
463
+ it "returns an array of the next N elements from the beginning of the collection" do
464
+ enumerable.shift(3).should eql(collection.shift(3))
465
+ end
466
+ end
467
+
468
+ it "removes the elements from the beginning of the collection" do
469
+ enumerable.shift
470
+ enumerable.shift(3)
471
+ enumerable.to_a.should eql(collection[4..-1])
472
+ end
473
+
474
+ it "changes #size" do
475
+ enumerable.shift
476
+ enumerable.shift(3)
477
+ enumerable.size.should eql(collection.size - 4)
478
+ end
479
+
480
+ it "does not harm the original collection" do
481
+ original = collection.dup
482
+ enumerable.shift
483
+ enumerable.shift(3)
484
+ collection.should eql(original)
485
+ end
486
+
487
+ end
488
+
489
+ it "depends on #(private)element_or_array" do
490
+ enumerable.should_receive(:element_or_array).twice.and_call_original
491
+ enumerable.shift
492
+ enumerable.shift(3)
493
+ end
494
+
495
+ end
496
+
497
+ context "#shuffle" do
498
+
499
+ it "returns a new HugeEnumerable" do
500
+ enumerable.shuffle.should_not equal(enumerable)
501
+ end
502
+
503
+ end
504
+
505
+ context "#shuffle!" do
506
+
507
+ it "randomly alters the order of the sequence" do
508
+ fake_random = Proc.new { |x| 2 % x }
509
+ enumerable.max_array_size = enumerable.size
510
+ original = enumerable.to_a
511
+ enumerable.shuffle!(fake_random)
512
+ shuffle1 = enumerable.to_a
513
+ fake_random = Proc.new { |x| 3 % x }
514
+ enumerable.shuffle!(fake_random)
515
+ shuffle2 = enumerable.to_a
516
+ original.should_not eq(shuffle1)
517
+ original.should_not eq(shuffle2)
518
+ shuffle1.should_not eq(shuffle2)
519
+ end
520
+
521
+ it "contains all of the original elements" do
522
+ enumerable.shuffle!.to_a.sort.should eq(collection)
523
+ end
524
+
525
+ it "does noy alter the original collection" do
526
+ original = collection.dup
527
+ enumerable.max_array_size = enumerable.size
528
+ enumerable.shuffle!
529
+ enumerable.to_a.should_not eq(original)
530
+ original.should eq(collection)
531
+ end
532
+
533
+ end
534
+
535
+ context "#size" do
536
+
537
+ it "returns the current size of the collection" do
538
+ enumerable.size.should eql(collection.size)
539
+ end
540
+
541
+ end
542
+
543
+ context "#(private)next_prime" do
544
+
545
+ it "should return the next prime following any integer" do
546
+ primes = Prime.first(100)
547
+ x = 0
548
+ until primes.empty?
549
+ enumerable.next_prime(x).should eq(primes.first)
550
+ x += 1
551
+ primes.shift if x.prime?
552
+ end
553
+ end
554
+
555
+ end
556
+
557
+ context "#(private)_fetch" do
558
+
559
+ context "with an index outside the range of (sequence_start..sequence_end)" do
560
+ it "should never relay to fetch" do
561
+ enumerable.should_not_receive(:fetch)
562
+ enumerable._fetch(-1 * enumerable.size - 1)
563
+ enumerable._fetch(enumerable.size)
564
+ end
565
+ end
566
+
567
+ context "with an index inside the range of (sequence_start..sequence_end)" do
568
+ it "should relay to fetch" do
569
+ enumerable.should_receive(:fetch).twice
570
+ enumerable._fetch(0)
571
+ enumerable._fetch(enumerable.size - 1)
572
+ end
573
+
574
+ it "should map the relative index to an absolute index before calling fetch" do
575
+ enumerable.shift(3)
576
+ enumerable.stub(:shuffle_index) { |index| index + 2 }
577
+ enumerable.should_receive(:fetch).with(5)
578
+ enumerable._fetch(0)
579
+ end
580
+
581
+ end
582
+
583
+ end
584
+
585
+ context "#(private)element_or_array" do
586
+
587
+ let(:block) { Proc.new { 1 } }
588
+
589
+ context "on a non empty collection" do
590
+ context "with no parameter" do
591
+ it "returns a single element" do
592
+ enumerable.element_or_array { block.call }.should eql(1)
593
+ end
594
+ end
595
+
596
+ context "with a nil parameter" do
597
+ it "returns a single element" do
598
+ enumerable.element_or_array(nil, &block).should eql(1)
599
+ end
600
+ end
601
+
602
+ context "with a non nil parameter" do
603
+ it "returns an array of N elements" do
604
+ enumerable.element_or_array(3, &block).should eql([1, 1, 1])
605
+ end
606
+
607
+ it "will not return more items than remain in the collection" do
608
+ size = enumerable.size
609
+ enumerable.element_or_array(size + 1, &block).should have(size).items
610
+ end
611
+ end
612
+ end
613
+
614
+ context "on an empty collection" do
615
+ context "with no parameter" do
616
+ it "returns nil" do
617
+ emptied_enumerable.element_or_array(&:block).should be_nil
618
+ end
619
+ end
620
+
621
+ context "with a parameter" do
622
+ it "returns an empty array" do
623
+ emptied_enumerable.element_or_array(3, &block).should eql([])
624
+ end
625
+ end
626
+ end
627
+
628
+ it "raises an exception if the parameter is negative" do
629
+ expect { enumerable.element_or_array(-1, &block) }.to raise_error(ArgumentError, 'negative array size')
630
+ end
631
+
632
+ end
633
+
634
+ context "#(private)full_cycle_increment" do
635
+
636
+ it "must never return a value equal to the domain size" do
637
+ (0...100).to_a.all? { |x| enumerable.full_cycle_increment(x) != x }.should be_true
638
+ end
639
+
640
+ end
641
+
642
+ end
@@ -0,0 +1 @@
1
+ require 'huge_enumerable'