Skip to content
This repository was archived by the owner on Aug 5, 2020. It is now read-only.

Commit c5348d2

Browse files
committed
add tests and benchmark
1 parent 1298975 commit c5348d2

10 files changed

+64
-34
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/.idea/
22
/.ruby-version
33
/.ruby-gemset
4-
/tmp
4+
/tmp
5+
/bin

README.rdoc

+10
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ Percentile calculations are based on the NIST formula[http://www.itl.nist.gov/di
3838

3939
* <tt>sudo gem install bkoski-array_stats --source http://gemcutter.org</tt>
4040

41+
== BENCHMARK
42+
43+
A comparison of percentile and fast_percentile methods.
44+
45+
```
46+
user system total real
47+
Ruby: 23.580000 0.480000 24.060000 ( 24.063252)
48+
Golang: 13.940000 0.440000 14.380000 ( 12.710602)
49+
```
50+
4151
== LICENSE:
4252

4353
(The MIT License)

Rakefile

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ require 'rubygems'
44
require 'rake'
55

66
gem = Gem::Specification.load(File.dirname(__FILE__) + '/array_stats.gemspec')
7-
Rake::ExtensionTask.new('array_stats', gem)
7+
Rake::ExtensionTask.new('array_stats', gem) do |ext|
8+
ext.lib_dir = 'bin'
9+
end
810

911
begin
1012
require 'jeweler'

benchmark

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env ruby
2+
require 'benchmark'
3+
require_relative 'lib/array_stats'
4+
5+
n = 100
6+
array = (1..1000000).map { rand(10) * 10 }
7+
Benchmark.bm do |x|
8+
x.report('Ruby: ') do
9+
n.times { array.percentile(65) }
10+
end
11+
12+
x.report('Golang: ') do
13+
n.times { array.fast_percentile(65) }
14+
end
15+
end

ext/array_stats/extconf.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
require 'mkmf'
22
find_executable('go')
3-
%x(go build -buildmode=c-shared -o libfastpercentile.so fast_percentile.go)
4-
create_makefile('ext/array_stats/fast_percentile')
3+
%x{go build -buildmode=c-shared -o libfastpercentile.so fast_percentile.go}
4+
create_makefile('array_stats/array_stats')

ext/array_stats/fast_percentile.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func fast_percentile(array unsafe.Pointer, size int, percent float64) float64 {
1919
sort.Float64s(elements)
2020
length := float64(len(elements))
2121
if length == 0 {
22-
return 0.0
22+
return 0
2323
}
2424
rank := (percent / 100) * (length + 1)
2525
_, rank_frac := math.Modf(rank)

lib/array_stats/array_stats.rb

+5-6
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
module ArrayStats
44
extend FFI::Library
5-
ffi_lib '../ext/array_stats/libfastpercentile.so'
5+
ffi_lib File.expand_path('../../../bin/libfastpercentile.so', __FILE__)
66
attach_function :fast_percentile, [:pointer, :int, :double], :double
77

88
# Returns the sum of all elements in the array; 0 if array is empty
@@ -35,12 +35,11 @@ def fast_median
3535
#
3636
# Algorithm from NIST[http://www.itl.nist.gov/div898/handbook/prc/section2/prc252.htm]
3737
def percentile p
38+
return nil if length == 0
3839
sorted_array = self.sort
3940
rank = (p.to_f / 100) * (self.length + 1)
4041

41-
if self.length == 0
42-
return nil
43-
elsif rank.fractional_part?
42+
if rank.fractional_part?
4443
sample_0 = sorted_array[rank.truncate - 1]
4544
sample_1 = sorted_array[rank.truncate]
4645

@@ -51,9 +50,9 @@ def percentile p
5150
end
5251

5352
def fast_percentile p
53+
return nil if length == 0
5454
pointer = FFI::MemoryPointer.new :double, size
5555
pointer.put_array_of_double 0, self
5656
ArrayStats.fast_percentile pointer, size, p
5757
end
58-
59-
end
58+
end

test/test_array_stats.rb

+21-18
Original file line numberDiff line numberDiff line change
@@ -4,67 +4,70 @@ class TestArrayStats < Test::Unit::TestCase
44
context "median" do
55
should "return the middle of the set if array length is odd" do
66
assert_equal 15, [1,2,15,22,38].median
7+
assert_equal 15, [1,2,15,22,38].fast_median
78
end
8-
9+
910
should "return the average of the middle of the set if array length is even" do
1011
assert_equal 10, [1,6,14,22].median
12+
assert_equal 10, [1,6,14,22].fast_median
1113
end
12-
14+
1315
should "return nil if the array is empty" do
1416
assert_nil [].median
17+
assert_nil [].fast_median
1518
end
16-
19+
1720
should "sort an array before deriving the median" do
1821
assert_equal 20, [1,20,50,60,10].median
22+
assert_equal 20.0, [1,20,50,60,10].fast_median
1923
end
2024
end
21-
22-
context "percentile" do
25+
26+
context "percentile" do
2327
should "choose the number at a particular rank if array divides cleanly" do
2428
assert_equal 36, [12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48].percentile(65)
29+
assert_equal 36, [12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48].fast_percentile(65)
2530
end
26-
31+
2732
should "interpolate according to algorithm if array does not divide cleanly" do
2833
assert_equal 5.5, [3,5,7,8,9,11,13,15].percentile(25)
34+
assert_equal 5.5, [3,5,7,8,9,11,13,15].fast_percentile(25)
2935
assert_equal 95.1981, [95.1772,95.1567,95.1937,95.1959,95.1442,95.0610,95.1591,95.1195,95.1065,95.0925,95.1990,95.1682].percentile(90).round_to(0.0001)
36+
assert_equal 95.1981, [95.1772,95.1567,95.1937,95.1959,95.1442,95.0610,95.1591,95.1195,95.1065,95.0925,95.1990,95.1682].fast_percentile(90).round_to(0.0001)
3037
end
31-
38+
3239
should "return nil if the array is empty" do
3340
assert_nil [].percentile(30)
41+
assert_nil [].fast_percentile(30)
3442
end
3543
end
36-
44+
3745
context "total_sum" do
3846
should "return the sum of all elements in the array" do
3947
assert_equal 12, [2,4,6].total_sum
4048
end
41-
49+
4250
should "return an integer if all elements are ints" do
4351
assert_kind_of Integer, [2,4,6].total_sum
4452
end
45-
53+
4654
should "return a float if at least some of the elements are floats" do
4755
assert_kind_of Float, [2.5,3.5,6].total_sum
4856
end
49-
57+
5058
should "return 0 if the array is empty" do
5159
assert_equal 0, [].total_sum
5260
end
5361
end
54-
62+
5563
context "mean" do
5664
should "return the mean for the array " do
5765
assert_equal 7, [2,4,6,8,10,12].mean
5866
assert_equal 25, [48,29,26,19,3].mean
5967
end
60-
68+
6169
should "return nil if the array is empty" do
6270
assert_nil [].mean
6371
end
6472
end
65-
66-
67-
6873
end
69-
70-

test/test_float_extensions.rb

+4-4
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,24 @@ class TestFloatExtensions < Test::Unit::TestCase
77
n = 1.249
88
assert n.fractional_part?
99
end
10-
10+
1111
should "return true for a number without a fractional part" do
1212
n = 1.0
1313
assert !n.fractional_part?
1414
end
1515
end
16-
16+
1717
context "fractional_part" do
1818
should "return the decimal parts for a number" do
1919
n = 12.2456
2020
assert_in_delta 0.2456, n.fractional_part, 0.00000001
2121
end
22-
22+
2323
should "retrun the decimal parts, even if they are 0" do
2424
n = 12.0
2525
assert_equal 0.0, n.fractional_part
2626
end
27-
27+
2828
should "retrun the decimal as a positive number, even if the original float is negative" do
2929
n = -12.2399
3030
assert_in_delta 0.2399, n.fractional_part, 0.00000001

test/test_helper.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
require 'test/unit'
33
require File.dirname(__FILE__) + '/../lib/array_stats'
44
require 'shoulda'
5-
require 'facets'
5+
require 'facets'

0 commit comments

Comments
 (0)