Ranking methods

From Rosetta Code
Task
Ranking methods
You are encouraged to solve this task according to the task description, using any language you may know.

The numerical rank of competitors in a competition shows if one is better than, equal to, or worse than another based on their results in a competition.

The numerical rank of a competitor can be assigned in several different ways.


Task

The following scores are accrued for all competitors of a competition (in best-first order):

44 Solomon
42 Jason
42 Errol
41 Garry
41 Bernard
41 Barry
39 Stephen

For each of the following ranking methods, create a function/method/procedure/subroutine... that applies the ranking method to an ordered list of scores with scorers:

  1. Standard. (Ties share what would have been their first ordinal number).
  2. Modified. (Ties share what would have been their last ordinal number).
  3. Dense. (Ties share the next available integer).
  4. Ordinal. ((Competitors take the next available integer. Ties are not treated otherwise).
  5. Fractional. (Ties share the mean of what would have been their ordinal numbers).


See the wikipedia article for a fuller description.

Show here, on this page, the ranking of the test scores under each of the numbered ranking methods.

AutoHotkey[edit]

Rank(data, opt:=1){ ; opt = 1 Standard (default), 2 Modified, 3 Dense, 4 Ordinal, 5 Fractional
for index, val in StrSplit(data, "`n", "`r") {
RegExMatch(val, "^(\d+)\s+(.*)", Match)
if !(Match1=prev)
n := index
prev := Match1
Res1 .= n "`t" Match "`n"
Res4 .= index "`t" Match "`n"
Temp .= n ":" index " " Match "`n"
}
n:=0
while pos := RegExMatch(Temp, "`asm)^(\d+).*?\R(?!\1)|.+", Match, pos?pos+StrLen(Match):1) {
n += StrSplit(Trim(Match, "`r`n"), "`n", "`r").MaxIndex()
Res2 .= RegExReplace(Match, "`am)^\d+:\d+", n "`t")
Res3 .= RegExReplace(Match, "`am)^\d+:\d+", A_Index "`t")
R := 0
for index, val in StrSplit(Match, "`n", "`r")
R += StrSplit(val, ":").2
Res5 .= RegExReplace(Match, "`am)^\d+:\d+", RegExReplace(R / StrSplit(Trim(Match, "`r`n"), "`n", "`r").MaxIndex(), "\.?0+$") "`t")
}
return Res%opt%
}
Example:
data =
(
44 Solomon
42 Jason
42 Errol
41 Garry
41 Bernard
41 Barry
39 Stephen
)
 
MsgBox, 262144, ,% ""
. "Standard Ranking:`n" Rank(data)
. "`nModified Ranking:`n" Rank(data, 2)
. "`nDense Ranking:`n" Rank(data, 3)
. "`nOrdinal Ranking:`n" Rank(data, 4)
. "`nFractional Ranking:`n" Rank(data, 5)
return
Output:
Standard Ranking:
1	44 Solomon
2	42 Jason
2	42 Errol
4	41 Garry
4	41 Bernard
4	41 Barry
7	39 Stephen

Modified Ranking:
1	 44 Solomon
3	 42 Jason
3	 42 Errol
6	 41 Garry
6	 41 Bernard
6	 41 Barry
7	 39 Stephen

Dense Ranking:
1	 44 Solomon
2	 42 Jason
2	 42 Errol
3	 41 Garry
3	 41 Bernard
3	 41 Barry
4	 39 Stephen

Ordinal Ranking:
1	44 Solomon
2	42 Jason
3	42 Errol
4	41 Garry
5	41 Bernard
6	41 Barry
7	39 Stephen

Fractional Ranking:
1	 44 Solomon
2.5	 42 Jason
2.5	 42 Errol
5	 41 Garry
5	 41 Bernard
5	 41 Barry
7	 39 Stephen

AWK[edit]

Translation of: Python

This uses separate files for each method of ranking:

##
## Dense ranking in file: ranking_d.awk
##
 
BEGIN{ lastresult = "!"; lastrank = 0 }
 
function d_rank(){
if($1==lastresult){
print lastrank, $0
}else{
lastresult = $1
print ++lastrank, $0 }
}
//{d_rank() }
 
##
## Fractional ranking in file: ranking_f.awk
##
 
BEGIN{
last = "!"
flen = 0 }
 
function f_rank(){
item = $0
if($1!=last){
if(flen){
sum = 0
for(fl=0; fl < flen;){
$0 = fifo[fl++]
sum += $1 }
mean = sum / flen
for(fl=0; fl < flen;){
$0 = fifo[fl++]
$1 = ""
printf("%3g %s\n", mean, $0) }
flen = 0
}}
$0 = item
last = $1
fifo[flen++] = sprintf("%i %s", FNR, item)
}
//{f_rank()}
 
END{ if(flen){
sum = 0
for(fl=0; fl < flen;){
$0 = fifo[fl++]
sum += $1 }
mean = sum / flen
for(fl=0; fl < flen;){
$0 = fifo[fl++]
$1 = ""
printf("%3g %s\n", mean, $0) }}}
 
##
## Modified competition ranking in file: ranking_mc.awk
##
 
BEGIN{
lastresult = "!"
flen = 0 }
 
function mc_rank(){
if($1==lastresult){
fifo[flen++] = $0
}else{
for(fl=0; fl < flen;){
print FNR-1, fifo[fl++]}
flen = 0
fifo[flen++] = $0
lastresult = $1}
}
//{mc_rank()}
 
END{ for(fl=0; fl < flen;){
print FNR, fifo[fl++]} }
 
##
## Ordinal ranking in file: ranking_o.awk
##
 
function o_rank(){ print FNR, $0 }
//{o_rank() }
 
##
## Standard competition ranking in file: ranking_sc.awk
##
 
BEGIN{ lastresult = lastrank = "!" }
 
function sc_rank(){
if($1==lastresult){
print lastrank, $0
}else{
print FNR, $0
lastresult = $1
lastrank = FNR}
}
//{sc_rank()}
 

The input as a file ranking.txt:

44 Solomon
42 Jason
42 Errol
41 Garry
41 Bernard
41 Barry
39 Stephen
Output:
C:\Users\RC\Code>awk -f ranking_sc.awk ranking.txt
1 44 Solomon
2 42 Jason
2 42 Errol
4 41 Garry
4 41 Bernard
4 41 Barry
7 39 Stephen

C:\Users\RC\Code>awk -f ranking_mc.awk ranking.txt
1 44 Solomon
3 42 Jason
3 42 Errol
6 41 Garry
6 41 Bernard
6 41 Barry
7 39 Stephen

C:\Users\RC\Code>awk -f ranking_d.awk ranking.txt
1 44 Solomon
2 42 Jason
2 42 Errol
3 41 Garry
3 41 Bernard
3 41 Barry
4 39 Stephen

C:\Users\RC\Code>awk -f ranking_o.awk ranking.txt
1 44 Solomon
2 42 Jason
3 42 Errol
4 41 Garry
5 41 Bernard
6 41 Barry
7 39 Stephen

C:\Users\RC\Code>awk -f ranking_f.awk ranking.txt
  1  44 Solomon
2.5  42 Jason
2.5  42 Errol
  5  41 Garry
  5  41 Bernard
  5  41 Barry
  7  39 Stephen

C:\Users\RC\Code>

Elixir[edit]

Translation of: Ruby
defmodule Ranking do
def methods(data) do
IO.puts "stand.\tmod.\tdense\tord.\tfract."
Enum.group_by(data, fn {score,_name} -> score end)
|> Enum.map(fn {score,pairs} ->
names = Enum.map(pairs, fn {_,name} -> name end) |> Enum.reverse
{score, names}
end)
|> Enum.sort_by(fn {score,_} -> -score end)
|> Enum.with_index
|> Enum.reduce({1,0,0}, fn {{score, names}, i}, {s_rnk, m_rnk, o_rnk} ->
d_rnk = i + 1
m_rnk = m_rnk + length(names)
f_rnk = ((s_rnk + m_rnk) / 2) |> to_string |> String.replace(".0","")
o_rnk = Enum.reduce(names, o_rnk, fn name,acc ->
IO.puts "#{s_rnk}\t#{m_rnk}\t#{d_rnk}\t#{acc+1}\t#{f_rnk}\t#{score} #{name}"
acc + 1
end)
{s_rnk+length(names), m_rnk, o_rnk}
end)
end
end
 
~w"44 Solomon
42 Jason
42 Errol
41 Garry
41 Bernard
41 Barry
39 Stephen"
|> Enum.chunk(2)
|> Enum.map(fn [score,name] -> {String.to_integer(score),name} end)
|> Ranking.methods
Output:
stand.  mod.    dense   ord.    fract.
1       1       1       1       1       44 Solomon
2       3       2       2       2.5     42 Jason
2       3       2       3       2.5     42 Errol
4       6       3       4       5       41 Garry
4       6       3       5       5       41 Bernard
4       6       3       6       5       41 Barry
7       7       4       7       7       39 Stephen

Go[edit]

package main
 
import (
"fmt"
"sort"
)
 
type rankable interface {
Len() int
RankEqual(int, int) bool
}
 
func StandardRank(d rankable) []float64 {
r := make([]float64, d.Len())
var k int
for i := range r {
if i == 0 || !d.RankEqual(i, i-1) {
k = i + 1
}
r[i] = float64(k)
}
return r
}
 
func ModifiedRank(d rankable) []float64 {
r := make([]float64, d.Len())
for i := range r {
k := i + 1
for j := i + 1; j < len(r) && d.RankEqual(i, j); j++ {
k = j + 1
}
r[i] = float64(k)
}
return r
}
 
func DenseRank(d rankable) []float64 {
r := make([]float64, d.Len())
var k int
for i := range r {
if i == 0 || !d.RankEqual(i, i-1) {
k++
}
r[i] = float64(k)
}
return r
}
 
func OrdinalRank(d rankable) []float64 {
r := make([]float64, d.Len())
for i := range r {
r[i] = float64(i + 1)
}
return r
}
 
func FractionalRank(d rankable) []float64 {
r := make([]float64, d.Len())
for i := 0; i < len(r); {
var j int
f := float64(i + 1)
for j = i + 1; j < len(r) && d.RankEqual(i, j); j++ {
f += float64(j + 1)
}
f /= float64(j - i)
for ; i < j; i++ {
r[i] = f
}
}
return r
}
 
type scores []struct {
score int
name string
}
 
func (s scores) Len() int { return len(s) }
func (s scores) RankEqual(i, j int) bool { return s[i].score == s[j].score }
func (s scores) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s scores) Less(i, j int) bool {
if s[i].score != s[j].score {
return s[i].score > s[j].score
}
return s[i].name < s[j].name
}
 
var data = scores{
{44, "Solomon"},
{42, "Jason"},
{42, "Errol"},
{41, "Garry"},
{41, "Bernard"},
{41, "Barry"},
{39, "Stephen"},
}
 
func main() {
show := func(name string, fn func(rankable) []float64) {
fmt.Println(name, "Ranking:")
r := fn(data)
for i, d := range data {
fmt.Printf("%4v - %2d %s\n", r[i], d.score, d.name)
}
}
 
sort.Sort(data)
show("Standard", StandardRank)
show("\nModified", ModifiedRank)
show("\nDense", DenseRank)
show("\nOrdinal", OrdinalRank)
show("\nFractional", FractionalRank)
}
Output:
Standard Ranking:
   1 - 44 Solomon
   2 - 42 Errol
   2 - 42 Jason
   4 - 41 Barry
   4 - 41 Bernard
   4 - 41 Garry
   7 - 39 Stephen

Modified Ranking:
   1 - 44 Solomon
   3 - 42 Errol
   3 - 42 Jason
   6 - 41 Barry
   6 - 41 Bernard
   6 - 41 Garry
   7 - 39 Stephen

Dense Ranking:
   1 - 44 Solomon
   2 - 42 Errol
   2 - 42 Jason
   3 - 41 Barry
   3 - 41 Bernard
   3 - 41 Garry
   4 - 39 Stephen

Ordinal Ranking:
   1 - 44 Solomon
   2 - 42 Errol
   3 - 42 Jason
   4 - 41 Barry
   5 - 41 Bernard
   6 - 41 Garry
   7 - 39 Stephen

Fractional Ranking:
   1 - 44 Solomon
 2.5 - 42 Errol
 2.5 - 42 Jason
   5 - 41 Barry
   5 - 41 Bernard
   5 - 41 Garry
   7 - 39 Stephen

Haskell[edit]

 
import Data.List (groupBy, sort, intercalate)
 
type Item = (Int, String)
type ItemList = [Item]
type ItemGroups = [ItemList]
type RankItem a = (a, Int, String)
type RankItemList a = [RankItem a]
 
-- make sure the input is ordered and grouped by score
prepare :: ItemList -> ItemGroups
prepare = groupBy gf . reverse . sort
where gf (a, _) (b, _) = a == b
 
-- give an item a rank
rank :: Num a => a -> Item -> RankItem a
rank n (a, b) = (n, a, b)
 
-- ranking methods
standard, modified, dense, ordinal :: ItemGroups -> RankItemList Int
 
standard = ms 1 where
ms _ [] = []
ms n (x:xs) = map (rank n) x ++ ms (n + length x) xs
 
modified = md 1 where
md _ [] = []
md n (x:xs) = let l = length x
nl = n + l
nl1 = nl - 1
in map (rank nl1) x ++ md (n + l) xs
 
dense = md 1 where
md _ [] = []
md n (x:xs) = map (rank n) x ++ md (n + 1) xs
 
ordinal = zipWith rank [1..] . concat
 
fractional :: ItemGroups -> RankItemList Double
fractional = mf 1.0 where
mf _ [] = []
mf n (x:xs) = let l = length x
o = take l [n ..]
ld = fromIntegral l
a = sum o / ld
in map (rank a) x ++ mf (n + ld) xs
 
-- sample data
test :: ItemGroups
test = prepare
[ (44, "Solomon")
, (42, "Jason")
, (42, "Errol")
, (41, "Garry")
, (41, "Bernard")
, (41, "Barry")
, (39, "Stephen") ]
 
-- print rank items nicely
nicePrint :: Show a => String -> RankItemList a -> IO ()
nicePrint xs items = do
putStrLn xs
mapM_ np items
putStr "\n"
where np (a, b, c) = putStrLn $ intercalate "\t" [show a, show b, c]
 
main :: IO ()
main = do
nicePrint "Standard:" $ standard test
nicePrint "Modified:" $ modified test
nicePrint "Dense:" $ dense test
nicePrint "Ordinal:" $ ordinal test
nicePrint "Fractional:" $ fractional test
 

Output:

Standard:
1	44	Solomon
2	42	Jason
2	42	Errol
4	41	Garry
4	41	Bernard
4	41	Barry
7	39	Stephen

Modified:
1	44	Solomon
3	42	Jason
3	42	Errol
6	41	Garry
6	41	Bernard
6	41	Barry
7	39	Stephen

Dense:
1	44	Solomon
2	42	Jason
2	42	Errol
3	41	Garry
3	41	Bernard
3	41	Barry
4	39	Stephen

Ordinal:
1	44	Solomon
2	42	Jason
3	42	Errol
4	41	Garry
5	41	Bernard
6	41	Barry
7	39	Stephen

Fractional:
1.0	44	Solomon
2.5	42	Jason
2.5	42	Errol
5.0	41	Garry
5.0	41	Bernard
5.0	41	Barry
7.0	39	Stephen

J[edit]

Implementation:

competitors=:<;._1;._2]0 :0
44 Solomon
42 Jason
42 Errol
41 Garry
41 Bernard
41 Barry
39 Stephen
)
 
scores=: {."1
 
standard=: 1+i.~
modified=: 1+i:~
dense=: #/.~ # #\@~.
ordinal=: #\
fractional=: #/.~ # ] (+/%#)/. #\
 
rank=:1 :'<"[email protected]@:scores,.]'

Note that we assume that the competitors are already in the right order. Also, of course (as is common when using J) we use the J command line, because that is portable across operating systems (for example: the OS command line is difficult to use on phones).

Task examples:

   standard rank competitors
┌─┬──┬───────┐
144│Solomon│
├─┼──┼───────┤
242│Jason │
├─┼──┼───────┤
242│Errol │
├─┼──┼───────┤
441│Garry │
├─┼──┼───────┤
441│Bernard│
├─┼──┼───────┤
441│Barry │
├─┼──┼───────┤
739│Stephen│
└─┴──┴───────┘
modified rank competitors
┌─┬──┬───────┐
144│Solomon│
├─┼──┼───────┤
342│Jason │
├─┼──┼───────┤
342│Errol │
├─┼──┼───────┤
641│Garry │
├─┼──┼───────┤
641│Bernard│
├─┼──┼───────┤
641│Barry │
├─┼──┼───────┤
739│Stephen│
└─┴──┴───────┘
dense rank competitors
┌─┬──┬───────┐
144│Solomon│
├─┼──┼───────┤
242│Jason │
├─┼──┼───────┤
242│Errol │
├─┼──┼───────┤
341│Garry │
├─┼──┼───────┤
341│Bernard│
├─┼──┼───────┤
341│Barry │
├─┼──┼───────┤
439│Stephen│
└─┴──┴───────┘
ordinal rank competitors
┌─┬──┬───────┐
144│Solomon│
├─┼──┼───────┤
242│Jason │
├─┼──┼───────┤
342│Errol │
├─┼──┼───────┤
441│Garry │
├─┼──┼───────┤
541│Bernard│
├─┼──┼───────┤
641│Barry │
├─┼──┼───────┤
739│Stephen│
└─┴──┴───────┘
fractional rank competitors
┌───┬──┬───────┐
144│Solomon│
├───┼──┼───────┤
2.542│Jason │
├───┼──┼───────┤
2.542│Errol │
├───┼──┼───────┤
541│Garry │
├───┼──┼───────┤
541│Bernard│
├───┼──┼───────┤
541│Barry │
├───┼──┼───────┤
739│Stephen│
└───┴──┴───────┘

Java[edit]

Works with: Java version 8
import java.util.*;
 
public class RankingMethods {
 
final static String[] input = {"44 Solomon", "42 Jason", "42 Errol",
"41 Garry", "41 Bernard", "41 Barry", "39 Stephen"};
 
public static void main(String[] args) {
int len = input.length;
 
Map<String, int[]> map = new TreeMap<>((a, b) -> b.compareTo(a));
for (int i = 0; i < len; i++) {
String key = input[i].split("\\s+")[0];
int[] arr;
if ((arr = map.get(key)) == null)
arr = new int[]{i, 0};
arr[1]++;
map.put(key, arr);
}
int[][] groups = map.values().toArray(new int[map.size()][]);
 
standardRanking(len, groups);
modifiedRanking(len, groups);
denseRanking(len, groups);
ordinalRanking(len);
fractionalRanking(len, groups);
}
 
private static void standardRanking(int len, int[][] groups) {
System.out.println("\nStandard ranking");
for (int i = 0, rank = 0, group = 0; i < len; i++) {
if (group < groups.length && i == groups[group][0]) {
rank = i + 1;
group++;
}
System.out.printf("%d %s%n", rank, input[i]);
}
}
 
private static void modifiedRanking(int len, int[][] groups) {
System.out.println("\nModified ranking");
for (int i = 0, rank = 0, group = 0; i < len; i++) {
if (group < groups.length && i == groups[group][0])
rank += groups[group++][1];
System.out.printf("%d %s%n", rank, input[i]);
}
}
 
private static void denseRanking(int len, int[][] groups) {
System.out.println("\nDense ranking");
for (int i = 0, rank = 0; i < len; i++) {
if (rank < groups.length && i == groups[rank][0])
rank++;
System.out.printf("%d %s%n", rank, input[i]);
}
}
 
private static void ordinalRanking(int len) {
System.out.println("\nOrdinal ranking");
for (int i = 0; i < len; i++)
System.out.printf("%d %s%n", i + 1, input[i]);
}
 
private static void fractionalRanking(int len, int[][] groups) {
System.out.println("\nFractional ranking");
float rank = 0;
for (int i = 0, tmp = 0, group = 0; i < len; i++) {
if (group < groups.length && i == groups[group][0]) {
tmp += groups[group++][1];
rank = (i + 1 + tmp) / 2.0F;
}
System.out.printf("%2.1f %s%n", rank, input[i]);
}
}
}
Standard ranking
1 44 Solomon
2 42 Jason
2 42 Errol
4 41 Garry
4 41 Bernard
4 41 Barry
7 39 Stephen

Modified ranking
1 44 Solomon
3 42 Jason
3 42 Errol
6 41 Garry
6 41 Bernard
6 41 Barry
7 39 Stephen

Dense ranking
1 44 Solomon
2 42 Jason
2 42 Errol
3 41 Garry
3 41 Bernard
3 41 Barry
4 39 Stephen

Ordinal ranking
1 44 Solomon
2 42 Jason
3 42 Errol
4 41 Garry
5 41 Bernard
6 41 Barry
7 39 Stephen

Fractional ranking
1,0 44 Solomon
2,5 42 Jason
2,5 42 Errol
5,0 41 Garry
5,0 41 Bernard
5,0 41 Barry
7,0 39 Stephen


JavaScript[edit]

ES5[edit]

The task formulation doesn't seem to directly explain or determine the order of listing for players whose score is the same.

( This version chooses to use a secondary (alphabetic) sort after the numeric sort by score. That does, of course, affect the ordinal placements for some players)

(function () {
 
var xs = 'Solomon Jason Errol Garry Bernard Barry Stephen'.split(' '),
ns = [44, 42, 42, 41, 41, 41, 39],
 
sorted = xs.map(function (x, i) {
return { name: x, score: ns[i] };
}).sort(function (a, b) {
var c = b.score - a.score;
return c ? c : a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
}),
 
names = sorted.map(function (x) { return x.name; }),
scores = sorted.map(function (x) { return x.score; }),
 
reversed = scores.slice(0).reverse(),
unique = scores.filter(function (x, i) {
return scores.indexOf(x) === i;
});
 
// RANKINGS AS FUNCTIONS OF SCORES: SORTED, REVERSED AND UNIQUE
 
var rankings = function (score, index) {
return {
name: names[index],
score: score,
 
Ordinal: index + 1,
 
Standard: function (n) {
return scores.indexOf(n) + 1;
}(score),
 
Modified: function (n) {
return reversed.length - reversed.indexOf(n);
}(score),
 
Dense: function (n) {
return unique.indexOf(n) + 1;
}(score),
 
Fractional: function (n) {
return (
(scores.indexOf(n) + 1) +
(reversed.length - reversed.indexOf(n))
) / 2;
}(score)
};
},
 
tbl = [
'Name Score Standard Modified Dense Ordinal Fractional'.split(' ')
].concat(scores.map(rankings).reduce(function (a, x) {
return a.concat([
[x.name, x.score,
x.Standard, x.Modified, x.Dense, x.Ordinal, x.Fractional
]
]);
}, [])),
 
//[[a]] -> bool -> s -> s
wikiTable = function (lstRows, blnHeaderRow, strStyle) {
return '{| class="wikitable" ' + (
strStyle ? 'style="' + strStyle + '"' : ''
) + lstRows.map(function (lstRow, iRow) {
var strDelim = ((blnHeaderRow && !iRow) ? '!' : '|');
 
return '\n|-\n' + strDelim + ' ' + lstRow.map(function (v) {
return typeof v === 'undefined' ? ' ' : v;
}).join(' ' + strDelim + strDelim + ' ');
}).join('') + '\n|}';
};
 
return wikiTable(tbl, true, 'text-align:center');
 
})();
Output:
Name Score Standard Modified Dense Ordinal Fractional
Solomon 44 1 1 1 1 1
Errol 42 2 3 2 2 2.5
Jason 42 2 3 2 3 2.5
Barry 41 4 6 3 4 5
Bernard 41 4 6 3 5 5
Garry 41 4 6 3 6 5
Stephen 39 7 7 4 7 7

ES6[edit]

((() => {
const xs = 'Solomon Jason Errol Garry Bernard Barry Stephen'.split(' '),
ns = [44, 42, 42, 41, 41, 41, 39];
 
const sorted = xs.map((x, i) => ({
name: x,
score: ns[i]
}))
.sort((a, b) => {
const c = b.score - a.score;
return c ? c : a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
});
 
const names = sorted.map(x => x.name),
scores = sorted.map(x => x.score),
reversed = scores.slice(0)
.reverse(),
unique = scores.filter((x, i) => scores.indexOf(x) === i);
 
// RANKINGS AS FUNCTIONS OF SCORES: SORTED, REVERSED AND UNIQUE
 
// rankings :: Int -> Int -> Dictonary
const rankings = (score, index) => ({
name: names[index],
score,
Ordinal: index + 1,
Standard: scores.indexOf(score) + 1,
Modified: reversed.length - reversed.indexOf(score),
Dense: unique.indexOf(score) + 1,
 
Fractional: (n => (
(scores.indexOf(n) + 1) +
(reversed.length - reversed.indexOf(n))
) / 2)(score)
});
 
// tbl :: [[[a]]]
const tbl = [
'Name Score Standard Modified Dense Ordinal Fractional'.split(' ')
].concat(scores.map(rankings)
.reduce((a, x) => a.concat([
[x.name, x.score,
x.Standard, x.Modified, x.Dense, x.Ordinal, x.Fractional
]
]), []));
 
// wikiTable :: [[[a]]] -> Bool -> String -> String
const wikiTable = (lstRows, blnHeaderRow, strStyle) =>
`{| class="wikitable" ${strStyle ? 'style="' + strStyle + '"' : ''}
${lstRows.map((lstRow, iRow) => {
const strDelim = ((blnHeaderRow && !iRow) ? '!' : '|');
 
return '\n|-\n' + strDelim + ' ' + lstRow
.map(v => typeof v === 'undefined' ? ' ' : v)
.join(' ' + strDelim + strDelim + ' ');
}).join('')}\n|}`;
 
return wikiTable(tbl, true, 'text-align:center');
}))();
Name Score Standard Modified Dense Ordinal Fractional
Solomon 44 1 1 1 1 1
Errol 42 2 3 2 2 2.5
Jason 42 2 3 2 3 2.5
Barry 41 4 6 3 4 5
Bernard 41 4 6 3 5 5
Garry 41 4 6 3 6 5
Stephen 39 7 7 4 7 7
Output:

jq[edit]

We assume the list of players and their scores has already been sorted, and that the list takes the form:

[ player1, score1, player2, score2, ...]

For the sake of brevity, only the ranks are printed.

 
# Ties share what would have been their first ordinal number
def standard_ranking:
. as $raw
| ([range(1;length;2) | $raw[.]]) as $scores
| reduce range(1; $scores|length) as $i
([1]; if $scores[$i - 1] == $scores[$i] then . + [.[-1]]
else . + [$i + 1]
end) ;
 
def modified_ranking:
# The helper function resolves [ranks, tentative]
# by appending the ranks of the ties to "ranks"
def resolve:
(.[1] | length) as $length
| if $length == 0 then .[0]
else .[1][-1] as $max
| .[0] + ( .[1] | map( $max) )
end ;
. as $raw
| ([range(1;length;2) | $raw[.]]) as $scores
| reduce range(1; $scores|length) as $i
# state: [ranks, tentative]
([ [], [1] ];
if $scores[$i - 1] == $scores[$i] then [.[0], .[1] + [ $i + 1 ]]
else [ resolve, [ $i + 1 ] ]
end )
| resolve ;
 
def dense_ranking: # next available
. as $raw
| ([range(1;length;2) | $raw[.]]) as $scores
| reduce range(1; $scores|length) as $i
([1]; if $scores[$i - 1] == $scores[$i] then . + [.[-1]]
else . + [ .[-1] + 1]
end );
 
def ordinal_ranking: # unfair to some!
[ range(1; 1 + length/2) ] ;
 
def fractional_ranking:
# The helper function resolves [ranks, tentative]
# by appending the averages of the tentative ranks to "ranks"
def resolve:
(.[1] | length) as $length
| if $length == 0 then .[0]
else (.[1] | add / $length) as $avg
| .[0] + ( .[1] | map( $avg) )
end ;
. as $raw
| ([range(1;length;2) | $raw[.]]) as $scores
| reduce range(1; $scores|length) as $i
# state: [ranks, tentative]
([ [], [1] ];
if $scores[$i - 1] == $scores[$i] then [.[0], .[1] + [ $i + 1 ]]
else [ resolve, [ $i + 1 ] ]
end )
| resolve ;
Task
def raw:
[
"Solomon", 44,
"Jason" , 42,
"Errol" , 42,
"Garry" , 41,
"Bernard", 41,
"Barry" , 41,
"Stephen", 39
] ;
 
def task:
"standard: \( raw | standard_ranking)",
"modified: \( raw | modified_ranking)",
"dense: \( raw | dense_ranking)",
"ordinal: \( raw | ordinal_ranking)",
"fractional: \( raw | fractional_ranking)" ;
 
task
 
Output:
standard:   [1,2,2,4,4,4,7]
modified:   [1,3,3,6,6,6,7]
dense:      [1,2,2,3,3,3,4]
ordinal:    [1,2,3,4,5,6,7]
fractional: [1,2.5,2.5,5,5,5,7]

Julia[edit]

ties, a helper function used by some of the ranking methods. It lists any duplicated scores.

 
function ties{T<:Real}(a::Array{T,1})
unique(a[2:end][a[2:end] .== a[1:end-1]])
end
 

ties assumes that the there are at least 2 scores in the list to be checked, and the calling functions are designed to avoid calls to it in this case.

Standard Ranking Function

 
function rankstandard{T<:Real}(a::Array{T,1})
r = collect(1:length(a))
1 < r[end] || return r
for i in ties(a)
r[a.==i] = r[a.==i][1]
end
return r
end
 

Modified Ranking Function

 
function rankmodified{T<:Real}(a::Array{T,1})
indexin(a, a)
end
 

Dense Ranking Function

 
function rankdense{T<:Real}(a::Array{T,1})
indexin(a, unique(a))
end
 

Ordinal Ranking Function

 
function rankordinal{T<:Real}(a::Array{T,1})
collect(1:length(a))
end
 

For ordinal ranking, there are a variety of ways of handling tied scores. I've taken the easy way out and assumed that the position in the list already reflects any tie-breaking policy. In this case, there is not much that needs to be done.

Fractional Ranking Function

 
function rankfractional{T<:Real}(a::Array{T,1})
r = float64(collect(1:length(a)))
1.0 < r[end] || return r
for i in ties(a)
r[a.==i] = mean(r[a.==i])
end
return r
end
 

Main

 
scores = [44, 42, 42, 41, 41, 41, 39]
names = ["Solomon", "Jason", "Errol", "Garry",
"Bernard", "Barry", "Stephen"]
 
srank = rankstandard(scores)
mrank = rankmodified(scores)
drank = rankdense(scores)
orank = rankordinal(scores)
frank = rankfractional(scores)
 
println(" Name Score Std Mod Den Ord Frac")
for i in 1:length(scores)
print(@sprintf("  %-7s", names[i]))
print(@sprintf("%5d ", scores[i]))
print(@sprintf("%5d", srank[i]))
print(@sprintf("%5d", mrank[i]))
print(@sprintf("%5d", drank[i]))
print(@sprintf("%5d", orank[i]))
print(@sprintf("%7.2f", frank[i]))
println()
end
 
Output:
    Name    Score  Std  Mod  Den  Ord  Frac
   Solomon   44     1    1    1    1   1.00
   Jason     42     2    3    2    2   2.50
   Errol     42     2    3    2    3   2.50
   Garry     41     4    6    3    4   5.00
   Bernard   41     4    6    3    5   5.00
   Barry     41     4    6    3    6   5.00
   Stephen   39     7    7    4    7   7.00

Kotlin[edit]

// version 1.0.6
 
/* all ranking functions assume the array of Pairs is non-empty and already sorted by decreasing order of scores
and then, if the scores are equal, by reverse alphabetic order of names
*/

 
fun standardRanking(scores: Array<Pair<Int, String>>): IntArray {
val rankings = IntArray(scores.size)
rankings[0] = 1
for (i in 1 until scores.size) rankings[i] = if (scores[i].first == scores[i - 1].first) rankings[i - 1] else i + 1
return rankings
}
 
fun modifiedRanking(scores: Array<Pair<Int, String>>): IntArray {
val rankings = IntArray(scores.size)
rankings[0] = 1
for (i in 1 until scores.size) {
rankings[i] = i + 1
val currScore = scores[i].first
for (j in i - 1 downTo 0) {
if (currScore != scores[j].first) break
rankings[j] = i + 1
}
}
return rankings
}
 
fun denseRanking(scores: Array<Pair<Int, String>>): IntArray {
val rankings = IntArray(scores.size)
rankings[0] = 1
var prevRanking = 1
for (i in 1 until scores.size) rankings[i] = if (scores[i].first == scores[i - 1].first) prevRanking else ++prevRanking
return rankings
}
 
fun ordinalRanking(scores: Array<Pair<Int, String>>) = IntArray(scores.size) { it + 1 }
 
fun fractionalRanking(scores: Array<Pair<Int, String>>): DoubleArray {
val rankings = DoubleArray(scores.size)
rankings[0] = 1.0
for (i in 1 until scores.size) {
var k = i
val currScore = scores[i].first
for (j in i - 1 downTo 0) {
if (currScore != scores[j].first) break
k = j
}
val avg = (k..i).average() + 1.0
for (m in k..i) rankings[m] = avg
}
return rankings
}
 
fun printRankings(title: String, rankings: IntArray, scores: Array<Pair<Int, String>>) {
println(title + ":")
for (i in 0 until rankings.size) {
print ("${rankings[i]} ")
println(scores[i].toString().removeSurrounding("(", ")").replace(",", ""))
}
println()
}
 
fun printFractionalRankings(title: String, rankings: DoubleArray, scores: Array<Pair<Int, String>>) {
println(title + ":")
for (i in 0 until rankings.size) {
print ("${"%3.2f".format(rankings[i])} ")
println(scores[i].toString().removeSurrounding("(", ")").replace(",", ""))
}
println()
}
 
fun main(args: Array<String>) {
val scores = arrayOf(44 to "Solomon", 42 to "Jason", 42 to "Errol", 41 to "Garry",
41 to "Bernard", 41 to "Barry", 39 to "Stephen")
printRankings("Standard ranking", standardRanking(scores), scores)
printRankings("Modified ranking", modifiedRanking(scores), scores)
printRankings("Dense ranking", denseRanking(scores), scores)
printRankings("Ordinal ranking", ordinalRanking(scores), scores)
printFractionalRankings("Fractional ranking", fractionalRanking(scores), scores)
}
Output:
Standard ranking:
1  44 Solomon
2  42 Jason
2  42 Errol
4  41 Garry
4  41 Bernard
4  41 Barry
7  39 Stephen

Modified ranking:
1  44 Solomon
3  42 Jason
3  42 Errol
6  41 Garry
6  41 Bernard
6  41 Barry
7  39 Stephen

Dense ranking:
1  44 Solomon
2  42 Jason
2  42 Errol
3  41 Garry
3  41 Bernard
3  41 Barry
4  39 Stephen

Ordinal ranking:
1  44 Solomon
2  42 Jason
3  42 Errol
4  41 Garry
5  41 Bernard
6  41 Barry
7  39 Stephen

Fractional ranking:
1.00  44 Solomon
2.50  42 Jason
2.50  42 Errol
5.00  41 Garry
5.00  41 Bernard
5.00  41 Barry
7.00  39 Stephen

Mathematica[edit]

 
data = Transpose@{{44, 42, 42, 41, 41, 41, 39}, {"Solomon", "Jason",
"Errol", "Garry", "Bernard", "Barry", "Stephen"}};
 
rank[data_, type_] :=
Module[{t = Transpose@[email protected], Range[[email protected], 1, -1]}},
Switch[type,
"standard", data/.Rule@@@[email protected][t, First],
"modified", data/.Rule@@@[email protected][t, First],
"dense", data/.Thread[#->Range[Length@#]]&@SplitBy[t, First][[All, 1, 1]],
"ordinal", [email protected][data],
"fractional", data/.Rule@@@(Mean[#]/.{a_Rational:>N[a]}&)/@ SplitBy[t, First]]]
 
fmtRankedData[data_, type_] :=
Labeled[Grid[
SortBy[ArrayFlatten@{{Transpose@{rank[data[[All, 1]], type]},
data}}, First], Alignment->Left], type<>" ranking:", Top]
 
Grid@{fmtRankedData[data, #] & /@ {"standard", "modified", "dense",
"ordinal", "fractional"}}
 
 
Output:
standard ranking:
1	44	Solomon
3	42	Errol
3	42	Jason
6	41	Barry
6	41	Bernard
6	41	Garry
7	39	Stephen

modified ranking:
1	44	Solomon
2	42	Errol
2	42	Jason
4	41	Barry
4	41	Bernard
4	41	Garry
7	39	Stephen

dense ranking:
1	39	Stephen
2	41	Barry
2	41	Bernard
2	41	Garry
3	42	Errol
3	42	Jason
4	44	Solomon

ordinal ranking:
1	44	Solomon
2	42	Errol
3	42	Jason
4	41	Barry
5	41	Bernard
6	41	Garry
7	39	Stephen

fractional ranking:
1	44	Solomon
2.5	42	Errol
2.5	42	Jason
5	41	Barry
5	41	Bernard
5	41	Garry
7	39	Stephen

PARI/GP[edit]

Replace "2" with "2.0" in fractional if you prefer decimal to fractional.

standard(v)=v=vecsort(v,1,4); my(last=v[1][1]+1); for(i=1,#v, v[i][1]=if(v[i][1]<last,last=v[i][1]; i, v[i-1][1])); v;
modified(v)=v=vecsort(v,1,4); my(last=v[#v][1]-1); forstep(i=#v,1,-1, v[i][1]=if(v[i][1]>last,last=v[i][1]; i, v[i+1][1])); v;
dense(v)=v=vecsort(v,1,4); my(last=v[1][1]+1,rank); for(i=1,#v, v[i][1]=if(v[i][1]<last,last=v[i][1]; rank++, rank)); v;
ordinal(v)=v=vecsort(v,1,4); for(i=1,#v,v[i][1]=i); v;
fractional(v)=my(a=standard(v),b=modified(v)); vector(#v,i,[(a[i][1]+b[i][1])/2,v[i][2]]);
 
v=[[44,"Solomon"], [42,"Jason"], [42,"Errol"], [41,"Garry"], [41,"Bernard"], [41,"Barry"], [39,"Stephen"]];
standard(v)
modified(v)
dense(v)
ordinal(v)
fractional(v)
Output:
%1 = [[1, "Solomon"], [2, "Errol"], [2, "Jason"], [4, "Barry"], [4, "Bernard"], [4, "Garry"], [7, "Stephen"]]
%2 = [[1, "Solomon"], [3, "Errol"], [3, "Jason"], [6, "Barry"], [6, "Bernard"], [6, "Garry"], [7, "Stephen"]]
%3 = [[1, "Solomon"], [2, "Errol"], [2, "Jason"], [3, "Barry"], [3, "Bernard"], [3, "Garry"], [4, "Stephen"]]
%4 = [[1, "Solomon"], [2, "Errol"], [3, "Jason"], [4, "Barry"], [5, "Bernard"], [6, "Garry"], [7, "Stephen"]]
%5 = [[1, "Solomon"], [5/2, "Jason"], [5/2, "Errol"], [5, "Garry"], [5, "Bernard"], [5, "Barry"], [7, "Stephen"]]

Perl 6[edit]

my @scores =
Solomon => 44,
Jason => 42,
Errol => 42,
Garry => 41,
Bernard => 41,
Barry => 41,
Stephen => 39;
 
sub tiers (@s) { @s.classify(*.value).pairs.sort.reverse.map: { [.value».key] } }
 
sub standard (@s) {
my $rank = 1;
gather for tiers @s -> @players {
take $rank => @players;
$rank += @players;
}
}
 
sub modified (@s) {
my $rank = 0;
gather for tiers @s -> @players {
$rank += @players;
take $rank => @players;
}
}
 
sub dense (@s) { tiers(@s).map: { ++$_ => @^players } }
 
sub ordinal (@s) { @s.map: ++$_ => *.key }
 
sub fractional (@s) {
my $rank = 1;
gather for tiers @s -> @players {
my $beg = $rank;
my $end = $rank += @players;
take [+]($beg ..^ $end) / @players => @players;
}
}
 
say "Standard:"; .say for standard @scores;
say "\nModified:"; .say for modified @scores;
say "\nDense:"; .say for dense @scores;
say "\nOrdinal:"; .say for ordinal @scores;
say "\nFractional:"; .say for fractional @scores;
Output:
Standard:
1 => ["Solomon"]
2 => ["Jason", "Errol"]
4 => ["Garry", "Bernard", "Barry"]
7 => ["Stephen"]

Modified:
1 => ["Solomon"]
3 => ["Jason", "Errol"]
6 => ["Garry", "Bernard", "Barry"]
7 => ["Stephen"]

Dense:
1 => ["Solomon"]
2 => ["Jason", "Errol"]
3 => ["Garry", "Bernard", "Barry"]
4 => ["Stephen"]

Ordinal:
1 => "Solomon"
2 => "Jason"
3 => "Errol"
4 => "Garry"
5 => "Bernard"
6 => "Barry"
7 => "Stephen"

Fractional:
1.0 => ["Solomon"]
2.5 => ["Jason", "Errol"]
5.0 => ["Garry", "Bernard", "Barry"]
7.0 => ["Stephen"]

PowerShell[edit]

 
function Get-Ranking
{
[CmdletBinding(DefaultParameterSetName="Standard")]
[OutputType([PSCustomObject])]
Param
(
[Parameter(Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0)]
[string]
$InputObject,
 
[Parameter(Mandatory=$false,
ParameterSetName="Standard")]
[switch]
$Standard,
 
[Parameter(Mandatory=$false,
ParameterSetName="Modified")]
[switch]
$Modified,
 
[Parameter(Mandatory=$false,
ParameterSetName="Dense")]
[switch]
$Dense,
 
[Parameter(Mandatory=$false,
ParameterSetName="Ordinal")]
[switch]
$Ordinal,
 
[Parameter(Mandatory=$false,
ParameterSetName="Fractional")]
[switch]
$Fractional
)
 
Begin
{
function Get-OrdinalRank ([PSCustomObject[]]$Values)
{
for ($i = 0; $i -lt $Values.Count; $i++)
{
$Values[$i].Rank = $i + 1
}
 
$Values
}
 
function Get-Rank ([PSCustomObject[]]$Scores)
{
foreach ($score in $Scores)
{
$score.Group | ForEach-Object {$_.Rank = $score.Rank}
}
 
$Scores.Group
}
 
function New-Competitor ([string]$Name, [int]$Score, [int]$Rank = 0)
{
[PSCustomObject]@{
Name = $Name
Score = $Score
Rank = $Rank
}
}
 
$competitors = @()
$scores = @()
}
Process
{
@($input) | ForEach-Object {$competitors += New-Competitor -Name $_.Split()[1] -Score $_.Split()[0]}
}
End
{
$scores = $competitors |
Sort-Object -Property Score -Descending |
Group-Object -Property Score |
Select-Object -Property @{Name="Score"; Expression={[int]$_.Name}}, @{Name="Rank"; Expression={0}}, Count, Group
 
switch ($PSCmdlet.ParameterSetName)
{
"Standard"
{
$rank = 1
 
for ($i = 0; $i -lt $scores.Count; $i++)
{
$scores[$i].Rank = $rank
$rank += $scores[$i].Count
}
 
Get-Rank $scores
}
"Modified"
{
$rank = 0
 
foreach ($score in $scores)
{
$rank = $score.Count + $rank
$score.Rank = $rank
}
 
Get-Rank $scores
}
"Dense"
{
for ($i = 0; $i -lt $scores.Count; $i++)
{
$scores[$i].Rank = $i + 1
}
 
Get-Rank $scores
}
"Ordinal"
{
Get-OrdinalRank $competitors
}
"Fractional"
{
Get-OrdinalRank $competitors | Group-Object -Property Score | ForEach-Object {
if ($_.Count -gt 1)
{
$rank = ($_.Group.Rank | Measure-Object -Average).Average
 
foreach ($competitor in $_.Group)
{
$competitor.Rank = $rank
}
}
}
 
$competitors
}
}
}
}
 
 
$scores = "44 Solomon","42 Jason","42 Errol","41 Garry","41 Bernard","41 Barry","39 Stephen"
 
 
$scores | Get-Ranking -Standard
 
Output:
Name    Score Rank
----    ----- ----
Solomon    44    1
Jason      42    2
Errol      42    2
Garry      41    4
Bernard    41    4
Barry      41    4
Stephen    39    7
 
$scores | Get-Ranking -Modified
 
Output:
Name    Score Rank
----    ----- ----
Solomon    44    1
Jason      42    3
Errol      42    3
Garry      41    6
Bernard    41    6
Barry      41    6
Stephen    39    7
 
$scores | Get-Ranking -Dense
 
Output:
Name    Score Rank
----    ----- ----
Solomon    44    1
Jason      42    2
Errol      42    2
Garry      41    3
Bernard    41    3
Barry      41    3
Stephen    39    4
 
$scores | Get-Ranking -Ordinal
 
Output:
Name    Score Rank
----    ----- ----
Solomon    44    1
Jason      42    2
Errol      42    3
Garry      41    4
Bernard    41    5
Barry      41    6
Stephen    39    7
 
$scores | Get-Ranking -Fractional
 
Output:
Name    Score Rank
----    ----- ----
Solomon    44    1
Jason      42  2.5
Errol      42  2.5
Garry      41    5
Bernard    41    5
Barry      41    5
Stephen    39    7


Python[edit]

def mc_rank(iterable, start=1):
"""Modified competition ranking"""
lastresult, fifo = None, []
for n, item in enumerate(iterable, start-1):
if item[0] == lastresult:
fifo += [item]
else:
while fifo:
yield n, fifo.pop(0)
lastresult, fifo = item[0], fifo + [item]
while fifo:
yield n+1, fifo.pop(0)
 
 
def sc_rank(iterable, start=1):
"""Standard competition ranking"""
lastresult, lastrank = None, None
for n, item in enumerate(iterable, start):
if item[0] == lastresult:
yield lastrank, item
else:
yield n, item
lastresult, lastrank = item[0], n
 
 
def d_rank(iterable, start=1):
"""Dense ranking"""
lastresult, lastrank = None, start - 1,
for item in iterable:
if item[0] == lastresult:
yield lastrank, item
else:
lastresult, lastrank = item[0], lastrank + 1
yield lastrank, item
 
 
def o_rank(iterable, start=1):
"""Ordinal ranking"""
yield from enumerate(iterable, start)
 
 
def f_rank(iterable, start=1):
"""Fractional ranking"""
last, fifo = None, []
for n, item in enumerate(iterable, start):
if item[0] != last:
if fifo:
mean = sum(f[0] for f in fifo) / len(fifo)
while fifo:
yield mean, fifo.pop(0)[1]
last = item[0]
fifo.append((n, item))
if fifo:
mean = sum(f[0] for f in fifo) / len(fifo)
while fifo:
yield mean, fifo.pop(0)[1]
 
 
if __name__ == '__main__':
scores = [(44, 'Solomon'),
(42, 'Jason'),
(42, 'Errol'),
(41, 'Garry'),
(41, 'Bernard'),
(41, 'Barry'),
(39, 'Stephen')]
 
print('\nScores to be ranked (best first):')
for s in scores:
print('  %2i %s' % (s ))
for ranker in [sc_rank, mc_rank, d_rank, o_rank, f_rank]:
print('\n%s:' % ranker.__doc__)
for rank, score in ranker(scores):
print('  %3g, %r' % (rank, score))
Output:
Scores to be ranked (best first):
        44 Solomon
        42 Jason
        42 Errol
        41 Garry
        41 Bernard
        41 Barry
        39 Stephen

Standard competition ranking:
    1, (44, 'Solomon')
    2, (42, 'Jason')
    2, (42, 'Errol')
    4, (41, 'Garry')
    4, (41, 'Bernard')
    4, (41, 'Barry')
    7, (39, 'Stephen')

Modified competition ranking:
    1, (44, 'Solomon')
    3, (42, 'Jason')
    3, (42, 'Errol')
    6, (41, 'Garry')
    6, (41, 'Bernard')
    6, (41, 'Barry')
    7, (39, 'Stephen')

Dense ranking:
    1, (44, 'Solomon')
    2, (42, 'Jason')
    2, (42, 'Errol')
    3, (41, 'Garry')
    3, (41, 'Bernard')
    3, (41, 'Barry')
    4, (39, 'Stephen')

Ordinal  ranking:
    1, (44, 'Solomon')
    2, (42, 'Jason')
    3, (42, 'Errol')
    4, (41, 'Garry')
    5, (41, 'Bernard')
    6, (41, 'Barry')
    7, (39, 'Stephen')

Fractional ranking:
    1, (44, 'Solomon')
  2.5, (42, 'Jason')
  2.5, (42, 'Errol')
    5, (41, 'Garry')
    5, (41, 'Bernard')
    5, (41, 'Barry')
    7, (39, 'Stephen')

Racket[edit]

#lang racket
;; Tim-brown 2014-09-11
 
;; produces a ranking according to ranking function: rfn
;; INPUT:
;; lst : (list (score . details))
;; rfn : (length-scores)
;; -> (values
;; ranks-added  ; how many ranks to add for the next iteration
;; (idx . rest -> rank-offset)) ; function that produces the rank for the idx^th element
;;  ; in the scoring (arity must be >= 1)
(define (rank-list all-scores rfn)
(let loop ((rank-0 0) (lst (sort all-scores > #:key car)) (acc empty))
(cond
[(null? lst) acc]
[else
(define 1st-score (caar lst))
(define (ties-with-1st? cand) (= (car cand) 1st-score))
(define-values (tied unranked) (splitf-at lst ties-with-1st?))
 ;; all ranking functions should properly handle a singleton tied list
(define tied-len (length tied))
(define tied? (> tied-len 1))
(define-values (ranks-added rank-offset-fn) (rfn tied-len))
(define ranked-tied (for/list ((t (in-list tied)) (i (in-naturals 1)))
(list* tied? (+ rank-0 (rank-offset-fn i)) t)))
(loop (+ ranks-added rank-0) unranked (append acc ranked-tied))])))
 
;; Ties share what would have been their first ordinal number
(define (rank-function:Standard l)
(values l (thunk* 1)))
 
;; Ties share what would have been their last ordinal number
(define (rank-function:Modified l)
(values l (thunk* l)))
 
;; Ties share the next available integer
(define (rank-function:Dense l)
(values 1 (thunk* 1)))
 
;; Competitors take the next available integer. Ties are not treated otherwise
(define (rank-function:Ordinal l)
(values l (λ (n . _) n)))
 
;; Ties share the mean of what would have been their ordinal numbers
(define (rank-function:Fractional l)
(values l (thunk* (/ (+ l 1) 2))))
 
(define score-board
'((44 . Solomon)
(42 . Jason)
(42 . Errol)
(41 . Garry)
(41 . Bernard)
(41 . Barry)
(39 . Stephen)))
 
(define format-number
(match-lambda
[(? integer? i) i]
[(and f (app numerator n) (app denominator d))
(define-values (q r) (quotient/remainder n d))
(format "~a ~a/~a" q r d)]))
 
(for ((fn (list
rank-function:Standard
rank-function:Modified
rank-function:Dense
rank-function:Ordinal
rank-function:Fractional)))
(printf "Function: ~s~%" fn)
(for ((r (in-list (rank-list score-board fn))))
(printf "~a ~a\t~a\t~a~%"
(if (car r) "=" " ")
(format-number (cadr r))
(caddr r)
(cdddr r)))
(newline))
Output:
Function: #<procedure:rank-function:Standard>
  1	44	Solomon
= 2	42	Jason
= 2	42	Errol
= 4	41	Garry
= 4	41	Bernard
= 4	41	Barry
  7	39	Stephen

Function: #<procedure:rank-function:Modified>
  1	44	Solomon
= 3	42	Jason
= 3	42	Errol
= 6	41	Garry
= 6	41	Bernard
= 6	41	Barry
  7	39	Stephen

Function: #<procedure:rank-function:Dense>
  1	44	Solomon
= 2	42	Jason
= 2	42	Errol
= 3	41	Garry
= 3	41	Bernard
= 3	41	Barry
  4	39	Stephen

Function: #<procedure:rank-function:Ordinal>
  1	44	Solomon
= 2	42	Jason
= 3	42	Errol
= 4	41	Garry
= 5	41	Bernard
= 6	41	Barry
  7	39	Stephen

Function: #<procedure:rank-function:Fractional>
  1	44	Solomon
= 2 1/2	42	Jason
= 2 1/2	42	Errol
= 5	41	Garry
= 5	41	Bernard
= 5	41	Barry
  7	39	Stephen

REXX[edit]

/**************************
44 Solomon 1 1 1 1 1
42 Jason 2 3 2 2 2.5
42 Errol 2 3 2 3 2.5
41 Garry 4 6 3 4 5
41 Bernard 4 6 3 5 5
41 Barry 4 6 3 6 5
39 Stephen 7 7 4 7 7
**************************/

Do i=1 To 7
Parse Value sourceline(i+1) With rank.i name.i .
/* say rank.i name.i */
End
pool=0
crank=0
Do i=1 To 7
If rank.i<>crank Then Do
pool=pool+1
lo.pool=i
hi.pool=i
n.pool=1
ii.pool=i
End
Else Do
n.pool=n.pool+1
hi.pool=i
ii.pool=ii.pool+i
End
crank=rank.i
pool.i=pool
End
/*
Do j=1 To pool
Say 'pool' j n.j lo.j hi.j
End
*/

cp=0
r=0
cnt.=0
Do i=1 To 7
p=pool.i
If p<>cp Then
r=r+1
res=rank.i left(name.i,8) lo.p hi.p r i ii.p/n.p
If res=sourceline(i+1) Then cnt.ok=cnt.ok+1
Say res
cp=p
End
Say cnt.ok 'correct lines'
Output:
44 Solomon  1 1 1 1 1
42 Jason    2 3 2 2 2.5
42 Errol    2 3 2 3 2.5
41 Garry    4 6 3 4 5
41 Bernard  4 6 3 5 5
41 Barry    4 6 3 6 5
39 Stephen  7 7 4 7 7
7 correct lines

Ruby[edit]

ar = "44 Solomon
42 Jason
42 Errol
41 Garry
41 Bernard
41 Barry
39 Stephen"
.lines.map{|line| line.split}
grouped = ar.group_by{|pair| pair.shift.to_i}
s_rnk = 1
m_rnk = o_rnk = 0
puts "stand.\tmod.\tdense\tord.\tfract."
 
grouped.each.with_index(1) do |(score, names), d_rnk|
m_rnk += names.flatten!.size
f_rnk = (s_rnk + m_rnk)/2.0
names.each do |name|
o_rnk += 1
puts "#{s_rnk}\t#{m_rnk}\t#{d_rnk}\t#{o_rnk}\t#{f_rnk.to_s.sub(".0","")}\t#{score} #{name}"
end
s_rnk += names.size
end
Output:
stand.	mod.	dense	ord.	fract.
1	1	1	1	1	44 Solomon
2	3	2	2	2.5	42 Jason
2	3	2	3	2.5	42 Errol
4	6	3	4	5	41 Garry
4	6	3	5	5	41 Bernard
4	6	3	6	5	41 Barry
7	7	4	7	7	39 Stephen

Scala[edit]

This example uses a type-safe singly-linked object model with no mutable state variables, which makes it longer than the Ruby version above, but demonstrates an object-oriented functional programming approach.

object RankingMethods extends App {
case class Score(score: Int, name: String) // incoming data
case class Rank[Precision](rank: Precision, names: List[String]) // outgoing results (can be int or double)
case class State[Precision](n: Int, done: List[Rank[Precision]]) { // internal state, no mutable variables
def next(n: Int, next: Rank[Precision]) = State(n, next :: done)
}
def grouped[Precision](list: List[Score]) = // group names together by score, with highest first
(scala.collection.immutable.TreeMap[Int, List[Score]]() ++ list.groupBy(-_.score))
.values.map(_.map(_.name)).foldLeft(State[Precision](1, Nil)) _
 
// Ranking methods:
 
def rankStandard(list: List[Score]) =
grouped[Int](list){case (state, names) => state.next(state.n+names.length, Rank(state.n, names))}.done.reverse
 
def rankModified(list: List[Score]) =
rankStandard(list).map(r => Rank(r.rank+r.names.length-1, r.names))
 
def rankDense(list: List[Score]) =
grouped[Int](list){case (state, names) => state.next(state.n+1, Rank(state.n, names))}.done.reverse
 
def rankOrdinal(list: List[Score]) =
list.zipWithIndex.map{case (score, n) => Rank(n+1, List(score.name))}
 
def rankFractional(list: List[Score]) =
rankStandard(list).map(r => Rank((2*r.rank+r.names.length-1.0)/2, r.names))
 
// Tests:
 
def parseScores(s: String) = s split "\\s+" match {case Array(s,n) => Score(s.toInt, n)}
val test = List("44 Solomon", "42 Jason", "42 Errol", "41 Garry", "41 Bernard", "41 Barry", "39 Stephen").map(parseScores)
 
println("Standard:")
println(rankStandard(test) mkString "\n")
println("\nModified:")
println(rankModified(test) mkString "\n")
println("\nDense:")
println(rankDense(test) mkString "\n")
println("\nOrdinal:")
println(rankOrdinal(test) mkString "\n")
println("\nFractional:")
println(rankFractional(test) mkString "\n")
 
}
Output:
Standard:
Rank(1,List(Solomon))
Rank(2,List(Jason, Errol))
Rank(4,List(Garry, Bernard, Barry))
Rank(7,List(Stephen))

Modified:
Rank(1,List(Solomon))
Rank(3,List(Jason, Errol))
Rank(6,List(Garry, Bernard, Barry))
Rank(7,List(Stephen))

Dense:
Rank(1,List(Solomon))
Rank(2,List(Jason, Errol))
Rank(3,List(Garry, Bernard, Barry))
Rank(4,List(Stephen))

Ordinal:
Rank(1,List(Solomon))
Rank(2,List(Jason))
Rank(3,List(Errol))
Rank(4,List(Garry))
Rank(5,List(Bernard))
Rank(6,List(Barry))
Rank(7,List(Stephen))

Fractional:
Rank(1.0,List(Solomon))
Rank(2.5,List(Jason, Errol))
Rank(5.0,List(Garry, Bernard, Barry))
Rank(7.0,List(Stephen))

Sidef[edit]

Translation of: Perl 6
var scores = [
Pair(Solomon => 44),
Pair(Jason => 42),
Pair(Errol => 42),
Pair(Garry => 41),
Pair(Bernard => 41),
Pair(Barry => 41),
Pair(Stephen => 39),
]
 
func tiers(s) {
s.group_by { .value }.kv.sort.flip.map { .value.map{.key} }
}
 
func standard(s) {
var rank = 1
gather {
for players in tiers(s) {
take(Pair(rank, players))
rank += players.len
}
}
}
 
func modified(s) {
var rank = 0
gather {
for players in tiers(s) {
rank += players.len
take(Pair(rank, players))
}
}
}
 
func dense(s) {
tiers(s).map_kv { |k,v| Pair(k+1, v) }
}
 
func ordinal(s) {
s.map_kv { |k,v| Pair(k+1, v.key) }
}
 
func fractional(s) {
var rank = 1
gather {
for players in tiers(s) {
var beg = rank
var end = (rank += players.len)
take(Pair(sum(beg ..^ end) / players.len, players))
}
}
}
 
func display(r) {
say r.map {|a| '%3s : %s' % a... }.join("\n")
}
 
say "Standard:"; display( standard(scores))
say "\nModified:"; display( modified(scores))
say "\nDense:"; display( dense(scores))
say "\nOrdinal:"; display( ordinal(scores))
say "\nFractional:"; display(fractional(scores))
Output:
Standard:
  1 : ["Solomon"]
  2 : ["Jason", "Errol"]
  4 : ["Garry", "Bernard", "Barry"]
  7 : ["Stephen"]

Modified:
  1 : ["Solomon"]
  3 : ["Jason", "Errol"]
  6 : ["Garry", "Bernard", "Barry"]
  7 : ["Stephen"]

Dense:
  1 : ["Solomon"]
  2 : ["Jason", "Errol"]
  3 : ["Garry", "Bernard", "Barry"]
  4 : ["Stephen"]

Ordinal:
  1 : Solomon
  2 : Jason
  3 : Errol
  4 : Garry
  5 : Bernard
  6 : Barry
  7 : Stephen

Fractional:
  1 : ["Solomon"]
2.5 : ["Jason", "Errol"]
  5 : ["Garry", "Bernard", "Barry"]
  7 : ["Stephen"]

Tcl[edit]

proc rank {rankingMethod sortedList} {
# Extract the groups in the data (this is pointless for ordinal...)
set s [set group [set groups {}]]
foreach {score who} $sortedList {
if {$score != $s} {
lappend groups [llength $group]
set s $score
set group {}
}
lappend group $who
}
lappend groups [llength $group]
# Construct the rankings; note that we have a zero-sized leading group
set n 1; set m 0
foreach g $groups {
switch $rankingMethod {
standard {
lappend result {*}[lrepeat $g $n]
incr n $g
}
modified {
lappend result {*}[lrepeat $g [incr m $g]]
}
dense {
lappend result {*}[lrepeat $g $m]
incr m
}
ordinal {
for {set i 0} {$i < $g} {incr i} {
lappend result [incr m]
}
}
fractional {
set val [expr {($n + [incr n $g] - 1) / 2.0}]
lappend result {*}[lrepeat $g [format %g $val]]
}
}
}
return $result
}
 
set data {
44 Solomon
42 Jason
42 Errol
41 Garry
41 Bernard
41 Barry
39 Stephen
}
foreach method {standard modified dense ordinal fractional} {
puts "Using method '$method'...\n Rank\tScore\tWho"
foreach rank [rank $method $data] {score who} $data {
puts " $rank\t$score\t$who"
}
}
Output:
Using method 'standard'...
  Rank	Score	Who
  1	44	Solomon
  2	42	Jason
  2	42	Errol
  4	41	Garry
  4	41	Bernard
  4	41	Barry
  7	39	Stephen
Using method 'modified'...
  Rank	Score	Who
  1	44	Solomon
  3	42	Jason
  3	42	Errol
  6	41	Garry
  6	41	Bernard
  6	41	Barry
  7	39	Stephen
Using method 'dense'...
  Rank	Score	Who
  1	44	Solomon
  2	42	Jason
  2	42	Errol
  3	41	Garry
  3	41	Bernard
  3	41	Barry
  4	39	Stephen
Using method 'ordinal'...
  Rank	Score	Who
  1	44	Solomon
  2	42	Jason
  3	42	Errol
  4	41	Garry
  5	41	Bernard
  6	41	Barry
  7	39	Stephen
Using method 'fractional'...
  Rank	Score	Who
  1	44	Solomon
  2.5	42	Jason
  2.5	42	Errol
  5	41	Garry
  5	41	Bernard
  5	41	Barry
  7	39	Stephen

Visual FoxPro[edit]

 
#DEFINE CTAB CHR(9)
#DEFINE CRLF CHR(13) + CHR(10)
LOCAL lcTxt As String, i As Integer
CLOSE DATABASES ALL
SET SAFETY OFF
CLEAR
CREATE CURSOR scores (score I, name V(8), rownum I AUTOINC)
INDEX ON score TAG score COLLATE "Machine"
SET ORDER TO 0
INSERT INTO scores (score, name) VALUES (44, "Solomon")
INSERT INTO scores (score, name) VALUES (42, "Jason")
INSERT INTO scores (score, name) VALUES (42, "Errol")
INSERT INTO scores (score, name) VALUES (41, "Garry")
INSERT INTO scores (score, name) VALUES (41, "Bernard")
INSERT INTO scores (score, name) VALUES (41, "Barry")
INSERT INTO scores (score, name) VALUES (39, "Stephen")
 
CREATE CURSOR ranks (sc_rank I, mod_rank I, dense I, ordinal I, fractional B(1), score I, name V(8))
INDEX ON score TAG score COLLATE "Machine"
SET ORDER TO 0
APPEND FROM DBF("scores") FIELDS score, name
Std_Comp()
Modified()
Dense()
Ordinal()
Fractional()
COPY TO ranks.txt TYPE DELIMITED WITH TAB
lcTxt = ""
FOR i = 1 TO FCOUNT()
lcTxt = lcTxt + FIELD(i) + CTAB
ENDFOR
lcTxt = LEFT(lcTxt, LEN(lcTxt) - 1) + CRLF + FILETOSTR("ranks.txt")
STRTOFILE(lcTxt, "ranks.txt")
MODIFY FILE ranks.txt
SET SAFETY ON
 
FUNCTION ScoreGroup(aa)
LOCAL n As Integer
SELECT score, COUNT(*) As num FROM scores ;
GROUP BY score ORDER BY score DESC INTO ARRAY aa
n = _TALLY
RETURN n
ENDFUNC
 
PROCEDURE Std_Comp
LOCAL n, i, nn
LOCAL ARRAY a[1]
SELECT ranks
BLANK FIELDS sc_rank ALL
nn = ScoreGroup(@a)
n = 1
FOR i = 1 TO nn
REPLACE sc_rank WITH n FOR score = a[i,1]
n = n + a[i,2]
ENDFOR
ENDPROC
 
PROCEDURE Modified
LOCAL n, i, nn
LOCAL ARRAY a[1]
SELECT ranks
BLANK FIELDS mod_rank ALL
nn = ScoreGroup(@a)
n = 0
FOR i = 1 TO nn
n = n + a[i,2]
REPLACE mod_rank WITH n FOR score = a[i,1]
ENDFOR
ENDPROC
 
PROCEDURE Dense
LOCAL n, i, nn
LOCAL ARRAY a[1]
SELECT ranks
BLANK FIELDS dense ALL
nn = ScoreGroup(@a)
SELECT ranks
n = 0
FOR i = 1 TO nn
n = n + a[i,2]
REPLACE dense WITH i FOR score = a[i,1]
ENDFOR
ENDPROC
 
PROCEDURE Ordinal
SELECT ranks
BLANK FIELDS ordinal ALL
REPLACE ordinal WITH RECNO() ALL
ENDPROC
 
PROCEDURE Fractional
LOCAL i As Integer, nn As Integer
LOCAL ARRAY a[1]
SELECT ranks
BLANK FIELDS fractional ALL
SELECT CAST(AVG(rownum) As B(1)), score FROM scores ;
GROUP BY score ORDER BY score DESC INTO ARRAY a
nn = _TALLY
FOR i = 1 TO nn
REPLACE fractional WITH a[i,1] FOR score = a[i,2]
ENDFOR
ENDPROC
 
Output:
SC_RANK	MOD_RANK	DENSE	ORDINAL	FRACTIONAL	SCORE	NAME
1	1	1	1	1.0	44	"Solomon"
2	3	2	2	2.5	42	"Jason"
2	3	2	3	2.5	42	"Errol"
4	6	3	4	5.0	41	"Garry"
4	6	3	5	5.0	41	"Bernard"
4	6	3	6	5.0	41	"Barry"
7	7	4	7	7.0	39	"Stephen"

zkl[edit]

fcn group(scores){ // group like scores into one list --> list of lists
sink:=List();
scores.reduce('wrap(ps,sn,buf){
if(sn[0]!=ps){ sink.append(buf.copy()); buf.clear(); }
buf+sn;
sn[0];
},scores[0][0],buf:=List());
sink.append(buf);
}
fcn print(list,rank){
list.apply2('wrap(sn){ "%2s: %s (%d)".fmt(rank,sn[1],sn[0]):println(_); });
}
fcn rankViaStandard(scores){
rank:=1;
foreach group in (group(scores)){ print(group,rank); rank+=group.len(); }
}
fcn rankViaModified(scores){
rank:=0;
foreach group in (group(scores)){ rank+=group.len(); print(group,rank); }
}
fcn rankViaDense(scores){
rank:=1;
foreach group in (group(scores)){ print(group,rank); rank+=1; }
}
fcn rankViaOrdinal(scores){
scores.apply2('wrap(sn,rr){ "%2s: %s (%d)".fmt(rr.inc(),sn[1],sn[0]):println(_); },Ref(1));
}
fcn rankViaFractional(scores){
rank:=1;
foreach group in (group(scores)){
n:=group.len(); r:=rank.reduce(n,'+,0.0)/n; rank+=n;
print(group,"%5.2f".fmt(r));
}
}
   // these are sorted!?!
scores:=T(T(44,"Solomon"), T(42,"Jason"), T(42,"Errol"), T(41,"Garry"),
T(41,"Bernard"),T(41,"Barry"),T(39,"Stephen"),);
"Standard:" .println(); rankViaStandard(scores);
"Modified:" .println(); rankViaModified(scores);
"Dense:" .println(); rankViaDense(scores);
"Ordinal:" .println(); rankViaOrdinal(scores);
"Fractional:".println(); rankViaFractional(scores);
Output:
Standard:
 1: Solomon (44)
 2: Jason (42)
 2: Errol (42)
 4: Garry (41)
 4: Bernard (41)
 4: Barry (41)
 7: Stephen (39)
Modified:
 1: Solomon (44)
 3: Jason (42)
 3: Errol (42)
 6: Garry (41)
 6: Bernard (41)
 6: Barry (41)
 7: Stephen (39)
Dense:
 1: Solomon (44)
 2: Jason (42)
 2: Errol (42)
 3: Garry (41)
 3: Bernard (41)
 3: Barry (41)
 4: Stephen (39)
Ordinal:
 1: Solomon (44)
 2: Jason (42)
 3: Errol (42)
 4: Garry (41)
 5: Bernard (41)
 6: Barry (41)
 7: Stephen (39)
Fractional:
 1.00: Solomon (44)
 2.50: Jason (42)
 2.50: Errol (42)
 5.00: Garry (41)
 5.00: Bernard (41)
 5.00: Barry (41)
 7.00: Stephen (39)