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

Commit 1500cca

Browse files
authored
Merge pull request #1 from annikoff/tenth-task
Tenth task
2 parents 437dc00 + 99b474a commit 1500cca

13 files changed

+148
-37
lines changed

Diff for: .gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/.idea/
2+
/.ruby-version
3+
/.ruby-gemset
4+
/tmp
5+
/bin
6+
/Gemfile.lock

Diff for: .travis.yml

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
dist: trusty
2+
language: ruby
3+
rvm:
4+
- 2.3.3
5+
- 2.2.2
6+
before_script:
7+
- rake compile
8+
script: rake test

Diff for: Gemfile

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
source 'https://rubygems.org'
2+
3+
gemspec
4+
5+
group :development, :test do
6+
gem 'facets'
7+
gem 'rake-compiler'
8+
gem 'jeweler'
9+
end

Diff for: README.rdoc

+12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
= array_stats
22

3+
{<img src="https://travis-ci.org/annikoff/array_stats.svg?branch=experiment" alt="Build Status" />}[https://travis-ci.org/annikoff/array_stats]
4+
35
* Docs[http://benkoski.com/array_stats/]
46
* Source[http://github.com/bkoski/array_stats]
57

@@ -38,6 +40,16 @@ Percentile calculations are based on the NIST formula[http://www.itl.nist.gov/di
3840

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

43+
== BENCHMARK
44+
45+
A comparison of percentile and fast_percentile methods.
46+
47+
```
48+
user system total real
49+
Ruby: 23.120000 0.080000 23.200000 ( 23.197993)
50+
Golang: 11.280000 0.040000 11.320000 ( 11.292624)
51+
```
52+
4153
== LICENSE:
4254

4355
(The MIT License)

Diff for: Rakefile

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1-
require 'rubygems'
21
require 'rake'
2+
require 'rake/extensiontask'
3+
require 'rubygems'
4+
5+
spec = Gem::Specification.load('array_stats.gemspec')
6+
Rake::ExtensionTask.new('array_stats', spec) do |ext|
7+
ext.lib_dir = 'bin'
8+
end
39

410
begin
511
require 'jeweler'
@@ -38,11 +44,9 @@ rescue LoadError
3844
end
3945
end
4046

41-
task :test => :check_dependencies
42-
43-
task :default => :test
47+
task :default => [:compile, :test]
4448

45-
require 'rake/rdoctask'
49+
require 'rdoc/task'
4650
Rake::RDocTask.new do |rdoc|
4751
version = File.exist?('VERSION') ? File.read('VERSION') : ""
4852

Diff for: array_stats.gemspec

+6-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
Gem::Specification.new do |s|
77
s.name = %q{array_stats}
88
s.version = "0.6.0"
9-
9+
s.platform = Gem::Platform::RUBY
1010
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
1111
s.authors = ["Ben Koski"]
1212
s.date = %q{2010-04-05}
@@ -26,7 +26,9 @@ Gem::Specification.new do |s|
2626
"lib/array_stats/float.rb",
2727
"test/test_array_stats.rb",
2828
"test/test_float_extensions.rb",
29-
"test/test_helper.rb"
29+
"test/test_helper.rb",
30+
"ext/array_stats/extconf.rb",
31+
"ext/array_stats/array_stats.go"
3032
]
3133
s.homepage = %q{http://github.com/bkoski/array_stats}
3234
s.rdoc_options = ["--charset=UTF-8"]
@@ -38,6 +40,7 @@ Gem::Specification.new do |s|
3840
"test/test_float_extensions.rb",
3941
"test/test_helper.rb"
4042
]
43+
s.extensions = ['ext/array_stats/extconf.rb']
4144

4245
if s.respond_to? :specification_version then
4346
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
@@ -51,4 +54,5 @@ Gem::Specification.new do |s|
5154
else
5255
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
5356
end
57+
s.add_dependency 'ffi'
5458
end

Diff for: 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

Diff for: ext/array_stats/array_stats.go

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package main
2+
3+
import (
4+
"C"
5+
"unsafe"
6+
"sort"
7+
"math"
8+
)
9+
10+
//export fast_percentile
11+
func fast_percentile(array unsafe.Pointer, size int, percent float64) float64 {
12+
if size == 0 {
13+
return 0
14+
}
15+
elements := (*[1<<30]float64)(array)[:size]
16+
sort.Float64s(elements)
17+
18+
rank := (percent / 100) * (float64(size) + 1)
19+
_, rank_frac := math.Modf(rank)
20+
rank_frac = math.Abs(rank_frac)
21+
rank_truncated := int(math.Trunc(rank))
22+
23+
if rank_frac > 0 {
24+
sample_0 := elements[rank_truncated - 1]
25+
sample_1 := elements[rank_truncated]
26+
return (rank_frac * (sample_1 - sample_0)) + sample_0
27+
}else {
28+
return elements[int(rank) - 1]
29+
}
30+
}
31+
32+
func main() {}

Diff for: ext/array_stats/extconf.rb

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
require 'mkmf'
2+
find_executable('go')
3+
create_makefile('array_stats/array_stats')
4+
exec 'go build -buildmode=c-shared -o array_stats.so ../../../../ext/array_stats/array_stats.go'

Diff for: lib/array_stats/array_stats.rb

+21-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
require 'ffi'
2+
13
module ArrayStats
2-
4+
extend FFI::Library
5+
ffi_lib File.expand_path('../../../bin/array_stats.so', __FILE__)
6+
attach_function :fast_percentile, [:pointer, :int, :double], :double
7+
38
# Returns the sum of all elements in the array; 0 if array is empty
49
def total_sum
510
self.inject(0) {|sum, sample| sum += sample}
@@ -18,19 +23,23 @@ def mean
1823
def median
1924
percentile(50)
2025
end
26+
27+
# Returns the median for the array; nil if array is empty
28+
def fast_median
29+
fast_percentile(50)
30+
end
2131

2232
# Returns the percentile value for percentile _p_; nil if array is empty.
2333
#
2434
# _p_ should be expressed as an integer; <tt>percentile(90)</tt> returns the 90th percentile of the array.
2535
#
2636
# Algorithm from NIST[http://www.itl.nist.gov/div898/handbook/prc/section2/prc252.htm]
2737
def percentile p
38+
return nil if length == 0
2839
sorted_array = self.sort
2940
rank = (p.to_f / 100) * (self.length + 1)
3041

31-
if self.length == 0
32-
return nil
33-
elsif rank.fractional_part?
42+
if rank.fractional_part?
3443
sample_0 = sorted_array[rank.truncate - 1]
3544
sample_1 = sorted_array[rank.truncate]
3645

@@ -39,5 +48,11 @@ def percentile p
3948
return sorted_array[rank - 1]
4049
end
4150
end
42-
43-
end
51+
52+
def fast_percentile p
53+
return nil if size == 0
54+
pointer = FFI::MemoryPointer.new :double, size
55+
pointer.put_array_of_double 0, self
56+
ArrayStats.fast_percentile pointer, size, p
57+
end
58+
end

Diff for: test/test_array_stats.rb

+21-19
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,73 @@
11
require File.dirname(__FILE__) + '/test_helper.rb'
22

33
class TestArrayStats < Test::Unit::TestCase
4-
54
context "median" do
65
should "return the middle of the set if array length is odd" do
76
assert_equal 15, [1,2,15,22,38].median
7+
assert_equal 15, [1,2,15,22,38].fast_median
88
end
9-
9+
1010
should "return the average of the middle of the set if array length is even" do
1111
assert_equal 10, [1,6,14,22].median
12+
assert_equal 10, [1,6,14,22].fast_median
1213
end
13-
14+
1415
should "return nil if the array is empty" do
1516
assert_nil [].median
17+
assert_nil [].fast_median
1618
end
17-
19+
1820
should "sort an array before deriving the median" do
1921
assert_equal 20, [1,20,50,60,10].median
22+
assert_equal 20.0, [1,20,50,60,10].fast_median
2023
end
2124
end
22-
23-
context "percentile" do
25+
26+
context "percentile" do
2427
should "choose the number at a particular rank if array divides cleanly" do
2528
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)
2630
end
27-
31+
2832
should "interpolate according to algorithm if array does not divide cleanly" do
2933
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)
3035
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)
3137
end
32-
38+
3339
should "return nil if the array is empty" do
3440
assert_nil [].percentile(30)
41+
assert_nil [].fast_percentile(30)
3542
end
3643
end
37-
44+
3845
context "total_sum" do
3946
should "return the sum of all elements in the array" do
4047
assert_equal 12, [2,4,6].total_sum
4148
end
42-
49+
4350
should "return an integer if all elements are ints" do
4451
assert_kind_of Integer, [2,4,6].total_sum
4552
end
46-
53+
4754
should "return a float if at least some of the elements are floats" do
4855
assert_kind_of Float, [2.5,3.5,6].total_sum
4956
end
50-
57+
5158
should "return 0 if the array is empty" do
5259
assert_equal 0, [].total_sum
5360
end
5461
end
55-
62+
5663
context "mean" do
5764
should "return the mean for the array " do
5865
assert_equal 7, [2,4,6,8,10,12].mean
5966
assert_equal 25, [48,29,26,19,3].mean
6067
end
61-
68+
6269
should "return nil if the array is empty" do
6370
assert_nil [].mean
6471
end
6572
end
66-
67-
68-
6973
end
70-
71-

Diff for: 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

Diff for: 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)