davidrichards-just_enumerable_stats 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,77 @@
1
+ require File.join(File.dirname(__FILE__), "/spec_helper")
2
+ require 'fixed_range'
3
+
4
+ describe FixedRange, "initialize" do
5
+ it "should take three paramaters, a min, max, and step_size" do
6
+ lambda{@f = FixedRange.new(1,5,1)}.should_not raise_error
7
+ @f.min.should eql(1)
8
+ @f.max.should eql(5)
9
+ @f.step_size.should eql(1)
10
+ end
11
+
12
+ it "should assume step of 1 if not given" do
13
+ FixedRange.new(1,5).step_size.should eql(1)
14
+ end
15
+
16
+ it "should straighten out min and max (no reverse ranges here)" do
17
+ @f = FixedRange.new(5,1)
18
+ @f.min.should eql(1)
19
+ @f.max.should eql(5)
20
+ end
21
+ end
22
+
23
+ describe FixedRange do
24
+
25
+ before(:each) do
26
+ @f = FixedRange.new(1,5)
27
+ end
28
+
29
+ it "should be able to step with an arbitrary step size" do
30
+ # 1.0 + 3.5
31
+ count_values(@f, 2.5).should eql(4.5)
32
+ # 1.0 + 1.5 + 2.0 ... 5.0
33
+ count_values(@f, 0.5).should eql(27.0)
34
+ count_values(@f, 5).should eql(1.0)
35
+ end
36
+
37
+ it "should expose each" do
38
+ sum = 0.0
39
+ @f.each {|x| sum += x}
40
+ sum.should eql(15.0)
41
+ end
42
+
43
+ it "should expose min and max" do
44
+ @f.min.should eql(1)
45
+ @f.max.should eql(5)
46
+ end
47
+
48
+ it "should expose size" do
49
+ @f.size.should eql(5.0)
50
+ end
51
+
52
+ it "should have an index lookup" do
53
+ @f[0].should eql(1)
54
+ @f[1].should eql(2)
55
+ @f[2].should eql(3)
56
+ @f[3].should eql(4)
57
+ @f[4].should eql(5)
58
+ lambda{@f[5].should eql(5)}.should raise_error
59
+ @f[-1].should eql(5)
60
+ @f[-2].should eql(4)
61
+ @f[-3].should eql(3)
62
+ @f[-4].should eql(2)
63
+ @f[-5].should eql(1)
64
+ end
65
+ end
66
+
67
+ def count_values(obj, step_size)
68
+ sum = 0.0
69
+ obj.step(step_size) {|x| sum += x}
70
+ return sum
71
+ end
72
+
73
+
74
+ # @f = FixedRange.new(1.0,5.0)
75
+ # @f.each {|x| puts x}
76
+ # puts @f.size, @f.min, @f.max
77
+ # @f.step(2.5) {|x| puts x}
@@ -0,0 +1,459 @@
1
+ require File.join(File.dirname(__FILE__), "/../spec_helper")
2
+
3
+ require 'just_enumerable_stats/stats'
4
+ class MyDataContainer
5
+ include Enumerable
6
+ include JustEnumerableStats::Stats
7
+
8
+ def initialize(*values)
9
+ @data = values
10
+ end
11
+
12
+ def method_missing(sym, *args, &block)
13
+ @data.send(sym, *args, &block)
14
+ end
15
+
16
+ def to_a
17
+ @data
18
+ end
19
+
20
+ end
21
+
22
+ describe JustEnumerableStats::Stats do
23
+ before do
24
+ @a = MyDataContainer.new(1,2,3)
25
+ @b = MyDataContainer.new(8,4,2)
26
+ @doubler = lambda{|e| e * 2}
27
+ @inverser = lambda{|e| 1/e.to_f}
28
+ @inverse_matcher = lambda{|a, b| 1/a <=> 1/b}
29
+ end
30
+
31
+ it "should be able to generate a random number between two integers" do
32
+ val = (1..100).map {rand_between(1,10)}
33
+ (val.min >= 1).should be_true
34
+ (val.max <= 10).should be_true
35
+ end
36
+
37
+ it "should be able to generate a random number between two floats" do
38
+ val = (1..100).map {rand_in_floats(1.0,10.0)}
39
+ (val.min >= 1).should be_true
40
+ (val.max <= 10).should be_true
41
+ val.all? {|v| v.should be_is_a(Float)}
42
+ end
43
+
44
+ it "should be able to work with floats from rand_between" do
45
+ val = (1..100).map {rand_between(1.0,10.0)}
46
+ (val.min >= 1).should be_true
47
+ (val.max <= 10).should be_true
48
+ val.all? {|v| v.should be_is_a(Float)}
49
+ end
50
+
51
+ it "should have a max" do
52
+ @a.max.should eql(3)
53
+ end
54
+
55
+ it "should have a max that takes a block" do
56
+ val = @a.max &@inverse_matcher
57
+ val.should eql(1)
58
+ end
59
+
60
+ it "should be able to use a default block for max" do
61
+ @a.default_block = @inverse_matcher
62
+ @a.max.should eql(1)
63
+ end
64
+
65
+ it "should know the index of the max value" do
66
+ @a.max_index.should eql(2)
67
+ end
68
+
69
+ it "should find the first index value with max_index, in case there are duplicates" do
70
+ MyDataContainer.new(1,2,3,3).max_index.should eql(2)
71
+ end
72
+
73
+ it "should use a block to find the max index" do
74
+ val = @a.max_index &@inverse_matcher
75
+ val.should eql(0)
76
+ end
77
+
78
+ it "should be able to use a default block to find the max index" do
79
+ @a.default_block = @inverse_matcher
80
+ @a.max_index.should eql(0)
81
+ end
82
+
83
+ it "should have a min" do
84
+ @a.min.should eql(1)
85
+ end
86
+
87
+ it "should have a min that takes a block" do
88
+ val = @a.min &@inverse_matcher
89
+ val.should eql(3)
90
+ end
91
+
92
+ it "should be able to use a default block for min" do
93
+ @a.default_block = @inverse_matcher
94
+ @a.min.should eql(3)
95
+ end
96
+
97
+ it "should know the index of the min value" do
98
+ @a.min_index.should eql(0)
99
+ end
100
+
101
+ it "should find the first index value with min_index, in case there are duplicates" do
102
+ MyDataContainer.new(1,1,2,3).min_index.should eql(0)
103
+ end
104
+
105
+ it "should use a block to find the min index" do
106
+ val = @a.min_index &@inverse_matcher
107
+ val.should eql(2)
108
+ end
109
+
110
+ it "should be able to use a default block to find the min index" do
111
+ @a.default_block = @inverse_matcher
112
+ @a.min_index.should eql(2)
113
+ end
114
+
115
+ it "should be able to sum a list" do
116
+ @a.sum.should eql(6)
117
+ MyDataContainer.new(1, 2, 3.0).sum.should eql(6.0)
118
+ end
119
+
120
+ it "should offer sum with a precision of 1.0e-15" do
121
+ MyDataContainer.new(0.1, 0.2, 0.3).sum.should be_close(0.6, 1.0e-15)
122
+ MyDataContainer.new(0.1, 0.2, 0.3).sum.should_not be_close(0.6, 1.0e-16)
123
+ end
124
+
125
+ it "should be able to evaluate a sum with a block" do
126
+ @a.sum(&@doubler).should eql(12)
127
+ end
128
+
129
+ it "should be able to use the default block to evaluate sum" do
130
+ @a.default_block = @doubler
131
+ @a.sum.should eql(12)
132
+ end
133
+
134
+ it "should be able to find the arithmetic average, mean, or avg" do
135
+ @a.average.should eql(2)
136
+ @a.mean.should eql(2)
137
+ @a.avg.should eql(2)
138
+ MyDataContainer.new(1, 2, 3.0).average.should eql(2.0)
139
+ end
140
+
141
+ it "should be able to calculate average with a block" do
142
+ @a.average(&@doubler).should eql(4)
143
+ @a.mean(&@doubler).should eql(4)
144
+ @a.avg(&@doubler).should eql(4)
145
+ MyDataContainer.new(1, 2, 3.0).average(&@doubler).should eql(4.0)
146
+ end
147
+
148
+ it "should be able to calculate average with a default block" do
149
+ @a.default_block = @doubler
150
+ @a.average.should eql(4)
151
+ @a.mean.should eql(4)
152
+ @a.avg.should eql(4)
153
+ b = MyDataContainer.new(1, 2, 3.0)
154
+ b.default_block = @doubler
155
+ b.average.should eql(4.0)
156
+ end
157
+
158
+ it "should be able to calculate the variance" do
159
+ @a.variance.should eql(1)
160
+ @a.var.should eql(1)
161
+ end
162
+
163
+ it "should be able to calculate the variance with a block" do
164
+ @a.variance(&@doubler).should eql(4)
165
+ @a.var(&@doubler).should eql(4)
166
+ end
167
+
168
+ it "should be able to calculate the variance with a default block" do
169
+ @a.default_block = @doubler
170
+ @a.variance.should eql(4)
171
+ @a.var.should eql(4)
172
+ end
173
+
174
+ it "should be able to calculate the standard deviation" do
175
+ @a.standard_deviation.should eql(1.0)
176
+ @a.std.should eql(1.0)
177
+ end
178
+
179
+ it "should be able to calculate the standard deviation with a block" do
180
+ @a.standard_deviation(&@doubler).should eql(2.0)
181
+ @a.std(&@doubler).should eql(2.0)
182
+ end
183
+
184
+ it "should be able to calculate the standard deviation with a default block" do
185
+ @a.default_block = @doubler
186
+ @a.standard_deviation.should eql(2.0)
187
+ @a.std.should eql(2.0)
188
+ end
189
+
190
+ it "should be able to calculate the median value" do
191
+ @a.median.should eql(2)
192
+ MyDataContainer.new(1,4,3,2,5).median.should eql(3)
193
+ MyDataContainer.new(1,9,2,8).median.should eql(5.0)
194
+ end
195
+
196
+ it "should be able to get a max and min range from the list" do
197
+ @a.range.should eql([1, 3])
198
+ end
199
+
200
+ it "should be able to pass a block to the range method" do
201
+ @a.range(&@inverse_matcher).should eql([3, 1])
202
+ end
203
+
204
+ it "should be able to use a default block for the range method" do
205
+ @a.default_block = @inverse_matcher
206
+ @a.range.should eql([3, 1])
207
+ end
208
+
209
+ it "should have a getter and a setter on the range class" do
210
+ @a.range_class = Array
211
+ @a.range_class.should eql(Array)
212
+ end
213
+
214
+ it "should be able to instantiate a range" do
215
+ @a.range_as_range.should eql(Range.new(1, 3))
216
+ end
217
+
218
+ it "should be able to instantiate a range with a block" do
219
+ @a.range_as_range(&@inverse_matcher).should eql(Range.new(3, 1))
220
+ end
221
+
222
+ it "should be able to instantiate a range with a default block" do
223
+ @a.default_block = @inverse_matcher
224
+ @a.range_as_range.should eql(Range.new(3, 1))
225
+ end
226
+
227
+ it "should be able to create a new object, sorted" do
228
+ a = [3,1,2]
229
+ b = a.new_sort
230
+ b.object_id.should_not eql(a.object_id)
231
+ b.should eql([1,2,3])
232
+ a << 4
233
+ b.should eql([1,2,3])
234
+ end
235
+
236
+ it "should be able to take a block and still do new_sort" do
237
+ @a.new_sort(&@doubler).should eql([2,4,6])
238
+ end
239
+
240
+ it "should be able to take a default block and still do new_sort" do
241
+ @a.default_block = @doubler
242
+ @a.new_sort.should eql([2,4,6])
243
+ end
244
+
245
+ it "should be able to rank a list" do
246
+ @b.rank.should eql([3,2,1])
247
+ end
248
+
249
+ it "should be able to use a block in rank" do
250
+ @b.rank(&@inverser).should eql([1,2,3])
251
+ end
252
+
253
+ it "should be able to use a default block in rank" do
254
+ @b.default_block = @inverser
255
+ @b.rank.should eql([1,2,3])
256
+ end
257
+
258
+ it "should be able to get the order of values, handling duplicates" do
259
+ [10,5,5,1].order.should eql([4,2,3,1])
260
+ end
261
+
262
+ it "should be able to take a block in the ordering" do
263
+ [10,5,5,1].order(&@inverser).should eql([1,2,3,4])
264
+ end
265
+
266
+ it "should be able to take a default block in the ordering" do
267
+ a = [10,5,5,1]
268
+ a.default_block = @inverser
269
+ a.order.should eql([1,2,3,4])
270
+ end
271
+
272
+ it "should be able to calculate the cumulative sum" do
273
+ @a.cumulative_sum.should eql([1,3,6])
274
+ @a.cum_sum.should eql([1,3,6])
275
+ [1,2,3.0].cum_sum.should eql([1.0, 3.0, 6.0])
276
+ end
277
+
278
+ it "should be able to take a block to produce the cumulative sum" do
279
+ @a.cumulative_sum(&@doubler).should eql([2,6,12])
280
+ @a.cum_sum(&@doubler).should eql([2,6,12])
281
+ [1,2,3.0].cum_sum(&@doubler).should eql([2.0, 6.0, 12.0])
282
+ end
283
+
284
+ it "should be able to take a default block to produce the cumlative sum" do
285
+ @a.default_block = @doubler
286
+ @a.cumulative_sum.should eql([2,6,12])
287
+ @a.cum_sum.should eql([2,6,12])
288
+ b = [1,2,3.0]
289
+ b.default_block = @doubler
290
+ b.cum_sum.should eql([2.0, 6.0, 12.0])
291
+ end
292
+
293
+ it "should be able to calculate the cumulative product" do
294
+ @a.cumulative_product.should eql([1,2,6])
295
+ @a.cum_prod.should eql([1,2,6])
296
+ [1,2,3.0].cum_prod.should eql([1.0, 2.0, 6.0])
297
+ end
298
+
299
+ it "should be able to take a block to produce the cumulative product" do
300
+ @a.cumulative_product(&@doubler).should eql([2,8,48])
301
+ @a.cum_prod(&@doubler).should eql([2,8,48])
302
+ [1,2,3.0].cum_prod(&@doubler).should eql([2.0, 8.0, 48.0])
303
+ end
304
+
305
+ it "should be able to take a default block to produce the cumlative product" do
306
+ @a.default_block = @doubler
307
+ @a.cumulative_product.should eql([2,8,48])
308
+ @a.cum_prod.should eql([2,8,48])
309
+ b = [1,2,3.0]
310
+ b.default_block = @doubler
311
+ b.cum_prod.should eql([2.0, 8.0, 48.0])
312
+ end
313
+
314
+ it "should be able to produce the cumulative max" do
315
+ @a.cumulative_max.should eql([1,2,3])
316
+ @a.cum_max.should eql([1,2,3])
317
+ end
318
+
319
+ it "should be able to produce the cumulative max with a block" do
320
+ @a.cumulative_max(&@doubler).should eql([2,4,6])
321
+ @a.cum_max(&@doubler).should eql([2,4,6])
322
+ end
323
+
324
+ it "should be able to produce the cumulative max with a default block" do
325
+ @a.default_block = @doubler
326
+ @a.cumulative_max.should eql([2,4,6])
327
+ @a.cum_max.should eql([2,4,6])
328
+ end
329
+
330
+ it "should be able to produce the cumulative min" do
331
+ @a.cumulative_min.should eql([1,1,1])
332
+ @a.cum_min.should eql([1,1,1])
333
+ end
334
+
335
+ it "should be able to produce the cumulative min with a block" do
336
+ @a.cumulative_min(&@doubler).should eql([2,2,2])
337
+ @a.cum_min(&@doubler).should eql([2,2,2])
338
+ end
339
+
340
+ it "should be able to produce the cumulative min with a default block" do
341
+ @a.default_block = @doubler
342
+ @a.cumulative_min.should eql([2,2,2])
343
+ @a.cum_min.should eql([2,2,2])
344
+ end
345
+
346
+ it "should be able to multiply the values in the list" do
347
+ @a.product.should eql(6)
348
+ end
349
+
350
+ it "should be able to yield an operation on pairs" do
351
+ val = @a.to_pairs(@b) {|a, b| a + b}
352
+ val.should eql([9,6,5])
353
+ end
354
+
355
+ # [1,2,3] and [2,3,4] have 2 items in common, and 4 unique items together.
356
+ # So 2 items / 4 items is 0.5
357
+ it "should be able to find the tanimoto coefficient" do
358
+ b = [2,3,4]
359
+ @a.tanimoto_pairs(b).should eql(0.5)
360
+ @a.tanimoto_correlation(b).should eql(0.5)
361
+ end
362
+
363
+ it "should have long hand for union" do
364
+ @a.union(@b).should eql([1, 2, 3, 8, 4])
365
+ end
366
+
367
+ it "should have long hand for intersect" do
368
+ @a.intersect(@b).should eql([2])
369
+ end
370
+
371
+ it "should have a long hand for compliment" do
372
+ @a.compliment(@b).should eql([1, 3])
373
+ end
374
+
375
+ it "should have a long hand for exclusive not" do
376
+ @a.exclusive_not(@b).should eql([1,3,8,4])
377
+ end
378
+
379
+ it "should be able to generate a cartesian product" do
380
+ @a.cartesian_product(@b).should eql([[1, 8], [1, 4], [1, 2], [2, 8], [2, 4], [3, 8], [3, 4], [3, 2]])
381
+ @a.cp(@b).should eql([[1, 8], [1, 4], [1, 2], [2, 8], [2, 4], [3, 8], [3, 4], [3, 2]])
382
+ @a.permutations(@b).should eql([[1, 8], [1, 4], [1, 2], [2, 8], [2, 4], [3, 8], [3, 4], [3, 2]])
383
+ end
384
+
385
+ it "should be able to add pairwise computations" do
386
+ # Remember:
387
+ # @a = [1,2,3]
388
+ # @b = [8,4,2]
389
+ val = @a.sigma_pairs(@b) {|a, b| a / b}
390
+ val.should eql(1/8 + 2/4 + 3/2)
391
+ val = @a.sigma_pairs(@b) {|a, b| a * b}
392
+ val.should eql(1*8 + 2*4 + 3*2)
393
+ end
394
+
395
+ it "should be able to find the euclidian distance between two lists" do
396
+ @a.euclidian_distance(@b).should be_close(7.348, 0.001)
397
+ [1,2,3].euclidian_distance([2,3,4]).should eql(Math.sqrt(3.0))
398
+ end
399
+
400
+ it "should be able to generate a list of random numbers, each within the range between two lists" do
401
+ a = [1,0,-1]
402
+ b = [10,0,-20]
403
+ list_min = a.min_of_lists(b)
404
+ list_max = a.max_of_lists(b)
405
+ 100.times do
406
+ val = a.rand_in_range(b)
407
+ val.each_with_index do |e, i|
408
+ e >= list_min[i]
409
+ e <= list_max[i]
410
+ end
411
+ end
412
+ end
413
+
414
+ it "should be able to find random numbers in the range between many lists" do
415
+ a = [1,0,-2]
416
+ b = [10,0,-20]
417
+ c = [2,0,-1]
418
+ list_min = a.min_of_lists(b)
419
+ list_max = a.max_of_lists(b)
420
+ 100.times do
421
+ val = a.rand_in_range(b)
422
+ val.each_with_index do |e, i|
423
+ e >= list_min[i]
424
+ e <= list_max[i]
425
+ end
426
+ end
427
+ end
428
+
429
+ it "should be able to yield a block for columns of values" do
430
+ a = [1,2,6.0]
431
+ b = [2,6.0,1]
432
+ c = [6.0,1,2]
433
+ val = a.yield_transpose(b, c) {|e| e.mean}
434
+ val.should eql([3.0, 3.0, 3.0])
435
+ end
436
+
437
+ it "should be able to transpose a list of lists (not dependent on Array#transpose)" do
438
+ a = MyDataContainer.new(1,2,6.0)
439
+ b = MyDataContainer.new(2,6.0,1)
440
+ c = MyDataContainer.new(6.0,1,2)
441
+ val = a.yield_transpose(b, c)
442
+ val.should eql( [[1, 2, 6.0], [2, 6.0, 1], [6.0, 1, 2]])
443
+ end
444
+
445
+ it "should be able to get the correlation between two lists" do
446
+ @a.correlation(MyDataContainer.new(2,3,5)).should be_close(0.981, 0.001)
447
+ @a.cor(MyDataContainer.new(2,3,5)).should be_close(0.981, 0.001)
448
+ end
449
+
450
+ it "should be able to return the max of lists" do
451
+ @a.max_of_lists(@b).should eql([8,4,3])
452
+ @a.max_of_lists(@b, [10,10,10]).should eql([10,10,10])
453
+ end
454
+
455
+ it "should be able to return the min of lists" do
456
+ @a.min_of_lists(@b).should eql([1,2,2])
457
+ end
458
+
459
+ end