davidrichards-just_enumerable_stats 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,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