ranker 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ data.tar.gz: 00ce8440e7681886fd6ae2c93f4db5af94c5a655
4
+ metadata.gz: 691b75e03742cc6ca452f8fc9a45cec01a1fa9f2
5
+ SHA512:
6
+ data.tar.gz: 458a61a409e17288fb97b90dea58417b99426d627f61c9aaa5d9417e88a27944b6137b2dbed40390bd0e0d281c5b6277ca329dc0bdafa5067cd4964ca8d6ee8f
7
+ metadata.gz: 77454afb9e3c29d72a8e3c223f9a0d47df41d7841e00a933e9c7f3de0adeffa9c882a173c432178d1fc621fe45dae2e37b6cff2f950b8c6339d0a36f173355f2
data/README.md ADDED
@@ -0,0 +1,135 @@
1
+ Ranker [![Build Status](https://travis-ci.org/quidproquo/ranker.png?branch=master)](http://travis-ci.org/quidproquo/ranker)
2
+ ======
3
+
4
+ A Ruby library for ranking scorable types using various ranking strategies.
5
+
6
+ Compatibility
7
+ -------------
8
+
9
+ Ranker is tested against MRI (1.8.7+) and JRuby (1.9.0+).
10
+
11
+ Installation
12
+ ------------
13
+
14
+ With bundler, add the `ranker` gem to your `Gemfile`.
15
+
16
+ ```ruby
17
+ gem "ranker", "~> 1.0"
18
+ ```
19
+
20
+ Require the `ranker` gem in your application.
21
+
22
+ ```ruby
23
+ require "ranker"
24
+ ```
25
+
26
+ Usage
27
+ -----
28
+
29
+ ### Default Ranking
30
+
31
+ Default ranking will assume values are numeric and rank them in descending order.
32
+
33
+ ```ruby
34
+ scores = [1, 1, 2, 3, 3, 1, 4, 4, 5, 6, 8, 1, 0]
35
+ rankings = Ranker.rank(scores)
36
+ rankings.count #=> 8
37
+ ranking_1 = rankings[0]
38
+ ranking_1.rank #=> 1
39
+ ranking_1.score #=> 8
40
+ ```
41
+
42
+ ### Custom Ranking
43
+
44
+ Custom ranking allows for ranking of objects by using a symbol or a lambda.
45
+
46
+ ```ruby
47
+ class Player
48
+ attr_accesor :score
49
+
50
+ def initalize(score)
51
+ @score = score
52
+ end
53
+ end
54
+
55
+ players = [Player.new(0), Player.new(100), Player.new(1000), Player.new(25)]
56
+ rankings = Ranker.rank(players, :by => lambda { |player| player.score })
57
+ # or
58
+ rankings = Ranker.rank(players, :by => :score)
59
+ ```
60
+
61
+ In some cases objects need to be ranked by score in ascending order, for example, if you were ranking golf players.
62
+
63
+
64
+ ```ruby
65
+ class GolfPlayer < Player
66
+ end
67
+
68
+ players = [GolfPlayer.new(72), GolfPlayer.new(100), GolfPlayer.new(138), GolfPlayer.new(54)]
69
+ rankings = Ranker.rank(players, :by => :score, :desc => false)
70
+ ```
71
+
72
+ ### Ranking Strategies
73
+
74
+ Ranker has a number of ranking strategies available to use, mostly based on the Wikipedia entry on [ranking](http://en.wikipedia.org/wiki/Ranking). Strategies can be passed in as an option to the rank method.
75
+
76
+ ```ruby
77
+ rankings = Ranker.rank(players, :by => :score, :strategy => :ordinal)
78
+ ```
79
+
80
+ #### Standard Competition Ranking ("1224" ranking)
81
+
82
+ This is the default ranking strategy. For more info, see the Wikipedia entry on [Standard Competition Ranking](http://en.wikipedia.org/wiki/Ranking#Standard_competition_ranking_.28.221224.22_ranking.29).
83
+
84
+ ```ruby
85
+ rankings = Ranker.rank(players, :by => :score, :strategy => :standard_competition)
86
+ ```
87
+
88
+ #### Modified Competition Ranking ("1334" ranking)
89
+
90
+ For more info, see the Wikipedia entry on [Modified Competition Ranking](http://en.wikipedia.org/wiki/Ranking#Modified_competition_ranking_.28.221334.22_ranking.29).
91
+
92
+ ```ruby
93
+ rankings = Ranker.rank(players, :by => :score, :strategy => :modified_competition)
94
+ ```
95
+
96
+ #### Dense Ranking ("1223" ranking)
97
+
98
+ For more info, see the Wikipedia entry on [Dense Ranking](http://en.wikipedia.org/wiki/Ranking#Dense_ranking_.28.221223.22_ranking.29).
99
+
100
+ ```ruby
101
+ rankings = Ranker.rank(players, :by => :score, :strategy => :dense)
102
+ ```
103
+
104
+ #### Ordinal Ranking ("1234" ranking)
105
+
106
+ For more info, see the Wikipedia entry on [Ordinal Ranking](http://en.wikipedia.org/wiki/Ranking#Ordinal_ranking_.28.221234.22_ranking.29).
107
+
108
+ ```ruby
109
+ rankings = Ranker.rank(players, :by => :score, :strategy => :ordinal)
110
+ ```
111
+
112
+ #### Custom Ranking
113
+
114
+ If you find the current strategies not to your liking, you can write your own and pass the class into the rank method.
115
+
116
+ ```ruby
117
+ class MyCustomStrategy < Ranker::Strategies::Strategy
118
+
119
+ def execute
120
+ # My code here
121
+ end
122
+
123
+ end
124
+
125
+ rankings = Ranker.rank(players, :by => :score, :strategy => MyCustomStrategy)
126
+ ```
127
+
128
+
129
+ Copyright
130
+ ---------
131
+
132
+ Copyright &copy; 2013 Ilya Scharrenbroich. Released under the MIT License.
133
+
134
+
135
+
data/lib/ranker.rb ADDED
@@ -0,0 +1,72 @@
1
+ require 'ranker/ranking'
2
+ require 'ranker/rankings'
3
+ require 'ranker/strategies'
4
+ require 'ranker/version'
5
+
6
+ ##
7
+ # Ranks are based on: http://en.wikipedia.org/wiki/Ranking
8
+ #
9
+ module Ranker
10
+
11
+ class << self
12
+
13
+ # Properties:
14
+
15
+ def strategies
16
+ @strategies ||= {
17
+ :standard_competition => Ranker::Strategies::StandardCompetition,
18
+ :modified_competition => Ranker::Strategies::ModifiedCompetition,
19
+ :dense => Ranker::Strategies::Dense,
20
+ :ordinal => Ranker::Strategies::Ordinal
21
+ }
22
+ end
23
+
24
+ # Methods:
25
+
26
+ def rank(rankables, *args)
27
+ options = args.pop
28
+ if options && options.kind_of?(Hash)
29
+ options = default_options.merge(options)
30
+ else
31
+ options = default_options
32
+ end
33
+ strategy = get_strategy(rankables, options)
34
+ strategy.rank
35
+ end
36
+
37
+
38
+ protected
39
+
40
+ # Properties:
41
+
42
+ def default_options
43
+ {
44
+ :by => lambda { |rankable| rankable },
45
+ :desc => true,
46
+ :strategy => :standard_competition
47
+ }
48
+ end
49
+
50
+ # Methods:
51
+
52
+ def get_strategy(rankables, options)
53
+ strategy_class = get_strategy_class(options[:strategy])
54
+ strategy_class.new(rankables, options)
55
+ end
56
+
57
+ def get_strategy_class(strategy)
58
+ if strategy.kind_of?(Class)
59
+ strategy
60
+ else
61
+ if strategy = strategies[strategy]
62
+ strategy
63
+ else
64
+ raise ArgumentError.new("Unknown strategy: #{strategy}")
65
+ end
66
+ end
67
+ end
68
+
69
+ end # class methods
70
+
71
+ end
72
+
@@ -0,0 +1,44 @@
1
+ module Ranker
2
+
3
+ class Ranking
4
+
5
+ attr_reader :rankings, :index, :rank, :score, :rankables
6
+
7
+ def initialize(rankings, index, rank, score, rankables)
8
+ @rankings = rankings
9
+ @index = index
10
+ @rank = rank
11
+ @score = score
12
+ @rankables = rankables
13
+ end
14
+
15
+ # Properties:
16
+
17
+ def num_rankables
18
+ rankables.count
19
+ end
20
+
21
+ def percentile
22
+ @percentile ||= (num_scores_at_or_below.to_f / rankings.num_scores) * 100
23
+ end
24
+
25
+ def z_score
26
+ @z_score ||= if rankings.standard_deviation == 0
27
+ 0
28
+ else
29
+ (score - rankings.mean) / rankings.standard_deviation
30
+ end
31
+ end
32
+
33
+ protected
34
+
35
+ def num_scores_at_or_below
36
+ @scores_at_or_below ||= rankings[index..rankings.num_scores].reduce(0) { |sum, ranking|
37
+ sum + ranking.num_rankables
38
+ }
39
+ end
40
+
41
+ end # class
42
+
43
+ end # module
44
+
@@ -0,0 +1,56 @@
1
+ module Ranker
2
+
3
+ class Rankings < Array
4
+
5
+ attr_reader :strategy, :scores
6
+
7
+ def initialize(strategy)
8
+ @strategy = strategy
9
+ @scores = []
10
+ end
11
+
12
+
13
+ # Properties:
14
+
15
+ def mean
16
+ @mean ||= total.to_f / num_scores
17
+ end
18
+
19
+ def standard_deviation
20
+ @standard_deviation ||= if variance.nan?
21
+ # For ruby-1.8.7 compatibility
22
+ variance
23
+ else
24
+ Math.sqrt(variance)
25
+ end
26
+ end
27
+
28
+ def num_scores
29
+ scores.size
30
+ end
31
+
32
+ def variance
33
+ @variance ||= total_difference.to_f / num_scores
34
+ end
35
+
36
+ def total
37
+ @total ||= scores.reduce(:+)
38
+ end
39
+
40
+ def total_difference
41
+ @total_difference ||= scores.reduce(0) { |sum, score|
42
+ sum + (score - mean) ** 2
43
+ }
44
+ end
45
+
46
+
47
+ # Methods:
48
+
49
+ def create(rank, score, rankables)
50
+ scores.concat(Array.new(rankables.count, score))
51
+ self << Ranking.new(self, self.count, rank, score, rankables)
52
+ end
53
+
54
+ end # class
55
+
56
+ end # module
@@ -0,0 +1,9 @@
1
+ require 'ranker/strategies/strategy.rb'
2
+ require 'ranker/strategies/dense.rb'
3
+ require 'ranker/strategies/modified_competition.rb'
4
+ require 'ranker/strategies/standard_competition.rb'
5
+ require 'ranker/strategies/ordinal.rb'
6
+
7
+ module Ranker::Strategies
8
+
9
+ end
@@ -0,0 +1,20 @@
1
+ module Ranker::Strategies
2
+
3
+ ##
4
+ # Ranks rankables according to: http://en.wikipedia.org/wiki/Ranking#Dense_ranking_.28.221223.22_ranking.29
5
+ #
6
+ class Dense < Strategy
7
+
8
+ # Methods:
9
+
10
+ def execute
11
+ scores_unique_sorted.each_with_index { |score, index|
12
+ rank = index + 1
13
+ rankables_for_score = rankables_for_score(score)
14
+ create_ranking(rank, score, rankables_for_score)
15
+ }
16
+ end
17
+
18
+ end # class
19
+
20
+ end # module
@@ -0,0 +1,26 @@
1
+ module Ranker::Strategies
2
+
3
+ ##
4
+ # Ranks rankables according to: http://en.wikipedia.org/wiki/Ranking#Modified_competition_ranking_.28.221334.22_ranking.29
5
+ #
6
+ class ModifiedCompetition < Strategy
7
+
8
+ # Methods:
9
+
10
+ def execute
11
+ rank = 0
12
+ scores_unique_sorted.each_with_index { |score, index|
13
+ rankables_for_score = rankables_for_score(score)
14
+ if rank == 0
15
+ create_ranking(1, score, rankables_for_score)
16
+ rank += rankables_for_score.count
17
+ else
18
+ rank += rankables_for_score.count
19
+ create_ranking(rank, score, rankables_for_score)
20
+ end
21
+ }
22
+ end
23
+
24
+ end # class
25
+
26
+ end # module
@@ -0,0 +1,23 @@
1
+ module Ranker::Strategies
2
+
3
+ ##
4
+ # Ranks rankables according to: http://en.wikipedia.org/wiki/Ranking#Ordinal_ranking_.28.221234.22_ranking.29
5
+ #
6
+ class Ordinal < Strategy
7
+
8
+ # Methods:
9
+
10
+ def execute
11
+ rank = 1
12
+ scores_unique_sorted.each_with_index { |score, index|
13
+ rankables_for_score = rankables_for_score(score)
14
+ rankables_for_score.each { |value|
15
+ create_ranking(rank, score, [value])
16
+ rank += 1
17
+ }
18
+ }
19
+ end
20
+
21
+ end # class
22
+
23
+ end # module
@@ -0,0 +1,21 @@
1
+ module Ranker::Strategies
2
+
3
+ ##
4
+ # Ranks rankables according to: http://en.wikipedia.org/wiki/Ranking#Standard_competition_ranking_.28.221224.22_ranking.29
5
+ #
6
+ class StandardCompetition < Strategy
7
+
8
+ # Methods:
9
+
10
+ def execute
11
+ rank = 1
12
+ scores_unique_sorted.each_with_index { |score, index|
13
+ rankables_for_score = rankables_for_score(score)
14
+ create_ranking(rank, score, rankables_for_score)
15
+ rank += rankables_for_score.count
16
+ }
17
+ end
18
+
19
+ end # class
20
+
21
+ end # module
@@ -0,0 +1,82 @@
1
+ module Ranker::Strategies
2
+
3
+ class Strategy
4
+
5
+ attr_reader :rankables, :options
6
+
7
+ def initialize(rankables, *args)
8
+ @rankables = rankables
9
+ options = args.pop
10
+ if options && options.kind_of?(Hash)
11
+ @options = default_options.merge(options)
12
+ else
13
+ @options = default_options
14
+ end
15
+ end
16
+
17
+
18
+ # Properties:
19
+
20
+ def rankings
21
+ @rankings ||= Ranker::Rankings.new(self)
22
+ end
23
+
24
+
25
+ # Methods:
26
+
27
+
28
+ def rank
29
+ execute
30
+ rankings
31
+ end
32
+
33
+
34
+ protected
35
+
36
+ # Properties:
37
+
38
+ def default_options
39
+ {
40
+ :by => lambda { |rankable| rankable },
41
+ :desc => true
42
+ }
43
+ end
44
+
45
+ def score
46
+ options[:by]
47
+ end
48
+
49
+ def sort_desc?
50
+ options[:desc]
51
+ end
52
+
53
+ def rankables_grouped_by_score
54
+ @rankables_grouped_by_score ||= rankables.group_by(&score)
55
+ end
56
+
57
+ def scores_unique_sorted
58
+ @scores_unique_sorted ||= unless sort_desc?
59
+ rankables_grouped_by_score.keys.sort!
60
+ else
61
+ rankables_grouped_by_score.keys.sort!.reverse!
62
+ end
63
+ end
64
+
65
+
66
+ # Methods:
67
+
68
+ def create_ranking(rank, score, rankables)
69
+ rankings.create(rank, score, rankables)
70
+ end
71
+
72
+ def execute
73
+ raise NotImplementedError.new('You must implement the execute method.')
74
+ end
75
+
76
+ def rankables_for_score(score)
77
+ rankables_grouped_by_score[score]
78
+ end
79
+
80
+ end # class
81
+
82
+ end # module